From: JINMEI Tatuya Date: Tue, 4 Oct 2011 00:25:58 +0000 (-0700) Subject: [1261] extended _create_query() so that it can send an IXFR query. X-Git-Tag: perftcpdns_before_epoll~37^2~21^2~13^2~33 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=e8e1bd309d449b23dae2b472b650a130300aa760;p=thirdparty%2Fkea.git [1261] extended _create_query() so that it can send an IXFR query. --- diff --git a/src/bin/xfrin/tests/xfrin_test.py b/src/bin/xfrin/tests/xfrin_test.py index 154f6c90f1..68018129d6 100644 --- a/src/bin/xfrin/tests/xfrin_test.py +++ b/src/bin/xfrin/tests/xfrin_test.py @@ -43,11 +43,20 @@ TEST_MASTER_PORT = '53535' TSIG_KEY = TSIGKey("example.com:SFuWd/q99SzF8Yzd1QbB9g==") +# SOA intended to be used for the new SOA as a result of transfer. soa_rdata = Rdata(RRType.SOA(), TEST_RRCLASS, 'master.example.com. admin.example.com ' + '1234 3600 1800 2419200 7200') soa_rrset = RRset(TEST_ZONE_NAME, TEST_RRCLASS, RRType.SOA(), RRTTL(3600)) soa_rrset.add_rdata(soa_rdata) + +# SOA intended to be used for the current SOA at the secondary side. +# Note that its serial is smaller than that of soa_rdata. +begin_soa_rdata = Rdata(RRType.SOA(), TEST_RRCLASS, + 'master.example.com. admin.example.com ' + + '1230 3600 1800 2419200 7200') +begin_soa_rrset = RRset(TEST_ZONE_NAME, TEST_RRCLASS, RRType.SOA(), RRTTL(3600)) +begin_soa_rrset.add_rdata(begin_soa_rdata) example_axfr_question = Question(TEST_ZONE_NAME, TEST_RRCLASS, RRType.AXFR()) example_soa_question = Question(TEST_ZONE_NAME, TEST_RRCLASS, RRType.SOA()) default_questions = [example_axfr_question] @@ -107,6 +116,48 @@ class MockXfrinConnection(XfrinConnection): self.qid = None self.response_generator = None + # The following three implement a simplified mock of DataSourceClient + # and ZoneFinder classes for testing purposes. + def _get_datasrc_client(self, rrclass): + return self + + def find_zone(self, zone_name): + '''Mock DataSourceClient.find_zone(). + + It returns itself (subsequently acting as a mock ZoneFinder) for + some test zone names. For some others it returns either NOTFOUND + or PARTIALMATCH. + + ''' + if zone_name == TEST_ZONE_NAME or \ + zone_name == Name('no-soa.example') or \ + zone_name == Name('dup-soa.example'): + return (isc.datasrc.DataSourceClient.SUCCESS, self) + elif zone_name == Name('no-such-zone.example'): + return (DataSourceClient.NOTFOUND, None) + elif zone_name == Name('partial-match-zone.example'): + return (DataSourceClient.PARTIALMATCH, self) + raise ValueError('Unexpected input to mock client: bug in test case?') + + def find(self, name, rrtype, target, options): + '''Mock ZoneFinder.find(). + + It returns the predefined SOA RRset to queries for SOA of the common + test zone name. It also emulates some unusual cases for special + zone names. + + ''' + if name == TEST_ZONE_NAME and rrtype == RRType.SOA(): + return (ZoneFinder.SUCCESS, begin_soa_rrset) + if name == Name('no-soa.example'): + return (ZoneFinder.NXDOMAIN, None) + if name == Name('dup-soa.example'): + dup_soa_rrset = RRset(name, TEST_RRCLASS, RRType.SOA(), RRTTL(0)) + dup_soa_rrset.add_rdata(begin_soa_rdata) + dup_soa_rrset.add_rdata(soa_rdata) + return (ZoneFinder.SUCCESS, dup_soa_rrset) + raise ValueError('Unexpected input to mock finder: bug in test case?') + def _asyncore_loop(self): if self.force_close: self.handle_close() @@ -450,7 +501,7 @@ class TestXfrinConnection(unittest.TestCase): c.close() def test_create_query(self): - def check_query(expected_qtype, expected_authority): + def check_query(expected_qtype, expected_auth): '''Helper method to repeat the same pattern of tests''' self.assertEqual(Opcode.QUERY(), msg.get_opcode()) self.assertEqual(Rcode.NOERROR(), msg.get_rcode()) @@ -459,15 +510,24 @@ class TestXfrinConnection(unittest.TestCase): self.assertEqual(expected_qtype, msg.get_question()[0].get_type()) self.assertEqual(0, msg.get_rr_count(Message.SECTION_ANSWER)) self.assertEqual(0, msg.get_rr_count(Message.SECTION_ADDITIONAL)) - if expected_authority is None: + if expected_auth is None: self.assertEqual(0, msg.get_rr_count(Message.SECTION_AUTHORITY)) else: self.assertEqual(1, msg.get_rr_count(Message.SECTION_AUTHORITY)) + auth_rr = msg.get_section(Message.SECTION_AUTHORITY)[0] + self.assertEqual(expected_auth.get_name(), auth_rr.get_name()) + self.assertEqual(expected_auth.get_type(), auth_rr.get_type()) + self.assertEqual(expected_auth.get_class(), + auth_rr.get_class()) + # In our test scenario RDATA must be 1 + self.assertEqual(1, expected_auth.get_rdata_count()) + self.assertEqual(1, auth_rr.get_rdata_count()) + self.assertEqual(expected_auth.get_rdata()[0], + auth_rr.get_rdata()[0]) # Actual tests start here - # SOA query msg = self.conn._create_query(RRType.SOA()) check_query(RRType.SOA(), None) @@ -476,6 +536,31 @@ class TestXfrinConnection(unittest.TestCase): msg = self.conn._create_query(RRType.AXFR()) check_query(RRType.AXFR(), None) + # IXFR query + msg = self.conn._create_query(RRType.IXFR()) + check_query(RRType.IXFR(), begin_soa_rrset) + self.assertEqual(1230, self.conn._request_serial) + + def test_create_ixfr_query_fail(self): + # In these cases _create_query() will fail to find a valid SOA RR to + # insert in the IXFR query, and should raise an exception. + + self.conn._zone_name = Name('no-such-zone.example') + self.assertRaises(XfrinException, self.conn._create_query, + RRType.IXFR()) + + self.conn._zone_name = Name('partial-match-zone.example') + self.assertRaises(XfrinException, self.conn._create_query, + RRType.IXFR()) + + self.conn._zone_name = Name('no-soa.example') + self.assertRaises(XfrinException, self.conn._create_query, + RRType.IXFR()) + + self.conn._zone_name = Name('dup-soa.example') + self.assertRaises(XfrinException, self.conn._create_query, + RRType.IXFR()) + def test_send_query(self): def message_has_tsig(data): # a simple check if the actual data contains a TSIG RR. diff --git a/src/bin/xfrin/xfrin.py.in b/src/bin/xfrin/xfrin.py.in index abd815155a..977c227263 100755 --- a/src/bin/xfrin/xfrin.py.in +++ b/src/bin/xfrin/xfrin.py.in @@ -28,8 +28,9 @@ from optparse import OptionParser, OptionValueError from isc.config.ccsession import * from isc.notify import notify_out import isc.util.process +from isc.datasrc import DataSourceClient, ZoneFinder import isc.net.parse -import isc.xfrin.diff +from isc.xfrin.diff import Diff from isc.log_messages.xfrin_messages import * isc.log.init("b10-xfrin") @@ -181,7 +182,7 @@ class XfrinIXFRDeleteSOA(XfrinState): def handle_rr(self, conn, rr): if rr.get_type() != RRType.SOA(): # this shouldn't happen; should this occur it means an internal - # + # bug. raise XfrinException(rr.get_type().to_text() + \ ' RR is given in IXFRDeleteSOA state') conn._diff.remove_data(rr) @@ -191,6 +192,7 @@ class XfrinIXFRDeleteSOA(XfrinState): class XfrinIXFRDelete(XfrinState): def handle_rr(self, conn, rr): if rr.get_type() == RRType.SOA(): + # This is the only place where current_serial is set conn._current_serial = get_soa_serial(rr.get_rdata()[0]) self.set_xfrstate(conn, XfrinIXFRAddSOA()) return False @@ -201,7 +203,7 @@ class XfrinIXFRAddSOA(XfrinState): def handle_rr(self, conn, rr): if rr.get_type() != RRType.SOA(): # this shouldn't happen; should this occur it means an internal - # + # bug. raise XfrinException(rr.get_type().to_text() + \ ' RR is given in IXFRAddSOA state') conn._diff.add_data(rr) @@ -256,12 +258,17 @@ class XfrinConnection(asyncore.dispatcher): # transfer type may differ due to IXFR->AXFR fallback: self._request_type = None self._end_serial = None # essentially private - self._current_serial = None - self.create_socket(master_addrinfo[0], master_addrinfo[1]) + + # Zone parameters self._zone_name = zone_name - self._sock_map = sock_map self._rrclass = rrclass - self._db_file = db_file + + # Data source handlers + self._db_file = db_file # temporary for sqlite3 specific code + self._datasrc_client = self._get_datasrc_client(rrclass) + + self.create_socket(master_addrinfo[0], master_addrinfo[1]) + self._sock_map = sock_map self._soa_rr_count = 0 self._idle_timeout = idle_timeout self.setblocking(1) @@ -277,6 +284,10 @@ class XfrinConnection(asyncore.dispatcher): def __create_tsig_ctx(self, key): return TSIGContext(key) + def _get_datasrc_client(self, rrclass): + # Create a client here once #1206 is done + return None + def __set_xfrstate(self, new_state): self.__state = new_state @@ -298,16 +309,47 @@ class XfrinConnection(asyncore.dispatcher): return False def _create_query(self, query_type): - '''Create dns query message. ''' + '''Create an XFR-related query message. + + query_type is either SOA, AXFR or IXFR. For type IXFR, it searches + the associated data source for the current SOA record to include + it in the query. If the corresponding zone or the SOA record + cannot be found, it raises an XfrinException exception. Note that + this may not necessarily a broken configuration; for the first attempt + of transfer the secondary may not have any boot-strap zone + information, in which case IXFR simply won't work. The xfrin + should then fall back to AXFR. _request_serial is recorded for + later use. + ''' msg = Message(Message.RENDER) query_id = random.randint(0, 0xFFFF) self._query_id = query_id msg.set_qid(query_id) msg.set_opcode(Opcode.QUERY()) msg.set_rcode(Rcode.NOERROR()) - query_question = Question(self._zone_name, self._rrclass, query_type) - msg.add_question(query_question) + msg.add_question(Question(self._zone_name, self._rrclass, query_type)) + if query_type == RRType.IXFR(): + # get the zone finder. this must be SUCCESS (not even + # PARTIALMATCH) because we are specifying the zone origin name. + result, finder = self._datasrc_client.find_zone(self._zone_name) + if result != DataSourceClient.SUCCESS: + raise XfrinException('Zone not found in the given data ' + + 'source: ' + self.zone_str()) + result, soa_rrset = finder.find(self._zone_name, RRType.SOA(), + None, ZoneFinder.FIND_DEFAULT) + if result != ZoneFinder.SUCCESS: + raise XfrinException('SOA RR not found in zone: ' + + self.zone_str()) + # Especially for database-based zones, working zones 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: + raise XfrinException('Invalid number of SOA RRs for ' + + self.zone_str() + ': ' + + str(soa_rrset.get_rdata_count())) + msg.add_rrset(Message.SECTION_AUTHORITY, soa_rrset) + self._request_serial = get_soa_serial(soa_rrset.get_rdata()[0]) return msg def _send_data(self, data):