Browse Source

[master] [1299] handle the case of primary's SOA serial is smaller than that of xfrin.
(there are some corner cases that haven't been addressed yet)

JINMEI Tatuya 13 years ago
parent
commit
9351ba5d54
3 changed files with 68 additions and 7 deletions
  1. 27 0
      src/bin/xfrin/tests/xfrin_test.py
  2. 31 7
      src/bin/xfrin/xfrin.py.in
  3. 10 0
      src/bin/xfrin/xfrin_messages.mes

+ 27 - 0
src/bin/xfrin/tests/xfrin_test.py

@@ -21,6 +21,7 @@ import sqlite3
 import sys
 import io
 from isc.testutils.tsigctx_mock import MockTSIGContext
+from isc.testutils.rrset_utils import *
 from xfrin import *
 import xfrin
 from isc.xfrin.diff import Diff
@@ -601,6 +602,7 @@ class TestXfrinConnection(unittest.TestCase):
             'response': True,
             'auth': True,
             'rcode': Rcode.NOERROR(),
+            'answers': default_answers,
             'tsig': False,
             'axfr_after_soa': self._create_normal_response_data
             }
@@ -659,6 +661,7 @@ class TestXfrinConnection(unittest.TestCase):
             response=self.soa_response_params['response'],
             auth=self.soa_response_params['auth'],
             rcode=self.soa_response_params['rcode'],
+            answers=self.soa_response_params['answers'],
             questions=self.soa_response_params['questions'],
             tsig_ctx=verify_ctx)
         if self.soa_response_params['axfr_after_soa'] != None:
@@ -948,6 +951,25 @@ class TestAXFR(TestXfrinConnection):
         self.conn.response_generator = self._create_soa_response_data
         self.assertRaises(XfrinException, self.conn._check_soa_serial)
 
+    def test_soacheck_uptodate(self):
+        # Primary's SOA serial is identical the local serial
+        self.soa_response_params['answers'] = [begin_soa_rrset]
+        self.conn.response_generator = self._create_soa_response_data
+        self.assertRaises(XfrinZoneUptodate, self.conn._check_soa_serial)
+
+    def test_soacheck_uptodate2(self):
+        # Primary's SOA serial is "smaller" than the local serial
+        self.soa_response_params['answers'] = [create_soa(1229)]
+        self.conn.response_generator = self._create_soa_response_data
+        self.assertRaises(XfrinZoneUptodate, self.conn._check_soa_serial)
+
+    def test_soacheck_uptodate3(self):
+        # Similar to the previous case, but checking the comparison is based
+        # on the serial number arithmetic.
+        self.soa_response_params['answers'] = [create_soa(0xffffffff)]
+        self.conn.response_generator = self._create_soa_response_data
+        self.assertRaises(XfrinZoneUptodate, self.conn._check_soa_serial)
+
     def test_soacheck_with_tsig(self):
         # Use a mock tsig context emulating a validly signed response
         self.conn._tsig_key = TSIG_KEY
@@ -1256,6 +1278,11 @@ class TestAXFR(TestXfrinConnection):
         self.conn.response_generator = self._create_soa_response_data
         self.assertEqual(self.conn.do_xfrin(True), XFRIN_OK)
 
+    def test_do_soacheck_uptodate(self):
+        self.soa_response_params['answers'] = [begin_soa_rrset]
+        self.conn.response_generator = self._create_soa_response_data
+        self.assertEqual(self.conn.do_xfrin(True), XFRIN_OK)
+
     def test_do_soacheck_and_xfrin_with_tsig(self):
         # We are going to have a SOA query/response transaction, followed by
         # AXFR, all TSIG signed.  xfrin should use a new TSIG context for

+ 31 - 7
src/bin/xfrin/xfrin.py.in

@@ -75,9 +75,10 @@ DEFAULT_MASTER_PORT = 53
 DEFAULT_ZONE_CLASS = RRClass.IN()
 
 __version__ = 'BIND10'
-# define xfrin rcode
-XFRIN_OK = 0
-XFRIN_FAIL = 1
+
+# Internal result codes of an xfr session
+XFRIN_OK = 0                    # normal success
+XFRIN_FAIL = 1                  # general failure (internal/external)
 
 class XfrinException(Exception):
     pass
@@ -87,6 +88,11 @@ class XfrinProtocolError(Exception):
     '''
     pass
 
+class XfrinZoneUptodate(Exception):
+    '''TBD
+    '''
+    pass
+
 class XfrinZoneInfoException(Exception):
     """This exception is raised if there is an error in the given
        configuration (part), or when a command does not have a required
@@ -709,15 +715,28 @@ class XfrinConnection(asyncore.dispatcher):
         msg = Message(Message.PARSE)
         msg.from_wire(soa_response)
 
-        # TSIG related checks, including an unexpected signed response
-        self._check_response_tsig(msg, soa_response)
-
-        # Validate the header.  Unlike AXFR/IXFR, we should be more strict
+        # Validate the message.  Unlike AXFR/IXFR, we should be more strict
         # for SOA queries and check the AA flag, too.
+        self._check_response_tsig(msg, soa_response)
         self._check_response_header(msg)
         if not msg.get_header_flag(Message.HEADERFLAG_AA):
             raise XfrinException('non-authoritative answer to SOA query')
 
+        # Examine the answer section
+        soa = None
+        for rrset in msg.get_section(Message.SECTION_ANSWER):
+            if rrset.get_type() == RRType.SOA():
+                soa = rrset
+        primary_serial = get_soa_serial(soa.get_rdata()[0])
+        if (self._request_serial is not None) and \
+                self._request_serial >= primary_serial:
+            if self._request_serial != primary_serial:
+                logger.info(XFRIN_ZONE_SERIAL_AHEAD, primary_serial,
+                            self.zone_str(),
+                            format_addrinfo(self._master_addrinfo),
+                            self._request_serial)
+            raise XfrinZoneUptodate
+
         # TODO, need select soa record from data source then compare the two
         # serial, current just return OK, since this function hasn't been used
         # now.
@@ -744,6 +763,11 @@ class XfrinConnection(asyncore.dispatcher):
                 logger.info(XFRIN_XFR_TRANSFER_SUCCESS, request_str,
                             self.zone_str())
 
+        except XfrinZoneUptodate:
+            # Eventually we'll probably have to treat this case as a trigger
+            # of trying another primary server, etc, but for now We treat it
+            # as "success".
+            pass
         except (XfrinException, XfrinProtocolError) as e:
             logger.error(XFRIN_XFR_TRANSFER_FAILURE, request_str,
                          self.zone_str(),

+ 10 - 0
src/bin/xfrin/xfrin_messages.mes

@@ -45,6 +45,16 @@ and tries to continue processing as if the zone were empty.  This
 means subsequent AXFR can succeed and possibly replace the zone with
 valid content, but an IXFR attempt will fail.
 
+% XFRIN_ZONE_SERIAL_AHEAD Serial number (%1) for %2 received from master %3 < ours (%4)
+The response to an SOA query prior to xfr indicated that the zone's
+SOA serial at the primary server is smaller than that of the xfrin
+client.  This is not necessarily an error especially if that
+particular primary server is another secondary server which hasn't got
+the latest version of the zone.  But if the primary server is known to
+be the real source of the zone, some unexpected inconsistency may have
+happened, and you may want to take a closer look.  In this case xfrin
+doesn't perform subsequent zone transfer.
+
 % XFRIN_XFR_OTHER_FAILURE %1 transfer of zone %2 failed: %3
 The XFR transfer for the given zone has failed due to a problem outside
 of the xfrin module.  Possible reasons are a broken DNS message or failure