From: Tomas Krizek Date: Tue, 13 Nov 2018 16:48:08 +0000 (+0100) Subject: pytests: relocate and refactor TCP prefix tests X-Git-Tag: v3.2.0~18^2~49 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=5621cbfaf8c27ca84051b7d14a51471d230f2e08;p=thirdparty%2Fknot-resolver.git pytests: relocate and refactor TCP prefix tests --- diff --git a/tests/pytests/test_conn_mgmt.py b/tests/pytests/test_conn_mgmt.py index a9678a2cc..78539dfad 100644 --- a/tests/pytests/test_conn_mgmt.py +++ b/tests/pytests/test_conn_mgmt.py @@ -1,10 +1,7 @@ """TCP Connection Management tests""" -import struct -import time import dns import dns.message -import pytest import utils @@ -44,146 +41,3 @@ def test_pipelining(kresd_sock): msg_answer = utils.receive_parse_answer(kresd_sock) assert msg_answer.id == MSG_ID_SECOND - - -def test_prefix_shorter_than_header(kresd_sock): - """ - Test prefixes message by the value, which is less then the length of the DNS - message header and sequentially sends it over TCP connection. (RFC1035 4.2.2) - - Expected: TCP connection must be closed after `net.tcp_in_idle` milliseconds. - (by default, after about 10s after connection is established) - """ - msg = dns.message.make_query('localhost.', dns.rdatatype.A, dns.rdataclass.IN) - data = msg.to_wire() - datalen = 11 # DNS Header size minus 1 - buf = struct.pack("!H", datalen) + data - - for _ in range(15): - try: - kresd_sock.sendall(buf) - except BrokenPipeError: - break - else: - time.sleep(1) - else: - assert False, "kresd didn't close connection" - - -def test_prefix_longer_than_message(kresd_sock): - """ - Test prefixes message by the value, which is greater then the length of the - whole message and sequentially sends it over TCP connection. - - Expected: TCP connection must be closed after net.tcp_in_idle milliseconds - """ - msg = dns.message.make_query('localhost.', dns.rdatatype.A, dns.rdataclass.IN) - data = msg.to_wire() - datalen = len(data) + 16 - buf = struct.pack("!H", datalen) + data - - for _ in range(15): - try: - kresd_sock.sendall(buf) - except BrokenPipeError: - break - else: - time.sleep(1) - else: - assert False, "kresd didn't close the connection" - - -def test_prefix_cuts_message(kresd_sock): - """ - Test prefixes message by value, which is greater than the - length of DNS message header but less than length of the whole DNS message - and sequentially sends it over TCP connection. - - Expected: TCP connection must be closed after approx. 13 seconds after establishing. - 13 s is a sum of two timeouts - 1) 3 seconds is a result of TCP_DEFER_ACCEPT server socket option - 2) 10 second is a default kresd idle timeout for tcp connection (net.tcp_in_idle()) - """ - msg = dns.message.make_query('localhost.', dns.rdatatype.A, dns.rdataclass.IN) - data = msg.to_wire() - datalen = 14 # DNS Header size plus 2 - assert datalen < len(data) - buf = struct.pack("!H", datalen) + data - - for _ in range(15): - try: - kresd_sock.sendall(buf) - except BrokenPipeError: - break - else: - time.sleep(1) - else: - assert False, "kresd didn't close the connection" - - -def test_prefix_cut_message_after_ok(kresd_sock): - """ - At first test send normal DNS message. Then, it sequentially sends DNS message - with incorrect prefix, which is greater than the length of DNS message header, - but less than length of the whole DNS message. - - Expected: TCP connection is closed after a timeout period. - """ - NORMAL_MSG_ID = 1 - CUT_MSG_ID = 2 - buf_normal = utils.get_msgbuf('localhost.', dns.rdatatype.A, NORMAL_MSG_ID) - - msg = dns.message.make_query('localhost.', dns.rdatatype.A, dns.rdataclass.IN) - msg.id = CUT_MSG_ID - data = msg.to_wire() - datalen = 14 # DNS Header size plus 2 - assert datalen < len(data) - buf_cut = struct.pack("!H", datalen) + data - - kresd_sock.sendall(buf_normal) - kresd_sock.sendall(buf_cut) - - msg_answer = utils.receive_parse_answer(kresd_sock) - assert msg_answer.id == NORMAL_MSG_ID - - for _ in range(12): - try: - kresd_sock.sendall(buf_cut) - except BrokenPipeError: - break - except ConnectionResetError: - break - else: - time.sleep(1) - else: - assert False, "kresd didn't close the connection" - - -def test_prefix_trailing_garbage(kresd_sock): - """ - Test repeatedly sends correct message with garbage after the message's end. - Message is prefixed by the length that includes garbage length. - - Expected: TCP connection must not be closed until all the queries have been sent - """ - msg = dns.message.make_query('localhost.', dns.rdatatype.A, dns.rdataclass.IN) - msg.id = 1 - - for _ in range(10): - msg.id += 1 - data = msg.to_wire() + b'garbage' - data_len = len(data) - buf = struct.pack("!H", data_len) + data - try: - kresd_sock.sendall(buf) - except BrokenPipeError: - raise pytest.fail("kresd closed the connection") - - try: - msg_answer = utils.receive_parse_answer(kresd_sock) - except BrokenPipeError: - raise pytest.fail("kresd closed the connection") - else: - assert msg_answer.id == msg.id - - time.sleep(0.1) diff --git a/tests/pytests/test_tcp_prefix.py b/tests/pytests/test_tcp_prefix.py new file mode 100644 index 000000000..2ad9d6e83 --- /dev/null +++ b/tests/pytests/test_tcp_prefix.py @@ -0,0 +1,108 @@ +"""TCP Connection Management tests - prefix length + +RFC1035 +4.2.2. TCP usage +The message is prefixed with a two byte length field which gives the message +length, excluding the two byte length field. + +The following test suite focuses on edge cases for the prefix - when it +is either too short or too long, instead of matching the length of DNS +message exactly. + +The tests with incorrect prefix attempt to sequentially send the incorrect +message. After a certain period of time (affected by net.tcp_in_idle, +TCP_DEFER_ACCEPT, ...), kresd should close the connection. +""" + +import time + +import pytest + +import utils + + +# default net.tcp_in_idle is 10s, TCP_DEFER_ACCEPT 3s, some extra for +# Python handling / edge cases +MAX_TIMEOUT = 16 + + +def send_incorrect_repeatedly(sock, buff, delay=1): + """Utility function to keep sending the buffer until MAX_TIMEOUT is reached. + + It is expected kresd will close the connection, since the buffer + contains incorrect prefix of the message. + + If the connection remains open, test is failed. + """ + end_time = time.time() + MAX_TIMEOUT + + with pytest.raises(BrokenPipeError, message="kresd didn't close connection"): + while time.time() < end_time: + try: + sock.sendall(buff) + except ConnectionResetError: + pytest.skip("kresd closed connection with TCP RST") + time.sleep(delay) + + +def test_less_than_header(kresd_sock): + """Prefix is less than the length of the DNS message header.""" + wire = utils.prepare_wire() + datalen = 11 # DNS header size minus 1 + buff = utils.prepare_buffer(wire, datalen) + send_incorrect_repeatedly(kresd_sock, buff) + + +def test_greater_than_message(kresd_sock): + """Prefix is greater than the length of the entire DNS message.""" + wire = utils.prepare_wire() + datalen = len(wire) + 16 + buff = utils.prepare_buffer(wire, datalen) + send_incorrect_repeatedly(kresd_sock, buff) + + +def test_cuts_message(kresd_sock): + """Prefix is greater than the length of the DNS message header, but shorter than + the entire DNS message.""" + wire = utils.prepare_wire() + datalen = 14 # DNS Header size plus 2 + assert datalen < len(wire) + buff = utils.prepare_buffer(wire, datalen) + send_incorrect_repeatedly(kresd_sock, buff) + + +def test_cuts_message_after_ok(kresd_sock): + """First, normal DNS message is sent. Afterwards, message with incorrect prefix + (greater than header, less than entire message) is sent. First message must be + answered, then the connection should be closed after timeout.""" + normal_msg_id = 1 + normal_wire = utils.prepare_wire(normal_msg_id) + normal_buff = utils.prepare_buffer(normal_wire) + + cut_wire = utils.prepare_wire() + cut_datalen = 14 + assert cut_datalen < len(cut_wire) + cut_buff = utils.prepare_buffer(cut_wire, cut_datalen) + + kresd_sock.sendall(normal_buff) + kresd_sock.sendall(cut_buff) + + msg_answer = utils.receive_parse_answer(kresd_sock) + assert msg_answer.id == normal_msgid + + send_incorrect_repeatedly(kresd_sock, cut_buff) + + +def test_trailing_garbage(kresd_sock): + """Prefix is correct, but the message has trailing garbage. The connection must + stay open until all message have been sent and answered.""" + for _ in range(10): + msgid = utils.random_msgid() + wire = utils.prepare_wire(msgid) + utils.get_garbage(8) + buff = utils.prepare_buffer(wire) + + kresd_sock.sendall(buff) + answer = utils.receive_parse_answer(kresd_sock) + assert answer.id == msgid + + time.sleep(0.1) diff --git a/tests/pytests/utils.py b/tests/pytests/utils.py index e4d138496..9ba4e6595 100644 --- a/tests/pytests/utils.py +++ b/tests/pytests/utils.py @@ -2,6 +2,7 @@ import struct import random import dns +import dns.message def random_msgid(): @@ -37,7 +38,28 @@ def receive_parse_answer(sock): return msg_answer +def prepare_wire( + msgid=None, + qname='localhost.', + qtype=dns.rdatatype.A, + qclass=dns.rdataclass.IN): + """Utility function to generate DNS wire format message""" + msg = dns.message.make_query(qname, qtype, qclass) + if msgid is not None: + msg.id = msgid + return msg.to_wire() + + +def prepare_buffer(wire, datalen=None): + """Utility function to prepare TCP buffer from DNS message in wire format""" + assert isinstance(wire, bytes) + if datalen is None: + datalen = len(wire) + return struct.pack("!H", datalen) + wire + + def get_msgbuf(qname, qtype, msgid): + # TODO remove/refactor in favor of prepare_wire, prepare_buffer msg = dns.message.make_query(qname, qtype, dns.rdataclass.IN) msg.id = msgid data = msg.to_wire()