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
This commit is contained in:
hamalq
2020-07-14 21:05:05 +00:00
committed by Erik Olof Gunnar Andersson
parent 45cb4376a2
commit 1fa892ea5a
2 changed files with 108 additions and 14 deletions

View File

@@ -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):

View File

@@ -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