test_conn_mgmt.py 6.29 KB
Newer Older
1 2
"""TCP Connection Management tests"""

Tomas Krizek's avatar
Tomas Krizek committed
3
import socket
4
import struct
5 6
import time

7 8
import pytest

9 10 11
import utils


12 13 14 15 16 17 18 19 20 21
@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):
22
    """Send chunk of garbage, prefixed by garbage length. It should be ignored."""
23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
    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)
43

44 45 46 47 48
    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
49 50 51


def test_pipelining(kresd_sock):
Tomas Krizek's avatar
Tomas Krizek committed
52 53 54 55 56
    """
    First query takes longer to resolve - answer to second query should arrive sooner.

    This test requires internet connection.
    """
57 58 59 60 61 62 63
    # 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
64
    buff1, msgid1 = utils.get_msgbuff('1500.delay.getdnsapi.net.', msgid=1)
65 66 67
    buff2, msgid2 = utils.get_msgbuff('1.delay.getdnsapi.net.', msgid=2)
    buff = buff1 + buff2
    kresd_sock.sendall(buff)
68 69

    msg_answer = utils.receive_parse_answer(kresd_sock)
70
    assert msg_answer.id == msgid2
71 72 73

    msg_answer = utils.receive_parse_answer(kresd_sock)
    assert msg_answer.id == msgid1
74 75


76 77 78
@pytest.mark.parametrize('duration, delay', [
    (utils.MAX_TIMEOUT, 0.1),
    (utils.MAX_TIMEOUT, 3),
79
    (utils.MAX_TIMEOUT, 7),
80 81 82
    (utils.MAX_TIMEOUT + 10, 3),
])
def test_long_lived(kresd_sock, duration, delay):
83
    """Establish and keep connection alive for longer than maximum timeout."""
84
    utils.ping_alive(kresd_sock)
85
    end_time = time.time() + duration
86 87

    while time.time() < end_time:
88
        time.sleep(delay)
89
        utils.ping_alive(kresd_sock)
90 91


92
def test_close(kresd_sock, query_before):
93
    """Establish a connection and wait for timeout from kresd."""
94 95
    if query_before:
        utils.ping_alive(kresd_sock)
96 97
    time.sleep(utils.MAX_TIMEOUT)

98 99
    with utils.expect_kresd_close():
        utils.ping_alive(kresd_sock)
100 101


102
def test_slow_lorris(kresd_sock, query_before):
103
    """Simulate slow-lorris attack by sending byte after byte with delays in between."""
104 105
    if query_before:
        utils.ping_alive(kresd_sock)
106

107
    buff, _ = utils.get_msgbuff()
108 109
    end_time = time.time() + utils.MAX_TIMEOUT

110 111 112 113
    with utils.expect_kresd_close():
        for i in range(len(buff)):
            b = buff[i:i+1]
            kresd_sock.send(b)
114 115
            if time.time() > end_time:
                break
116
            time.sleep(1)
117 118


Tomas Krizek's avatar
Tomas Krizek committed
119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140
@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)


141 142 143 144 145 146 147 148 149 150 151 152 153
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


154
def test_query_flood_close(make_kresd_sock):
155
    """Flood resolver with queries and close the connection."""
156 157 158 159 160 161 162
    buff = flood_buffer(10000)
    sock1 = make_kresd_sock()
    sock1.sendall(buff)
    sock1.close()

    sock2 = make_kresd_sock()
    utils.ping_alive(sock2)
163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185


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
186 187


188 189 190 191 192 193 194 195 196 197
@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_sock, glength, gcount, delay, query_before):
    """Flood resolver with prefixed garbage."""
198
    sock1 = make_kresd_sock()
199 200 201 202 203 204 205
    if query_before:
        utils.ping_alive(sock1)

    gbuff = utils.get_prefixed_garbage(glength)
    buff = gbuff * gcount

    end_time = time.time() + utils.MAX_TIMEOUT
206 207 208 209

    with utils.expect_kresd_close(rst_ok=True):  # connection must be closed
        while time.time() < end_time:
            sock1.sendall(buff)
210
            time.sleep(delay)
211 212 213

    sock2 = make_kresd_sock()
    utils.ping_alive(sock2)  # resolver must stay alive