From a4cf9bab2f4ffaf72dad3ec0976e569aee2599da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pol=20Nicola=C3=AF?= Date: Thu, 2 Dec 2021 15:05:25 +0000 Subject: [PATCH] libconvert: send cookie TLV on client side MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It is now possible to send a cookie TLV from the client using libconvert. New tests have been added to check that the expected Cookie message is sent and the Cookie TLV error is properly received and understood. Signed-off-by: Pol Nicolaï Change-Id: I51fe5dce0bc1d24c10808b44d906224a91a82bab --- convert_client.c | 19 ++++++++++++++++--- tests/CMakeLists.txt | 2 +- tests/check_convert_util.c | 2 +- tests/converter_mock.py | 37 ++++++++++++++++++++++++++++++++++++- tests/test-wrapper | 7 ++++++- tests/test_cookie_error.py | 21 +++++++++++++++++++++ tests/test_cookie_ok.py | 19 +++++++++++++++++++ tests/test_lib.py | 3 +++ 8 files changed, 103 insertions(+), 7 deletions(-) create mode 100755 tests/test_cookie_error.py create mode 100755 tests/test_cookie_ok.py diff --git a/convert_client.c b/convert_client.c index a320ec0..86fb9ac 100644 --- a/convert_client.c +++ b/convert_client.c @@ -76,7 +76,8 @@ static LIST_HEAD(socket_htbl_t, socket_state) _socket_htable[NUM_BUCKETS]; static pthread_mutex_t _socket_htable_mutex = PTHREAD_MUTEX_INITIALIZER; static struct addrinfo *_converter_addr; -static const char * _convert_port = CONVERT_PORT; +static const char * _convert_port = CONVERT_PORT; +static const char * _convert_cookie = NULL; static FILE * _log; static pthread_mutex_t _log_mutex = PTHREAD_MUTEX_INITIALIZER; @@ -175,6 +176,12 @@ _redirect_connect_tlv(uint8_t *buf, size_t buf_len, struct sockaddr *addr) opts.flags = CONVERT_F_CONNECT; + if (_convert_cookie) { + opts.flags |= CONVERT_F_COOKIE; + opts.cookie_len = strlen(_convert_cookie); + opts.cookie_data = (uint8_t *)_convert_cookie; + } + switch (addr->sa_family) { case AF_INET: { struct sockaddr_in *in = (struct sockaddr_in *)addr; @@ -543,6 +550,7 @@ _validate_parameters(char *err_buf, size_t len) { const char * convert_addr = getenv("CONVERT_ADDR"); const char * convert_port = getenv("CONVERT_PORT"); + const char * convert_cookie = getenv("CONVERT_COOKIE"); if (!convert_addr) { snprintf(err_buf, len, @@ -565,6 +573,9 @@ _validate_parameters(char *err_buf, size_t len) _convert_port = convert_port; } + if (convert_cookie) + _convert_cookie = convert_cookie; + /* resolve address */ if (getaddrinfo(convert_addr, _convert_port, NULL, &_converter_addr) != 0) { @@ -572,8 +583,10 @@ _validate_parameters(char *err_buf, size_t len) return -1; } - log_info("connecting to convert service at %s:%s", - convert_addr, _convert_port); + log_info("connecting to convert service at %s:%s%s%s", + convert_addr, _convert_port, + _convert_cookie ? " with cookie: " : "", + _convert_cookie ? : ""); return 0; } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 9d77a2f..ca50e76 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -25,7 +25,7 @@ add_test(NAME check_convert_util set(TEST_WRAPPER "${PROJECT_SOURCE_DIR}/tests/test-wrapper") foreach(TEST_CMD "curl" "wget") - foreach(TEST_TYPE "test_ok" "test_error") + foreach(TEST_TYPE "test_ok" "test_error" "test_cookie_ok" "test_cookie_error") set (test_name "${TEST_CMD}-${TEST_TYPE}") add_test(NAME "${test_name}" diff --git a/tests/check_convert_util.c b/tests/check_convert_util.c index 0e69774..feef676 100644 --- a/tests/check_convert_util.c +++ b/tests/check_convert_util.c @@ -279,7 +279,7 @@ START_TEST (test_convert_parse_tlvs_cookie) { for (i = 0; i < cookie_len; ++i) ck_assert_msg(opts->cookie_data[i] == cookie->opaque[i], - "Should return exact copy TCP options"); + "Should return exact cookie value"); convert_free_opts(opts); free(buff); diff --git a/tests/converter_mock.py b/tests/converter_mock.py index 48fd046..0b09da3 100644 --- a/tests/converter_mock.py +++ b/tests/converter_mock.py @@ -53,7 +53,30 @@ def run(self, converter): converter.recv_end_seq += 1 payload = pkt.getlayer(Raw) if payload: - converter.recv_end_seq += len(payload.load) + payload = payload.load + + converter.recv_end_seq += len(payload) + + # For the moment, we assume the TLV connect contains the header (4 bytes) + # and the TLV Connect (4 + 16 bytes). The cookie is then at the offset 24. + cookie_flag_offset = 24 + + # The cookie will only be received in the syn pkt. + if tcp.flags.S and len(payload) > cookie_flag_offset and bytes(payload)[cookie_flag_offset] == 0x16: + + # The cookie TLV is as follows: 1 byte flag + 1 byte TLV length + # + 2 byte zeros, then Cookie data + cookie_size_offset = cookie_flag_offset + 1 + cookie_data_offset = cookie_flag_offset + 4 + + # Length is given in 32-bit words, and includes flag byte + length + # byte and the 2 bytes of zeros. + cookie_data_size = (bytes(payload)[cookie_size_offset] - 1) * 4 + + cookie_data_offset_end = cookie_data_offset + cookie_data_size + + cookie_bytes = bytes(payload)[cookie_data_offset:cookie_data_offset_end] + converter.cookie = cookie_bytes.decode('utf-8').rstrip('\x00') class RecvSyn(RecvPkt): @@ -98,6 +121,18 @@ def run(self, converter): super(SendSynAck, self).run(converter) +class SendSynAckCheckCookie(SendSynAck): + def __init__(self, payload, cookie): + SendSynAck.__init__(self, payload=payload) + self.cookie = cookie + + def run(self, converter): + # Check cookie value + if self.cookie != converter.cookie: + raise Exception("Wrong cookie '{}' instead of '{}'".format(converter.cookie, self.cookie)) + super(SendSynAckCheckCookie, self).run(converter) + + class SendHTTPResp(SendPkt): def __init__(self, data): payload = "HTTP/1.1 200 OK\r\n" diff --git a/tests/test-wrapper b/tests/test-wrapper index 49b62cf..92e737f 100755 --- a/tests/test-wrapper +++ b/tests/test-wrapper @@ -13,6 +13,7 @@ export TEST_CONVERT_LOG=convert.log export TEST_OUTPUT_LOG=output.log export TEST_CONVERT_MOCK_LOG=convert_mock.log export TEST_VALIDATE_LOG=validate.log +export TEST_CONVERT_COOKIE="cookie" SCAPY_PID= @@ -60,7 +61,11 @@ sleep 1 COMMAND=$(test_step run_cmd) -CONVERT_LOG=${TEST_CONVERT_LOG} CONVERT_ADDR=127.0.0.1 LD_PRELOAD="../libconvert_client.so" ${COMMAND} > ${TEST_OUTPUT_LOG} 2>&1 || true +CONVERT_LOG=${TEST_CONVERT_LOG} \ + CONVERT_ADDR=127.0.0.1 \ + CONVERT_COOKIE=${TEST_CONVERT_COOKIE} \ + LD_PRELOAD="../libconvert_client.so" \ + ${COMMAND} > ${TEST_OUTPUT_LOG} 2>&1 || true ret=$(test_step validate 2>&1 | tee ${TEST_VALIDATE_LOG}) [ "${ret}" == "" ] && RETURN_CODE=0 diff --git a/tests/test_cookie_error.py b/tests/test_cookie_error.py new file mode 100755 index 0000000..03a84d4 --- /dev/null +++ b/tests/test_cookie_error.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python3 + +from converter_mock import * +from test_lib import * + + +class TestCookieError(TestInstance): + def run(self): + converter = Convert(tlvs=[ConvertTLV_Error(error_code=3)]) + print(converter.build()) + cookie = self.get_cookie() + + class MockConverterCookieError(MockConverter): + actions = [RecvSyn(), SendSynAckCheckCookie(converter.build(), cookie), Wait(1), SendPkt(flags='R')] + MockConverterCookieError() + + def validate(self): + self.assert_log_contains("received TLV error: 3") + + +TestCookieError() diff --git a/tests/test_cookie_ok.py b/tests/test_cookie_ok.py new file mode 100755 index 0000000..c9bfc7e --- /dev/null +++ b/tests/test_cookie_ok.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python3 + +from converter_mock import * +from test_lib import * + + +class TestCookieOK(TestInstance): + def run(self): + cookie = self.get_cookie() + class MockConverterCookieOK(MockConverter): + actions = [RecvSyn(), SendSynAckCheckCookie(Convert().build(), cookie), RecvHTTPGet( + ), SendHTTPResp("HELLO, WORLD!"), SendPkt(flags='RA')] + MockConverterCookieOK() + + def validate(self): + self.assert_result("HELLO, WORLD!") + + +TestCookieOK() diff --git a/tests/test_lib.py b/tests/test_lib.py index 773ea78..9134ed6 100644 --- a/tests/test_lib.py +++ b/tests/test_lib.py @@ -35,6 +35,9 @@ def _get_log(self): with open(os.environ["TEST_CONVERT_LOG"]) as f: return f.read() + def get_cookie(self): + return os.environ["TEST_CONVERT_COOKIE"] + def assert_result(self, result): assert result in self._get_result(), "Couldn't find '{}' in output".format(result)