diff options
Diffstat (limited to 'tests/pytests/test_conn_mgmt.py')
-rw-r--r-- | tests/pytests/test_conn_mgmt.py | 214 |
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 |