Browse Source

[master] [1299] added more detailed validity check on SOA response.
also introduce a separate log message ID for XfrinProtocolError at the
INFO level (ERROR isn't appropriate because these are generally uncontrollable
events), and made sure we use the XfrinProtocolError for errors due to
the remote behavior.

JINMEI Tatuya 13 years ago
parent
commit
a4f7763150

+ 77 - 17
src/bin/xfrin/tests/xfrin_test.py

@@ -277,6 +277,7 @@ class MockXfrinConnection(XfrinConnection):
                              rcode=Rcode.NOERROR(),
                              rcode=Rcode.NOERROR(),
                              questions=default_questions,
                              questions=default_questions,
                              answers=default_answers,
                              answers=default_answers,
+                             authorities=[],
                              tsig_ctx=None):
                              tsig_ctx=None):
         resp = Message(Message.RENDER)
         resp = Message(Message.RENDER)
         qid = self.qid
         qid = self.qid
@@ -291,6 +292,7 @@ class MockXfrinConnection(XfrinConnection):
             resp.set_header_flag(Message.HEADERFLAG_AA)
             resp.set_header_flag(Message.HEADERFLAG_AA)
         [resp.add_question(q) for q in questions]
         [resp.add_question(q) for q in questions]
         [resp.add_rrset(Message.SECTION_ANSWER, a) for a in answers]
         [resp.add_rrset(Message.SECTION_ANSWER, a) for a in answers]
+        [resp.add_rrset(Message.SECTION_AUTHORITY, a) for a in authorities]
 
 
         renderer = MessageRenderer()
         renderer = MessageRenderer()
         if tsig_ctx is not None:
         if tsig_ctx is not None:
@@ -603,6 +605,7 @@ class TestXfrinConnection(unittest.TestCase):
             'auth': True,
             'auth': True,
             'rcode': Rcode.NOERROR(),
             'rcode': Rcode.NOERROR(),
             'answers': default_answers,
             'answers': default_answers,
+            'authorities': [],
             'tsig': False,
             'tsig': False,
             'axfr_after_soa': self._create_normal_response_data
             'axfr_after_soa': self._create_normal_response_data
             }
             }
@@ -661,8 +664,9 @@ class TestXfrinConnection(unittest.TestCase):
             response=self.soa_response_params['response'],
             response=self.soa_response_params['response'],
             auth=self.soa_response_params['auth'],
             auth=self.soa_response_params['auth'],
             rcode=self.soa_response_params['rcode'],
             rcode=self.soa_response_params['rcode'],
-            answers=self.soa_response_params['answers'],
             questions=self.soa_response_params['questions'],
             questions=self.soa_response_params['questions'],
+            answers=self.soa_response_params['answers'],
+            authorities=self.soa_response_params['authorities'],
             tsig_ctx=verify_ctx)
             tsig_ctx=verify_ctx)
         if self.soa_response_params['axfr_after_soa'] != None:
         if self.soa_response_params['axfr_after_soa'] != None:
             self.conn.response_generator = \
             self.conn.response_generator = \
@@ -846,8 +850,10 @@ class TestAXFR(TestXfrinConnection):
         self.conn._tsig_key = TSIG_KEY
         self.conn._tsig_key = TSIG_KEY
         # server tsig check fail, return with RCODE 9 (NOTAUTH)
         # server tsig check fail, return with RCODE 9 (NOTAUTH)
         self.conn._send_query(RRType.SOA())
         self.conn._send_query(RRType.SOA())
-        self.conn.reply_data = self.conn.create_response_data(rcode=Rcode.NOTAUTH())
-        self.assertRaises(XfrinException, self.conn._handle_xfrin_responses)
+        self.conn.reply_data = \
+            self.conn.create_response_data(rcode=Rcode.NOTAUTH())
+        self.assertRaises(XfrinProtocolError,
+                          self.conn._handle_xfrin_responses)
 
 
     def test_response_without_end_soa(self):
     def test_response_without_end_soa(self):
         self.conn._send_query(RRType.AXFR())
         self.conn._send_query(RRType.AXFR())
@@ -860,7 +866,8 @@ class TestAXFR(TestXfrinConnection):
     def test_response_bad_qid(self):
     def test_response_bad_qid(self):
         self.conn._send_query(RRType.AXFR())
         self.conn._send_query(RRType.AXFR())
         self.conn.reply_data = self.conn.create_response_data(bad_qid=True)
         self.conn.reply_data = self.conn.create_response_data(bad_qid=True)
-        self.assertRaises(XfrinException, self.conn._handle_xfrin_responses)
+        self.assertRaises(XfrinProtocolError,
+                          self.conn._handle_xfrin_responses)
 
 
     def test_response_error_code_bad_sig(self):
     def test_response_error_code_bad_sig(self):
         self.conn._tsig_key = TSIG_KEY
         self.conn._tsig_key = TSIG_KEY
@@ -871,7 +878,7 @@ class TestAXFR(TestXfrinConnection):
                 rcode=Rcode.SERVFAIL())
                 rcode=Rcode.SERVFAIL())
         # xfrin should check TSIG before other part of incoming message
         # xfrin should check TSIG before other part of incoming message
         # validate log message for XfrinException
         # validate log message for XfrinException
-        self.__match_exception(XfrinException,
+        self.__match_exception(XfrinProtocolError,
                                "TSIG verify fail: BADSIG",
                                "TSIG verify fail: BADSIG",
                                self.conn._handle_xfrin_responses)
                                self.conn._handle_xfrin_responses)
 
 
@@ -883,7 +890,7 @@ class TestAXFR(TestXfrinConnection):
         self.conn.reply_data = self.conn.create_response_data(bad_qid=True)
         self.conn.reply_data = self.conn.create_response_data(bad_qid=True)
         # xfrin should check TSIG before other part of incoming message
         # xfrin should check TSIG before other part of incoming message
         # validate log message for XfrinException
         # validate log message for XfrinException
-        self.__match_exception(XfrinException,
+        self.__match_exception(XfrinProtocolError,
                                "TSIG verify fail: BADKEY",
                                "TSIG verify fail: BADKEY",
                                self.conn._handle_xfrin_responses)
                                self.conn._handle_xfrin_responses)
 
 
@@ -896,18 +903,21 @@ class TestAXFR(TestXfrinConnection):
         self.conn._send_query(RRType.AXFR())
         self.conn._send_query(RRType.AXFR())
         self.conn.reply_data = self.conn.create_response_data(
         self.conn.reply_data = self.conn.create_response_data(
             rcode=Rcode.SERVFAIL())
             rcode=Rcode.SERVFAIL())
-        self.assertRaises(XfrinException, self.conn._handle_xfrin_responses)
+        self.assertRaises(XfrinProtocolError,
+                          self.conn._handle_xfrin_responses)
 
 
     def test_response_multi_question(self):
     def test_response_multi_question(self):
         self.conn._send_query(RRType.AXFR())
         self.conn._send_query(RRType.AXFR())
         self.conn.reply_data = self.conn.create_response_data(
         self.conn.reply_data = self.conn.create_response_data(
             questions=[example_axfr_question, example_axfr_question])
             questions=[example_axfr_question, example_axfr_question])
-        self.assertRaises(XfrinException, self.conn._handle_xfrin_responses)
+        self.assertRaises(XfrinProtocolError,
+                          self.conn._handle_xfrin_responses)
 
 
     def test_response_non_response(self):
     def test_response_non_response(self):
         self.conn._send_query(RRType.AXFR())
         self.conn._send_query(RRType.AXFR())
         self.conn.reply_data = self.conn.create_response_data(response = False)
         self.conn.reply_data = self.conn.create_response_data(response = False)
-        self.assertRaises(XfrinException, self.conn._handle_xfrin_responses)
+        self.assertRaises(XfrinProtocolError,
+                          self.conn._handle_xfrin_responses)
 
 
     def test_soacheck(self):
     def test_soacheck(self):
         # we need to defer the creation until we know the QID, which is
         # we need to defer the creation until we know the QID, which is
@@ -922,7 +932,7 @@ class TestAXFR(TestXfrinConnection):
     def test_soacheck_badqid(self):
     def test_soacheck_badqid(self):
         self.soa_response_params['bad_qid'] = True
         self.soa_response_params['bad_qid'] = True
         self.conn.response_generator = self._create_soa_response_data
         self.conn.response_generator = self._create_soa_response_data
-        self.assertRaises(XfrinException, self.conn._check_soa_serial)
+        self.assertRaises(XfrinProtocolError, self.conn._check_soa_serial)
 
 
     def test_soacheck_bad_qid_bad_sig(self):
     def test_soacheck_bad_qid_bad_sig(self):
         self.conn._tsig_key = TSIG_KEY
         self.conn._tsig_key = TSIG_KEY
@@ -932,19 +942,19 @@ class TestAXFR(TestXfrinConnection):
         self.conn.response_generator = self._create_soa_response_data
         self.conn.response_generator = self._create_soa_response_data
         # xfrin should check TSIG before other part of incoming message
         # xfrin should check TSIG before other part of incoming message
         # validate log message for XfrinException
         # validate log message for XfrinException
-        self.__match_exception(XfrinException,
+        self.__match_exception(XfrinProtocolError,
                                "TSIG verify fail: BADSIG",
                                "TSIG verify fail: BADSIG",
                                self.conn._check_soa_serial)
                                self.conn._check_soa_serial)
 
 
     def test_soacheck_non_response(self):
     def test_soacheck_non_response(self):
         self.soa_response_params['response'] = False
         self.soa_response_params['response'] = False
         self.conn.response_generator = self._create_soa_response_data
         self.conn.response_generator = self._create_soa_response_data
-        self.assertRaises(XfrinException, self.conn._check_soa_serial)
+        self.assertRaises(XfrinProtocolError, self.conn._check_soa_serial)
 
 
     def test_soacheck_error_code(self):
     def test_soacheck_error_code(self):
         self.soa_response_params['rcode'] = Rcode.SERVFAIL()
         self.soa_response_params['rcode'] = Rcode.SERVFAIL()
         self.conn.response_generator = self._create_soa_response_data
         self.conn.response_generator = self._create_soa_response_data
-        self.assertRaises(XfrinException, self.conn._check_soa_serial)
+        self.assertRaises(XfrinProtocolError, self.conn._check_soa_serial)
 
 
     def test_soacheck_notauth(self):
     def test_soacheck_notauth(self):
         self.soa_response_params['auth'] = False
         self.soa_response_params['auth'] = False
@@ -996,6 +1006,49 @@ class TestAXFR(TestXfrinConnection):
                                                           RRType.AAAA())]
                                                           RRType.AAAA())]
         self.assertRaises(XfrinProtocolError, self.conn._check_soa_serial)
         self.assertRaises(XfrinProtocolError, self.conn._check_soa_serial)
 
 
+    def test_soacheck_no_soa(self):
+        # The response just doesn't contain SOA without any other indication
+        # of errors.
+        self.conn.response_generator = self._create_soa_response_data
+        self.soa_response_params['answers'] = []
+        self.assertRaises(XfrinProtocolError, self.conn._check_soa_serial)
+
+    def test_soacheck_soa_name_mismatch(self):
+        self.conn.response_generator = self._create_soa_response_data
+        self.soa_response_params['answers'] = [create_soa(1234,
+                                                          Name('example.org'))]
+        self.assertRaises(XfrinProtocolError, self.conn._check_soa_serial)
+
+    def test_soacheck_soa_class_mismatch(self):
+        self.conn.response_generator = self._create_soa_response_data
+        soa = RRset(TEST_ZONE_NAME, RRClass.CH(), RRType.SOA(), RRTTL(0))
+        soa.add_rdata(Rdata(RRType.SOA(), RRClass.CH(), 'm. r. 1234 0 0 0 0'))
+        self.soa_response_params['answers'] = [soa]
+        self.assertRaises(XfrinProtocolError, self.conn._check_soa_serial)
+
+    def test_soacheck_multiple_soa(self):
+        self.conn.response_generator = self._create_soa_response_data
+        self.soa_response_params['answers'] = [soa_rrset, soa_rrset]
+        self.assertRaises(XfrinProtocolError, self.conn._check_soa_serial)
+
+    def test_soacheck_cname_response(self):
+        self.conn.response_generator = self._create_soa_response_data
+        # Add SOA to answer, too, to make sure that it that deceives the parser
+        self.soa_response_params['answers'] = [soa_rrset, create_cname()]
+        self.assertRaises(XfrinProtocolError, self.conn._check_soa_serial)
+
+    def test_soacheck_referral_response(self):
+        self.conn.response_generator = self._create_soa_response_data
+        self.soa_response_params['answers'] = []
+        self.soa_response_params['authorities'] = [create_ns('ns.example.com')]
+        self.assertRaises(XfrinProtocolError, self.conn._check_soa_serial)
+
+    def test_soacheck_nodata_response(self):
+        self.conn.response_generator = self._create_soa_response_data
+        self.soa_response_params['answers'] = []
+        self.soa_response_params['authorities'] = [soa_rrset]
+        self.assertRaises(XfrinProtocolError, self.conn._check_soa_serial)
+
     def test_soacheck_with_tsig(self):
     def test_soacheck_with_tsig(self):
         # Use a mock tsig context emulating a validly signed response
         # Use a mock tsig context emulating a validly signed response
         self.conn._tsig_key = TSIG_KEY
         self.conn._tsig_key = TSIG_KEY
@@ -1013,7 +1066,7 @@ class TestAXFR(TestXfrinConnection):
         self.soa_response_params['rcode'] = Rcode.NOTAUTH()
         self.soa_response_params['rcode'] = Rcode.NOTAUTH()
         self.conn.response_generator = self._create_soa_response_data
         self.conn.response_generator = self._create_soa_response_data
 
 
-        self.assertRaises(XfrinException, self.conn._check_soa_serial)
+        self.assertRaises(XfrinProtocolError, self.conn._check_soa_serial)
 
 
     def test_soacheck_with_tsig_noerror_badsig(self):
     def test_soacheck_with_tsig_noerror_badsig(self):
         self.conn._tsig_key = TSIG_KEY
         self.conn._tsig_key = TSIG_KEY
@@ -1026,7 +1079,7 @@ class TestAXFR(TestXfrinConnection):
         # treat this as a final failure (just as BIND 9 does).
         # treat this as a final failure (just as BIND 9 does).
         self.conn.response_generator = self._create_soa_response_data
         self.conn.response_generator = self._create_soa_response_data
 
 
-        self.assertRaises(XfrinException, self.conn._check_soa_serial)
+        self.assertRaises(XfrinProtocolError, self.conn._check_soa_serial)
 
 
     def test_soacheck_with_tsig_unsigned_response(self):
     def test_soacheck_with_tsig_unsigned_response(self):
         # we can use a real TSIGContext for this.  the response doesn't
         # we can use a real TSIGContext for this.  the response doesn't
@@ -1035,14 +1088,14 @@ class TestAXFR(TestXfrinConnection):
         # it as a fatal transaction failure, too.
         # it as a fatal transaction failure, too.
         self.conn._tsig_key = TSIG_KEY
         self.conn._tsig_key = TSIG_KEY
         self.conn.response_generator = self._create_soa_response_data
         self.conn.response_generator = self._create_soa_response_data
-        self.assertRaises(XfrinException, self.conn._check_soa_serial)
+        self.assertRaises(XfrinProtocolError, self.conn._check_soa_serial)
 
 
     def test_soacheck_with_unexpected_tsig_response(self):
     def test_soacheck_with_unexpected_tsig_response(self):
         # we reject unexpected TSIG in responses (following BIND 9's
         # we reject unexpected TSIG in responses (following BIND 9's
         # behavior)
         # behavior)
         self.soa_response_params['tsig'] = True
         self.soa_response_params['tsig'] = True
         self.conn.response_generator = self._create_soa_response_data
         self.conn.response_generator = self._create_soa_response_data
-        self.assertRaises(XfrinException, self.conn._check_soa_serial)
+        self.assertRaises(XfrinProtocolError, self.conn._check_soa_serial)
 
 
     def test_response_shutdown(self):
     def test_response_shutdown(self):
         self.conn.response_generator = self._create_normal_response_data
         self.conn.response_generator = self._create_normal_response_data
@@ -1309,6 +1362,13 @@ class TestAXFR(TestXfrinConnection):
         self.conn.response_generator = self._create_soa_response_data
         self.conn.response_generator = self._create_soa_response_data
         self.assertEqual(self.conn.do_xfrin(True), XFRIN_OK)
         self.assertEqual(self.conn.do_xfrin(True), XFRIN_OK)
 
 
+    def test_do_soacheck_protocol_error(self):
+        # There are several cases, but at this level it's sufficient to check
+        # only one.  We use the case where there's no SOA in the response.
+        self.soa_response_params['answers'] = []
+        self.conn.response_generator = self._create_soa_response_data
+        self.assertEqual(self.conn.do_xfrin(True), XFRIN_FAIL)
+
     def test_do_soacheck_and_xfrin_with_tsig(self):
     def test_do_soacheck_and_xfrin_with_tsig(self):
         # We are going to have a SOA query/response transaction, followed by
         # We are going to have a SOA query/response transaction, followed by
         # AXFR, all TSIG signed.  xfrin should use a new TSIG context for
         # AXFR, all TSIG signed.  xfrin should use a new TSIG context for

+ 44 - 14
src/bin/xfrin/xfrin.py.in

@@ -24,6 +24,7 @@ import struct
 import threading
 import threading
 import socket
 import socket
 import random
 import random
+from functools import reduce
 from optparse import OptionParser, OptionValueError
 from optparse import OptionParser, OptionValueError
 from isc.config.ccsession import *
 from isc.config.ccsession import *
 from isc.notify import notify_out
 from isc.notify import notify_out
@@ -690,7 +691,8 @@ class XfrinConnection(asyncore.dispatcher):
         if self._tsig_ctx is not None:
         if self._tsig_ctx is not None:
             tsig_error = self._tsig_ctx.verify(tsig_record, response_data)
             tsig_error = self._tsig_ctx.verify(tsig_record, response_data)
             if tsig_error != TSIGError.NOERROR:
             if tsig_error != TSIGError.NOERROR:
-                raise XfrinException('TSIG verify fail: %s' % str(tsig_error))
+                raise XfrinProtocolError('TSIG verify fail: %s' %
+                                         str(tsig_error))
         elif tsig_record is not None:
         elif tsig_record is not None:
             # If the response includes a TSIG while we didn't sign the query,
             # If the response includes a TSIG while we didn't sign the query,
             # we treat it as an error.  RFC doesn't say anything about this
             # we treat it as an error.  RFC doesn't say anything about this
@@ -699,7 +701,7 @@ class XfrinConnection(asyncore.dispatcher):
             # implementation would return such a response, and since this is
             # implementation would return such a response, and since this is
             # part of security mechanism, it's probably better to be more
             # part of security mechanism, it's probably better to be more
             # strict.
             # strict.
-            raise XfrinException('Unexpected TSIG in response')
+            raise XfrinProtocolError('Unexpected TSIG in response')
 
 
     def __parse_soa_response(self, msg, response_data):
     def __parse_soa_response(self, msg, response_data):
         '''Parse a response to SOA query and extranct the SOA from ansser.
         '''Parse a response to SOA query and extranct the SOA from ansser.
@@ -730,12 +732,34 @@ class XfrinConnection(asyncore.dispatcher):
             raise XfrinProtocolError('Questions mismatch to ' +
             raise XfrinProtocolError('Questions mismatch to ' +
                                      'SOA query: ' + str(resp_question))
                                      'SOA query: ' + str(resp_question))
 
 
-        # Examine the answer section
+        # Look into the answer section for SOA
         soa = None
         soa = None
-        for rrset in msg.get_section(Message.SECTION_ANSWER):
-            if rrset.get_type() == RRType.SOA():
-                soa = rrset
-
+        for rr in msg.get_section(Message.SECTION_ANSWER):
+            if rr.get_type() == RRType.SOA():
+                if soa is not None:
+                    raise XfrinProtocolError('SOA response had multiple SOAs')
+                soa = rr
+            # There should not be a CNAME record at top of zone.
+            if rr.get_type() == RRType.CNAME():
+                raise XfrinProtocolError('SOA query resulted in CNAME')
+
+        # If SOA is not found, try to figure out the reason then report it.
+        if soa is None:
+            # See if we have any SOA records in the authority section.
+            for rr in msg.get_section(Message.SECTION_AUTHORITY):
+                if rr.get_type() == RRType.NS():
+                    raise XfrinProtocolError('SOA query resulted in referral')
+                if rr.get_type() == RRType.SOA():
+                    raise XfrinProtocolError('SOA query resulted in NODATA')
+            raise XfrinProtocolError('SOA query resulted in no SOA at all')
+
+        # Check if the SOA is really what we asked for
+        if soa.get_name() != self._zone_name or \
+                soa.get_class() != self._rrclass:
+            raise XfrinProtocolError("SOA response doesn't match query: " +
+                                     str(soa))
+
+        # All okay, return it
         return soa
         return soa
 
 
 
 
@@ -751,14 +775,14 @@ class XfrinConnection(asyncore.dispatcher):
         msg_len = socket.htons(struct.unpack('H', data_len)[0])
         msg_len = socket.htons(struct.unpack('H', data_len)[0])
         soa_response = self._get_request_response(msg_len)
         soa_response = self._get_request_response(msg_len)
         msg = Message(Message.PARSE)
         msg = Message(Message.PARSE)
-        msg.from_wire(soa_response)
+        msg.from_wire(soa_response, Message.PRESERVE_ORDER)
 
 
         # Validate/parse the rest of the response, and extract the SOA
         # Validate/parse the rest of the response, and extract the SOA
         # from the answer section
         # from the answer section
         soa = self.__parse_soa_response(msg, soa_response)
         soa = self.__parse_soa_response(msg, soa_response)
 
 
         primary_serial = get_soa_serial(soa.get_rdata()[0])
         primary_serial = get_soa_serial(soa.get_rdata()[0])
-        if (self._request_serial is not None) and \
+        if self._request_serial is not None and \
                 self._request_serial >= primary_serial:
                 self._request_serial >= primary_serial:
             if self._request_serial != primary_serial:
             if self._request_serial != primary_serial:
                 logger.info(XFRIN_ZONE_SERIAL_AHEAD, primary_serial,
                 logger.info(XFRIN_ZONE_SERIAL_AHEAD, primary_serial,
@@ -798,7 +822,12 @@ class XfrinConnection(asyncore.dispatcher):
             # of trying another primary server, etc, but for now We treat it
             # of trying another primary server, etc, but for now We treat it
             # as "success".
             # as "success".
             pass
             pass
-        except (XfrinException, XfrinProtocolError) as e:
+        except XfrinProtocolError as e:
+            logger.info(XFRIN_XFR_TRANSFER_PROTOCOL_ERROR, request_str,
+                        self.zone_str(),
+                        format_addrinfo(self._master_addrinfo), str(e))
+            ret = XFRIN_FAIL
+        except XfrinException as e:
             logger.error(XFRIN_XFR_TRANSFER_FAILURE, request_str,
             logger.error(XFRIN_XFR_TRANSFER_FAILURE, request_str,
                          self.zone_str(),
                          self.zone_str(),
                          format_addrinfo(self._master_addrinfo), str(e))
                          format_addrinfo(self._master_addrinfo), str(e))
@@ -836,13 +865,14 @@ class XfrinConnection(asyncore.dispatcher):
 
 
         msg_rcode = msg.get_rcode()
         msg_rcode = msg.get_rcode()
         if msg_rcode != Rcode.NOERROR():
         if msg_rcode != Rcode.NOERROR():
-            raise XfrinException('error response: %s' % msg_rcode.to_text())
+            raise XfrinProtocolError('error response: %s' %
+                                     msg_rcode.to_text())
 
 
         if not msg.get_header_flag(Message.HEADERFLAG_QR):
         if not msg.get_header_flag(Message.HEADERFLAG_QR):
-            raise XfrinException('response is not a response')
+            raise XfrinProtocolError('response is not a response')
 
 
         if msg.get_qid() != self._query_id:
         if msg.get_qid() != self._query_id:
-            raise XfrinException('bad query id')
+            raise XfrinProtocolError('bad query id')
 
 
     def _check_response_status(self, msg):
     def _check_response_status(self, msg):
         '''Check validation of xfr response. '''
         '''Check validation of xfr response. '''
@@ -850,7 +880,7 @@ class XfrinConnection(asyncore.dispatcher):
         self._check_response_header(msg)
         self._check_response_header(msg)
 
 
         if msg.get_rr_count(Message.SECTION_QUESTION) > 1:
         if msg.get_rr_count(Message.SECTION_QUESTION) > 1:
-            raise XfrinException('query section count greater than 1')
+            raise XfrinProtocolError('query section count greater than 1')
 
 
     def _handle_xfrin_responses(self):
     def _handle_xfrin_responses(self):
         read_next_msg = True
         read_next_msg = True

+ 11 - 1
src/bin/xfrin/xfrin_messages.mes

@@ -60,8 +60,18 @@ 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
 of the xfrin module.  Possible reasons are a broken DNS message or failure
 in database connection.  The error is shown in the log message.
 in database connection.  The error is shown in the log message.
 
 
+% XFRIN_XFR_TRANSFER_PROTOCOL_ERROR %1 transfer of zone %2 with %3 failed: %4
+The XFR transfer for the given zone has failed due to a protocol
+error, such as an unexpected response from the primary server.  The
+error is shown in the log message.  It may be because the primary
+server implementation is broken or (although less likely) there was
+some attack attempt, but it can also happen due to configuration
+mismatch such as the remote server does not have authority for the
+zone any more but the local configuration hasn't been updated.  So it
+is recommended to check the primary server configuration.
+
 % XFRIN_XFR_TRANSFER_FAILURE %1 transfer of zone %2 with %3 failed: %4
 % XFRIN_XFR_TRANSFER_FAILURE %1 transfer of zone %2 with %3 failed: %4
-The XFR transfer for the given zone has failed due to a protocol error.
+The XFR transfer for the given zone has failed due to an internal error.
 The error is shown in the log message.
 The error is shown in the log message.
 
 
 % XFRIN_XFR_TRANSFER_FALLBACK falling back from IXFR to AXFR for %1
 % XFRIN_XFR_TRANSFER_FALLBACK falling back from IXFR to AXFR for %1

+ 6 - 0
src/lib/python/isc/testutils/rrset_utils.py

@@ -53,6 +53,12 @@ def create_ns(nsname, name=Name('example.com'), ttl=3600):
     rrset.add_rdata(Rdata(RRType.NS(), RRClass.IN(), nsname))
     rrset.add_rdata(Rdata(RRType.NS(), RRClass.IN(), nsname))
     return rrset
     return rrset
 
 
+def create_cname(target='target.example.com', name=Name('example.com'),
+                 ttl=3600):
+    rrset = RRset(name, RRClass.IN(), RRType.CNAME(), RRTTL(ttl))
+    rrset.add_rdata(Rdata(RRType.CNAME(), RRClass.IN(), target))
+    return rrset
+
 def create_generic(name, rdlen, type=RRType('TYPE65300'), ttl=3600):
 def create_generic(name, rdlen, type=RRType('TYPE65300'), ttl=3600):
     '''Create an RR of a general type with an arbitrary length of RDATA
     '''Create an RR of a general type with an arbitrary length of RDATA