BIND 10 trac1372, updated. 5cea4cfbee9770f4299f5a701af89f7cbf977ef4 [1372] covered a bit unusual case: get_journal_reader() fails with NO_SUCH_ZONE.
BIND 10 source code commits
bind10-changes at lists.isc.org
Sat Nov 19 00:01:22 UTC 2011
The branch, trac1372 has been updated
via 5cea4cfbee9770f4299f5a701af89f7cbf977ef4 (commit)
via 1af57091dc0c38cff538de2470275f25caeb2eab (commit)
via 256c0a08483ac2bf396dfa8424b4c02f0681a0f4 (commit)
from 5c92f567d93977bd56a7ed2898c7bee098b552ab (commit)
Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.
- Log -----------------------------------------------------------------
commit 5cea4cfbee9770f4299f5a701af89f7cbf977ef4
Author: JINMEI Tatuya <jinmei at isc.org>
Date: Fri Nov 18 15:41:58 2011 -0800
[1372] covered a bit unusual case: get_journal_reader() fails with
NO_SUCH_ZONE.
commit 1af57091dc0c38cff538de2470275f25caeb2eab
Author: JINMEI Tatuya <jinmei at isc.org>
Date: Fri Nov 18 15:22:55 2011 -0800
[1372] refactoring: renamed _check_xfrout_available to _xfrout_setup
as it's not really about availability anymore.
commit 256c0a08483ac2bf396dfa8424b4c02f0681a0f4
Author: JINMEI Tatuya <jinmei at isc.org>
Date: Fri Nov 18 15:16:16 2011 -0800
[1372] more complete setup for IXFR. falling back to AXFR-style was supported.
also refactored the code to better clarify the code flow.
-----------------------------------------------------------------------
Summary of changes:
src/bin/xfrout/tests/xfrout_test.py.in | 94 +++++++++++++++------
src/bin/xfrout/xfrout.py.in | 143 ++++++++++++++++++++------------
2 files changed, 157 insertions(+), 80 deletions(-)
-----------------------------------------------------------------------
diff --git a/src/bin/xfrout/tests/xfrout_test.py.in b/src/bin/xfrout/tests/xfrout_test.py.in
index b9e181a..19c65cd 100644
--- a/src/bin/xfrout/tests/xfrout_test.py.in
+++ b/src/bin/xfrout/tests/xfrout_test.py.in
@@ -36,6 +36,8 @@ TSIG_KEY = TSIGKey("example.com:SFuWd/q99SzF8Yzd1QbB9g==")
TEST_ZONE_NAME_STR = "example.com."
TEST_ZONE_NAME = Name(TEST_ZONE_NAME_STR)
TEST_RRCLASS = RRClass.IN()
+IXFR_OK_VERSION = 2011111802
+IXFR_NG_VERSION = 2011112800
# SOA intended to be used for the new SOA as a result of transfer.
soa_rdata = Rdata(RRType.SOA(), TEST_RRCLASS,
@@ -83,6 +85,15 @@ class MockDataSrcClient:
def __init__(self, type, config):
pass
+ def __create_soa(self):
+ soa_rrset = RRset(self._zone_name, RRClass.IN(), RRType.SOA(),
+ RRTTL(3600))
+ soa_rrset.add_rdata(Rdata(RRType.SOA(), RRClass.IN(),
+ 'master.example.com. ' +
+ 'admin.example.com. 1234 ' +
+ '3600 1800 2419200 7200'))
+ return soa_rrset
+
def find_zone(self, zone_name):
'''Mock version of find_zone().
@@ -91,9 +102,10 @@ class MockDataSrcClient:
or PARTIALMATCH.
'''
- if zone_name == TEST_ZONE_NAME:
- return (isc.datasrc.DataSourceClient.SUCCESS, self)
- raise ValueError('Unexpected input to mock client: bug in test case?')
+ self._zone_name = zone_name
+ if zone_name == Name('notauth.example.com'):
+ return (isc.datasrc.DataSourceClient.NOTFOUND, None)
+ return (isc.datasrc.DataSourceClient.SUCCESS, self)
def find(self, name, rrtype, target, options):
'''Mock ZoneFinder.find().
@@ -104,8 +116,15 @@ class MockDataSrcClient:
'''
if name == TEST_ZONE_NAME and rrtype == RRType.SOA():
+ return (ZoneFinder.SUCCESS, self.__create_soa())
+ elif name == Name('nosoa.example.com') and rrtype == RRType.SOA():
+ return (ZoneFinder.NXDOMAIN, None)
+ elif name == Name('multisoa.example.com') and rrtype == RRType.SOA():
+ soa_rrset = self.__create_soa()
+ soa_rrset.add_rdata(soa_rrset.get_rdata()[0])
return (ZoneFinder.SUCCESS, soa_rrset)
- raise ValueError('Unexpected input to mock finder: bug in test case?')
+ else:
+ return (ZoneFinder.SUCCESS, self.__create_soa())
def get_iterator(self, zone_name, adjust_ttl=False):
if zone_name == Name('notauth.example.com'):
@@ -116,20 +135,16 @@ class MockDataSrcClient:
def get_soa(self): # emulate ZoneIterator.get_soa()
if self._zone_name == Name('nosoa.example.com'):
return None
- soa_rrset = RRset(self._zone_name, RRClass.IN(), RRType.SOA(),
- RRTTL(3600))
- soa_rrset.add_rdata(Rdata(RRType.SOA(), RRClass.IN(),
- 'master.example.com. ' +
- 'admin.example.com. 1234 ' +
- '3600 1800 2419200 7200'))
+ soa_rrset = self.__create_soa()
if self._zone_name == Name('multisoa.example.com'):
- soa_rrset.add_rdata(Rdata(RRType.SOA(), RRClass.IN(),
- 'master.example.com. ' +
- 'admin.example.com. 1300 ' +
- '3600 1800 2419200 7200'))
+ soa_rrset.add_rdata(soa_rrset.get_rdata()[0])
return soa_rrset
def get_journal_reader(self, zone_name, begin_serial, end_serial):
+ if zone_name == Name('notauth2.example.com'):
+ return isc.datasrc.ZoneJournalReader.NO_SUCH_ZONE, None
+ if begin_serial == IXFR_NG_VERSION:
+ return isc.datasrc.ZoneJournalReader.NO_SUCH_VERSION, None
return isc.datasrc.ZoneJournalReader.SUCCESS, self
class MyCCSession(isc.config.ConfigData):
@@ -661,24 +676,51 @@ class TestXfroutSession(TestXfroutSessionBase):
def test_get_rrset_len(self):
self.assertEqual(82, get_rrset_len(self.soa_rrset))
- def test_check_xfrout_axfr_available(self):
+ def test_xfrout_axfr_setup(self):
self.xfrsess.ClientClass = MockDataSrcClient
- self.assertEqual(self.xfrsess._check_xfrout_available(
+ # Successful case. A zone iterator should be set up.
+ self.assertEqual(self.xfrsess._xfrout_setup(
self.getmsg(), Name('example.com')), Rcode.NOERROR())
- self.assertEqual(self.xfrsess._check_xfrout_available(
+ self.assertNotEqual(None, self.xfrsess._iterator)
+
+ # Failure cases
+ self.assertEqual(self.xfrsess._xfrout_setup(
self.getmsg(), Name('notauth.example.com')), Rcode.NOTAUTH())
- self.assertEqual(self.xfrsess._check_xfrout_available(
+ self.assertEqual(self.xfrsess._xfrout_setup(
self.getmsg(), Name('nosoa.example.com')), Rcode.SERVFAIL())
- self.assertEqual(self.xfrsess._check_xfrout_available(
+ self.assertEqual(self.xfrsess._xfrout_setup(
self.getmsg(), Name('multisoa.example.com')), Rcode.SERVFAIL())
- def test_check_xfrout_ixfr_available(self):
+ def test_xfrout_ixfr_setup(self):
self.xfrsess.ClientClass = MockDataSrcClient
self.set_request_type(RRType.IXFR())
- self.mdata = self.create_request_data(ixfr=2011111802)
- request_msg = self.getmsg()
- self.assertEqual(self.xfrsess._check_xfrout_available(
- self.getmsg(), Name('example.com')), Rcode.NOERROR())
+
+ # Successful case of pure IXFR. A zone journal reader should be set
+ # up.
+ self.mdata = self.create_request_data(ixfr=IXFR_OK_VERSION)
+ self.assertEqual(self.xfrsess._xfrout_setup(
+ self.getmsg(), TEST_ZONE_NAME), Rcode.NOERROR())
+ self.assertNotEqual(None, self.xfrsess._jnl_reader)
+
+ # Successful case, but as a result of falling back to AXFR-style
+ # IXFR. A zone iterator should be set up instead of a journal reader.
+ self.mdata = self.create_request_data(ixfr=IXFR_NG_VERSION)
+ self.assertEqual(self.xfrsess._xfrout_setup(
+ self.getmsg(), TEST_ZONE_NAME), Rcode.NOERROR())
+ self.assertNotEqual(None, self.xfrsess._iterator)
+ self.assertEqual(None, self.xfrsess._jnl_reader)
+
+ # Failure cases
+ self.assertEqual(self.xfrsess._xfrout_setup(
+ self.getmsg(), Name('notauth.example.com')), Rcode.NOTAUTH())
+ # this is a strange case: zone's SOA will be found but the journal
+ # reader won't be created due to 'no such zone'.
+ self.assertEqual(self.xfrsess._xfrout_setup(
+ self.getmsg(), Name('notauth2.example.com')), Rcode.NOTAUTH())
+ self.assertEqual(self.xfrsess._xfrout_setup(
+ self.getmsg(), Name('nosoa.example.com')), Rcode.SERVFAIL())
+ self.assertEqual(self.xfrsess._xfrout_setup(
+ self.getmsg(), Name('multisoa.example.com')), Rcode.SERVFAIL())
def test_dns_xfrout_start_formerror(self):
# formerror
@@ -692,7 +734,7 @@ class TestXfroutSession(TestXfroutSessionBase):
def test_dns_xfrout_start_notauth(self):
def notauth(msg, name):
return Rcode.NOTAUTH()
- self.xfrsess._check_xfrout_available = notauth
+ self.xfrsess._xfrout_setup = notauth
self.xfrsess.dns_xfrout_start(self.sock, self.mdata)
get_msg = self.sock.read_msg()
self.assertEqual(get_msg.get_rcode().to_text(), "NOTAUTH")
@@ -707,7 +749,7 @@ class TestXfroutSession(TestXfroutSessionBase):
def test_dns_xfrout_start_noerror(self):
def noerror(msg, name):
return Rcode.NOERROR()
- self.xfrsess._check_xfrout_available = noerror
+ self.xfrsess._xfrout_setup = noerror
def myreply(msg, sock):
self.sock.send(b"success")
diff --git a/src/bin/xfrout/xfrout.py.in b/src/bin/xfrout/xfrout.py.in
index cb966d0..1b6c317 100755
--- a/src/bin/xfrout/xfrout.py.in
+++ b/src/bin/xfrout/xfrout.py.in
@@ -22,7 +22,7 @@ import isc.cc
import threading
import struct
import signal
-from isc.datasrc import DataSourceClient, ZoneFinder
+from isc.datasrc import DataSourceClient, ZoneFinder, ZoneJournalReader
from socketserver import *
import os
from isc.config.ccsession import *
@@ -153,7 +153,7 @@ class XfroutSession():
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._handle()
def create_tsig_ctx(self, tsig_record, tsig_key_ring):
@@ -237,8 +237,8 @@ class XfroutSession():
elif self._request_type == RRType.IXFR():
self._request_typestr = 'IXFR'
else:
- # Likewise, this should be impossible.
- raise Runtimeerror('Unexpected XFR type: ' + \
+ # Likewise, this should be impossible. (TBD: to be tested)
+ raise RuntimeError('Unexpected XFR type: ' + \
str(self._request_type))
# ACL checks
@@ -314,24 +314,95 @@ class XfroutSession():
self._send_message(sock_fd, msg, self._tsig_ctx)
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 None # XXX
- result, soa_rrset = finder.find(zone_name, RRType.SOA(),
- None, ZoneFinder.FIND_DEFAULT)
+ return (Rcode.NOTAUTH(), None)
+ result, soa_rrset = finder.find(zone_name, RRType.SOA(), None,
+ ZoneFinder.FIND_DEFAULT)
if result != ZoneFinder.SUCCESS:
- return None
+ 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 None
- return soa_rrset
+ return (Rcode.SERVFAIL(), None)
+ return (Rcode.NOERROR(), soa_rrset)
+
+ def __setup_axfr(self, zone_name):
+ '''Setup a zone iterator for AXFR or AXFR-style IXFR.
+
+ '''
+ try:
+ # Note that we disable 'adjust_ttl'. In xfr-out we need to
+ # preserve as many things as possible (even if it's half
+ # broken) stored in the zone.
+ self._iterator = self._datasrc_client.get_iterator(zone_name,
+ False)
+ except isc.datasrc.Error:
+ # If the current name server does not have authority for the
+ # zone, xfrout can't serve for it, return rcode NOTAUTH.
+ # Note: this exception can happen for other reasons. We should
+ # update get_iterator() API so that we can distinguish "no such
+ # zone" and other cases (#1373). For now we consider all these
+ # cases as NOTAUTH.
+ return Rcode.NOTAUTH()
+
+ # If we are an authoritative name server for the zone, but fail
+ # to find the zone's SOA record in datasource, xfrout can't
+ # provide zone transfer for it.
+ self._soa = self._iterator.get_soa()
+ if self._soa is None or self._soa.get_rdata_count() != 1:
+ return Rcode.SERVFAIL()
+
+ return Rcode.NOERROR()
+
+ def __setup_ixfr(self, request_msg, zone_name):
+ '''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.
+
+ '''
+ # TODO: more error case handling
+ remote_soa = None
+ for auth_rrset in request_msg.get_section(Message.SECTION_AUTHORITY):
+ if auth_rrset.get_type() != RRType.SOA():
+ continue
+ remote_soa = auth_rrset
+ rcode, self._soa = self._get_zone_soa(zone_name)
+ if rcode != Rcode.NOERROR():
+ return rcode
+ code, self._jnl_reader = self._datasrc_client.get_journal_reader(
+ zone_name, get_soa_serial(remote_soa.get_rdata()[0]),
+ get_soa_serial(self._soa.get_rdata()[0]))
+ if code == ZoneJournalReader.NO_SUCH_VERSION:
+ # fallback to AXFR-style IXFR
+ return self.__setup_axfr(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.
+ return Rcode.NOTAUTH()
+
+ return Rcode.NOERROR()
- def _check_xfrout_available(self, request_msg, 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 _xfrout_setup(self, request_msg, zone_name):
+ '''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.
'''
@@ -340,51 +411,16 @@ class XfroutSession():
# 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
+ # 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():
- try:
- # Note that we disable 'adjust_ttl'. In xfr-out we need to
- # preserve as many things as possible (even if it's half
- # broken) stored in the zone.
- self._iterator = self._datasrc_client.get_iterator(zone_name,
- False)
- except isc.datasrc.Error:
- # If the current name server does not have authority for the
- # zone, xfrout can't serve for it, return rcode NOTAUTH.
- # Note: this exception can happen for other reasons. We should
- # update get_iterator() API so that we can distinguish "no such
- # zone" and other cases (#1373). For now we consider all these
- # cases as NOTAUTH.
- return Rcode.NOTAUTH()
-
- self._soa = self._iterator.get_soa()
+ return self.__setup_axfr(zone_name)
else:
- # TODO: error case handling
- remote_soa = None
- for auth_rrset in \
- request_msg.get_section(Message.SECTION_AUTHORITY):
- if auth_rrset.get_type() != RRType.SOA():
- continue
- remote_soa = auth_rrset
- self._soa = self._get_zone_soa(remote_soa.get_name())
- code, self._jnl_reader = self._datasrc_client.get_journal_reader(
- remote_soa.get_name(),
- get_soa_serial(remote_soa.get_rdata()[0]),
- get_soa_serial(self._soa.get_rdata()[0]))
-
- # If we are an authoritative name server for the zone, but fail
- # to find the zone's SOA record in datasource, xfrout can't
- # provide zone transfer for it.
- if self._soa is None or self._soa.get_rdata_count() != 1:
- return Rcode.SERVFAIL()
-
- return Rcode.NOERROR()
-
+ return self.__setup_ixfr(request_msg, zone_name)
def dns_xfrout_start(self, sock_fd, msg_query, quota_ok=True):
rcode_, msg = self._parse_query_message(msg_query)
@@ -410,7 +446,7 @@ class XfroutSession():
# TODO: we should also include class in the check
try:
- rcode_ = self._check_xfrout_available(msg, zone_name)
+ rcode_ = self._xfrout_setup(msg, zone_name)
except Exception as ex:
logger.error(XFROUT_XFR_TRANSFER_CHECK_ERROR, self._request_typestr,
format_addrinfo(self._remote), zone_str, ex)
@@ -458,7 +494,6 @@ 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()
More information about the bind10-changes
mailing list