31 changed files with 854 additions and 103 deletions
@ -0,0 +1,77 @@
|
||||
#include "etcp_padding.h" |
||||
#include "etcp.h" |
||||
#include "utun_instance.h" |
||||
#include "../lib/debug_config.h" |
||||
#include "../lib/u_async.h" |
||||
#include <stdlib.h> |
||||
|
||||
static unsigned padding_random(unsigned min, unsigned max) { |
||||
return min + (unsigned)rand() % (max - min + 1); |
||||
} |
||||
|
||||
static void padding_generate(struct ETCP_PADDING* padding) { |
||||
padding->min = padding_random(0, 30); |
||||
padding->max = padding->min + padding_random(32, 128); |
||||
} |
||||
|
||||
static void padding_timer_cb(void* arg); |
||||
|
||||
static int padding_refresh(struct ETCP_PADDING* padding) { |
||||
padding_generate(padding); |
||||
unsigned seconds = padding_random(50, 300); |
||||
padding->next_refresh_tb = get_time_tb() + (uint64_t)seconds * 10000; |
||||
padding->timer = uasync_set_timeout(padding->ua, seconds * 10000, padding, padding_timer_cb, "etcp_padding"); |
||||
if (!padding->timer) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "[%s] padding=%p: refresh timer allocation failed", padding->name, (void*)padding); |
||||
return -1; |
||||
} |
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] padding=%p: range=%u..%u refresh_in=%us", |
||||
padding->name, (void*)padding, padding->min, padding->max, seconds); |
||||
return 0; |
||||
} |
||||
|
||||
static void padding_timer_cb(void* arg) { |
||||
struct ETCP_PADDING* padding = arg; |
||||
padding->timer = NULL; |
||||
padding_refresh(padding); |
||||
} |
||||
|
||||
int etcp_padding_start(struct ETCP_PADDING* padding, struct UASYNC* ua, const char* name) { |
||||
if (!padding || !ua || padding->timer) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "padding: invalid start padding=%p ua=%p", (void*)padding, (void*)ua); |
||||
return -1; |
||||
} |
||||
padding->ua = ua; |
||||
padding->name = name && name[0] ? name : "instance"; |
||||
return padding_refresh(padding); |
||||
} |
||||
|
||||
void etcp_padding_stop(struct ETCP_PADDING* padding) { |
||||
if (!padding) return; |
||||
if (padding->timer) { |
||||
uasync_cancel_timeout(padding->ua, padding->timer); |
||||
padding->timer = NULL; |
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] padding=%p: refresh timer cancelled", padding->name, (void*)padding); |
||||
} |
||||
padding->ua = NULL; |
||||
padding->next_refresh_tb = 0; |
||||
} |
||||
|
||||
int etcp_padding_size(const struct ETCP_PADDING* padding, size_t available) { |
||||
struct ETCP_PADDING temporary = {0}; |
||||
if (!padding) { padding_generate(&temporary); padding = &temporary; } |
||||
if (padding->min > padding->max || padding->max > ETCP_PADDING_MAX || available < padding->min) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "padding: invalid range/capacity min=%u max=%u available=%zu", |
||||
padding->min, padding->max, available); |
||||
return -1; |
||||
} |
||||
unsigned max = available < padding->max ? (unsigned)available : padding->max; |
||||
return padding_random(padding->min, max); |
||||
} |
||||
|
||||
const struct ETCP_PADDING* etcp_padding_for_peer(struct UTUN_INSTANCE* inst, const uint8_t* public_key) { |
||||
if (!inst || !public_key) return NULL; |
||||
struct ETCP_CONN* conn = instance_find_conn(inst, sc_derive_node_id_from_pubkey(public_key)); |
||||
if (conn && !conn->close_requested) return &conn->padding; |
||||
return &inst->padding; |
||||
} |
||||
@ -0,0 +1,36 @@
|
||||
/* Независимый диапазон padding владельца; таймер и выбор длины работают в его uasync-потоке. */ |
||||
#ifndef ETCP_PADDING_H |
||||
#define ETCP_PADDING_H |
||||
|
||||
#include <stddef.h> |
||||
#include <stdint.h> |
||||
|
||||
#ifdef __cplusplus |
||||
extern "C" { |
||||
#endif |
||||
|
||||
struct UASYNC; |
||||
struct UTUN_INSTANCE; |
||||
|
||||
#define ETCP_PADDING_MAX 158 |
||||
|
||||
struct ETCP_PADDING { |
||||
uint16_t min, max; |
||||
void* timer; |
||||
struct UASYNC* ua; |
||||
const char* name; // заимствованное имя владельца для диагностики
|
||||
uint64_t next_refresh_tb; |
||||
}; |
||||
|
||||
int etcp_padding_start(struct ETCP_PADDING* padding, struct UASYNC* ua, const char* name); |
||||
void etcp_padding_stop(struct ETCP_PADDING* padding); |
||||
/* NULL допустим только для автономного STCP без instance: диапазон генерируется на операцию. */ |
||||
int etcp_padding_size(const struct ETCP_PADDING* padding, size_t available); |
||||
/* Существующий незакрываемый ETCP_CONN владеет своим диапазоном; иначе используется instance. */ |
||||
const struct ETCP_PADDING* etcp_padding_for_peer(struct UTUN_INSTANCE* inst, const uint8_t* public_key); |
||||
|
||||
#ifdef __cplusplus |
||||
} |
||||
#endif |
||||
|
||||
#endif |
||||
@ -0,0 +1,259 @@
|
||||
/* 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; |
||||
} |
||||
Loading…
Reference in new issue