pytests: relocate and refactor TCP prefix tests

parent ac0fbd64
"""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)
"""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)
......@@ -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()
......
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