Commit 3fdce880 authored by JINMEI Tatuya's avatar JINMEI Tatuya
Browse files

[master] Merge branch 'trac1389'

parents 9682bf18 144549c0
......@@ -67,10 +67,12 @@ class MySocket():
self.sendqueue = self.sendqueue[size:]
return result
def read_msg(self, parse_options=Message.PARSE_DEFAULT):
def read_msg(self, parse_options=Message.PARSE_DEFAULT, need_len=False):
sent_data = self.readsent()
get_msg = Message(Message.PARSE)
get_msg.from_wire(bytes(sent_data[2:]), parse_options)
if need_len:
return (get_msg, len(sent_data) - 2)
return get_msg
def clear_send(self):
......@@ -863,7 +865,150 @@ class TestXfroutSession(TestXfroutSessionBase):
self.assertEqual(len(expected_records), len(actual_records))
for (expected_rr, actual_rr) in zip(expected_records, actual_records):
self.assertTrue(expected_rr, actual_rr)
self.assertTrue(rrsets_equal(expected_rr, actual_rr))
def test_reply_xfrout_query_axfr_maxlen(self):
# The test RR(set) has the length of 65535 - 12 (size of hdr) bytes:
# owner name = 1 (root), fixed fields (type,class,TTL,RDLEN) = 10
# RDATA = 65512 (= 65535 - 12 - 1 - 10)
self.xfrsess._soa = self.soa_rrset
test_rr = create_generic(Name('.'), 65512)
self.xfrsess._iterator = [self.soa_rrset, test_rr]
self.xfrsess._reply_xfrout_query(self.getmsg(), self.sock)
# The first message should contain the beginning SOA, and only that RR
r = self.sock.read_msg()
self.assertEqual(1, r.get_rr_count(Message.SECTION_ANSWER))
self.assertTrue(rrsets_equal(self.soa_rrset,
r.get_section(Message.SECTION_ANSWER)[0]))
# The second message should contain the beginning SOA, and only that RR
# The wire format data should have the possible maximum size.
r, rlen = self.sock.read_msg(need_len=True)
self.assertEqual(65535, rlen)
self.assertEqual(1, r.get_rr_count(Message.SECTION_ANSWER))
self.assertTrue(rrsets_equal(test_rr,
r.get_section(Message.SECTION_ANSWER)[0]))
# The third message should contain the ending SOA, and only that RR
r = self.sock.read_msg()
self.assertEqual(1, r.get_rr_count(Message.SECTION_ANSWER))
self.assertTrue(rrsets_equal(self.soa_rrset,
r.get_section(Message.SECTION_ANSWER)[0]))
# there should be no more message
self.assertEqual(0, len(self.sock.sendqueue))
def maxlen_test_common_setup(self, tsig=False):
'''Common initialization for some of the tests below
For those tests we use '.' for all owner names and names in RDATA
to avoid having unexpected results due to compression. It returns
the created SOA for convenience.
If tsig is True, also setup TSIG (mock) context. In our test cases
the size of the TSIG RR is 81 bytes (key name = example.com,
algorithm = hmac-md5)
'''
soa = RRset(Name('.'), RRClass.IN(), RRType.SOA(), RRTTL(3600))
soa.add_rdata(Rdata(RRType.SOA(), RRClass.IN(), '. . 0 0 0 0 0'))
self.mdata = self.create_request_data(zone_name=Name('.'))
self.xfrsess._soa = soa
if tsig:
self.xfrsess._tsig_ctx = \
self.create_mock_tsig_ctx(TSIGError.NOERROR)
self.xfrsess._tsig_len = 81
return soa
def maxlen_test_common_checks(self, soa_rr, test_rr, expected_n_rr):
'''A set of common assertion checks for some tests below.
In all cases two AXFR response messages should have been created.
expected_n_rr is a list of two elements, each specifies the expected
number of answer RRs for each message: expected_n_rr[0] is the expected
number of the first answer RRs; expected_n_rr[1] is the expected number
of the second answer RRs. The message that contains two RRs should
have the maximum possible wire length (65535 bytes). And, in all
cases, the resulting RRs should be in the order of SOA, another RR,
SOA.
'''
# Check the first message
r, rlen = self.sock.read_msg(need_len=True)
if expected_n_rr[0] == 2:
self.assertEqual(65535, rlen)
self.assertEqual(expected_n_rr[0],
r.get_rr_count(Message.SECTION_ANSWER))
actual_rrs = r.get_section(Message.SECTION_ANSWER)[:]
# Check the second message
r, rlen = self.sock.read_msg(need_len=True)
if expected_n_rr[1] == 2:
self.assertEqual(65535, rlen)
self.assertEqual(expected_n_rr[1],
r.get_rr_count(Message.SECTION_ANSWER))
actual_rrs.extend(r.get_section(Message.SECTION_ANSWER))
for (expected_rr, actual_rr) in zip([soa_rr, test_rr, soa_rr],
actual_rrs):
self.assertTrue(rrsets_equal(expected_rr, actual_rr))
# there should be no more message
self.assertEqual(0, len(self.sock.sendqueue))
def test_reply_xfrout_query_axfr_maxlen_with_soa(self):
# Similar to the 'maxlen' test, but the first message should be
# able to contain both SOA and the large RR.
soa = self.maxlen_test_common_setup()
# The first message will contain the question (5 bytes), so the
# test RDATA should allow a room for that.
test_rr = create_generic(Name('.'), 65512 - 5 - get_rrset_len(soa))
self.xfrsess._iterator = [soa, test_rr]
self.xfrsess._reply_xfrout_query(self.getmsg(), self.sock)
self.maxlen_test_common_checks(soa, test_rr, [2, 1])
def test_reply_xfrout_query_axfr_maxlen_with_soa_with_tsig(self):
# Similar to the previous case, but with TSIG (whose size is 81 bytes).
soa = self.maxlen_test_common_setup(True)
test_rr = create_generic(Name('.'), 65512 - 5 - 81 -
get_rrset_len(soa))
self.xfrsess._iterator = [soa, test_rr]
self.xfrsess._reply_xfrout_query(self.getmsg(), self.sock)
self.maxlen_test_common_checks(soa, test_rr, [2, 1])
def test_reply_xfrout_query_axfr_maxlen_with_endsoa(self):
# Similar to the max w/ soa test, but the first message cannot contain
# both SOA and the long RR due to the question section. The second
# message should be able to contain both.
soa = self.maxlen_test_common_setup()
test_rr = create_generic(Name('.'), 65512 - get_rrset_len(soa))
self.xfrsess._iterator = [soa, test_rr]
self.xfrsess._reply_xfrout_query(self.getmsg(), self.sock)
self.maxlen_test_common_checks(soa, test_rr, [1, 2])
def test_reply_xfrout_query_axfr_maxlen_with_endsoa_with_tsig(self):
# Similar to the previous case, but with TSIG.
soa = self.maxlen_test_common_setup(True)
test_rr = create_generic(Name('.'), 65512 - 81 - get_rrset_len(soa))
self.xfrsess._iterator = [soa, test_rr]
self.xfrsess._reply_xfrout_query(self.getmsg(), self.sock)
self.maxlen_test_common_checks(soa, test_rr, [1, 2])
def test_reply_xfrout_query_axfr_toobigdata(self):
# Similar to the 'maxlen' test, but the RR doesn't even fit in a
# single message.
self.xfrsess._soa = self.soa_rrset
test_rr = create_generic(Name('.'), 65513) # 1 byte larger than 'max'
self.xfrsess._iterator = [self.soa_rrset, test_rr]
# the reply method should fail with exception
self.assertRaises(XfroutSessionError, self.xfrsess._reply_xfrout_query,
self.getmsg(), self.sock)
# The first message should still have been sent and contain the
# beginning SOA, and only that RR
r = self.sock.read_msg()
self.assertEqual(1, r.get_rr_count(Message.SECTION_ANSWER))
self.assertTrue(rrsets_equal(self.soa_rrset,
r.get_section(Message.SECTION_ANSWER)[0]))
# And there should have been no other messages sent
self.assertEqual(0, len(self.sock.sendqueue))
def test_reply_xfrout_query_ixfr_soa_only(self):
# Creating an IXFR response that contains only one RR, which is the
......@@ -875,7 +1020,8 @@ class TestXfroutSession(TestXfroutSessionBase):
reply_msg = self.sock.read_msg(Message.PRESERVE_ORDER)
answer = reply_msg.get_section(Message.SECTION_ANSWER)
self.assertEqual(1, len(answer))
self.assertTrue(create_soa(SOA_CURRENT_VERSION), answer[0])
self.assertTrue(rrsets_equal(create_soa(SOA_CURRENT_VERSION),
answer[0]))
class TestXfroutSessionWithSQLite3(TestXfroutSessionBase):
'''Tests for XFR-out sessions using an SQLite3 DB.
......@@ -899,14 +1045,23 @@ class TestXfroutSessionWithSQLite3(TestXfroutSessionBase):
# This zone contains two A RRs for the same name with different TTLs.
# These TTLs should be preseved in the AXFR stream.
actual_records = response.get_section(Message.SECTION_ANSWER)
expected_records = [create_soa(2011112001),
create_ns(self.ns_name),
create_a(Name(self.ns_name), '192.0.2.1', 3600),
create_a(Name(self.ns_name), '192.0.2.2', 7200),
create_soa(2011112001)]
self.assertEqual(len(expected_records), len(actual_records))
for (expected_rr, actual_rr) in zip(expected_records, actual_records):
self.assertTrue(expected_rr, actual_rr)
self.assertEqual(5, len(actual_records))
# The first and last RR should be the expected SOA
expected_soa = create_soa(2011112001)
self.assertTrue(rrsets_equal(expected_soa, actual_records[0]))
self.assertTrue(rrsets_equal(expected_soa, actual_records[-1]))
# The ordering of the intermediate RRs can differ depending on the
# internal details of the SQLite3 library, so we sort them by a simple
# rule sufficient for the purpose here, and then compare them.
expected_others = [create_ns(self.ns_name),
create_a(Name(self.ns_name), '192.0.2.1', 3600),
create_a(Name(self.ns_name), '192.0.2.2', 7200)]
keyfn = lambda x: (x.get_type(), x.get_ttl())
for (expected_rr, actual_rr) in zip(sorted(expected_others, key=keyfn),
sorted(actual_records[1:4],
key=keyfn)):
self.assertTrue(rrsets_equal(expected_rr, actual_rr))
def test_axfr_normal_session(self):
XfroutSession._handle(self.xfrsess)
......@@ -945,7 +1100,7 @@ class TestXfroutSessionWithSQLite3(TestXfroutSessionBase):
create_soa(2011112001)]
self.assertEqual(len(expected_records), len(actual_records))
for (expected_rr, actual_rr) in zip(expected_records, actual_records):
self.assertTrue(expected_rr, actual_rr)
self.assertTrue(rrsets_equal(expected_rr, actual_rr))
def test_ixfr_soa_only(self):
# The requested SOA serial is the latest one. The response should
......@@ -956,7 +1111,8 @@ class TestXfroutSessionWithSQLite3(TestXfroutSessionBase):
response = self.sock.read_msg(Message.PRESERVE_ORDER);
answers = response.get_section(Message.SECTION_ANSWER)
self.assertEqual(1, len(answers))
self.assertTrue(create_soa(SOA_CURRENT_VERSION), answers[0])
self.assertTrue(rrsets_equal(create_soa(SOA_CURRENT_VERSION),
answers[0]))
class MyUnixSockServer(UnixSockServer):
def __init__(self):
......
......@@ -66,6 +66,11 @@ class XfroutConfigError(Exception):
"""
pass
class XfroutSessionError(Exception):
'''An exception raised for some unexpected events during an xfrout session.
'''
pass
def init_paths():
global SPECFILE_PATH
global AUTH_SPECFILE_PATH
......@@ -93,7 +98,8 @@ init_paths()
SPECFILE_LOCATION = SPECFILE_PATH + "/xfrout.spec"
AUTH_SPECFILE_LOCATION = AUTH_SPECFILE_PATH + os.sep + "auth.spec"
VERBOSE_MODE = False
XFROUT_MAX_MESSAGE_SIZE = 65535
XFROUT_DNS_HEADER_SIZE = 12 # protocol constant
XFROUT_MAX_MESSAGE_SIZE = 65535 # ditto
# borrowed from xfrin.py @ #1298. We should eventually unify it.
def format_zone_str(zone_name, zone_class):
......@@ -534,32 +540,44 @@ class XfroutSession():
def _send_message_with_last_soa(self, msg, sock_fd, rrset_soa,
message_upper_len):
'''Add the SOA record to the end of message. If it can't be
added, a new message should be created to send out the last soa .
'''Add the SOA record to the end of message.
If it would exceed the maximum allowable size of a message, a new
message will be created to send out the last SOA.
We assume a message with a single SOA can always fit the buffer
with or without TSIG. In theory this could be wrong if TSIG is
stupidly large, but in practice this assumption should be reasonable.
'''
if (message_upper_len + self._tsig_len + get_rrset_len(rrset_soa) >=
XFROUT_MAX_MESSAGE_SIZE):
if message_upper_len + get_rrset_len(rrset_soa) > \
XFROUT_MAX_MESSAGE_SIZE:
self._send_message(sock_fd, msg, self._tsig_ctx)
msg = self._clear_message(msg)
# If tsig context exist, sign the last packet
msg.add_rrset(Message.SECTION_ANSWER, rrset_soa)
self._send_message(sock_fd, msg, self._tsig_ctx)
def _reply_xfrout_query(self, msg, sock_fd):
#TODO, there should be a better way to insert rrset.
msg.make_response()
msg.set_header_flag(Message.HEADERFLAG_AA)
# Reserved space for the fixed header size, the size of the question
# section, and TSIG size (when included). The size of the question
# section is the sum of the qname length and the size of the
# fixed-length fields (type and class, 2 bytes each).
message_upper_len = XFROUT_DNS_HEADER_SIZE + \
msg.get_question()[0].get_name().get_length() + 4 + \
self._tsig_len
# If the iterator is None, we are responding to IXFR with a single
# SOA RR.
if self._iterator is None:
self._send_message_with_last_soa(msg, sock_fd, self._soa, 0)
self._send_message_with_last_soa(msg, sock_fd, self._soa,
message_upper_len)
return
# Add the beginning SOA
msg.add_rrset(Message.SECTION_ANSWER, self._soa)
message_upper_len = get_rrset_len(self._soa) + self._tsig_len
message_upper_len += get_rrset_len(self._soa)
# Add the rest of the zone/diff contets
for rrset in self._iterator:
......@@ -577,20 +595,33 @@ class XfroutSession():
# size without compression) and use that to see if we
# may have reached the limit
rrset_len = get_rrset_len(rrset)
if message_upper_len + rrset_len < XFROUT_MAX_MESSAGE_SIZE:
if message_upper_len + rrset_len <= XFROUT_MAX_MESSAGE_SIZE:
msg.add_rrset(Message.SECTION_ANSWER, rrset)
message_upper_len += rrset_len
continue
# RR would not fit. If there are other RRs in the buffer, send
# them now and leave this RR to the next message.
self._send_message(sock_fd, msg, self._tsig_ctx)
# Create a new message and reserve space for the carried-over
# RR (and TSIG space in case it's to be TSIG signed)
msg = self._clear_message(msg)
message_upper_len = XFROUT_DNS_HEADER_SIZE + rrset_len + \
self._tsig_len
# If this RR overflows the buffer all by itself, fail. In theory
# some RRs might fit in a TCP message when compressed even if they
# do not fit when uncompressed, but surely we don't want to send
# such monstrosities to an unsuspecting slave.
if message_upper_len > XFROUT_MAX_MESSAGE_SIZE:
raise XfroutSessionError('RR too large for zone transfer (' +
str(rrset_len) + ' bytes)')
# Add the RRset to the new message
msg.add_rrset(Message.SECTION_ANSWER, rrset)
# Reserve tsig space for signed packet
message_upper_len = rrset_len + self._tsig_len
# Add and send the trailing SOA
self._send_message_with_last_soa(msg, sock_fd, self._soa,
message_upper_len)
......@@ -782,7 +813,6 @@ class UnixSockServer(socketserver_mixin.NoPollMixIn,
os.unlink(self._sock_file)
except Exception as e:
logger.error(XFROUT_REMOVE_UNIX_SOCKET_FILE_ERROR, self._sock_file, str(e))
pass
def update_config_data(self, new_config):
'''Apply the new config setting of xfrout module.
......
......@@ -53,6 +53,19 @@ def create_ns(nsname, name=Name('example.com'), ttl=3600):
rrset.add_rdata(Rdata(RRType.NS(), RRClass.IN(), nsname))
return rrset
def create_generic(name, rdlen, type=RRType('TYPE65300'), ttl=3600):
'''Create an RR of a general type with an arbitrary length of RDATA
If the RR type isn't specified, type of 65300 will be used, which is
arbitrarily chosen from the IANA "Reserved for Private Usage" range.
The RDATA will be filled with specified length of all-0 data.
'''
rrset = RRset(name, RRClass.IN(), type, RRTTL(ttl))
rrset.add_rdata(Rdata(type, RRClass.IN(), '\\# ' +
str(rdlen) + ' ' + '00' * rdlen))
return rrset
def create_soa(serial, name=Name('example.com'), ttl=3600):
'''For convenience we use a default name often used as a zone name'''
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment