summaryrefslogtreecommitdiffstats
path: root/tests/pytests/test_conn_mgmt.py
diff options
context:
space:
mode:
Diffstat (limited to 'tests/pytests/test_conn_mgmt.py')
-rw-r--r--tests/pytests/test_conn_mgmt.py214
1 files changed, 214 insertions, 0 deletions
diff --git a/tests/pytests/test_conn_mgmt.py b/tests/pytests/test_conn_mgmt.py
new file mode 100644
index 0000000..1d15091
--- /dev/null
+++ b/tests/pytests/test_conn_mgmt.py
@@ -0,0 +1,214 @@
+# SPDX-License-Identifier: GPL-3.0-or-later
+"""TCP Connection Management tests"""
+
+import socket
+import struct
+import time
+
+import pytest
+
+import utils
+
+
+@pytest.mark.parametrize('garbage_lengths', [
+ (1,),
+ (1024,),
+ (65533,), # max size garbage
+ (65533, 65533),
+ (1024, 1024, 1024),
+ # (0,), # currently kresd uses this as a heuristic of "lost in bytestream"
+ # (0, 1024), # and closes the connection
+])
+def test_ignore_garbage(kresd_sock, garbage_lengths, single_buffer, query_before):
+ """Send chunk of garbage, prefixed by garbage length. It should be ignored."""
+ buff = b''
+ if query_before: # optionally send initial query
+ msg_buff_before, msgid_before = utils.get_msgbuff()
+ if single_buffer:
+ buff += msg_buff_before
+ else:
+ kresd_sock.sendall(msg_buff_before)
+
+ for glength in garbage_lengths: # prepare garbage data
+ if glength is None:
+ continue
+ garbage_buff = utils.get_prefixed_garbage(glength)
+ if single_buffer:
+ buff += garbage_buff
+ else:
+ kresd_sock.sendall(garbage_buff)
+
+ msg_buff, msgid = utils.get_msgbuff() # final query
+ buff += msg_buff
+ kresd_sock.sendall(buff)
+
+ if query_before:
+ answer_before = utils.receive_parse_answer(kresd_sock)
+ assert answer_before.id == msgid_before
+ answer = utils.receive_parse_answer(kresd_sock)
+ assert answer.id == msgid
+
+
+def test_pipelining(kresd_sock):
+ """
+ First query takes longer to resolve - answer to second query should arrive sooner.
+
+ This test requires internet connection.
+ """
+ # initialization (to avoid issues with net.ipv6=true)
+ buff_pre, msgid_pre = utils.get_msgbuff('0.delay.getdnsapi.net.')
+ kresd_sock.sendall(buff_pre)
+ msg_answer = utils.receive_parse_answer(kresd_sock)
+ assert msg_answer.id == msgid_pre
+
+ # test
+ buff1, msgid1 = utils.get_msgbuff('1500.delay.getdnsapi.net.', msgid=1)
+ buff2, msgid2 = utils.get_msgbuff('1.delay.getdnsapi.net.', msgid=2)
+ buff = buff1 + buff2
+ kresd_sock.sendall(buff)
+
+ msg_answer = utils.receive_parse_answer(kresd_sock)
+ assert msg_answer.id == msgid2
+
+ msg_answer = utils.receive_parse_answer(kresd_sock)
+ assert msg_answer.id == msgid1
+
+
+@pytest.mark.parametrize('duration, delay', [
+ (utils.MAX_TIMEOUT, 0.1),
+ (utils.MAX_TIMEOUT, 3),
+ (utils.MAX_TIMEOUT, 7),
+ (utils.MAX_TIMEOUT + 10, 3),
+])
+def test_long_lived(kresd_sock, duration, delay):
+ """Establish and keep connection alive for longer than maximum timeout."""
+ utils.ping_alive(kresd_sock)
+ end_time = time.time() + duration
+
+ while time.time() < end_time:
+ time.sleep(delay)
+ utils.ping_alive(kresd_sock)
+
+
+def test_close(kresd_sock, query_before):
+ """Establish a connection and wait for timeout from kresd."""
+ if query_before:
+ utils.ping_alive(kresd_sock)
+ time.sleep(utils.MAX_TIMEOUT)
+
+ with utils.expect_kresd_close():
+ utils.ping_alive(kresd_sock)
+
+
+def test_slow_lorris(kresd_sock, query_before):
+ """Simulate slow-lorris attack by sending byte after byte with delays in between."""
+ if query_before:
+ utils.ping_alive(kresd_sock)
+
+ buff, _ = utils.get_msgbuff()
+ end_time = time.time() + utils.MAX_TIMEOUT
+
+ with utils.expect_kresd_close():
+ for i in range(len(buff)):
+ b = buff[i:i+1]
+ kresd_sock.send(b)
+ if time.time() > end_time:
+ break
+ time.sleep(1)
+
+
+@pytest.mark.parametrize('sock_func_name', [
+ 'ip_tcp_socket',
+ 'ip6_tcp_socket',
+])
+def test_oob(kresd, sock_func_name):
+ """TCP out-of-band (urgent) data must not crash resolver."""
+ make_sock = getattr(kresd, sock_func_name)
+ sock = make_sock()
+ msg_buff, msgid = utils.get_msgbuff()
+ sock.sendall(msg_buff, socket.MSG_OOB)
+
+ try:
+ msg_answer = utils.receive_parse_answer(sock)
+ assert msg_answer.id == msgid
+ except ConnectionError:
+ pass # TODO kresd responds with TCP RST, this should be fixed
+
+ # check kresd is alive
+ sock2 = make_sock()
+ utils.ping_alive(sock2)
+
+
+def flood_buffer(msgcount):
+ flood_buff = bytes()
+ msgbuff, _ = utils.get_msgbuff()
+ noid_msgbuff = msgbuff[2:]
+
+ def gen_msg(msgid):
+ return struct.pack("!H", len(msgbuff)) + struct.pack("!H", msgid) + noid_msgbuff
+
+ for i in range(msgcount):
+ flood_buff += gen_msg(i)
+ return flood_buff
+
+
+def test_query_flood_close(make_kresd_sock):
+ """Flood resolver with queries and close the connection."""
+ buff = flood_buffer(10000)
+ sock1 = make_kresd_sock()
+ sock1.sendall(buff)
+ sock1.close()
+
+ sock2 = make_kresd_sock()
+ utils.ping_alive(sock2)
+
+
+def test_query_flood_no_recv(make_kresd_sock):
+ """Flood resolver with queries but don't read any data."""
+ # A use-case for TCP_USER_TIMEOUT socket option? See RFC 793 and RFC 5482
+
+ # It seems it doesn't works as expected. libuv doesn't return any error
+ # (neither on uv_write() call, not in the callback) when kresd sends answers,
+ # so kresd can't recognize that client didn't read any answers. At a certain
+ # point, kresd stops receiving queries from the client (whilst client keep
+ # sending) and closes connection due to timeout.
+
+ buff = flood_buffer(10000)
+ sock1 = make_kresd_sock()
+ end_time = time.time() + utils.MAX_TIMEOUT
+
+ with utils.expect_kresd_close(rst_ok=True): # connection must be closed
+ while time.time() < end_time:
+ sock1.sendall(buff)
+ time.sleep(0.5)
+
+ sock2 = make_kresd_sock()
+ utils.ping_alive(sock2) # resolver must stay alive
+
+
+@pytest.mark.parametrize('glength, gcount, delay', [
+ (65533, 100, 0.5),
+ (0, 100000, 0.5),
+ (1024, 1000, 0.5),
+ (65533, 1, 0),
+ (0, 1, 0),
+ (1024, 1, 0),
+])
+def test_query_flood_garbage(make_kresd_silent_sock, glength, gcount, delay, query_before):
+ """Flood resolver with prefixed garbage."""
+ sock1 = make_kresd_silent_sock()
+ if query_before:
+ utils.ping_alive(sock1)
+
+ gbuff = utils.get_prefixed_garbage(glength)
+ buff = gbuff * gcount
+
+ end_time = time.time() + utils.MAX_TIMEOUT
+
+ with utils.expect_kresd_close(rst_ok=True): # connection must be closed
+ while time.time() < end_time:
+ sock1.sendall(buff)
+ time.sleep(delay)
+
+ sock2 = make_kresd_silent_sock()
+ utils.ping_alive(sock2) # resolver must stay alive