diff options
author | hamalq <hamalq@verizonmedia.com> | 2020-07-14 21:05:05 +0000 |
---|---|---|
committer | Erik Olof Gunnar Andersson <eandersson@blizzard.com> | 2020-07-30 18:05:05 +0000 |
commit | 1fa892ea5a6db061d415a3ebe2b94105aaba57fd (patch) | |
tree | b779741c669f85249d7f821de86489153cf58152 | |
parent | 45cb4376a24d244fa677f3736299b4effa092657 (diff) | |
download | designate-1fa892ea5a6db061d415a3ebe2b94105aaba57fd.tar.gz |
Fix multi messages AXFR with TSIG
TSIG sign for multi messages AXFR was using the wrong dnspython
signing function which does not support sequence messages in
response which cause the AXFR error below
WARNING -- Some TSIG could not be validated
Change-Id: I7ce84ac9cf5bc5f6fca47dc79c51fe0becf96ac6
Closes-Bug: 1886685
-rw-r--r-- | designate/mdns/handler.py | 44 | ||||
-rw-r--r-- | designate/tests/test_mdns/test_handler.py | 78 |
2 files changed, 108 insertions, 14 deletions
diff --git a/designate/mdns/handler.py b/designate/mdns/handler.py index 315b4c2f..5db9f593 100644 --- a/designate/mdns/handler.py +++ b/designate/mdns/handler.py @@ -239,6 +239,10 @@ class RequestHandler(xfr.XFRMixin): records.insert(0, soa_records[0]) records.append(soa_records[0]) + # Handle multi message response with tsig + multi_messages = False + multi_messages_context = None + # Render the results, yielding a packet after each TooBig exception. renderer = None while records: @@ -260,6 +264,9 @@ class RequestHandler(xfr.XFRMixin): renderer.add_rrset(dns.renderer.ANSWER, rrset) break except dns.exception.TooBig: + # The response will span multiple messages since one + # message is not enough + multi_messages = True if renderer.counts[dns.renderer.ANSWER] == 0: # We've received a TooBig from the first attempted # RRSet in this packet. Log a warning and abort the @@ -280,11 +287,16 @@ class RequestHandler(xfr.XFRMixin): ) return - yield self._finalize_packet(renderer, request) + renderer, multi_messages_context = self._finalize_packet( + renderer, request, multi_messages, + multi_messages_context) + yield renderer renderer = None if renderer: - yield self._finalize_packet(renderer, request) + renderer, multi_messages_context = self._finalize_packet( + renderer, request, multi_messages, multi_messages_context) + yield renderer return def _handle_record_query(self, request): @@ -396,22 +408,26 @@ class RequestHandler(xfr.XFRMixin): recordset.name, ttl, dns.rdataclass.IN, recordset.type, rdata) @staticmethod - def _finalize_packet(renderer, request): + def _finalize_packet(renderer, request, multi_messages=False, + multi_messages_context=None): renderer.write_header() if request.had_tsig: # Make the space we reserved for TSIG available for use renderer.max_size += TSIG_RRSIZE - renderer.add_tsig( - request.keyname, - request.keyring[request.keyname], - request.fudge, - request.original_id, - request.tsig_error, - request.other_data, - request.mac, - request.keyalgorithm - ) - return renderer + if multi_messages: + # The first message context will be None then the + # context for the prev message is used for the next + multi_messages_context = renderer.add_multi_tsig( + multi_messages_context, request.keyname, + request.keyring[request.keyname], request.fudge, + request.original_id, request.tsig_error, + request.other_data, request.mac, request.keyalgorithm) + else: + renderer.add_tsig(request.keyname, + request.keyring[request.keyname], request.fudge, + request.original_id, request.tsig_error, + request.other_data, request.mac, request.keyalgorithm) + return renderer, multi_messages_context @staticmethod def _get_max_message_size(had_tsig): diff --git a/designate/tests/test_mdns/test_handler.py b/designate/tests/test_mdns/test_handler.py index 56725505..4ca68e00 100644 --- a/designate/tests/test_mdns/test_handler.py +++ b/designate/tests/test_mdns/test_handler.py @@ -642,6 +642,84 @@ class MdnsRequestHandlerTest(MdnsTestCase): self.assertEqual( expected_response[1], binascii.b2a_hex(response_two)) + @mock.patch.object(dns.renderer.Renderer, 'add_multi_tsig') + def test_dispatch_opcode_query_AXFR_multiple_messages_with_tsig(self, + mock_multi_tsig): + # Query is for example.com. IN AXFR + # id 18883 + # opcode QUERY + # rcode NOERROR + # flags AD + # edns 0 + # payload 4096 + # ;QUESTION + # example.com. IN AXFR + # ;ANSWER + # ;AUTHORITY + # ;ADDITIONAL + payload = ("49c300200001000000000001076578616d706c6503636f6d0000fc0001" + "0000291000000000000000") + + expected_response = [ + (b"49c384000001000300000000076578616d706c6503636f6d0000fc0001c00c" + b"0006000100000e10002f036e7331076578616d706c65036f726700076578616" + b"d706c65c00c551c063900000e10000002580001518000000e10c00c0002000" + b"100000e100002c029046d61696cc00c0001000100000e100004c0000201"), + + (b"49c384000001000100000000076578616d706c6503636f6d0000fc0001c00c" + b"0006000100000e10002f036e7331076578616d706c65036f72670007657861" + b"6d706c65c00c551c063900000e10000002580001518000000e10"), + ] + + # Set the max-message-size to 363 + self.config(max_message_size=363, group='service:mdns') + + zone = objects.Zone.from_dict({ + 'name': 'example.com.', + 'ttl': 3600, + 'serial': 1427899961, + 'email': 'example@example.com', + }) + + def _find_recordsets_axfr(context, criterion): + if criterion['type'] == 'SOA': + return [['UUID1', 'SOA', '3600', 'example.com.', + 'ns1.example.org. example.example.com. 1427899961 ' + '3600 600 86400 3600', 'ACTION']] + + elif criterion['type'] == '!SOA': + return [ + ['UUID2', 'NS', '3600', 'example.com.', 'ns1.example.org.', + 'ACTION'], + ['UUID3', 'A', '3600', 'mail.example.com.', '192.0.2.1', + 'ACTION'], + ] + + with mock.patch.object(self.storage, 'find_zone', + return_value=zone): + with mock.patch.object(self.storage, 'find_recordsets_axfr', + side_effect=_find_recordsets_axfr): + request = dns.message.from_wire(binascii.a2b_hex(payload)) + request.environ = {'addr': self.addr, 'context': self.context} + request.keyring = {request.keyname: ''} + request.had_tsig = True + args = [request.keyname, request.keyring[request.keyname], + request.fudge, request.original_id, request.tsig_error, + request.other_data, request.mac, request.keyalgorithm] + response_generator = self.handler(request) + # Validate the first response + response_one = next(response_generator).get_wire() + mock_multi_tsig.assert_called_with(None, *args) + self.assertEqual( + expected_response[0], binascii.b2a_hex(response_one)) + + # Validate the second response + response_two = next(response_generator).get_wire() + first_msg_ctx = mock_multi_tsig.return_value + mock_multi_tsig.assert_called_with(first_msg_ctx, *args) + self.assertEqual( + expected_response[1], binascii.b2a_hex(response_two)) + def test_dispatch_opcode_query_AXFR_rrset_over_max_size(self): # Query is for example.com. IN AXFR # id 18883 |