Merge "Ensure a single RRSet over max_packet_size doesn't loop forever"
This commit is contained in:
commit
ac614adc47
@ -225,41 +225,6 @@ class RequestHandler(xfr.XFRMixin):
|
||||
|
||||
return r_rrset
|
||||
|
||||
def _prep_rrsets(self, raw_records, domain_ttl):
|
||||
rrsets = []
|
||||
rrset_id = None
|
||||
current_rrset = None
|
||||
|
||||
for record in raw_records:
|
||||
# If we're looking at the first, or a new rrset
|
||||
if record[0] != rrset_id:
|
||||
if current_rrset is not None:
|
||||
# If this isn't the first iteration
|
||||
rrsets.append(current_rrset)
|
||||
# Set up a new rrset
|
||||
rrset_id = record[0]
|
||||
rrtype = str(record[1])
|
||||
# gross
|
||||
ttl = int(record[2]) if record[2] is not None else domain_ttl
|
||||
name = str(record[3])
|
||||
rdata = str(record[4])
|
||||
current_rrset = dns.rrset.from_text_list(
|
||||
name, ttl, dns.rdataclass.IN, rrtype, [rdata])
|
||||
else:
|
||||
# We've already got an rrset, add the rdata
|
||||
rrtype = str(record[1])
|
||||
rdata = str(record[4])
|
||||
rd = dns.rdata.from_text(dns.rdataclass.IN,
|
||||
dns.rdatatype.from_text(rrtype), rdata)
|
||||
current_rrset.add(rd)
|
||||
|
||||
# If the last record examined was a new rrset, or there is only 1 rrset
|
||||
if rrsets == [] or (rrsets != [] and rrsets[-1] != current_rrset):
|
||||
if current_rrset is not None:
|
||||
rrsets.append(current_rrset)
|
||||
|
||||
return rrsets
|
||||
|
||||
def _handle_axfr(self, request):
|
||||
context = request.environ['context']
|
||||
q_rrset = request.question[0]
|
||||
@ -298,9 +263,6 @@ class RequestHandler(xfr.XFRMixin):
|
||||
records.insert(0, soa_records[0])
|
||||
records.append(soa_records[0])
|
||||
|
||||
# Build the DNSPython RRSets from the Records
|
||||
rrsets = self._prep_rrsets(records, domain.ttl)
|
||||
|
||||
# Build up a dummy response, we're stealing it's logic for building
|
||||
# the Flags.
|
||||
response = dns.message.make_response(request)
|
||||
@ -321,7 +283,9 @@ class RequestHandler(xfr.XFRMixin):
|
||||
|
||||
# Render the results, yielding a packet after each TooBig exception.
|
||||
i, renderer = 0, None
|
||||
while i < len(rrsets):
|
||||
while i < len(records):
|
||||
record = records[i]
|
||||
|
||||
# No renderer? Build one
|
||||
if renderer is None:
|
||||
renderer = dns.renderer.Renderer(
|
||||
@ -329,12 +293,35 @@ class RequestHandler(xfr.XFRMixin):
|
||||
for q in request.question:
|
||||
renderer.add_question(q.name, q.rdtype, q.rdclass)
|
||||
|
||||
# Build a DNSPython RRSet from the RR
|
||||
rrset = dns.rrset.from_text_list(
|
||||
str(record[3]), # name
|
||||
int(record[2]) if record[2] is not None else domain.ttl, # ttl
|
||||
dns.rdataclass.IN, # class
|
||||
str(record[1]), # rrtype
|
||||
[str(record[4])], # rdata
|
||||
)
|
||||
|
||||
try:
|
||||
renderer.add_rrset(dns.renderer.ANSWER, rrsets[i])
|
||||
renderer.add_rrset(dns.renderer.ANSWER, rrset)
|
||||
i += 1
|
||||
except dns.exception.TooBig:
|
||||
yield self._finalize_packet(renderer, request)
|
||||
renderer = None
|
||||
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 AXFR.
|
||||
LOG.warning(_LW('Aborted AXFR of %(domain)s, a single RR '
|
||||
'(%(rrset_type)s %(rrset_name)s) '
|
||||
'exceeded the max message size.'),
|
||||
{'domain': domain.name,
|
||||
'rrset_type': record[1],
|
||||
'rrset_name': record[3]})
|
||||
|
||||
yield self._handle_query_error(request, dns.rcode.SERVFAIL)
|
||||
raise StopIteration
|
||||
|
||||
else:
|
||||
yield self._finalize_packet(renderer, request)
|
||||
renderer = None
|
||||
|
||||
if renderer is not None:
|
||||
yield self._finalize_packet(renderer, request)
|
||||
|
@ -21,6 +21,7 @@ import dns.rdatatype
|
||||
import dns.resolver
|
||||
import dns.rrset
|
||||
import mock
|
||||
import testtools
|
||||
from oslo_config import cfg
|
||||
|
||||
from designate import context
|
||||
@ -582,6 +583,164 @@ class MdnsRequestHandlerTest(MdnsTestCase):
|
||||
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
|
||||
# opcode QUERY
|
||||
# rcode NOERROR
|
||||
# flags AD
|
||||
# edns 0
|
||||
# payload 4096
|
||||
# ;QUESTION
|
||||
# example.com. IN AXFR
|
||||
# ;ANSWER
|
||||
# ;AUTHORITY
|
||||
# ;ADDITIONAL
|
||||
payload = ("49c300200001000000000001076578616d706c6503636f6d0000fc0001"
|
||||
"0000291000000000000000")
|
||||
|
||||
expected_response = [
|
||||
# Initial SOA
|
||||
("49c384000001000100000000076578616d706c6503636f6d0000fc0001c00c00"
|
||||
"06000100000e10002f036e7331076578616d706c65036f726700076578616d70"
|
||||
"6c65c00c551c063900000e10000002580001518000000e10"),
|
||||
|
||||
# First NS record
|
||||
("49c384000001000100000000076578616d706c6503636f6d0000fc0001c00c00"
|
||||
"02000100000e1000413f61616161616161616161616161616161616161616161"
|
||||
"6161616161616161616161616161616161616161616161616161616161616161"
|
||||
"61616161616161616100"),
|
||||
|
||||
# Second NS Record and SOA trailer
|
||||
("49c384000001000200000000076578616d706c6503636f6d0000fc0001c00c00"
|
||||
"02000100000e10000c0a6262626262626262626200c00c0006000100000e1000"
|
||||
"2f036e7331076578616d706c65036f726700076578616d706c65c00c551c0639"
|
||||
"00000e10000002580001518000000e10"),
|
||||
]
|
||||
|
||||
# Set the max-message-size to 128
|
||||
self.config(max_message_size=128, group='service:mdns')
|
||||
|
||||
domain = objects.Domain.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.', 'a' * 63 + '.',
|
||||
'ACTION'],
|
||||
['UUID2', 'NS', '3600', 'example.com.', 'b' * 10 + '.',
|
||||
'ACTION'],
|
||||
]
|
||||
|
||||
with mock.patch.object(self.storage, 'find_domain',
|
||||
return_value=domain):
|
||||
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}
|
||||
|
||||
response_generator = self.handler(request)
|
||||
|
||||
# Validate the first response
|
||||
response_one = next(response_generator).get_wire()
|
||||
self.assertEqual(
|
||||
expected_response[0], binascii.b2a_hex(response_one))
|
||||
|
||||
# Validate the second response
|
||||
response_two = next(response_generator).get_wire()
|
||||
self.assertEqual(
|
||||
expected_response[1], binascii.b2a_hex(response_two))
|
||||
|
||||
# Validate the third response
|
||||
response_three = next(response_generator).get_wire()
|
||||
self.assertEqual(
|
||||
expected_response[2], binascii.b2a_hex(response_three))
|
||||
|
||||
# Ensure a StopIteration is raised after the final response.
|
||||
with testtools.ExpectedException(StopIteration):
|
||||
next(response_generator)
|
||||
|
||||
def test_dispatch_opcode_query_AXFR_rr_over_max_size(self):
|
||||
# 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 = [
|
||||
# Initial SOA
|
||||
("49c384000001000100000000076578616d706c6503636f6d0000fc0001c00c00"
|
||||
"06000100000e10002f036e7331076578616d706c65036f726700076578616d70"
|
||||
"6c65c00c551c063900000e10000002580001518000000e10"),
|
||||
|
||||
# SRVFAIL
|
||||
(""),
|
||||
]
|
||||
|
||||
# Set the max-message-size to 128
|
||||
self.config(max_message_size=128, group='service:mdns')
|
||||
|
||||
domain = objects.Domain.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.',
|
||||
'a' * 63 + '.' + 'a' * 63 + '.', 'ACTION'],
|
||||
]
|
||||
|
||||
with mock.patch.object(self.storage, 'find_domain',
|
||||
return_value=domain):
|
||||
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}
|
||||
|
||||
response_generator = self.handler(request)
|
||||
|
||||
# Validate the first response
|
||||
response_one = next(response_generator).get_wire()
|
||||
self.assertEqual(
|
||||
expected_response[0], binascii.b2a_hex(response_one))
|
||||
|
||||
# Validate the second response is a SERVFAIL
|
||||
response_two = next(response_generator)
|
||||
self.assertEqual(
|
||||
dns.rcode.SERVFAIL, response_two.rcode())
|
||||
|
||||
# Ensure a StopIteration is raised after the final response.
|
||||
with testtools.ExpectedException(StopIteration):
|
||||
next(response_generator)
|
||||
|
||||
def test_dispatch_opcode_query_nonexistent_recordtype(self):
|
||||
# query is for mail.example.com. IN CNAME
|
||||
payload = ("271801000001000000000000046d61696c076578616d706c6503636f6d"
|
||||
|
Loading…
Reference in New Issue
Block a user