Protect REALITY STCP traffic with authenticated TLS-like records
This commit is contained in:
1 parent
a49cf5434e
commit
a069d11ef9
33 files changed
+1786
-421
No files matched your search
+25
-3
@@ -1,6 +1,6 @@
|
||||
# Tests Makefile.am for utun - all tests with new ll_queue library
|
||||
|
||||
EXTRA_DIST = data/silero_speech.pcm data/silero_speech.txt
|
||||
EXTRA_DIST = fixtures/reality_openssl_hello.hex data/silero_speech.pcm data/silero_speech.txt
|
||||
|
||||
# All available tests (check_PROGRAMS runs via automake check-TESTS)
|
||||
check_PROGRAMS = \
|
||||
@@ -120,6 +120,8 @@ check_PROGRAMS = \
|
||||
test_media_delivery_full \
|
||||
test_media_delivery_chat \
|
||||
test_etcp_link_stress \
|
||||
test_reality_tls \
|
||||
test_reality_record \
|
||||
test_reality_hello \
|
||||
test_reality_bgp \
|
||||
test_reality_config \
|
||||
@@ -130,7 +132,7 @@ check_PROGRAMS = \
|
||||
# Linux-only: test_auto_socket_dynamic требует root + dummy-интерфейсы + iproute2/netlink.
|
||||
if OS_LINUX
|
||||
check_PROGRAMS += test_auto_socket_dynamic test_standby test_standby_transport test_group_connect_standby test_android_udp_log test_pool_corruption_log
|
||||
check_PROGRAMS += test_uasync_regressions test_ll_queue_regressions test_etcp_padding
|
||||
check_PROGRAMS += test_reality_fallback test_stcp_reality_rotation test_uasync_regressions test_ll_queue_regressions test_etcp_padding
|
||||
endif
|
||||
|
||||
# Silero VAD: только при включённом ONNX Runtime (--with-silero-vad)
|
||||
@@ -164,7 +166,7 @@ COMMON_LIBS = $(top_builddir)/lib/libuasync.a $(CRYPTO_LIBS) -lpthread $(WIN_LIB
|
||||
|
||||
# Вариант общего ядра с Android duty-cycle, без JNI и устройства.
|
||||
if OS_LINUX
|
||||
test_stcp_LDFLAGS = -Wl,--wrap=send -Wl,--wrap=random_bytes
|
||||
test_stcp_LDFLAGS = -Wl,--wrap=send -Wl,--wrap=recv -Wl,--wrap=random_bytes -Wl,--wrap=u_malloc_impl -Wl,--wrap=u_calloc_impl
|
||||
standby_test_sources = ../tools/chatgui-android/libutun_lite/standby.c ../src/utun_instance.c ../src/transport_layer/etcp_keepalive.c
|
||||
standby_test_cppflags = $(AM_CPPFLAGS) -I$(top_srcdir)/src/chat -I$(top_srcdir)/tools/chatgui-android/libutun_lite -DUTUN_HAVE_STANDBY
|
||||
test_standby_SOURCES = ../tools/chatgui-android/libutun_lite/tests/test_standby.c $(standby_test_sources)
|
||||
@@ -776,3 +778,23 @@ check_PROGRAMS += test_chat_poll_network
|
||||
test_chat_poll_network_SOURCES = test_chat_poll_network.c
|
||||
test_chat_poll_network_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/src/chat -I$(top_srcdir)/lib
|
||||
test_chat_poll_network_LDADD = $(LIBUTUN) $(CRYPTO_LIBS) $(COMMON_LIBS)
|
||||
|
||||
test_reality_record_SOURCES = test_reality_record.c
|
||||
test_reality_record_LDADD = $(LIBUTUN) $(COMMON_LIBS)
|
||||
|
||||
if OS_LINUX
|
||||
# Низкий лимит проверяет реальную смену ключей в обе стороны без миллиона пакетов.
|
||||
test_stcp_reality_rotation_SOURCES = test_stcp.c ../src/transport_layer/reality_io.c ../src/transport_layer/reality_record.c
|
||||
test_stcp_reality_rotation_CFLAGS = $(AM_CFLAGS) -DREALITY_KEY_LIMIT=8
|
||||
test_stcp_reality_rotation_LDFLAGS = -Wl,--wrap=send -Wl,--wrap=recv -Wl,--wrap=random_bytes -Wl,--wrap=u_malloc_impl -Wl,--wrap=u_calloc_impl
|
||||
test_stcp_reality_rotation_LDADD = $(LIBUTUN) $(COMMON_LIBS)
|
||||
endif
|
||||
|
||||
if OS_LINUX
|
||||
test_reality_fallback_SOURCES = test_reality_fallback.c
|
||||
test_reality_fallback_LDFLAGS = -Wl,--wrap=ntp_time_get_seconds
|
||||
test_reality_fallback_LDADD = $(LIBUTUN) $(COMMON_LIBS)
|
||||
endif
|
||||
|
||||
test_reality_tls_SOURCES = test_reality_tls.c
|
||||
test_reality_tls_LDADD = $(LIBUTUN) -lssl $(COMMON_LIBS)
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
# OpenSSL 3.5.5, 27 Jan 2026; local capture 2026-10-07.
|
||||
# openssl s_client -tls1_3 -groups X25519 -ciphersuites TLS_AES_128_GCM_SHA256 -alpn http/1.1 -servername www.microsoft.com -connect 127.0.0.1:PORT
|
||||
1603010102010000fe0303495c3aad4d7fbc7f0ea12245795f052c33c7700a56c9f15bd58374992a75f617204f5583134eaae74da0bd4cf1ed233297a41f0ff7bd0118d3b0c418098ca5542e00021301010000b30000001600140000117777772e6d6963726f736f66742e636f6d000b000403000102000a00040002001d002300000010000b000908687474702f312e310016000000170000000d002a002809050906090404030503060308070808081a081b081c0809080a080b080408050806040105010601002b0003020304002d00020101003300260024001d002016a64663eb5b32977d0fbbe63eea3498ee62dd67428c7c25d26ac567b4d06904001b00050400010003
|
||||
@@ -0,0 +1,112 @@
|
||||
/* Реальный relay: нормализация replay, сохранение хвоста, независимое владение instance. */
|
||||
#include "stcp_server.h"
|
||||
#include "utun_instance.h"
|
||||
#include "../lib/mem.h"
|
||||
#include "../lib/debug_config.h"
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <errno.h>
|
||||
|
||||
#define CHECK(x) do { if (!(x)) { DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "FAIL line=%d: %s", __LINE__, #x); return 1; } } while (0)
|
||||
static uint32_t fake_now;
|
||||
time_t __wrap_ntp_time_get_seconds(struct UTUN_INSTANCE *inst) { (void)inst; return fake_now; }
|
||||
struct destination_peer { socket_t sock; struct UASYNC *ua; uint8_t data[4096]; size_t len; };
|
||||
struct destination { socket_t sock; struct UASYNC *ua; unsigned count; struct destination_peer peers[4]; };
|
||||
|
||||
static void destination_read(socket_t sock, void *arg) {
|
||||
struct destination_peer *p = arg;
|
||||
ssize_t n = recv(sock, p->data + p->len, sizeof(p->data) - p->len, 0);
|
||||
if (n > 0) p->len += (size_t)n;
|
||||
else if (!n) { uasync_remove_socket_t(p->ua, sock); socket_close_wrapper(sock); p->sock = SOCKET_INVALID; }
|
||||
}
|
||||
static void destination_accept(socket_t sock, void *arg) {
|
||||
struct destination *d = arg;
|
||||
socket_t peer = accept(sock, NULL, NULL);
|
||||
if (peer == SOCKET_INVALID || d->count >= 4) return;
|
||||
struct destination_peer *p = &d->peers[d->count++];
|
||||
p->sock = peer; p->ua = d->ua;
|
||||
socket_set_nonblocking(peer);
|
||||
uasync_add_socket_t(d->ua, peer, destination_read, NULL, NULL, "reality_test_dest", p);
|
||||
}
|
||||
static void unexpected_stcp(struct stcp_conn *c, void *arg) {
|
||||
(void)c; (void)arg;
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "unexpected STCP before Client Finished");
|
||||
}
|
||||
static socket_t connect_to(uint16_t port) {
|
||||
socket_t s = socket(AF_INET, SOCK_STREAM, 0);
|
||||
struct sockaddr_in address = {0};
|
||||
address.sin_family = AF_INET; address.sin_port = htons(port); address.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
|
||||
if (connect(s, (struct sockaddr *)&address, sizeof(address))) { socket_close_wrapper(s); return SOCKET_INVALID; }
|
||||
socket_set_nonblocking(s);
|
||||
return s;
|
||||
}
|
||||
static void poll_some(struct UASYNC *ua) { for (unsigned i = 0; i < 100; i++) uasync_poll(ua, 0); }
|
||||
|
||||
int main(void) {
|
||||
debug_config_init(); debug_set_level(DEBUG_LEVEL_DEBUG); debug_set_category_level(DEBUG_CATEGORY_SYS, DEBUG_LEVEL_WARN);
|
||||
size_t allocated = u_get_allocated_count();
|
||||
struct UASYNC *ua = uasync_create(); CHECK(ua);
|
||||
struct UTUN_INSTANCE a = {0}, b = {0}; a.ua = b.ua = ua;
|
||||
struct SC_MYKEYS keys; CHECK(sc_generate_keypair(&keys) == SC_OK);
|
||||
struct reality_config cfg; reality_config_set_defaults(&cfg);
|
||||
struct reality_client_config cc; reality_client_config_set_defaults(&cc);
|
||||
cfg.enabled = 1; cfg.short_id_count = 1;
|
||||
CHECK(!reality_generate_keypair(cfg.private_key, cc.server_static_pubkey));
|
||||
strcpy(cc.server_name, "www.microsoft.com");
|
||||
struct destination dest = {0}; dest.ua = ua;
|
||||
dest.sock = socket(AF_INET, SOCK_STREAM, 0); CHECK(dest.sock != SOCKET_INVALID);
|
||||
struct sockaddr_in address = {0}; address.sin_family = AF_INET; address.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
|
||||
CHECK(!bind(dest.sock, (struct sockaddr *)&address, sizeof(address)) && !listen(dest.sock, 4));
|
||||
socklen_t size = sizeof(address); CHECK(!getsockname(dest.sock, (struct sockaddr *)&address, &size));
|
||||
socket_set_nonblocking(dest.sock);
|
||||
CHECK(uasync_add_socket_t(ua, dest.sock, destination_accept, NULL, NULL, "reality_test_listener", &dest));
|
||||
snprintf(cfg.dest, sizeof(cfg.dest), "127.0.0.1:%u", ntohs(address.sin_port));
|
||||
uint16_t port = 32000 + (uint16_t)(time(NULL) % 1000);
|
||||
struct stcp_server *sa = stcp_server_create(ua, port, &keys, NULL, &a, unexpected_stcp, NULL, NULL, NULL, AF_INET);
|
||||
struct stcp_server *sb = stcp_server_create(ua, port + 1, &keys, NULL, &b, unexpected_stcp, NULL, NULL, NULL, AF_INET);
|
||||
CHECK(sa && sb); stcp_server_set_reality(sa, &cfg); stcp_server_set_reality(sb, &cfg);
|
||||
uint8_t ch[REALITY_MAX_CH_SIZE], answer[4096]; size_t cn;
|
||||
fake_now = (uint32_t)time(NULL);
|
||||
CHECK(!reality_client_hello_build_at(&cc, fake_now + 30, ch, sizeof(ch), &cn));
|
||||
socket_t first = connect_to(port); CHECK(first != SOCKET_INVALID && send(first, ch, cn, 0) == (ssize_t)cn);
|
||||
poll_some(ua);
|
||||
CHECK(recv(first, answer, sizeof(answer), 0) >= 127 && answer[0] == 22);
|
||||
/* Через 31 секунду тот же future timestamp ещё допустим: replay обязан оставаться в кеше. */
|
||||
fake_now += 31;
|
||||
uint8_t split[4096]; size_t first_part = 11, rest = cn - 5 - first_part;
|
||||
memcpy(split, ch, 5); split[3] = 0; split[4] = first_part;
|
||||
memcpy(split + 5, ch + 5, first_part);
|
||||
size_t pos = 5 + first_part;
|
||||
memcpy(split + pos, ch, 5); split[pos + 2] = 3; split[pos + 3] = (uint8_t)(rest >> 8); split[pos + 4] = (uint8_t)rest;
|
||||
memcpy(split + pos + 5, ch + 5 + first_part, rest);
|
||||
pos += 5 + rest;
|
||||
const uint8_t tail[] = {20, 3, 3, 0, 1, 1, 23, 3, 3, 0, 4, 1, 2, 3, 4};
|
||||
memcpy(split + pos, tail, sizeof(tail)); pos += sizeof(tail);
|
||||
socket_t replay = connect_to(port); CHECK(replay != SOCKET_INVALID);
|
||||
CHECK(send(replay, split, 7, 0) == 7); poll_some(ua);
|
||||
CHECK(send(replay, split + 7, pos - 7, 0) == (ssize_t)(pos - 7)); poll_some(ua);
|
||||
CHECK(dest.count == 1 && dest.peers[0].len == pos && !memcmp(dest.peers[0].data, split, pos));
|
||||
CHECK(a.reality_owner.relay_count == 1);
|
||||
socket_t other = connect_to(port + 1); CHECK(other != SOCKET_INVALID && send(other, ch, cn, 0) == (ssize_t)cn);
|
||||
poll_some(ua);
|
||||
CHECK(recv(other, answer, sizeof(answer), 0) >= 127 && answer[0] == 22 && dest.count == 1);
|
||||
|
||||
cc.short_id[0] = 1;
|
||||
CHECK(!reality_client_hello_build_at(&cc, fake_now, ch, sizeof(ch), &cn));
|
||||
socket_t unauthorized = connect_to(port + 1);
|
||||
CHECK(unauthorized != SOCKET_INVALID && send(unauthorized, ch, cn, 0) == (ssize_t)cn);
|
||||
poll_some(ua); CHECK(dest.count == 2 && b.reality_owner.relay_count == 1);
|
||||
stcp_server_destroy(sb); reality_owner_cleanup(&b.reality_owner); poll_some(ua);
|
||||
CHECK(a.reality_owner.relay_count == 1 && dest.peers[1].sock == SOCKET_INVALID);
|
||||
CHECK(send(replay, tail, sizeof(tail), 0) == sizeof(tail)); poll_some(ua);
|
||||
CHECK(dest.peers[0].len == pos + sizeof(tail) && !memcmp(dest.peers[0].data + pos, tail, sizeof(tail)));
|
||||
stcp_server_destroy(sa); reality_owner_cleanup(&a.reality_owner); poll_some(ua);
|
||||
socket_close_wrapper(first); socket_close_wrapper(replay); socket_close_wrapper(other); socket_close_wrapper(unauthorized);
|
||||
for (unsigned i = 0; i < dest.count; i++) if (dest.peers[i].sock != SOCKET_INVALID) {
|
||||
uasync_remove_socket_t(ua, dest.peers[i].sock); socket_close_wrapper(dest.peers[i].sock);
|
||||
}
|
||||
uasync_remove_socket_t(ua, dest.sock); socket_close_wrapper(dest.sock); uasync_destroy(ua, 1);
|
||||
CHECK(u_get_allocated_count() == allocated);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_REALITY, "fragmented replay, future expiry, exact fallback bytes, instance isolation and teardown passed");
|
||||
return 0;
|
||||
}
|
||||
+12
-12
@@ -43,7 +43,7 @@ static void init_client_cfg(struct reality_client_config *cc,
|
||||
memcpy(cc->short_id, short_id, REALITY_SHORT_ID_SIZE);
|
||||
memcpy(cc->version, version, REALITY_VERSION_SIZE);
|
||||
snprintf(cc->server_name, sizeof(cc->server_name), "www.microsoft.com");
|
||||
cc->fingerprint = REALITY_FP_CHROME;
|
||||
cc->fingerprint = REALITY_FP_OPENSSL;
|
||||
}
|
||||
|
||||
static void init_server_cfg(struct reality_server_config *sc,
|
||||
@@ -57,7 +57,7 @@ static void init_server_cfg(struct reality_server_config *sc,
|
||||
sc->short_id_count = 1;
|
||||
memcpy(sc->version, version, REALITY_VERSION_SIZE);
|
||||
sc->time_window_sec = window;
|
||||
sc->fingerprint = REALITY_FP_CHROME;
|
||||
sc->fingerprint = REALITY_FP_OPENSSL;
|
||||
}
|
||||
|
||||
// Собирает структурно-валидный ClientHello (TLS record + handshake), тело которого
|
||||
@@ -196,7 +196,7 @@ int main(void) {
|
||||
}
|
||||
|
||||
// ── Сценарий 8: fingerprint-геттер ──
|
||||
CHECK(reality_fingerprint_get(REALITY_FP_CHROME) != NULL, "fingerprint chrome found");
|
||||
CHECK(reality_fingerprint_get(REALITY_FP_OPENSSL) != NULL, "fingerprint openssl found");
|
||||
CHECK(reality_fingerprint_get(999) == NULL, "unknown fingerprint = NULL");
|
||||
|
||||
// ── Сценарий 9: невалидные аргументы ──
|
||||
@@ -230,8 +230,8 @@ int main(void) {
|
||||
ks[6] = 0x00; ks[7] = 0x1d; // group = X25519
|
||||
ks[8] = 0x00; ks[9] = 0x20; // klen = 32 (тела нет)
|
||||
ch2_len = build_ch_with_ext(ch2, sizeof(ch2), ks, sizeof(ks));
|
||||
CHECK(reality_server_hello_build(&sc, ch2, ch2_len, sh2, sizeof(sh2), &sh2_len) == REALITY_ERR_AUTH,
|
||||
"key_share truncated X25519 entry → AUTH");
|
||||
CHECK(reality_server_hello_build(&sc, ch2, ch2_len, sh2, sizeof(sh2), &sh2_len) == REALITY_ERR_FORMAT,
|
||||
"key_share truncated X25519 entry → FORMAT");
|
||||
|
||||
// цепочка записей не-X25519 с klen=0 (в границах) → AUTH, без OOB
|
||||
memset(ks, 0, sizeof(ks));
|
||||
@@ -241,8 +241,8 @@ int main(void) {
|
||||
ks[6] = 0x00; ks[7] = 0x17; // group = secp256r1
|
||||
ks[8] = 0x00; ks[9] = 0x00; // klen = 0
|
||||
ch2_len = build_ch_with_ext(ch2, sizeof(ch2), ks, sizeof(ks));
|
||||
CHECK(reality_server_hello_build(&sc, ch2, ch2_len, sh2, sizeof(sh2), &sh2_len) == REALITY_ERR_AUTH,
|
||||
"key_share without X25519 → AUTH");
|
||||
CHECK(reality_server_hello_build(&sc, ch2, ch2_len, sh2, sizeof(sh2), &sh2_len) == REALITY_ERR_FORMAT,
|
||||
"key_share zero-length entry → FORMAT");
|
||||
}
|
||||
|
||||
// ── Сценарий 10: парсинг секции [reality] из конфига ──
|
||||
@@ -259,7 +259,7 @@ int main(void) {
|
||||
"private_key=2222222222222222222222222222222222222222222222222222222222222222\n"
|
||||
"version=2.3.4\n"
|
||||
"time_window=45\n"
|
||||
"fingerprint=chrome\n";
|
||||
"fingerprint=openssl\n";
|
||||
struct utun_config *uc = parse_config_from_buf(cfg_text, strlen(cfg_text), "mem");
|
||||
CHECK(uc != NULL, "parse config with [reality]");
|
||||
if (uc) {
|
||||
@@ -270,7 +270,7 @@ int main(void) {
|
||||
CHECK(uc->global.reality.short_id_count == 2, "short_ids parsed");
|
||||
CHECK(uc->global.reality.version[0] == 2 && uc->global.reality.version[1] == 3 && uc->global.reality.version[2] == 4, "version parsed");
|
||||
CHECK(uc->global.reality.time_window_sec == 45, "time_window parsed");
|
||||
CHECK(uc->global.reality.fingerprint == REALITY_FP_CHROME, "fingerprint parsed");
|
||||
CHECK(uc->global.reality.fingerprint == REALITY_FP_OPENSSL, "fingerprint parsed");
|
||||
free_config(uc);
|
||||
}
|
||||
}
|
||||
@@ -288,7 +288,7 @@ int main(void) {
|
||||
"short_id=0102030405060708\n"
|
||||
"public_key=2222222222222222222222222222222222222222222222222222222222222222\n"
|
||||
"version=2.3.4\n"
|
||||
"fingerprint=chrome\n"
|
||||
"fingerprint=openssl\n"
|
||||
"[client: c2]\n"
|
||||
"peer_public_key=1111111111111111111111111111111111111111111111111111111111111111\n"
|
||||
"link=bind_srv:5.6.7.8:1444\n"
|
||||
@@ -306,7 +306,7 @@ int main(void) {
|
||||
if (c1) {
|
||||
CHECK(c1->reality_enabled == 1, "c1 reality enabled");
|
||||
CHECK(strcmp(c1->reality.server_name, "www.microsoft.com") == 0, "c1 server_name");
|
||||
CHECK(c1->reality.fingerprint == REALITY_FP_CHROME, "c1 fingerprint");
|
||||
CHECK(c1->reality.fingerprint == REALITY_FP_OPENSSL, "c1 fingerprint");
|
||||
CHECK(c1->reality.version[0] == 2 && c1->reality.version[1] == 3 && c1->reality.version[2] == 4, "c1 version parsed");
|
||||
{ uint8_t sid[8] = {0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08};
|
||||
CHECK(memcmp(c1->reality.short_id, sid, 8) == 0, "c1 short_id"); }
|
||||
@@ -320,7 +320,7 @@ int main(void) {
|
||||
}
|
||||
if (c2) {
|
||||
CHECK(c2->reality_enabled == 1, "c2 reality enabled");
|
||||
CHECK(c2->reality.version[0] == 1 && c2->reality.version[1] == 0 && c2->reality.version[2] == 0, "c2 version default 1.0.0");
|
||||
CHECK(c2->reality.version[0] == 2 && c2->reality.version[1] == 0 && c2->reality.version[2] == 0, "c2 version default 2.0.0");
|
||||
CHECK(c2->links != NULL && c2->links->local_srv != NULL, "c2 link с bind (local_srv set)");
|
||||
if (c2->links && c2->links->local_srv)
|
||||
CHECK(strcmp(c2->links->local_srv->name, "bind_srv") == 0, "c2 bind socket name");
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
/* Независимые HKDF/GCM-векторы и проверка привязки записей к обоим Hello. */
|
||||
#include "reality.h"
|
||||
#include "../lib/debug_config.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <openssl/crypto.h>
|
||||
|
||||
#define CHECK(x) do { if (!(x)) { DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "FAIL line=%d: %s", __LINE__, #x); return 1; } } while (0)
|
||||
static size_t unhex(const char *s, uint8_t *out) {
|
||||
size_t n = 0;
|
||||
while (s[0] && s[1]) {
|
||||
unsigned value;
|
||||
if (sscanf(s, "%2x", &value) != 1) break;
|
||||
out[n++] = (uint8_t)value;
|
||||
s += 2;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
static int vectors(void) {
|
||||
struct reality_traffic send = {0}, recv = {0};
|
||||
uint8_t secret[32], wire[REALITY_RECORD_MAX], plain[REALITY_INNER_MAX], expected[64], type;
|
||||
for (unsigned i = 0; i < 32; i++) secret[i] = i;
|
||||
CHECK(!reality_traffic_init(&send, secret));
|
||||
CHECK(unhex("1519da96243cb6965fbfa3b28ae25a27", expected) == 16 && !memcmp(send.key, expected, 16));
|
||||
CHECK(unhex("16e5473250aa12b61fbacec7", expected) == 12 && !memcmp(send.iv, expected, 12));
|
||||
const char *golden[] = {
|
||||
"1703030018a128808be00c4bdf042cf5ae940649c8f0d4701075afa09b",
|
||||
"170303001811fa393d1f1db1a3ee840870bedcc6f5c9387a00c9b41c84"
|
||||
};
|
||||
const uint8_t data[] = {1, 2, 3, 0};
|
||||
size_t n, decoded;
|
||||
for (unsigned i = 0; i < 2; i++) {
|
||||
CHECK(!reality_traffic_init(&send, secret));
|
||||
recv = send;
|
||||
send.sequence = recv.sequence = i ? 7 : 0;
|
||||
CHECK(!reality_record_seal(&send, 23, data, sizeof(data), 3, wire, sizeof(wire), &n));
|
||||
CHECK(unhex(golden[i], expected) == n && !memcmp(wire, expected, n));
|
||||
CHECK(!reality_record_open(&recv, wire, n, plain, sizeof(plain), &decoded, &type));
|
||||
CHECK(decoded == sizeof(data) && type == 23 && !memcmp(plain, data, decoded));
|
||||
CHECK(reality_record_open(&recv, wire, n, plain, sizeof(plain), &decoded, &type) < 0); // повтор sequence
|
||||
recv.sequence--;
|
||||
wire[n - 1] ^= 1;
|
||||
CHECK(reality_record_open(&recv, wire, n, plain, sizeof(plain), &decoded, &type) < 0);
|
||||
CHECK(recv.sequence == (i ? 7 : 0));
|
||||
}
|
||||
CHECK(!reality_traffic_init(&send, secret)); recv = send;
|
||||
memset(plain, 0xaa, sizeof(plain));
|
||||
CHECK(!reality_record_seal(&send, 23, plain, 16384, 0, wire, sizeof(wire), &n));
|
||||
CHECK(n == REALITY_RECORD_MAX);
|
||||
CHECK(!reality_record_open(&recv, wire, n, plain, sizeof(plain), &decoded, &type) && decoded == 16384);
|
||||
CHECK(reality_record_seal(&send, 23, plain, 16384, 1, wire, sizeof(wire), &n) < 0);
|
||||
CHECK(reality_record_seal(&send, 22, NULL, 0, 0, wire, sizeof(wire), &n) < 0);
|
||||
CHECK(!reality_record_seal(&send, 23, NULL, 0, 0, wire, sizeof(wire), &n));
|
||||
CHECK(!reality_record_open(&recv, wire, n, plain, sizeof(plain), &decoded, &type) && decoded == 0);
|
||||
send.sequence = REALITY_KEY_LIMIT;
|
||||
CHECK(reality_record_seal(&send, 23, data, sizeof(data), 0, wire, sizeof(wire), &n) < 0);
|
||||
uint8_t old_key[16]; memcpy(old_key, send.key, 16);
|
||||
CHECK(!reality_traffic_update(&send) && send.sequence == 0 && send.generation == 1 && memcmp(old_key, send.key, 16));
|
||||
recv.sequence = REALITY_KEY_LIMIT;
|
||||
CHECK(!reality_traffic_update(&recv));
|
||||
CHECK(!reality_record_seal(&send, 23, data, sizeof(data), 0, wire, sizeof(wire), &n));
|
||||
CHECK(!reality_record_open(&recv, wire, n, plain, sizeof(plain), &decoded, &type));
|
||||
DEBUG_INFO(DEBUG_CATEGORY_REALITY, "HKDF, GCM nonce/AAD, replay, limits, empty DATA and KeyUpdate vectors passed");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int hello_session(void) {
|
||||
struct reality_session client = {0}, server = {0};
|
||||
struct reality_client_config cc;
|
||||
struct reality_server_config sc = {0};
|
||||
reality_client_config_set_defaults(&cc);
|
||||
CHECK(!reality_generate_keypair(sc.static_privkey, cc.server_static_pubkey));
|
||||
strcpy(cc.server_name, "www.microsoft.com");
|
||||
sc.version[0] = 2; sc.short_id_count = 1; sc.time_window_sec = 30;
|
||||
uint8_t ch[REALITY_MAX_CH_SIZE], sh[REALITY_MAX_SH_SIZE], finished[32], check[32];
|
||||
size_t cn, sn;
|
||||
CHECK(!reality_session_client_start(&client, &cc, 1000, ch, sizeof(ch), &cn));
|
||||
CHECK(!reality_session_server_start(&server, &sc, 1000, ch, cn, sh, sizeof(sh), &sn));
|
||||
const size_t offsets[] = {0, 4, 5, 8, 10, 43, 44, 76, 77, 78, 80, 84, 86, 88, 92, 94};
|
||||
for (unsigned i = 0; i < sizeof(offsets) / sizeof(offsets[0]); i++) {
|
||||
sh[offsets[i]] ^= 1;
|
||||
CHECK(reality_session_client_accept(&client, sh, sn) != REALITY_OK);
|
||||
sh[offsets[i]] ^= 1;
|
||||
}
|
||||
CHECK(!reality_session_client_accept(&client, sh, sn));
|
||||
CHECK(!memcmp(client.send.key, server.recv.key, 16) && !memcmp(client.recv.key, server.send.key, 16));
|
||||
CHECK(memcmp(client.send.key, client.recv.key, 16));
|
||||
CHECK(!reality_finished(&server, 1, finished) && !reality_finished(&client, 0, check) && !memcmp(finished, check, 32));
|
||||
const uint8_t ee[] = {8, 0, 0, 2, 0, 0};
|
||||
CHECK(!reality_transcript_add(&client, ee, sizeof(ee)));
|
||||
CHECK(!reality_finished(&client, 0, check) && memcmp(finished, check, 32));
|
||||
CHECK(!reality_transcript_add(&server, ee, sizeof(ee)));
|
||||
CHECK(!reality_finished(&server, 1, finished) && !reality_finished(&client, 0, check) && !memcmp(finished, check, 32));
|
||||
for (unsigned i = 0; i < 32; i++) CHECK(!client.auth_key[i] && !server.auth_key[i] && !client.ephemeral_private[i]);
|
||||
reality_session_cleanup(&client); reality_session_cleanup(&server);
|
||||
|
||||
/* Все открытые байты совпадают с локальным эталоном OpenSSL; динамические поля исключены. */
|
||||
FILE *f = fopen("fixtures/reality_openssl_hello.hex", "r");
|
||||
if (!f) f = fopen("tests/fixtures/reality_openssl_hello.hex", "r");
|
||||
CHECK(f);
|
||||
char line[2048]; uint8_t reference[1024]; size_t rn = 0;
|
||||
while (fgets(line, sizeof(line), f)) if (line[0] != '#') { rn = unhex(line, reference); break; }
|
||||
fclose(f);
|
||||
CHECK(rn == cn && cn == 263);
|
||||
memset(ch + 11, 0, 32); memset(reference + 11, 0, 32);
|
||||
memset(ch + 44, 0, 32); memset(reference + 44, 0, 32);
|
||||
memset(ch + cn - 41, 0, 32); memset(reference + cn - 41, 0, 32);
|
||||
CHECK(!memcmp(ch, reference, cn));
|
||||
DEBUG_INFO(DEBUG_CATEGORY_REALITY, "Hello validation, independent traffic keys, transcript Finished and OpenSSL profile passed");
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
debug_config_init(); debug_set_level(DEBUG_LEVEL_INFO);
|
||||
return vectors() || hello_session();
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
/* Обычный TLS 1.3 и HTTP-запрос проходят через REALITY fallback на локальный HTTPS. */
|
||||
#define OPENSSL_API_COMPAT 0x10100000L
|
||||
#include "stcp_server.h"
|
||||
#include "utun_instance.h"
|
||||
#include "../lib/debug_config.h"
|
||||
#include "../lib/mem.h"
|
||||
#include <openssl/ssl.h>
|
||||
#include <openssl/x509.h>
|
||||
#include <openssl/err.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
|
||||
#define CHECK(x) do { if (!(x)) { DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "FAIL line=%d: %s", __LINE__, #x); return 1; } } while (0)
|
||||
struct https_peer {
|
||||
struct UASYNC *ua;
|
||||
SSL *ssl;
|
||||
void *socket_id;
|
||||
socket_t sock;
|
||||
uint8_t ready, done, failed;
|
||||
uint8_t response[256]; size_t response_len;
|
||||
};
|
||||
struct https_server { struct UASYNC *ua; SSL_CTX *ctx; socket_t sock; struct https_peer peer; };
|
||||
static const char response[] = "HTTP/1.1 200 OK\r\nContent-Length: 5\r\nConnection: close\r\n\r\nhello";
|
||||
|
||||
static void https_progress(socket_t sock, void *arg) {
|
||||
struct https_peer *p = arg;
|
||||
int rc;
|
||||
if (!p->ready) {
|
||||
rc = SSL_accept(p->ssl);
|
||||
if (rc == 1) p->ready = 1;
|
||||
else {
|
||||
int error = SSL_get_error(p->ssl, rc);
|
||||
if (error == SSL_ERROR_WANT_READ || error == SSL_ERROR_WANT_WRITE) {
|
||||
uasync_set_socket_write(p->ua, p->socket_id, error == SSL_ERROR_WANT_WRITE);
|
||||
return;
|
||||
}
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "HTTPS handshake failed: ssl=%d", error);
|
||||
p->failed = 1;
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (!p->done) {
|
||||
uint8_t request[512];
|
||||
rc = SSL_read(p->ssl, request, sizeof(request));
|
||||
if (rc > 0) {
|
||||
if (rc < 4 || memcmp(request, "GET ", 4)) { DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "invalid HTTP request"); p->failed = 1; return; }
|
||||
rc = SSL_write(p->ssl, response, sizeof(response) - 1);
|
||||
if (rc != sizeof(response) - 1) { DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "HTTPS response write failed"); p->failed = 1; return; }
|
||||
p->done = 1;
|
||||
uasync_set_socket_write(p->ua, p->socket_id, 0);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_REALITY, "local HTTPS received HTTP request through relay");
|
||||
} else {
|
||||
int error = SSL_get_error(p->ssl, rc);
|
||||
if (error != SSL_ERROR_WANT_READ && error != SSL_ERROR_WANT_WRITE) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "HTTPS read failed: ssl=%d openssl=%lu", error, ERR_get_error());
|
||||
p->failed = 1;
|
||||
}
|
||||
uasync_set_socket_write(p->ua, p->socket_id, error == SSL_ERROR_WANT_WRITE);
|
||||
}
|
||||
}
|
||||
(void)sock;
|
||||
}
|
||||
static void https_accept(socket_t sock, void *arg) {
|
||||
struct https_server *s = arg;
|
||||
struct https_peer *p = &s->peer;
|
||||
p->sock = accept(sock, NULL, NULL); p->ua = s->ua;
|
||||
if (p->sock == SOCKET_INVALID) { p->failed = 1; return; }
|
||||
socket_set_nonblocking(p->sock);
|
||||
p->ssl = SSL_new(s->ctx);
|
||||
if (!p->ssl || SSL_set_fd(p->ssl, p->sock) != 1) { DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "HTTPS SSL setup failed"); p->failed = 1; return; }
|
||||
p->socket_id = uasync_add_socket_t(s->ua, p->sock, https_progress, https_progress, NULL, "test_https", p);
|
||||
if (!p->socket_id) { DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "HTTPS socket registration failed"); p->failed = 1; }
|
||||
}
|
||||
static void unexpected_stcp(struct stcp_conn *c, void *arg) {
|
||||
(void)c; (void)arg;
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "ordinary HTTPS client entered STCP");
|
||||
}
|
||||
static int client_step(SSL *client, int rc) {
|
||||
if (rc > 0) return rc;
|
||||
int error = SSL_get_error(client, rc);
|
||||
return error == SSL_ERROR_WANT_READ || error == SSL_ERROR_WANT_WRITE ? 0 : -1;
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
debug_config_init(); debug_set_level(DEBUG_LEVEL_INFO);
|
||||
size_t allocated = u_get_allocated_count();
|
||||
struct UASYNC *ua = uasync_create(); CHECK(ua);
|
||||
struct UTUN_INSTANCE inst = {0}; inst.ua = ua;
|
||||
struct SC_MYKEYS keys; CHECK(sc_generate_keypair(&keys) == SC_OK);
|
||||
EVP_PKEY_CTX *key_ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_ED25519, NULL); EVP_PKEY *key = NULL;
|
||||
CHECK(key_ctx && EVP_PKEY_keygen_init(key_ctx) == 1 && EVP_PKEY_keygen(key_ctx, &key) == 1);
|
||||
X509 *cert = X509_new(); CHECK(cert);
|
||||
CHECK(X509_set_version(cert, 2) && ASN1_INTEGER_set(X509_get_serialNumber(cert), 1));
|
||||
CHECK(X509_gmtime_adj(X509_getm_notBefore(cert), 0) && X509_gmtime_adj(X509_getm_notAfter(cert), 3600));
|
||||
X509_NAME *name = X509_get_subject_name(cert);
|
||||
CHECK(X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, (const unsigned char *)"localhost", -1, -1, 0));
|
||||
CHECK(X509_set_issuer_name(cert, name) && X509_set_pubkey(cert, key) && X509_sign(cert, key, NULL) > 0);
|
||||
struct https_server server = {0}; server.ua = ua; server.peer.sock = SOCKET_INVALID;
|
||||
server.ctx = SSL_CTX_new(TLS_server_method()); SSL_CTX *client_ctx = SSL_CTX_new(TLS_client_method());
|
||||
CHECK(server.ctx && client_ctx && SSL_CTX_use_certificate(server.ctx, cert) && SSL_CTX_use_PrivateKey(server.ctx, key));
|
||||
CHECK(SSL_CTX_set_min_proto_version(server.ctx, TLS1_3_VERSION) && SSL_CTX_set_min_proto_version(client_ctx, TLS1_3_VERSION));
|
||||
CHECK(SSL_CTX_set1_groups_list(client_ctx, "X25519") && SSL_CTX_set_ciphersuites(client_ctx, "TLS_AES_128_GCM_SHA256"));
|
||||
SSL_CTX_set_num_tickets(server.ctx, 0); SSL_CTX_set_verify(client_ctx, SSL_VERIFY_NONE, NULL);
|
||||
server.sock = socket(AF_INET, SOCK_STREAM, 0); CHECK(server.sock != SOCKET_INVALID);
|
||||
struct sockaddr_in address = {0}; address.sin_family = AF_INET; address.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
|
||||
CHECK(!bind(server.sock, (struct sockaddr *)&address, sizeof(address)) && !listen(server.sock, 1));
|
||||
socklen_t size = sizeof(address); CHECK(!getsockname(server.sock, (struct sockaddr *)&address, &size));
|
||||
socket_set_nonblocking(server.sock);
|
||||
CHECK(uasync_add_socket_t(ua, server.sock, https_accept, NULL, NULL, "test_https_listener", &server));
|
||||
struct reality_config cfg; reality_config_set_defaults(&cfg);
|
||||
cfg.enabled = 1; cfg.short_id_count = 1;
|
||||
uint8_t pub[32]; CHECK(!reality_generate_keypair(cfg.private_key, pub));
|
||||
snprintf(cfg.dest, sizeof(cfg.dest), "127.0.0.1:%u", ntohs(address.sin_port));
|
||||
uint16_t port = 34000 + (uint16_t)(time(NULL) % 1000);
|
||||
struct stcp_server *reality = stcp_server_create(ua, port, &keys, NULL, &inst, unexpected_stcp, NULL, NULL, NULL, AF_INET);
|
||||
CHECK(reality); stcp_server_set_reality(reality, &cfg);
|
||||
socket_t sock = socket(AF_INET, SOCK_STREAM, 0); CHECK(sock != SOCKET_INVALID);
|
||||
address.sin_port = htons(port); CHECK(!connect(sock, (struct sockaddr *)&address, sizeof(address)));
|
||||
socket_set_nonblocking(sock);
|
||||
SSL *client = SSL_new(client_ctx); CHECK(client && SSL_set_fd(client, sock));
|
||||
CHECK(SSL_set_tlsext_host_name(client, "localhost"));
|
||||
int ready = 0;
|
||||
for (unsigned i = 0; !ready && !server.peer.failed && i < 1000; i++) {
|
||||
ready = client_step(client, SSL_connect(client)); CHECK(ready >= 0); uasync_poll(ua, 1);
|
||||
}
|
||||
CHECK(ready == 1 && SSL_version(client) == TLS1_3_VERSION);
|
||||
const char request[] = "GET / HTTP/1.1\r\nHost: localhost\r\n\r\n";
|
||||
CHECK(SSL_write(client, request, sizeof(request) - 1) == sizeof(request) - 1);
|
||||
uint8_t data[256]; size_t received = 0;
|
||||
for (unsigned i = 0; received < sizeof(response) - 1 && !server.peer.failed && i < 1000; i++) {
|
||||
int n = client_step(client, SSL_read(client, data + received, sizeof(data) - received));
|
||||
CHECK(n >= 0); received += n; uasync_poll(ua, 1);
|
||||
}
|
||||
DEBUG_INFO(DEBUG_CATEGORY_REALITY, "HTTPS result: ready=%u done=%u failed=%u received=%zu", server.peer.ready, server.peer.done, server.peer.failed, received);
|
||||
CHECK(!server.peer.failed && received == sizeof(response) - 1 && !memcmp(data, response, received));
|
||||
CHECK(inst.reality_owner.relay_count == 1);
|
||||
SSL_free(client); socket_close_wrapper(sock);
|
||||
stcp_server_destroy(reality); reality_owner_cleanup(&inst.reality_owner);
|
||||
if (server.peer.sock != SOCKET_INVALID) {
|
||||
uasync_remove_socket_t(ua, server.peer.sock); SSL_free(server.peer.ssl); socket_close_wrapper(server.peer.sock);
|
||||
}
|
||||
uasync_remove_socket_t(ua, server.sock); socket_close_wrapper(server.sock);
|
||||
SSL_CTX_free(client_ctx); SSL_CTX_free(server.ctx); X509_free(cert); EVP_PKEY_free(key); EVP_PKEY_CTX_free(key_ctx);
|
||||
uasync_drain_immediate(ua); uasync_destroy(ua, 1); CHECK(u_get_allocated_count() == allocated);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_REALITY, "real TLS 1.3 + HTTP over fallback passed without leaked resources");
|
||||
return 0;
|
||||
}
|
||||
+194
-15
@@ -12,6 +12,7 @@
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
|
||||
static int tests_passed = 0, tests_total = 0;
|
||||
static struct SC_MYKEYS s_keys, c_keys;
|
||||
@@ -22,6 +23,9 @@ static struct SC_MYKEYS s_keys, c_keys;
|
||||
#define WIRE_PADDING 32
|
||||
#define WIRE_HANDSHAKE_SIZE (STCP_HS_CLIENT_MIN + WIRE_PADDING)
|
||||
static int capture_wire, salt_number;
|
||||
static int reality_capture, reality_send_calls, reality_tamper, reality_fault, reality_block_recv;
|
||||
static size_t reality_wire_len[2];
|
||||
static uint8_t reality_wire[2][1600000];
|
||||
static uint16_t capture_port;
|
||||
static size_t wire_count;
|
||||
static struct {
|
||||
@@ -30,6 +34,21 @@ static struct {
|
||||
uint8_t bytes[2][WIRE_HANDSHAKE_SIZE];
|
||||
} wire[2];
|
||||
|
||||
void *__real_u_malloc_impl(uint32_t, const char *);
|
||||
void *__real_u_calloc_impl(uint32_t, uint32_t, const char *);
|
||||
static int reality_allocation_fail(const char *location) {
|
||||
if (!reality_fault || !strstr(location, "reality_io.c:")) return 0;
|
||||
if (--reality_fault) return 0;
|
||||
DEBUG_WARN(DEBUG_CATEGORY_REALITY, "injected allocation failure: %s", location);
|
||||
return 1;
|
||||
}
|
||||
void *__wrap_u_malloc_impl(uint32_t size, const char *location) {
|
||||
return reality_allocation_fail(location) ? NULL : __real_u_malloc_impl(size, location);
|
||||
}
|
||||
void *__wrap_u_calloc_impl(uint32_t count, uint32_t size, const char *location) {
|
||||
return reality_allocation_fail(location) ? NULL : __real_u_calloc_impl(count, size, location);
|
||||
}
|
||||
|
||||
int __real_random_bytes(uint8_t*, size_t);
|
||||
int __wrap_random_bytes(uint8_t* out, size_t len) {
|
||||
if (capture_wire && len == SC_PUBKEY_ENC_SALT_SIZE) {
|
||||
@@ -39,8 +58,37 @@ int __wrap_random_bytes(uint8_t* out, size_t len) {
|
||||
return __real_random_bytes(out, len);
|
||||
}
|
||||
|
||||
ssize_t __real_recv(socket_t, void *, size_t, int);
|
||||
ssize_t __wrap_recv(socket_t sock, void *buf, size_t len, int flags) {
|
||||
if (reality_block_recv) {
|
||||
struct sockaddr_in local; socklen_t size = sizeof(local);
|
||||
if (!getsockname(sock, (struct sockaddr *)&local, &size) && ntohs(local.sin_port) == capture_port) {
|
||||
errno = EAGAIN; return -1;
|
||||
}
|
||||
}
|
||||
return __real_recv(sock, buf, len, flags);
|
||||
}
|
||||
|
||||
ssize_t __real_send(socket_t, const void*, size_t, int);
|
||||
ssize_t __wrap_send(socket_t sock, const void* buf, size_t len, int flags) {
|
||||
if (reality_capture) {
|
||||
struct sockaddr_in local; socklen_t alen = sizeof(local);
|
||||
if (getsockname(sock, (struct sockaddr *)&local, &alen)) return -1;
|
||||
int direction = ntohs(local.sin_port) == capture_port;
|
||||
if (++reality_send_calls % 17 == 0) { errno = EAGAIN; return -1; }
|
||||
uint8_t part[37]; size_t take = len > sizeof(part) ? sizeof(part) : len;
|
||||
memcpy(part, buf, take);
|
||||
if (reality_tamper && direction && len >= 7 && part[0] == 23 && part[1] == 3 && part[2] == 3) {
|
||||
part[6] ^= 1;
|
||||
reality_tamper = 0;
|
||||
}
|
||||
ssize_t n = __real_send(sock, part, take, flags);
|
||||
if (n > 0 && reality_wire_len[direction] + (size_t)n <= sizeof(reality_wire[direction])) {
|
||||
memcpy(reality_wire[direction] + reality_wire_len[direction], part, (size_t)n);
|
||||
reality_wire_len[direction] += (size_t)n;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
if (!capture_wire) return __real_send(sock, buf, len, flags);
|
||||
struct sockaddr_in local, peer; socklen_t alen = sizeof(local);
|
||||
if (getsockname(sock, (struct sockaddr*)&local, &alen) != 0) {
|
||||
@@ -102,6 +150,7 @@ static void peer_rx_cb(struct ll_queue *q, void *arg) {
|
||||
if (need > p->accum_cap) { p->accum_cap = need + 4096; p->accum = u_realloc(p->accum, p->accum_cap); }
|
||||
memcpy(p->accum + p->accum_len, e->dgram, e->len);
|
||||
p->accum_len += e->len;
|
||||
queue_dgram_free(e);
|
||||
queue_entry_free(e);
|
||||
queue_resume_callback(q);
|
||||
if (p->conn) stcp_rx_resume_if_needed(p->conn);
|
||||
@@ -187,7 +236,7 @@ static int test1_sizes(void) {
|
||||
peer_cleanup(&srv); peer_cleanup(&cli);
|
||||
if (srv.conn) stcp_conn_free(srv.conn);
|
||||
stcp_client_destroy(sc); stcp_server_destroy(ss);
|
||||
uasync_destroy(ua, 1);
|
||||
uasync_drain_immediate(ua); uasync_destroy(ua, 1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -225,7 +274,7 @@ static int test2_many(void) {
|
||||
peer_cleanup(&srv); peer_cleanup(&cli);
|
||||
if (srv.conn) stcp_conn_free(srv.conn);
|
||||
stcp_client_destroy(sc); stcp_server_destroy(ss);
|
||||
uasync_destroy(ua, 1);
|
||||
uasync_drain_immediate(ua); uasync_destroy(ua, 1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -253,7 +302,7 @@ static int test3_wrong_key(void) {
|
||||
|
||||
peer_cleanup(&srv); peer_cleanup(&cli);
|
||||
stcp_client_destroy(sc); stcp_server_destroy(ss);
|
||||
uasync_destroy(ua, 1);
|
||||
uasync_drain_immediate(ua); uasync_destroy(ua, 1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -284,7 +333,7 @@ static int test4_close(void) {
|
||||
|
||||
peer_cleanup(&srv); peer_cleanup(&cli);
|
||||
stcp_client_destroy(sc); stcp_server_destroy(ss);
|
||||
uasync_destroy(ua, 1);
|
||||
uasync_drain_immediate(ua); uasync_destroy(ua, 1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -352,7 +401,7 @@ static int test5_multi(void) {
|
||||
stcp_client_destroy(clients[i]);
|
||||
}
|
||||
stcp_server_destroy(ss);
|
||||
uasync_destroy(ua, 1);
|
||||
uasync_drain_immediate(ua); uasync_destroy(ua, 1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -386,7 +435,7 @@ static int test6_interleaved(void) {
|
||||
peer_cleanup(&srv); peer_cleanup(&cli);
|
||||
if (srv.conn) stcp_conn_free(srv.conn);
|
||||
stcp_client_destroy(sc); stcp_server_destroy(ss);
|
||||
uasync_destroy(ua, 1);
|
||||
uasync_drain_immediate(ua); uasync_destroy(ua, 1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -425,7 +474,7 @@ static int test7_bulk_4mb(void) {
|
||||
peer_cleanup(&srv); peer_cleanup(&cli);
|
||||
if (srv.conn) stcp_conn_free(srv.conn);
|
||||
stcp_client_destroy(sc); stcp_server_destroy(ss);
|
||||
uasync_destroy(ua, 1);
|
||||
uasync_drain_immediate(ua); uasync_destroy(ua, 1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -455,7 +504,7 @@ static int test8_srv_recv_close(void) {
|
||||
|
||||
peer_cleanup(&srv); peer_cleanup(&cli);
|
||||
stcp_client_destroy(sc); stcp_server_destroy(ss);
|
||||
uasync_destroy(ua, 1); // flush deferred → stcp_conn_free(srv_conn)
|
||||
uasync_drain_immediate(ua); uasync_destroy(ua, 1); // flush deferred → stcp_conn_free(srv_conn)
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -543,7 +592,7 @@ static int test9_raw_handshake_ok(void) {
|
||||
peer_cleanup(&srv);
|
||||
if (srv.conn) stcp_conn_free(srv.conn);
|
||||
stcp_server_destroy(ss);
|
||||
uasync_destroy(ua, 1);
|
||||
uasync_drain_immediate(ua); uasync_destroy(ua, 1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -570,7 +619,7 @@ static int test10_oversized_padding(void) {
|
||||
peer_cleanup(&srv);
|
||||
srv.conn = NULL; // уже освобождён deferred-close (allocated=1)
|
||||
stcp_server_destroy(ss);
|
||||
uasync_destroy(ua, 1);
|
||||
uasync_drain_immediate(ua); uasync_destroy(ua, 1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -597,7 +646,7 @@ static int test11_garbage_handshake(void) {
|
||||
peer_cleanup(&srv);
|
||||
srv.conn = NULL;
|
||||
stcp_server_destroy(ss);
|
||||
uasync_destroy(ua, 1);
|
||||
uasync_drain_immediate(ua); uasync_destroy(ua, 1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -627,7 +676,7 @@ static int test12_padding_boundaries(void) {
|
||||
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "padding boundary: client=%u server=%u byte-exact DATA OK", ci.padding.max, si.padding.max);
|
||||
peer_cleanup(&srv); peer_cleanup(&cli);
|
||||
if (srv.conn) stcp_conn_free(srv.conn);
|
||||
stcp_client_destroy(sc); stcp_server_destroy(ss); uasync_destroy(ua, 1);
|
||||
stcp_client_destroy(sc); stcp_server_destroy(ss); uasync_drain_immediate(ua); uasync_destroy(ua, 1);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -706,7 +755,7 @@ static int test13_salted_handshakes(void) {
|
||||
peer_cleanup(&srv[i]); peer_cleanup(&cli[i]);
|
||||
stcp_conn_free(srv[i].conn); stcp_client_destroy(clients[i]);
|
||||
}
|
||||
stcp_server_destroy(ss); uasync_destroy(ua, 1);
|
||||
stcp_server_destroy(ss); uasync_drain_immediate(ua); uasync_destroy(ua, 1);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -726,16 +775,141 @@ static int test14_modified_salt(void) {
|
||||
socket_t sock = raw_connect_send(port, packet, sizeof(packet)); TASSERT(sock != SOCKET_INVALID);
|
||||
for (int i = 0; !srv.closed && i < 1000; i++) uasync_poll(ua, 10);
|
||||
TASSERT(srv.closed && !srv.ready && srv.close_err == 2);
|
||||
socket_close_wrapper(sock); stcp_server_destroy(ss); uasync_destroy(ua, 1);
|
||||
socket_close_wrapper(sock); stcp_server_destroy(ss); uasync_drain_immediate(ua); uasync_destroy(ua, 1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int test15_reality_records(void) {
|
||||
for (int round = 0; round < 4; round++) {
|
||||
struct UASYNC *ua = uasync_create(); TASSERT(ua);
|
||||
struct test_peer srv = {0}, cli = {0};
|
||||
struct UTUN_INSTANCE si = {0}, ci = {0};
|
||||
si.ua = ci.ua = ua; si.my_keys = s_keys; ci.my_keys = c_keys;
|
||||
si.padding.min = si.padding.max = round ? ETCP_PADDING_MAX : 0;
|
||||
ci.padding.min = ci.padding.max = round ? 0 : ETCP_PADDING_MAX;
|
||||
struct reality_config cfg; reality_config_set_defaults(&cfg);
|
||||
struct reality_client_config cc; reality_client_config_set_defaults(&cc);
|
||||
cfg.enabled = 1; cfg.short_id_count = 1;
|
||||
TASSERT(!reality_generate_keypair(cfg.private_key, cc.server_static_pubkey));
|
||||
strcpy(cc.server_name, "www.microsoft.com");
|
||||
uint16_t port = BASE_PORT + 17;
|
||||
struct stcp_server *ss = stcp_server_create(ua, port, &s_keys, NULL, &si,
|
||||
server_connect_cb, &srv, peer_close_cb, &srv, AF_INET); TASSERT(ss);
|
||||
stcp_server_set_reality(ss, &cfg);
|
||||
#ifdef __linux__
|
||||
capture_port = port; reality_capture = 1; reality_send_calls = 0; reality_tamper = round == 2;
|
||||
memset(reality_wire_len, 0, sizeof(reality_wire_len));
|
||||
#endif
|
||||
struct stcp_client *sc = stcp_client_connect(ua, "127.0.0.1", port, &c_keys, s_keys.public_key,
|
||||
NULL, 0, NULL, 0, 200, 0, client_ready_cb, &cli,
|
||||
NULL, NULL, peer_close_cb, &cli, NULL, 3000, &cc, NULL, &ci); TASSERT(sc);
|
||||
for (int j = 0; !cli.closed && (!srv.ready || !cli.ready) && j < 10000; j++) uasync_poll(ua, 1);
|
||||
#ifdef __linux__
|
||||
if (round == 2) {
|
||||
TASSERT(cli.closed && !cli.ready && !srv.ready);
|
||||
reality_capture = 0;
|
||||
stcp_client_destroy(sc); stcp_server_destroy(ss);
|
||||
reality_owner_cleanup(&si.reality_owner); uasync_drain_immediate(ua); uasync_destroy(ua, 1);
|
||||
continue;
|
||||
}
|
||||
#endif
|
||||
TASSERT(srv.ready && cli.ready && !srv.closed && !cli.closed);
|
||||
uint8_t payload[65535];
|
||||
for (unsigned i = 0; i < sizeof(payload); i++) payload[i] = (uint8_t)(i * 7);
|
||||
int messages = round == 3 ? 100 : 1;
|
||||
size_t payload_len = round == 3 ? 1024 : sizeof(payload);
|
||||
#ifdef __linux__
|
||||
if (round == 3) reality_capture = 0;
|
||||
#endif
|
||||
if (round == 3) queue_set_callback(srv.rx, NULL, NULL);
|
||||
for (int i = 0; i < messages; i++) {
|
||||
TASSERT(!peer_send(&cli, payload, payload_len) && !peer_send(&srv, payload, payload_len));
|
||||
}
|
||||
TASSERT(!peer_send(&cli, payload, 0) && !peer_send(&srv, payload, 0));
|
||||
if (round == 3) {
|
||||
for (int j = 0; !srv.conn->rx_paused && j < 100000; j++) uasync_poll(ua, 0);
|
||||
TASSERT(srv.conn->rx_paused && srv.rx->count > STCP_RX_QUEUE_MAX_PACKETS);
|
||||
#ifdef __linux__
|
||||
reality_block_recv = 1;
|
||||
#endif
|
||||
queue_set_callback(srv.rx, peer_rx_cb, &srv); queue_resume_callback(srv.rx);
|
||||
for (unsigned i = 0; i < 100; i++) uasync_poll(ua, 0);
|
||||
#ifdef __linux__
|
||||
TASSERT(srv.msg_count > STCP_RX_QUEUE_MAX_PACKETS + 1);
|
||||
reality_block_recv = 0;
|
||||
#endif
|
||||
DEBUG_INFO(DEBUG_CATEGORY_REALITY, "backpressure resume delivered %d frames with socket recv blocked", srv.msg_count);
|
||||
}
|
||||
for (int j = 0; (srv.msg_count < messages + 1 || cli.msg_count < messages + 1) && j < 100000; j++) uasync_poll(ua, 0);
|
||||
TASSERT(srv.accum_len == payload_len * messages && cli.accum_len == payload_len * messages);
|
||||
for (int i = 0; i < messages; i++) {
|
||||
TASSERT(!memcmp(srv.accum + i * payload_len, payload, payload_len));
|
||||
TASSERT(!memcmp(cli.accum + i * payload_len, payload, payload_len));
|
||||
}
|
||||
#ifdef __linux__
|
||||
reality_capture = 0;
|
||||
for (int direction = 0; round != 3 && direction < 2; direction++) {
|
||||
size_t offset = 0, records = 0, ccs = 0, updates = 0;
|
||||
while (offset < reality_wire_len[direction]) {
|
||||
const uint8_t *record = reality_wire[direction] + offset;
|
||||
TASSERT(reality_wire_len[direction] - offset >= 5);
|
||||
size_t len = 5 + ((size_t)record[3] << 8) + record[4];
|
||||
TASSERT(len <= reality_wire_len[direction] - offset);
|
||||
if (!records) TASSERT(record[0] == 22 && len == (direction ? 127 : 263));
|
||||
else if (record[0] == 20) { TASSERT(len == 6 && record[5] == 1 && !ccs++); }
|
||||
else TASSERT(record[0] == 23 && record[1] == 3 && record[2] == 3 && len >= 22 && len <= REALITY_RECORD_MAX);
|
||||
if (record[0] == 23 && len == 27) updates++;
|
||||
offset += len; records++;
|
||||
}
|
||||
TASSERT(records >= 8 && ccs == 1);
|
||||
#if REALITY_KEY_LIMIT < 16
|
||||
TASSERT(updates >= 1);
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
peer_cleanup(&srv); peer_cleanup(&cli);
|
||||
stcp_conn_free(srv.conn); stcp_client_destroy(sc); stcp_server_destroy(ss);
|
||||
reality_owner_cleanup(&si.reality_owner); uasync_drain_immediate(ua); uasync_destroy(ua, 1);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef __linux__
|
||||
static int test16_reality_allocation_failures(void) {
|
||||
for (int failure = 1; failure <= 18; failure++) {
|
||||
size_t allocated = u_get_allocated_count();
|
||||
struct UASYNC *ua = uasync_create(); TASSERT(ua);
|
||||
struct test_peer srv = {0}, cli = {0};
|
||||
struct UTUN_INSTANCE si = {0}; si.ua = ua; si.my_keys = s_keys;
|
||||
struct reality_config cfg; reality_config_set_defaults(&cfg);
|
||||
struct reality_client_config cc; reality_client_config_set_defaults(&cc);
|
||||
cfg.enabled = 1; cfg.short_id_count = 1;
|
||||
TASSERT(!reality_generate_keypair(cfg.private_key, cc.server_static_pubkey));
|
||||
strcpy(cc.server_name, "www.microsoft.com");
|
||||
struct stcp_server *ss = stcp_server_create(ua, BASE_PORT + 18, &s_keys, NULL, &si,
|
||||
server_connect_cb, &srv, peer_close_cb, &srv, AF_INET); TASSERT(ss);
|
||||
stcp_server_set_reality(ss, &cfg);
|
||||
reality_fault = failure;
|
||||
struct stcp_client *sc = stcp_client_connect(ua, "127.0.0.1", BASE_PORT + 18, &c_keys, s_keys.public_key,
|
||||
NULL, 0, NULL, 0, 200, 0, client_ready_cb, &cli,
|
||||
NULL, NULL, peer_close_cb, &cli, NULL, 3000, &cc, NULL, NULL); TASSERT(sc);
|
||||
for (unsigned i = 0; !cli.closed && !srv.closed && i < 1000; i++) uasync_poll(ua, 1);
|
||||
TASSERT(!reality_fault && !cli.ready && !srv.ready && (cli.closed || srv.closed));
|
||||
stcp_client_destroy(sc); stcp_server_destroy(ss);
|
||||
reality_owner_cleanup(&si.reality_owner); uasync_drain_immediate(ua); uasync_destroy(ua, 1);
|
||||
TASSERT(u_get_allocated_count() == allocated);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
// ======================= main =======================
|
||||
|
||||
int main(void) {
|
||||
debug_config_init();
|
||||
debug_set_level(DEBUG_LEVEL_INFO);
|
||||
debug_set_categories(DEBUG_CATEGORY_GENERAL | DEBUG_CATEGORY_SOCKET | DEBUG_CATEGORY_CRYPTO);
|
||||
debug_set_categories(DEBUG_CATEGORY_GENERAL | DEBUG_CATEGORY_SOCKET | DEBUG_CATEGORY_CRYPTO | DEBUG_CATEGORY_REALITY);
|
||||
size_t allocated = u_get_allocated_count();
|
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "============================================");
|
||||
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "=== STCP Integration Tests ===");
|
||||
@@ -759,8 +933,13 @@ int main(void) {
|
||||
TRUN(test13_salted_handshakes);
|
||||
#endif
|
||||
TRUN(test14_modified_salt);
|
||||
TRUN(test15_reality_records);
|
||||
#ifdef __linux__
|
||||
TRUN(test16_reality_allocation_failures);
|
||||
#endif
|
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "============================================");
|
||||
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "Results: %d/%d passed", tests_passed, tests_total);
|
||||
TASSERT(u_get_allocated_count() == allocated);
|
||||
return tests_passed == tests_total ? 0 : 1;
|
||||
}
|
||||
+23
-4
@@ -1,5 +1,6 @@
|
||||
// test_stcp_ping.c — TCP-ping (etcp_send_tcp_ping / stcp_ping_send) end-to-end check
|
||||
#include "stcp_client.h"
|
||||
#include "stcp_server.h"
|
||||
#include "etcp_connections.h"
|
||||
#include "etcp.h"
|
||||
#include "topo_group.h"
|
||||
@@ -21,6 +22,12 @@ static void ping_cb(int success, uint16_t rtt, void *arg, uint64_t nonce,
|
||||
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "TCP ping cb: success=%d rtt=%u", success, rtt);
|
||||
}
|
||||
|
||||
static void unexpected_ready(struct stcp_conn *c, void *arg) {
|
||||
(void)c; (void)arg;
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_REALITY, "TCP ping unexpectedly entered DATA callback");
|
||||
ping_ok = 0;
|
||||
}
|
||||
|
||||
static char *build_config(const char *node_id, const char *priv, const char *pub, int port) {
|
||||
static char buf[512];
|
||||
snprintf(buf, sizeof(buf),
|
||||
@@ -71,7 +78,7 @@ int main(void) {
|
||||
struct sockaddr_in *sin = (struct sockaddr_in *)&addr;
|
||||
sin->sin_family = AF_INET; sin->sin_addr.s_addr = htonl(0x7f000001); sin->sin_port = htons(port);
|
||||
|
||||
int rc = etcp_send_tcp_ping(cli, srv_pub, &addr, 2000, ping_cb, NULL, NULL);
|
||||
int rc = etcp_send_tcp_ping(cli, srv_pub, &addr, 2000, ping_cb, NULL, NULL, NULL);
|
||||
if (rc != 0) { DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "etcp_send_tcp_ping failed rc=%d", rc); return 1; }
|
||||
|
||||
int ticks = 0;
|
||||
@@ -83,7 +90,7 @@ int main(void) {
|
||||
ping_ok = -1;
|
||||
cli->padding.min = cli->padding.max = ETCP_PADDING_MAX;
|
||||
srv->padding.min = srv->padding.max = 0;
|
||||
rc = etcp_send_tcp_ping(cli, srv_pub, &addr, 2000, ping_cb, NULL, NULL);
|
||||
rc = etcp_send_tcp_ping(cli, srv_pub, &addr, 2000, ping_cb, NULL, NULL, NULL);
|
||||
if (rc != 0) return 1;
|
||||
ticks = 0;
|
||||
while (ping_ok < 0 && ticks < 5000) { uasync_poll(ua, 10); ticks++; }
|
||||
@@ -95,21 +102,33 @@ int main(void) {
|
||||
struct sockaddr_storage bad_addr; memset(&bad_addr, 0, sizeof(bad_addr));
|
||||
struct sockaddr_in *bsin = (struct sockaddr_in *)&bad_addr;
|
||||
bsin->sin_family = AF_INET; bsin->sin_addr.s_addr = htonl(0x7f000001); bsin->sin_port = htons(port + 5000);
|
||||
rc = etcp_send_tcp_ping(cli, srv_pub, &bad_addr, 1000, ping_cb, NULL, NULL);
|
||||
rc = etcp_send_tcp_ping(cli, srv_pub, &bad_addr, 1000, ping_cb, NULL, NULL, NULL);
|
||||
ticks = 0;
|
||||
while (ping_ok < 0 && ticks < 3000) { uasync_poll(ua, 10); ticks++; }
|
||||
if (ping_ok != 0) { DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "=== FAILED (failure path): success=%d rtt=%u rc=%d ===", ping_ok, ping_rtt, rc); return 1; }
|
||||
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "=== failure path PASSED: success=%d ===", ping_ok);
|
||||
|
||||
struct reality_config reality; reality_config_set_defaults(&reality);
|
||||
struct reality_client_config cc; reality_client_config_set_defaults(&cc);
|
||||
reality.enabled = 1; reality.short_id_count = 1;
|
||||
if (reality_generate_keypair(reality.private_key, cc.server_static_pubkey)) return 1;
|
||||
strcpy(cc.server_name, "www.microsoft.com");
|
||||
struct stcp_server *rs = stcp_server_create(ua, port + 2, &srv->my_keys, srv->my_ed25519_pubkey,
|
||||
srv, unexpected_ready, NULL, NULL, NULL, AF_INET);
|
||||
if (!rs) return 1;
|
||||
stcp_server_set_reality(rs, &reality);
|
||||
sin->sin_port = htons(port + 2);
|
||||
|
||||
/* Одноразовый TCP-пинг не удерживает instance: длина выбирается до асинхронного connect. */
|
||||
ping_ok = -1;
|
||||
rc = etcp_send_tcp_ping(cli, srv_pub, &addr, 2000, ping_cb, NULL, NULL);
|
||||
rc = etcp_send_tcp_ping(cli, srv_pub, &addr, 2000, ping_cb, NULL, NULL, &cc);
|
||||
if (rc != 0) return 1;
|
||||
cli->running = 0; utun_instance_destroy(cli); cli = NULL;
|
||||
ticks = 0;
|
||||
while (ping_ok < 0 && ticks < 5000) { uasync_poll(ua, 10); ticks++; }
|
||||
if (ping_ok != 1) { DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "TCP ping outliving instance failed"); return 1; }
|
||||
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "=== TCP ping without retained instance PASSED ===");
|
||||
stcp_server_destroy(rs);
|
||||
srv->running = 0;
|
||||
utun_instance_destroy(srv);
|
||||
uasync_destroy(ua, 0);
|
||||
|
||||
Reference in new issue
Block a user