"""TCP Connection Management tests"""
-import struct
-import time
import dns
import dns.message
-import pytest
import utils
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)
--- /dev/null
+"""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)
import random
import dns
+import dns.message
def random_msgid():
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()