]> git.ipfire.org Git - thirdparty/knot-resolver.git/commitdiff
pytests: relocate and refactor TCP prefix tests
authorTomas Krizek <tomas.krizek@nic.cz>
Tue, 13 Nov 2018 16:48:08 +0000 (17:48 +0100)
committerTomas Krizek <tomas.krizek@nic.cz>
Tue, 4 Dec 2018 16:13:42 +0000 (17:13 +0100)
tests/pytests/test_conn_mgmt.py
tests/pytests/test_tcp_prefix.py [new file with mode: 0644]
tests/pytests/utils.py

index a9678a2cc3db2b1339f63b9fb5afef148e149252..78539dfad28749acaea4076d208d16cf386875fa 100644 (file)
@@ -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 (file)
index 0000000..2ad9d6e
--- /dev/null
@@ -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)
index e4d138496df226f79be4578d8e35324546758f51..9ba4e6595e7a4a5bd85cd9ff44a17621d89bc23f 100644 (file)
@@ -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()