Fix NAT packet ownership after router send failures

This commit is contained in:
evgeny committed 2026-10-21 21:43:00 +02:00
1 parent ef673eb3b7
commit fbbd62acc4
3 files changed
+81 -6

No files matched your search

+6 -6
View File
@@ -47,11 +47,11 @@ static void nat_transport_client_tun_out_cb(struct ll_queue* q, void* arg) {
queue_dgram_free(pkt); queue_entry_free(pkt);
queue_resume_callback(q);
// Роутер принимает владение new_entry и dgram при любом результате, включая ошибку.
int ret = etcp_route_send(inst, TOPO_GROUP_UTUN, tr->nat_via_node_id, new_entry, 0, 0);
if (ret != 0) {
DEBUG_WARN(DEBUG_CATEGORY_NAT, "NAT client: etcp_route_send to provider %016llx failed",
(unsigned long long)tr->nat_via_node_id);
queue_dgram_free(new_entry); queue_entry_free(new_entry);
DEBUG_WARN(DEBUG_CATEGORY_NAT, "NAT client: etcp_route_send to provider %016llx failed: error=%d ip_bytes=%zu",
(unsigned long long)tr->nat_via_node_id, ret, ip_len);
}
}
@@ -89,11 +89,11 @@ static void nat_transport_provider_tun_out_cb(struct ll_queue* q, void* arg) {
queue_dgram_free(pkt); queue_entry_free(pkt);
queue_resume_callback(q);
// После передачи роутеру new_entry и dgram больше не принадлежат NAT, даже при ошибке.
int send_ret = etcp_route_send(inst, TOPO_GROUP_UTUN, entry->src_node_id, new_entry, 0, 0);
if (send_ret != 0) {
DEBUG_WARN(DEBUG_CATEGORY_NAT, "NAT provider: etcp_route_send back to node %016llx failed",
(unsigned long long)entry->src_node_id);
queue_dgram_free(new_entry); queue_entry_free(new_entry);
DEBUG_WARN(DEBUG_CATEGORY_NAT, "NAT provider: etcp_route_send back to node %016llx failed: error=%d ip_bytes=%zu",
(unsigned long long)entry->src_node_id, send_ret, ip_len);
}
}
+3
View File
@@ -524,6 +524,9 @@ test_nat_engine_LDADD = $(LIBUTUN) $(COMMON_LIBS)
test_nat_transport_SOURCES = test_nat_transport.c
test_nat_transport_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib
test_nat_transport_LDADD = $(LIBUTUN) $(CRYPTO_LIBS) $(COMMON_LIBS)
if OS_LINUX
test_nat_transport_LDFLAGS = -Wl,--wrap=etcp_route_send
endif
test_nat_stress_SOURCES = test_nat_stress.c
test_nat_stress_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib
+72
View File
@@ -43,6 +43,34 @@ static struct UASYNC* ua = NULL;
static int test_timed_out = 0;
static void* test_timeout_id = NULL;
#ifdef __linux__
/* Отказ с реальным освобождением в router API и успешный приём с тем же контрактом владения. */
static int send_test_mode, send_test_calls, send_test_valid;
static struct UTUN_INSTANCE* send_test_instance;
static uint64_t send_test_destination;
static size_t send_test_ip_len;
int __real_etcp_route_send(struct UTUN_INSTANCE*, uint64_t, uint64_t, struct ll_entry*, int, int);
int __wrap_etcp_route_send(struct UTUN_INSTANCE* inst, uint64_t group, uint64_t destination,
struct ll_entry* entry, int force, int mode) {
if (!send_test_mode) return __real_etcp_route_send(inst, group, destination, entry, force, mode);
send_test_calls++;
if (inst != send_test_instance || group != TOPO_GROUP_UTUN || destination != send_test_destination ||
!entry || !entry->dgram || entry->len != send_test_ip_len + 1 || entry->dgram[0] != ETCP_RT_ID_NAT || force || mode) {
DEBUG_ERROR(DEBUG_CATEGORY_NAT, "Unexpected NAT send in ownership test: destination=%016llx call=%d",
(unsigned long long)destination, send_test_calls);
send_test_valid = 0;
}
if (send_test_mode == 1) {
DEBUG_INFO(DEBUG_CATEGORY_NAT, "Inject NAT router refusal: packet ownership transfers on error");
// NULL instance вызывает отказ в реальном router API; переданный entry он освобождает сам.
return __real_etcp_route_send(NULL, group, destination, entry, force, mode);
}
queue_dgram_free(entry); queue_entry_free(entry);
return 0;
}
#endif
static char temp_dir[] = "/tmp/utun_nat_transport_XXXXXX";
static char config_provider[256];
static char config_client[256];
@@ -431,6 +459,45 @@ static int test_provider_ingress_response(void) {
return 1;
}
#ifdef __linux__
static int test_send_failure_ownership(int provider) {
struct UTUN_INSTANCE* sender = provider ? inst_provider : inst_client;
struct tun_if* tun = sender->nat_tr.nat_tun;
struct eim_nat_entry saved = {0};
uint16_t external_port = inst_provider->nat.port_start;
if (provider) {
saved = inst_provider->nat.table[external_port];
inst_provider->nat.table[external_port] = (struct eim_nat_entry){.internal_ip = 0x0a0000cd,
.internal_port = htobe16(44444), .proto = IPPROTO_UDP_UINT8,
.state = EIM_NAT_ENTRY_ACTIVE, .src_node_id = g_client_node_id};
}
size_t ip_len;
uint8_t* ip = provider ? build_udp_pkt(0x08080808, 80, inst_provider->nat.gateway_ip, external_port, &ip_len) :
build_udp_pkt(0x0a0000cd, 44444, 0x08080808, 80, &ip_len);
send_test_instance = sender;
send_test_destination = provider ? g_client_node_id : g_provider_node_id;
send_test_ip_len = ip_len;
send_test_calls = 0; send_test_valid = 1;
send_test_mode = 2;
// Прогреть пул entry до измерения живых блоков; исходный IP остаётся у вызывающего.
int ok = tun_inject_packet(tun, ip, ip_len) == 0 && send_test_calls == 1;
size_t baseline = u_get_allocated_count();
send_test_mode = 1;
if (tun_inject_packet(tun, ip, ip_len) != 0 || send_test_calls != 2 || u_get_allocated_count() != baseline) ok = 0;
send_test_mode = 2;
if (tun_inject_packet(tun, ip, ip_len) != 0 || send_test_calls != 3 || u_get_allocated_count() != baseline) ok = 0;
if (!send_test_valid || queue_entry_count(tun->output_queue)) ok = 0;
send_test_mode = 0; send_test_instance = NULL;
free(ip);
if (provider) inst_provider->nat.table[external_port] = saved;
if (ok) DEBUG_INFO(DEBUG_CATEGORY_NAT, "PASS NAT %s send failure: no double free/leak, next packet accepted",
provider ? "provider" : "client");
else DEBUG_ERROR(DEBUG_CATEGORY_NAT, "FAIL NAT %s send failure: calls=%d valid=%d live_blocks=%zu baseline=%zu",
provider ? "provider" : "client", send_test_calls, send_test_valid, u_get_allocated_count(), baseline);
return ok;
}
#endif
static int test_full_roundtrip(void) {
/* Полный путь через client socketpair → ETCP → provider TUN и обратно. */
DEBUG_INFO(DEBUG_CATEGORY_NAT, "=== test_full_roundtrip ===");
@@ -563,6 +630,11 @@ int main(void) {
total++; if (test_init_destroy()) passed++; else DEBUG_ERROR(DEBUG_CATEGORY_NAT, "FAIL: init_destroy");
total++; if (test_provider_egress()) passed++; else DEBUG_ERROR(DEBUG_CATEGORY_NAT, "FAIL: provider_egress");
total++; if (test_provider_ingress_response()) passed++; else DEBUG_ERROR(DEBUG_CATEGORY_NAT, "FAIL: provider_ingress");
#ifdef __linux__
total++; if (test_send_failure_ownership(0)) passed++; else DEBUG_ERROR(DEBUG_CATEGORY_NAT, "FAIL: client send ownership");
total++; if (test_send_failure_ownership(1)) passed++; else DEBUG_ERROR(DEBUG_CATEGORY_NAT, "FAIL: provider send ownership");
#endif
// Реальная доставка после инъекций проверяет восстановление обеих веток без перезапуска NAT.
total++; if (test_full_roundtrip()) passed++; else DEBUG_ERROR(DEBUG_CATEGORY_NAT, "FAIL: full_roundtrip");
printf("\n=== NAT Transport Tests: %d/%d passed ===\n", passed, total);