Browse Source

[1209] provided backward compatible behavior for AXFR + non existent zone

JINMEI Tatuya 13 years ago
parent
commit
3a7f572d5a
4 changed files with 64 additions and 43 deletions
  1. 1 10
      ChangeLog
  2. 0 7
      doc/guide/bind10-guide.xml
  3. 23 8
      src/bin/xfrin/tests/xfrin_test.py
  4. 40 18
      src/bin/xfrin/xfrin.py.in

+ 1 - 10
ChangeLog

@@ -1,4 +1,4 @@
-296.	[bug]		jinmei
+295.	[bug]		jinmei
 	__init__.py for isc.dns was installed in the wrong directory,
 	which would now make xfrin fail to start.  It was also bad
 	in that it replaced any existing __init__.py in th public
@@ -7,15 +7,6 @@
 	case it should be removed.
 	(Trac #1285, git af3b17472694f58b3d6a56d0baf64601b0f6a6a1)
 
-295.	[func]*		jinmei
-	b10-xfrin: the AXFR implementation is unified with IXFR, and
-	handles corner cases more carefully.  Note: As a result of this
-	change, xfrin does not create a new (SQLite3) zone in a fresh DB
-	file upon receiving AXFR any more.  Initial zone content must be
-	prepared by hand (e.g. with b10-loadzone) until a more generic
-	tool for zone management is provided.
-	(Trac #1209, git 5ca7b409bccc815cee58c804236504fda1c1c147)
-
 294.	[func]		jelte, jinmei, vorner
 	b10-xfrin now supports incoming IXFR.  See BIND 10 Guide for
 	how to configure it and operational notes.

+ 0 - 7
doc/guide/bind10-guide.xml

@@ -1276,13 +1276,6 @@ TODO
      In the current development release of BIND 10, incoming zone
      transfers are only available for SQLite3-based data sources,
      that is, they don't work for an in-memory data source.
-     Furthermore, the corresponding SQLite3 database must be
-     configured with a list of zone names by hand.  One possible way
-     to do this is to use the <command>b10-loadzone</command> command
-     to load dummy zone content of the zone for which the secondary
-     service is provided (and then force transfer using AXFR from the primary
-     server).  In future versions we will provide more convenient way
-     to set up the secondary.
     </simpara></note>
 
     <para>

+ 23 - 8
src/bin/xfrin/tests/xfrin_test.py

@@ -1483,11 +1483,14 @@ class TestXFRSessionWithSQLite3(TestXfrinConnection):
     def setUp(self):
         self.sqlite3db_src = TESTDATA_SRCDIR + '/example.com.sqlite3'
         self.sqlite3db_obj = TESTDATA_OBJDIR + '/example.com.sqlite3.copy'
+        self.empty_sqlite3db_obj = TESTDATA_OBJDIR + '/empty.sqlite3'
         self.sqlite3db_cfg = "{ \"database_file\": \"" +\
                              self.sqlite3db_obj + "\"}"
         super().setUp()
         if os.path.exists(self.sqlite3db_obj):
             os.unlink(self.sqlite3db_obj)
+        if os.path.exists(self.empty_sqlite3db_obj):
+            os.unlink(self.empty_sqlite3db_obj)
         shutil.copyfile(self.sqlite3db_src, self.sqlite3db_obj)
         self.conn._datasrc_client = DataSourceClient("sqlite3",
                                                      self.sqlite3db_cfg)
@@ -1495,6 +1498,8 @@ class TestXFRSessionWithSQLite3(TestXfrinConnection):
     def tearDown(self):
         if os.path.exists(self.sqlite3db_obj):
             os.unlink(self.sqlite3db_obj)
+        if os.path.exists(self.empty_sqlite3db_obj):
+            os.unlink(self.empty_sqlite3db_obj)
 
     def get_zone_serial(self):
         result, finder = self.conn._datasrc_client.find_zone(TEST_ZONE_NAME)
@@ -1612,20 +1617,30 @@ class TestXFRSessionWithSQLite3(TestXfrinConnection):
         self.axfr_failure_check(RRType.AXFR())
 
     def test_do_axfrin_nozone_sqlite3(self):
+        '''AXFR test with an empty SQLite3 DB file, thus no target zone there.
+
+        For now, we provide backward compatible behavior: xfrin will create
+        the zone (after even setting up the entire schema) in the zone.
+        Note: a future version of this test will make it fail.
+
+        '''
+        self.conn._db_file = self.empty_sqlite3db_obj
+        self.conn._datasrc_client = DataSourceClient(
+            "sqlite3",
+            "{ \"database_file\": \"" + self.empty_sqlite3db_obj + "\"}")
         def create_response():
-            # Within this test, owner names of the question/RRs don't matter,
-            # so we use pre-defined names (which are "out of zone") for
-            # simplicity.
             self.conn.reply_data = self.conn.create_response_data(
                 questions=[Question(TEST_ZONE_NAME, TEST_RRCLASS,
                                     RRType.AXFR())],
-                answers=[soa_rrset, self._create_ns(), soa_rrset, soa_rrset])
+                answers=[soa_rrset, self._create_ns(), soa_rrset])
         self.conn.response_generator = create_response
-        self.conn._zone_name = Name('nosuchzone.example')
-        self.assertEqual(XFRIN_FAIL, self.conn.do_xfrin(False, RRType.AXFR()))
-        # This should fail in the FirstData state
-        self.assertEqual(type(XfrinFirstData()),
+        self.conn._zone_name = Name('example.com')
+        self.assertEqual(XFRIN_OK, self.conn.do_xfrin(False, RRType.AXFR()))
+        self.assertEqual(type(XfrinAXFREnd()),
                          type(self.conn.get_xfrstate()))
+        self.assertEqual(1234, self.get_zone_serial())
+        self.assertFalse(self.record_exist(Name('dns01.example.com'),
+                                           RRType.A()))
 
 class TestXfrinRecorder(unittest.TestCase):
     def setUp(self):

+ 40 - 18
src/bin/xfrin/xfrin.py.in

@@ -504,6 +504,25 @@ class XfrinConnection(asyncore.dispatcher):
             logger.error(XFRIN_CONNECT_MASTER, self._master_address, str(e))
             return False
 
+    def _get_zone_soa(self):
+        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, 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:
+            raise XfrinException('Invalid number of SOA RRs for ' +
+                                 self.zone_str() + ': ' +
+                                 str(soa_rrset.get_rdata_count()))
+        return soa_rrset
+
     def _create_query(self, query_type):
         '''Create an XFR-related query message.
 
@@ -528,24 +547,24 @@ class XfrinConnection(asyncore.dispatcher):
         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, 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:
-                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])
+            zone_soa_rr = self._get_zone_soa()
+            msg.add_rrset(Message.SECTION_AUTHORITY, zone_soa_rr)
+            self._request_serial = get_soa_serial(zone_soa_rr.get_rdata()[0])
+        else:
+            # For AXFR, we temporariy provide backward compatible behavior
+            # where xfrin is responsible create zone in the corresponding
+            # DB table.  Note that the code below uses the old data source
+            # API and assumes SQLite3 in an ugly manner.  We'll have to
+            # develop a better way of managing zones in a generic way and
+            # eliminate the code like the one here.
+            try:
+                self._get_zone_soa()
+            except XfrinException:
+                def empty_rr_generator():
+                    return []
+                isc.datasrc.sqlite3_ds.load(self._db_file,
+                                            self._zone_name.to_text(),
+                                            empty_rr_generator)
         return msg
 
     def _send_data(self, data):
@@ -803,6 +822,9 @@ def process_xfrin(server, xfrin_recorder, zone_name, rrclass, db_file,
     sock_map = {}
     conn = XfrinConnection(sock_map, zone_name, rrclass, datasrc_client,
                            shutdown_event, master_addrinfo, tsig_key)
+    # XXX: We still need _db_file for temporary workaround in _create_query().
+    # This should be removed when we eliminate the need for the workaround.
+    conn._db_file = db_file
     ret = XFRIN_FAIL
     if conn.connect_to_master():
         ret = conn.do_xfrin(check_soa, request_type)