Commit 80c131f5 authored by JINMEI Tatuya's avatar JINMEI Tatuya
Browse files

[master] Merge branch 'trac1371'

parents ec1cc2b4 a01eb512
......@@ -1369,20 +1369,72 @@ what if a NOTIFY is sent?
The <command>b10-xfrout</command> process is started by
<command>bind10</command>.
When the <command>b10-auth</command> authoritative DNS server
receives an AXFR request, <command>b10-xfrout</command>
sends the zone.
This is used to provide master DNS service to share zones
receives an AXFR or IXFR request, <command>b10-auth</command>
internally forwards the request to <command>b10-xfrout</command>,
which handles the rest of request processing.
This is used to provide primary DNS service to share zones
to secondary name servers.
The <command>b10-xfrout</command> is also used to send
NOTIFY messages to slaves.
NOTIFY messages to secondary servers.
</para>
<para>
A global or per zone <option>transfer_acl</option> configuration
can be used to control accessibility of the outbound zone
transfer service.
By default, <command>b10-xfrout</command> allows any clients to
perform zone transfers for any zones:
</para>
<screen>&gt; <userinput>config show Xfrout/transfer_acl</userinput>
Xfrout/transfer_acl[0] {"action": "ACCEPT"} any (default)</screen>
<para>
You can change this to, for example, rejecting all transfer
requests by default while allowing requests for the transfer
of zone "example.com" from 192.0.2.1 and 2001:db8::1 as follows:
</para>
<screen>&gt; <userinput>config set Xfrout/transfer_acl[0] {"action": "REJECT"}</userinput>
&gt; <userinput>config add Xfrout/zone_config</userinput>
&gt; <userinput>config set Xfrout/zone_config[0]/origin "example.com"</userinput>
&gt; <userinput>config set Xfrout/zone_config[0]/transfer_acl [{"action": "ACCEPT", "from": "192.0.2.1"},</userinput>
<userinput> {"action": "ACCEPT", "from": "2001:db8::1"}]</userinput>
&gt; <userinput>config commit</userinput></screen>
<note><simpara>
The current development release of BIND 10 only supports
AXFR. (IXFR is not supported.)
Access control is not yet provided.
In the above example the lines
for <option>transfer_acl</option> were divided for
readability. In the actual input it must be in a single line.
</simpara></note>
<para>
If you want to require TSIG in access control, a separate TSIG
"key ring" must be configured specifically
for <command>b10-xfrout</command> as well as a system wide
key ring, both containing a consistent set of keys.
For example, to change the previous example to allowing requests
from 192.0.2.1 signed by a TSIG with a key name of
"key.example", you'll need to do this:
</para>
<screen>&gt; <userinput>config set tsig_keys/keys ["key.example:&lt;base64-key&gt;"]</userinput>
&gt; <userinput>config set Xfrout/tsig_keys/keys ["key.example:&lt;base64-key&gt;"]</userinput>
&gt; <userinput>config set Xfrout/zone_config[0]/transfer_acl [{"action": "ACCEPT", "from": "192.0.2.1", "key": "key.example"}]</userinput>
&gt; <userinput>config commit</userinput></screen>
<para>
The first line of configuration defines a system wide key ring.
This is necessary because the <command>b10-auth</command> server
also checks TSIGs and it uses the system wide configuration.
</para>
<note><simpara>
In a future version, <command>b10-xfrout</command> will also
use the system wide TSIG configuration.
The way to specify zone specific configuration (ACLs, etc) is
likely to be changed, too.
</simpara></note>
<!--
TODO:
......
......@@ -97,6 +97,31 @@
defines the maximum number of outgoing zone transfers
that can run concurrently. The default is 10.
</para>
<para>
<varname>tsig_key_ring</varname>
A list of TSIG keys (each of which is in the form of
name:base64-key[:algorithm]) used for access control on transfer
requests.
The default is an empty list.
</para>
<para>
<varname>transfer_acl</varname>
A list of ACL elements that apply to all transfer requests by
default (unless overridden in zone_config). See the BIND 10
guide for configuration examples.
The default is an element that allows any transfer requests.
</para>
<para>
<varname>zone_config</varname>
A list of JSON objects (i.e. maps) that define per zone
configuration concerning <command>b10-xfrout</command>.
The supported names of each object are "origin" (the origin
name of the zone), "class" (the RR class of the zone, optional,
default to "IN"), and "acl_element" (ACL only applicable to
transfer requests for that zone).
See the BIND 10 guide for configuration examples.
The default is an empty list, that is, no zone specific configuration.
</para>
<para>
<varname>log_name</varname>
<!-- TODO -->
......
......@@ -3,8 +3,8 @@ PYTESTS = xfrout_test.py
noinst_SCRIPTS = $(PYTESTS)
EXTRA_DIST = testdata/test.sqlite3
# This one is actually not necessary, but added for reference
EXTRA_DIST += testdata/example.com
# These are actually not necessary, but added for reference
EXTRA_DIST += testdata/example.com testdata/creatediff.py
# If necessary (rare cases), explicitly specify paths to dynamic libraries
# required by loadable python modules.
......
#!/usr/bin/env python3.1
# Copyright (C) 2011 Internet Systems Consortium.
#
# Permission to use, copy, modify, and distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
'''This script was used to create zone differences for IXFR tests.
The result was stored in the test SQLite3 database file, so this script
itself isn't necessary for testing. It's provided here for reference
purposes.
'''
import isc.datasrc
import isc.log
from isc.dns import *
from isc.testutils.rrset_utils import *
isc.log.init("dummy") # XXX
ZONE_NAME = Name('example.com')
NS_NAME_STR = 'a.dns.example.com'
NS_NAME = Name(NS_NAME_STR)
client = isc.datasrc.DataSourceClient('sqlite3',
'{ "database_file": "test.sqlite3" }')
# Install the initial data
updater = client.get_updater(ZONE_NAME, True)
updater.add_rrset(create_soa(2011111802))
updater.add_rrset(create_ns(NS_NAME_STR))
updater.add_rrset(create_a(NS_NAME, '192.0.2.53'))
updater.add_rrset(create_aaaa(NS_NAME, '2001:db8::1'))
updater.commit()
# Incremental update to generate diffs
updater = client.get_updater(ZONE_NAME, False, True)
updater.delete_rrset(create_soa(2011111802))
updater.add_rrset(create_soa(2011111900))
updater.add_rrset(create_a(NS_NAME, '192.0.2.2', 7200))
updater.delete_rrset(create_soa(2011111900))
updater.delete_rrset(create_a(NS_NAME, '192.0.2.53'))
updater.delete_rrset(create_aaaa(NS_NAME, '2001:db8::1'))
updater.add_rrset(create_soa(2011112001))
updater.add_rrset(create_a(NS_NAME, '192.0.2.1'))
updater.commit()
;; This is the source of a zone stored in test.sqlite3. It's provided
;; for reference purposes only.
example.com. 3600 IN SOA a.dns.example.com. mail.example.com. 1 1 1 1 1
example.com. 3600 IN SOA master.example.com. admin.example.com. 2011112001 3600 1800 2419200 7200
example.com. 3600 IN NS a.dns.example.com.
a.dns.example.com. 3600 IN A 192.0.2.1
a.dns.example.com. 7200 IN A 192.0.2.2
This diff is collapsed.
......@@ -22,7 +22,7 @@ import isc.cc
import threading
import struct
import signal
from isc.datasrc import DataSourceClient
from isc.datasrc import DataSourceClient, ZoneFinder, ZoneJournalReader
from socketserver import *
import os
from isc.config.ccsession import *
......@@ -102,7 +102,7 @@ def format_zone_str(zone_name, zone_class):
zone_name (isc.dns.Name) name to format
zone_class (isc.dns.RRClass) class to format
"""
return zone_name.to_text() + '/' + str(zone_class)
return zone_name.to_text(True) + '/' + str(zone_class)
# borrowed from xfrin.py @ #1298.
def format_addrinfo(addrinfo):
......@@ -132,6 +132,11 @@ def get_rrset_len(rrset):
rrset.to_wire(bytes)
return len(bytes)
def get_soa_serial(soa_rdata):
'''Extract the serial field of an SOA RDATA and returns it as an intger.
(borrowed from xfrin)
'''
return int(soa_rdata.to_text().split()[2])
class XfroutSession():
def __init__(self, sock_fd, request_data, server, tsig_key_ring, remote,
......@@ -143,11 +148,13 @@ class XfroutSession():
self._tsig_ctx = None
self._tsig_len = 0
self._remote = remote
self._request_type = 'AXFR' # could be IXFR when we support it
self._request_type = None
self._request_typestr = None
self._acl = default_acl
self._zone_config = zone_config
self.ClientClass = client_class # parameterize this for testing
self._soa = None # will be set in _check_xfrout_available or in tests
self._soa = None # will be set in _xfrout_setup or in tests
self._jnl_reader = None # will be set to a reader for IXFR
self._handle()
def create_tsig_ctx(self, tsig_record, tsig_key_ring):
......@@ -195,7 +202,8 @@ class XfroutSession():
tsig_record = msg.get_tsig_record()
if tsig_record is not None:
self._tsig_len = tsig_record.get_length()
self._tsig_ctx = self.create_tsig_ctx(tsig_record, self._tsig_key_ring)
self._tsig_ctx = self.create_tsig_ctx(tsig_record,
self._tsig_key_ring)
tsig_error = self._tsig_ctx.verify(tsig_record, request_data)
if tsig_error != TSIGError.NOERROR:
return Rcode.NOTAUTH()
......@@ -218,24 +226,38 @@ class XfroutSession():
return rcode, msg
# Make sure the question is valid. This should be ensured by
# the auth server, but since it's far from our xfrout itself,
# we check it by ourselves.
# the auth server, but since it's far from xfrout itself, we check
# it by ourselves. A viloation would be an internal bug, so we
# raise and stop here rather than returning a FORMERR or SERVFAIL.
if msg.get_rr_count(Message.SECTION_QUESTION) != 1:
return Rcode.FORMERR(), msg
raise RuntimeError('Invalid number of question for XFR: ' +
str(msg.get_rr_count(Message.SECTION_QUESTION)))
question = msg.get_question()[0]
# Identify the request type
self._request_type = question.get_type()
if self._request_type == RRType.AXFR():
self._request_typestr = 'AXFR'
elif self._request_type == RRType.IXFR():
self._request_typestr = 'IXFR'
else:
# Likewise, this should be impossible.
raise RuntimeError('Unexpected XFR type: ' +
str(self._request_type))
# ACL checks
zone_name = msg.get_question()[0].get_name()
zone_class = msg.get_question()[0].get_class()
zone_name = question.get_name()
zone_class = question.get_class()
acl = self._get_transfer_acl(zone_name, zone_class)
acl_result = acl.execute(
isc.acl.dns.RequestContext(self._remote[2], msg.get_tsig_record()))
if acl_result == DROP:
logger.info(XFROUT_QUERY_DROPPED, self._request_type,
logger.info(XFROUT_QUERY_DROPPED, self._request_typestr,
format_addrinfo(self._remote),
format_zone_str(zone_name, zone_class))
return None, None
elif acl_result == REJECT:
logger.info(XFROUT_QUERY_REJECTED, self._request_type,
logger.info(XFROUT_QUERY_REJECTED, self._request_typestr,
format_addrinfo(self._remote),
format_zone_str(zone_name, zone_class))
return Rcode.REFUSED(), msg
......@@ -295,23 +317,33 @@ class XfroutSession():
msg.set_rcode(rcode_)
self._send_message(sock_fd, msg, self._tsig_ctx)
def _check_xfrout_available(self, zone_name):
'''Check if xfr request can be responsed.
TODO, Get zone's configuration from cfgmgr or some other place
eg. check allow_transfer setting,
def _get_zone_soa(self, zone_name):
'''Retrieve the SOA RR of the given zone.
It returns a pair of RCODE and the SOA (in the form of RRset).
On success RCODE is NOERROR and returned SOA is not None;
on failure RCODE indicates the appropriate code in the context of
xfr processing, and the returned SOA is None.
'''
result, finder = self._datasrc_client.find_zone(zone_name)
if result != DataSourceClient.SUCCESS:
return (Rcode.NOTAUTH(), None)
result, soa_rrset = finder.find(zone_name, RRType.SOA(), None,
ZoneFinder.FIND_DEFAULT)
if result != ZoneFinder.SUCCESS:
return (Rcode.SERVFAIL(), None)
# Especially for database-based zones, a working zone may be in
# a broken state where it has more than one SOA RR. We proactively
# check the condition and abort the xfr attempt if we identify it.
if soa_rrset.get_rdata_count() != 1:
return (Rcode.SERVFAIL(), None)
return (Rcode.NOERROR(), soa_rrset)
def __axfr_setup(self, zone_name):
'''Setup a zone iterator for AXFR or AXFR-style IXFR.
# Identify the data source for the requested zone and see if it has
# SOA while initializing objects used for request processing later.
# We should eventually generalize this so that we can choose the
# appropriate data source from (possible) multiple candidates.
# We should eventually take into account the RR class here.
# For now, we hardcode a particular type (SQLite3-based), and only
# consider that one.
datasrc_config = '{ "database_file": "' + \
self._server.get_db_file() + '"}'
self._datasrc_client = self.ClientClass('sqlite3', datasrc_config)
'''
try:
# Note that we enable 'separate_rrs'. In xfr-out we need to
# preserve as many things as possible (even if it's half broken)
......@@ -336,6 +368,112 @@ class XfroutSession():
return Rcode.NOERROR()
def __ixfr_setup(self, request_msg, zone_name, zone_class):
'''Setup a zone journal reader for IXFR.
If the underlying data source does not know the requested range
of zone differences it automatically falls back to AXFR-style
IXFR by setting up a zone iterator instead of a journal reader.
'''
# Check the authority section. Look for a SOA record with
# the same name and class as the question.
remote_soa = None
for auth_rrset in request_msg.get_section(Message.SECTION_AUTHORITY):
# Ignore data whose owner name is not the zone apex, and
# ignore non-SOA or different class of records.
if auth_rrset.get_name() != zone_name or \
auth_rrset.get_type() != RRType.SOA() or \
auth_rrset.get_class() != zone_class:
continue
if auth_rrset.get_rdata_count() != 1:
logger.info(XFROUT_IXFR_MULTIPLE_SOA,
format_addrinfo(self._remote))
return Rcode.FORMERR()
remote_soa = auth_rrset
if remote_soa is None:
logger.info(XFROUT_IXFR_NO_SOA, format_addrinfo(self._remote))
return Rcode.FORMERR()
# Retrieve the local SOA
rcode, self._soa = self._get_zone_soa(zone_name)
if rcode != Rcode.NOERROR():
return rcode
# RFC1995 says "If an IXFR query with the same or newer version
# number than that of the server is received, it is replied to with
# a single SOA record of the server's current version, just as
# in AXFR". The claim about AXFR is incorrect, but other than that,
# we do as the RFC says.
# Note: until we complete #1278 we can only check equality of the
# two serials. The "newer version" case would fall back to AXFR-style.
begin_serial = get_soa_serial(remote_soa.get_rdata()[0])
end_serial = get_soa_serial(self._soa.get_rdata()[0])
if begin_serial == end_serial:
# clear both iterator and jnl_reader to signal we won't do
# iteration in response generation
self._iterator = None
self._jnl_reader = None
logger.info(XFROUT_IXFR_UPTODATE, format_addrinfo(self._remote),
format_zone_str(zone_name, zone_class),
begin_serial, end_serial)
return Rcode.NOERROR()
# Set up the journal reader or fall back to AXFR-style IXFR
try:
code, self._jnl_reader = self._datasrc_client.get_journal_reader(
zone_name, begin_serial, end_serial)
except isc.datasrc.NotImplemented as ex:
# The underlying data source doesn't support journaling.
# Fall back to AXFR-style IXFR.
logger.info(XFROUT_IXFR_NO_JOURNAL_SUPPORT,
format_addrinfo(self._remote),
format_zone_str(zone_name, zone_class))
return self.__axfr_setup(zone_name)
if code == ZoneJournalReader.NO_SUCH_VERSION:
logger.info(XFROUT_IXFR_NO_VERSION, format_addrinfo(self._remote),
format_zone_str(zone_name, zone_class),
begin_serial, end_serial)
return self.__axfr_setup(zone_name)
if code == ZoneJournalReader.NO_SUCH_ZONE:
# this is quite unexpected as we know zone's SOA exists.
# It might be a bug or the data source is somehow broken,
# but it can still happen if someone has removed the zone
# between these two operations. We treat it as NOTAUTH.
logger.warn(XFROUT_IXFR_NO_ZONE, format_addrinfo(self._remote),
format_zone_str(zone_name, zone_class))
return Rcode.NOTAUTH()
# Use the reader as the iterator to generate the response.
self._iterator = self._jnl_reader
return Rcode.NOERROR()
def _xfrout_setup(self, request_msg, zone_name, zone_class):
'''Setup a context for xfr responses according to the request type.
This method identifies the most appropriate data source for the
request and set up a zone iterator or journal reader depending on
whether the request is AXFR or IXFR. If it identifies any protocol
level error it returns an RCODE other than NOERROR.
'''
# Identify the data source for the requested zone and see if it has
# SOA while initializing objects used for request processing later.
# We should eventually generalize this so that we can choose the
# appropriate data source from (possible) multiple candidates.
# We should eventually take into account the RR class here.
# For now, we hardcode a particular type (SQLite3-based), and only
# consider that one.
datasrc_config = '{ "database_file": "' + \
self._server.get_db_file() + '"}'
self._datasrc_client = self.ClientClass('sqlite3', datasrc_config)
if self._request_type == RRType.AXFR():
return self.__axfr_setup(zone_name)
else:
return self.__ixfr_setup(request_msg, zone_name, zone_class)
def dns_xfrout_start(self, sock_fd, msg_query, quota_ok=True):
rcode_, msg = self._parse_query_message(msg_query)
......@@ -348,7 +486,7 @@ class XfroutSession():
return self._reply_query_with_error_rcode(msg, sock_fd,
Rcode.FORMERR())
elif not quota_ok:
logger.warn(XFROUT_QUERY_QUOTA_EXCCEEDED, self._request_type,
logger.warn(XFROUT_QUERY_QUOTA_EXCCEEDED, self._request_typestr,
format_addrinfo(self._remote),
self._server._max_transfers_out)
return self._reply_query_with_error_rcode(msg, sock_fd,
......@@ -359,27 +497,26 @@ class XfroutSession():
zone_class = question.get_class()
zone_str = format_zone_str(zone_name, zone_class) # for logging
# TODO: we should also include class in the check
try:
rcode_ = self._check_xfrout_available(zone_name)
rcode_ = self._xfrout_setup(msg, zone_name, zone_class)
except Exception as ex:
logger.error(XFROUT_XFR_TRANSFER_CHECK_ERROR, self._request_type,
logger.error(XFROUT_XFR_TRANSFER_CHECK_ERROR, self._request_typestr,
format_addrinfo(self._remote), zone_str, ex)
rcode_ = Rcode.SERVFAIL()
if rcode_ != Rcode.NOERROR():
logger.info(XFROUT_AXFR_TRANSFER_FAILED, self._request_type,
logger.info(XFROUT_AXFR_TRANSFER_FAILED, self._request_typestr,
format_addrinfo(self._remote), zone_str, rcode_)
return self._reply_query_with_error_rcode(msg, sock_fd, rcode_)
try:
logger.info(XFROUT_AXFR_TRANSFER_STARTED, self._request_type,
logger.info(XFROUT_AXFR_TRANSFER_STARTED, self._request_typestr,
format_addrinfo(self._remote), zone_str)
self._reply_xfrout_query(msg, sock_fd)
except Exception as err:
logger.error(XFROUT_AXFR_TRANSFER_ERROR, self._request_type,
logger.error(XFROUT_AXFR_TRANSFER_ERROR, self._request_typestr,
format_addrinfo(self._remote), zone_str, err)
pass
logger.info(XFROUT_AXFR_TRANSFER_DONE, self._request_type,
logger.info(XFROUT_AXFR_TRANSFER_DONE, self._request_typestr,
format_addrinfo(self._remote), zone_str)
def _clear_message(self, msg):
......@@ -409,22 +546,31 @@ class XfroutSession():
msg.add_rrset(Message.SECTION_ANSWER, rrset_soa)
self._send_message(sock_fd, msg, self._tsig_ctx)
def _reply_xfrout_query(self, msg, sock_fd):
#TODO, there should be a better way to insert rrset.
msg.make_response()
msg.set_header_flag(Message.HEADERFLAG_AA)
msg.add_rrset(Message.SECTION_ANSWER, self._soa)
# If the iterator is None, we are responding to IXFR with a single
# SOA RR.
if self._iterator is None:
self._send_message_with_last_soa(msg, sock_fd, self._soa, 0)
return
# Add the beginning SOA
msg.add_rrset(Message.SECTION_ANSWER, self._soa)
message_upper_len = get_rrset_len(self._soa) + self._tsig_len
# Add the rest of the zone/diff contets
for rrset in self._iterator:
# Check if xfrout is shutdown
if self._server._shutdown_event.is_set():
logger.info(XFROUT_STOPPING)
return
if rrset.get_type() == RRType.SOA():
# For AXFR (or AXFR-style IXFR), in which case _jnl_reader is None,
# we should skip SOAs from the iterator.
if self._jnl_reader is None and rrset.get_type() == RRType.SOA():
continue
# We calculate the maximum size of the RRset (i.e. the
......@@ -445,6 +591,7 @@ class XfroutSession():
# Reserve tsig space for signed packet
message_upper_len = rrset_len + self._tsig_len
# Add and send the trailing SOA
self._send_message_with_last_soa(msg, sock_fd, self._soa,
message_upper_len)
......
......@@ -178,3 +178,42 @@ on, but the file is in use. The most likely cause is that another
xfrout daemon process is still running. This xfrout daemon (the one
printing this message) will not start.
% XFROUT_IXFR_MULTIPLE_SOA IXFR client %1: authority section has multiple SOAs
An IXFR request was received with more than one SOA RRs in the authority
section. The xfrout daemon rejects the request with an RCODE of
FORMERR.
% XFROUT_IXFR_NO_SOA IXFR client %1: missing SOA
An IXFR request was received with no SOA RR in the authority section.
The xfrout daemon rejects the request with an RCODE of FORMERR.
% XFROUT_IXFR_NO_JOURNAL_SUPPORT IXFR client %1, %2: journaling not supported in the data source, falling back to AXFR
An IXFR request was received but the underlying data source did
not support journaling. The xfrout daemon fell back to AXFR-style
IXFR.
% XFROUT_IXFR_UPTODATE IXFR client %1, %2: client version is new enough (theirs=%3, ours=%4)
An IXFR request was received, but the client's SOA version is the same as
or newer than that of the server. The xfrout server responds to the
request with the answer section being just one SOA of that version.
Note: as of this wrting the 'newer version' cannot be identified due to
the lack of support for the serial number arithmetic. This will soon
be implemented.
% XFROUT_IXFR_NO_VERSION IXFR client %1, %2: version (%3 to %4) not in journal, falling back to AXFR
An IXFR request was received, but the requested range of differences
were not found in the data source. The xfrout daemon fell back to
AXFR-style IXFR.
% XFROUT_IXFR_NO_ZONE IXFR client %1, %2: zone not found with journal
The requested zone in IXFR was not found in the data source
even though the xfrout daemon sucessfully found the SOA RR of the zone
in the data source. This can happen if the administrator removed the
zone from the data source within the small duration between these
operations, but it's more likely to be a bug or broken data source.
Unless you know why this message was logged, and especially if it
happens often, it's advisable to check whether the data source is
valid for this zone. The xfrout daemon considers it a possible,
though unlikely, event, and returns a response with an RCODE of
NOTAUTH.
......@@ -233,6 +233,8 @@ isc.datasrc.NotImplemented.\n\
Exceptions:\n\
isc.datasrc.NotImplemented The data source does not support differences.\n\
isc.datasrc.Error Other operational errors at the data source level.\n\
SystemError An unexpected error in the backend C++ code. Either a rare\n\
system error such as short memory or an implementation bug.\n\
\n\
Parameters:\n\
zone The name of the zone for which the difference should be\n\
......
......@@ -182,19 +182,31 @@ DataSourceClient_getJournalReader(PyObject* po_self, PyObject* args) {
if (PyArg_ParseTuple(args, "O!kk", &name_type, &name_obj,
&begin_obj, &end_obj)) {
pair<ZoneJournalReader::Result, ZoneJournalReaderPtr> result =
self->cppobj->getInstance().getJournalReader(
PyName_ToName(name_obj), static_cast<uint32_t>(begin_obj),
static_cast<uint32_t>(end_obj));
PyObject* po_reader;
if (result.first == ZoneJournalReader::SUCCESS) {
po_reader = createZoneJournalReaderObject(result.second, po_self);
} else {
po_reader = Py_None;
Py_INCREF(po_reader); // this will soon be released
try {
pair<ZoneJournalReader::Result, ZoneJournalReaderPtr> result =
self->cppobj->getInstance().getJournalReader(
PyName_ToName(name_obj), static_cast<uint32_t>(begin_obj),
static_cast<uint32_t>(end_obj));
PyObject* po_reader;
if (result.first == ZoneJournalReader::SUCCESS) {
po_reader = createZoneJournalReaderObject(result.second,
po_self);
} else {
po_reader = Py_None;
Py_INCREF(po_reader); // this will soon be released
}
PyObjectContainer container(po_reader);
return (Py_BuildValue("(iO)", result.first, container.get()));
} catch (const isc::NotImplemented& ex) {
PyErr_SetString(getDataSourceException("NotImplemented"),
ex.what());
} catch (const DataSourceError& ex) {
PyErr_SetString(getDataSourceException("Error"), ex.what());
} catch (const std::exception& ex) {
PyErr_SetString(PyExc_SystemError, ex.what());
} catch (...) {