Fix NAT packet ownership after router send failures
This commit is contained in:
1 parent
ef673eb3b7
commit
fbbd62acc4
3 files changed
+81
-6
No files matched your search
+6
-6
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in new issue
Block a user