You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
259 lines
14 KiB
259 lines
14 KiB
/* Padding: реальные таймеры/UDP, проверка расшифрованных пакетов и независимости владельцев. */ |
|
#include <stdio.h> |
|
#include <stdlib.h> |
|
#include <string.h> |
|
#include "etcp.h" |
|
#include "etcp_keepalive.h" |
|
#include "utun_instance.h" |
|
#include "../lib/debug_config.h" |
|
#include "../lib/mem.h" |
|
|
|
#define CHECK(x) do { if (!(x)) { fprintf(stderr, "FAIL %s:%d: %s\n", __func__, __LINE__, #x); exit(1); } } while (0) |
|
|
|
static struct { struct ETCP_PADDING* padding; timeout_callback_t callback; void* timer; int delay; } timers[128]; |
|
static size_t timer_count; |
|
static int fail_timer; |
|
static struct UTUN_INSTANCE *sender, *receiver; |
|
static sc_context_t wire_crypto; |
|
static int capture_ping, ping_requests, ping_responses, ping_done, expected_req_padding, expected_resp_padding, wire_calls; |
|
static int keepalive_packets, expected_keepalive_padding; |
|
static const uint8_t payload[] = {0, 0xff, 0x12, 0, 0x34, 0x56}; |
|
|
|
void* __real_uasync_set_timeout(struct UASYNC*, int, void*, timeout_callback_t, const char*); |
|
void* __wrap_uasync_set_timeout(struct UASYNC* ua, int delay, void* arg, timeout_callback_t cb, const char* name) { |
|
int padding = name && strcmp(name, "etcp_padding") == 0; |
|
if (padding && fail_timer) return NULL; |
|
void* timer = __real_uasync_set_timeout(ua, delay, arg, cb, name); |
|
if (padding && timer) { |
|
CHECK(timer_count < sizeof(timers) / sizeof(timers[0])); |
|
CHECK(delay >= 500000 && delay <= 3000000); |
|
timers[timer_count].padding = arg; timers[timer_count].callback = cb; |
|
timers[timer_count].timer = timer; timers[timer_count++].delay = delay; |
|
} |
|
return timer; |
|
} |
|
|
|
static void check_zeroes(const uint8_t* data, size_t size) { |
|
for (size_t i = 0; i < size; i++) CHECK(data[i] == 0); |
|
} |
|
|
|
ssize_t __real_socket_sendto(socket_t, const void*, size_t, const struct sockaddr*, socklen_t); |
|
ssize_t __wrap_socket_sendto(socket_t sock, const void* buf, size_t len, const struct sockaddr* dest, socklen_t dest_len) { |
|
wire_calls++; |
|
if (capture_ping) { |
|
CHECK(len <= PACKET_DATA_MAX_MTU - IP_UDP_OVERHEAD_V4); |
|
CHECK(len >= 96); |
|
const uint8_t* bytes = buf; |
|
uint8_t plain[PACKET_DATA_SIZE]; size_t plain_len = 0; |
|
CHECK(sc_decrypt(&wire_crypto, bytes, len - SC_PUBKEY_ENC_SIZE, plain, &plain_len) == SC_OK); |
|
int code = plain[3]; CHECK(code == ETCP_PING || code == ETCP_PONG); |
|
size_t ulen = ((size_t)plain[21] << 8) | plain[22]; |
|
CHECK(ulen == sizeof(payload) && memcmp(plain + 3 + 20, payload, ulen) == 0); |
|
size_t padding = plain_len - 3 - 20 - ulen; |
|
CHECK(padding == (size_t)(code == ETCP_PING ? expected_req_padding : expected_resp_padding)); |
|
check_zeroes(plain + 3 + 20 + ulen, padding); |
|
if (code == ETCP_PING) ping_requests++; else ping_responses++; |
|
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "wire PING/PONG code=%u payload=%zu padding=%zu bytes=%zu", code, ulen, padding, len); |
|
} |
|
return __real_socket_sendto(sock, buf, len, dest, dest_len); |
|
} |
|
|
|
static ssize_t keepalive_hook(socket_t fd, const void* buf, size_t len, const struct sockaddr* addr, socklen_t alen, |
|
struct ETCP_LINK* link, void* arg) { |
|
(void)fd; (void)addr; (void)alen; (void)arg; |
|
uint8_t plain[PACKET_DATA_SIZE]; size_t plain_len = 0; |
|
CHECK(sc_decrypt(&wire_crypto, buf, len, plain, &plain_len) == SC_OK); |
|
CHECK(plain[3] == ETCP_KEEPALIVE && plain[4] == 0); |
|
CHECK(plain_len == 3 + 4 + (size_t)expected_keepalive_padding); |
|
check_zeroes(plain + 3 + 4, expected_keepalive_padding); |
|
struct ETCP_DGRAM* pkt = memory_pool_alloc(sender->pkt_pool); CHECK(pkt); |
|
memcpy(pkt->data, plain + 3, plain_len - 3); pkt->data_len = plain_len - 3; |
|
uint32_t before = link->keepalive_recv_count; |
|
CHECK(etcp_keepalive_on_recv(sender->etcp_sockets, pkt, link, plain_len) == 1); |
|
CHECK(link->keepalive_recv_count == before + 1); |
|
keepalive_packets++; |
|
return len; |
|
} |
|
|
|
static void force_refresh(struct ETCP_PADDING* padding) { |
|
size_t i = timer_count; |
|
while (i && timers[i - 1].timer != padding->timer) i--; |
|
CHECK(i > 0 && timers[i - 1].padding == padding); |
|
CHECK(uasync_cancel_timeout(padding->ua, padding->timer) == 0); |
|
timers[i - 1].callback(padding); |
|
CHECK(padding->timer && padding->min <= 30); |
|
CHECK(padding->max >= padding->min + 32 && padding->max <= padding->min + 128); |
|
uint64_t now = get_time_tb(); |
|
CHECK(padding->next_refresh_tb > now && padding->next_refresh_tb <= now + 3000000); |
|
} |
|
|
|
static struct UTUN_INSTANCE* make_instance(struct UASYNC* ua, const char* private_key, const char* public_key) { |
|
char config[1024]; |
|
snprintf(config, sizeof(config), "[global]\nmy_private_key=%s\nmy_public_key=%s\nauto_sockets=no\n" |
|
"[server: padding]\naddr=127.0.0.1:0\ntype=public\n", private_key, public_key); |
|
struct UTUN_INSTANCE* inst = utun_instance_create_from_str(ua, config); CHECK(inst); |
|
CHECK(inst->padding.timer && inst->padding.min <= 30 && inst->padding.max <= ETCP_PADDING_MAX); |
|
return inst; |
|
} |
|
|
|
static struct ETCP_CONN* make_connection(struct UTUN_INSTANCE* inst, const struct SC_MYKEYS* peer) { |
|
struct ETCP_CONN* conn = etcp_connection_create(inst, "padding-test"); CHECK(conn); |
|
conn->peer_node_id = sc_derive_node_id_from_pubkey(peer->public_key); |
|
etcp_conn_queue_set_ready(conn); |
|
CHECK(sc_init_ctx(&conn->crypto_ctx, &inst->my_keys) == SC_OK); |
|
CHECK(sc_set_peer_public_key(&conn->crypto_ctx, peer->public_key, SC_PEER_PUBKEY_BIN) == SC_OK); |
|
CHECK(conn->padding.timer && conn->padding.timer != inst->padding.timer); |
|
return conn; |
|
} |
|
|
|
static void ping_cb(int success, uint16_t rtt, void* arg, uint64_t nonce, const uint8_t* data, size_t size) { |
|
(void)rtt; (void)arg; (void)nonce; |
|
CHECK(success && size == sizeof(payload) && memcmp(data, payload, size) == 0); |
|
ping_done = 1; |
|
} |
|
|
|
static void ping_case(int req_padding, int resp_padding) { |
|
expected_req_padding = req_padding; expected_resp_padding = resp_padding; |
|
ping_requests = ping_responses = ping_done = 0; |
|
struct sockaddr_storage addr; socklen_t size = sizeof(addr); |
|
CHECK(getsockname(receiver->etcp_sockets->fd, (struct sockaddr*)&addr, &size) == 0); |
|
capture_ping = 1; |
|
CHECK(etcp_send_ping(sender, receiver->my_keys.public_key, &addr, 500, ping_cb, NULL, payload, sizeof(payload)) == 0); |
|
for (int i = 0; !ping_done && i < 1000; i++) uasync_poll(sender->ua, 10); |
|
capture_ping = 0; |
|
CHECK(ping_done && ping_requests == 1 && ping_responses == 1 && !sender->pending_pings); |
|
} |
|
|
|
/* Слишком длинный user_data и лишний padding отбрасываются до эхо/доставки callback. */ |
|
static void malformed_ping_cases(void) { |
|
for (int code = ETCP_PING; code <= ETCP_PONG; code++) { |
|
for (int extra = 0; extra <= 1; extra++) { |
|
struct sockaddr_storage addr; socklen_t alen = sizeof(addr); |
|
CHECK(getsockname(receiver->etcp_sockets->fd, (struct sockaddr*)&addr, &alen) == 0); |
|
uint8_t plain[3 + 20 + ETCP_PADDING_MAX + 1] = {0}, packet[512]; |
|
plain[3] = code; |
|
plain[22] = extra ? 0 : 1; |
|
struct UTUN_INSTANCE *source = sender, *target = receiver; |
|
if (code == ETCP_PONG) { |
|
/* Битый ответ имеет nonce настоящего ожидающего пинга и приходит раньше корректного ответа. */ |
|
ping_done = 0; |
|
CHECK(etcp_send_ping(sender, receiver->my_keys.public_key, &addr, 500, |
|
ping_cb, NULL, payload, sizeof(payload)) == 0); |
|
CHECK(sender->pending_pings); |
|
uint64_t nonce = sender->pending_pings->nonce; |
|
for (int i = 0; i < 8; i++) plain[3 + 10 + i] = nonce >> (56 - i * 8); |
|
source = receiver; target = sender; |
|
alen = sizeof(addr); |
|
CHECK(getsockname(target->etcp_sockets->fd, (struct sockaddr*)&addr, &alen) == 0); |
|
} |
|
size_t enc_len = 0; |
|
size_t plain_len = extra ? sizeof(plain) : 23; |
|
CHECK(sc_encrypt(&wire_crypto, plain, plain_len, packet, &enc_len) == SC_OK); |
|
memset(packet + enc_len, 0, SC_PUBKEY_ENC_SALT_SIZE); |
|
CHECK(sc_obfuscate_pubkey(packet + enc_len, target->my_keys.public_key, source->my_keys.public_key, |
|
packet + enc_len + SC_PUBKEY_ENC_SALT_SIZE) == SC_OK); |
|
int before = wire_calls; |
|
size_t len = enc_len + SC_PUBKEY_ENC_SIZE; |
|
CHECK(__real_socket_sendto(source->etcp_sockets->fd, packet, len, (const struct sockaddr*)&addr, alen) == (ssize_t)len); |
|
if (code == ETCP_PING) { |
|
for (int i = 0; i < 10; i++) uasync_poll(sender->ua, 10); |
|
CHECK(wire_calls == before); |
|
} else { |
|
for (int i = 0; !ping_done && i < 1000; i++) uasync_poll(sender->ua, 10); |
|
CHECK(ping_done && !sender->pending_pings && wire_calls == before + 1); |
|
} |
|
} |
|
} |
|
puts("PASS malformed PING/PONG: invalid user length and oversized padding rejected"); |
|
} |
|
|
|
int main(void) { |
|
debug_config_init(); debug_set_level(DEBUG_LEVEL_WARN); |
|
debug_set_category_level(DEBUG_CATEGORY_GENERAL, DEBUG_LEVEL_INFO); |
|
debug_set_category_level(DEBUG_CATEGORY_ETCP, DEBUG_LEVEL_DEBUG); |
|
debug_set_category_level(DEBUG_CATEGORY_KEEPALIVE, DEBUG_LEVEL_DEBUG); |
|
utun_instance_set_tun_init_enabled(0); utun_instance_set_topo_group_enabled(0); |
|
struct UASYNC* ua = uasync_create(); CHECK(ua); |
|
sender = make_instance(ua, "38240cb82199e504686507f11f6eaa4f740fde6f0c425c495e49a523019a5d68", |
|
"ce8871f07fa056c636d297115f231b08c29cdf94e0d440fce83a07c34416d36a"); |
|
receiver = make_instance(ua, "704f2e012c8fa8768130cb0f988a997dccb628372bc5ceccacc78dcbfec5916f", |
|
"b3193173def895bd0fcea6f86af077c7d77216f10395275f627ac18242ec0f01"); |
|
sc_init_ctx(&wire_crypto, &sender->my_keys); |
|
CHECK(sc_set_peer_public_key(&wire_crypto, receiver->my_keys.public_key, SC_PEER_PUBKEY_BIN) == SC_OK); |
|
sender->padding.min = sender->padding.max = 0; |
|
receiver->padding.min = receiver->padding.max = ETCP_PADDING_MAX; |
|
ping_case(0, ETCP_PADDING_MAX); |
|
sender->padding.min = sender->padding.max = ETCP_PADDING_MAX; |
|
receiver->padding.min = receiver->padding.max = 0; |
|
ping_case(ETCP_PADDING_MAX, 0); |
|
puts("PASS stateless PING/PONG: encrypted padding boundaries and byte-exact echo"); |
|
|
|
struct ETCP_CONN* conn = make_connection(sender, &receiver->my_keys); |
|
struct ETCP_CONN* peer_conn = make_connection(receiver, &sender->my_keys); |
|
conn->padding.min = conn->padding.max = 7; |
|
peer_conn->padding.min = peer_conn->padding.max = 13; |
|
CHECK(etcp_padding_for_peer(sender, receiver->my_keys.public_key) == &conn->padding); |
|
ping_case(7, 13); |
|
puts("PASS PING/PONG: connection ranges override independent instance ranges"); |
|
malformed_ping_cases(); |
|
uint8_t large[1370] = {0}; struct sockaddr_storage addr; socklen_t alen = sizeof(addr); |
|
CHECK(getsockname(receiver->etcp_sockets->fd, (struct sockaddr*)&addr, &alen) == 0); |
|
int before = wire_calls; |
|
CHECK(etcp_send_ping(sender, receiver->my_keys.public_key, &addr, 500, ping_cb, NULL, large, sizeof(large)) < 0); |
|
CHECK(wire_calls == before && !sender->pending_pings); |
|
puts("PASS oversized PING: minimum padding must fit MTU, user_data is not truncated"); |
|
|
|
struct ETCP_LINK link = {0}; link.etcp = conn; link.conn = sender->etcp_sockets; |
|
link.mtu = 1280; link.remote_addr.ss_family = AF_INET; link.ka_period_ms = 200; |
|
link.send_hook = keepalive_hook; |
|
for (int i = 0; i < 2; i++) { |
|
expected_keepalive_padding = i ? ETCP_PADDING_MAX : 0; |
|
conn->padding.min = conn->padding.max = expected_keepalive_padding; |
|
etcp_link_send_keepalive(&link); |
|
} |
|
CHECK(keepalive_packets == 2); |
|
struct ETCP_DGRAM* malformed = memory_pool_alloc(sender->pkt_pool); CHECK(malformed); |
|
memset(malformed->data, 0, 7); malformed->data[0] = ETCP_KEEPALIVE; |
|
malformed->data[1] = KA_FLAG_SLEEP; malformed->data_len = 7; |
|
uint32_t received = link.keepalive_recv_count; |
|
CHECK(etcp_keepalive_on_recv(sender->etcp_sockets, malformed, &link, 10) == 1); |
|
CHECK(link.keepalive_recv_count == received && !link.ka_peer_sleeping); |
|
puts("PASS keepalive: encrypted padding boundaries and receive parsing"); |
|
|
|
struct ETCP_CONN* sibling = etcp_connection_create(sender, "padding-sibling"); CHECK(sibling); |
|
struct ETCP_PADDING saved = sender->padding, saved_peer = peer_conn->padding, saved_sibling = sibling->padding; |
|
force_refresh(&conn->padding); |
|
CHECK(memcmp(&saved, &sender->padding, sizeof(saved)) == 0); |
|
CHECK(memcmp(&saved_peer, &peer_conn->padding, sizeof(saved_peer)) == 0); |
|
CHECK(memcmp(&saved_sibling, &sibling->padding, sizeof(saved_sibling)) == 0); |
|
saved = conn->padding; |
|
force_refresh(&sender->padding); |
|
CHECK(memcmp(&saved, &conn->padding, sizeof(saved)) == 0); |
|
int seen[ETCP_PADDING_MAX + 1] = {0}, different = 0; |
|
for (int i = 0; i < 512; i++) { |
|
int size = etcp_padding_size(&conn->padding, ETCP_PADDING_MAX); |
|
CHECK(size >= conn->padding.min && size <= conn->padding.max); |
|
if (!seen[size]++) different++; |
|
} |
|
CHECK(different > 1); |
|
struct ETCP_PADDING boundary = {.min = 10, .max = 20}; |
|
CHECK(etcp_padding_size(&boundary, 9) == -1 && etcp_padding_size(&boundary, 10) == 10); |
|
puts("PASS independent refresh timers, range invariants and MTU capacity"); |
|
|
|
CHECK(etcp_conn_ref_take(conn) == 0); |
|
etcp_connection_close(conn); CHECK(!conn->padding.timer); |
|
CHECK(etcp_padding_for_peer(sender, receiver->my_keys.public_key) == &sender->padding); |
|
etcp_conn_ref_free(conn); etcp_connection_close(peer_conn); etcp_connection_close(sibling); uasync_poll(ua, 0); |
|
fail_timer = 1; |
|
CHECK(etcp_connection_create(sender, "padding-allocation-failure") == NULL); |
|
size_t allocations = u_get_allocated_count(); |
|
CHECK(utun_instance_create_from_config(ua, sender->config) == NULL); |
|
CHECK(u_get_allocated_count() == allocations); |
|
fail_timer = 0; uasync_poll(ua, 0); |
|
CHECK(!sender->connections->count); |
|
utun_instance_destroy(sender); utun_instance_destroy(receiver); uasync_poll(ua, 0); |
|
CHECK(ua->timer_alloc_count == ua->timer_free_count); |
|
uasync_destroy(ua, 0); |
|
puts("PASS timer cancellation and connection creation failure cleanup"); |
|
return 0; |
|
}
|
|
|