From a9820bea42585107d0b4cf977537ba59ba0f34d5 Mon Sep 17 00:00:00 2001 From: evgeny Date: Wed, 21 Oct 2026 21:51:49 +0200 Subject: [PATCH] Preserve IPv4 UDP checksum encoding through NAT --- src/eim_nat.c | 2 ++ src/eim_nat.h | 1 + tests/test_nat_engine.c | 47 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 50 insertions(+) diff --git a/src/eim_nat.c b/src/eim_nat.c index 0711b6bb..c0a475fb 100644 --- a/src/eim_nat.c +++ b/src/eim_nat.c @@ -55,10 +55,12 @@ static void csum_update_transport(uint8_t* transport, int csum_off, uint32_t old_ip_host, uint32_t new_ip_host, uint16_t old_port_net, uint16_t new_port_net) { uint16_t old_csum; memcpy(&old_csum, transport + csum_off, 2); + if (csum_off == UDP_CHECKSUM_OFFSET && old_csum == 0) return; // IPv4 UDP: проверка отключена отправителем. uint32_t old_ip_net = htonl(old_ip_host), new_ip_net = htonl(new_ip_host); uint16_t old_w[3] = {(uint16_t)(old_ip_net & 0xFFFF), (uint16_t)(old_ip_net >> 16), old_port_net}; uint16_t new_w[3] = {(uint16_t)(new_ip_net & 0xFFFF), (uint16_t)(new_ip_net >> 16), new_port_net}; uint16_t new_csum = csum_update_n(old_csum, old_w, new_w, 3); + if (csum_off == UDP_CHECKSUM_OFFSET && new_csum == 0) new_csum = 0xffff; memcpy(transport + csum_off, &new_csum, 2); } diff --git a/src/eim_nat.h b/src/eim_nat.h index 70677523..a058a1ad 100644 --- a/src/eim_nat.h +++ b/src/eim_nat.h @@ -1,6 +1,7 @@ /* eim_nat — таблица Endpoint-Independent Mapping для IPv4 TCP/UDP и ICMP echo. * init_ctx создаёт состояние из global_config; при отключённом NAT оставляет initialized=0. * egress/ingress меняют IP/порты и checksums на месте, не отправляют пакеты и не владеют TUN/ETCP. + * IPv4 UDP: отключённая checksum=0 сохраняется; вычисленный ноль записывается как 0xffff. * Перед обработкой нужен инициализированный ctx; вызовы последовательны в uasync-потоке. * gateway/internal IP — host order, internal_port — network order, внешний порт — host order. * src_conn заимствован; срок жизни и выбор транспорта обратной отправки контролирует nat_transport. */ diff --git a/tests/test_nat_engine.c b/tests/test_nat_engine.c index fae4b5e7..160ce947 100644 --- a/tests/test_nat_engine.c +++ b/tests/test_nat_engine.c @@ -892,6 +892,51 @@ static void test_checksum_update(void) { } } +/* UDP: отключённая checksum остаётся нулевой; вычисленный ноль передаётся как 0xffff. */ +static void test_udp_checksum_encoding(int computed_zero) { + for (int ingress = 0; ingress < 2; ingress++) { + TEST(computed_zero ? (ingress ? "udp_zero_result_ingress" : "udp_zero_result_egress") : + (ingress ? "udp_disabled_checksum_ingress" : "udp_disabled_checksum_egress")); + struct global_config g = make_global_config(); + struct eim_nat_ctx ctx; + ASSERT_EQ(eim_nat_init_ctx(&ctx, &g), 0, "UDP checksum context initialized"); + ctx.table[TEST_PORT_START] = (struct eim_nat_entry){.internal_ip = TEST_IP_SRC_HOST, + .internal_port = htobe16(TEST_SRC_PORT), .proto = IPPROTO_UDP_UINT8, + .state = EIM_NAT_ENTRY_ACTIVE, .src_node_id = 1}; + const size_t len = 28 + TEST_PAYLOAD_LEN; + uint8_t* pkt = ingress ? make_udp_pkt(TEST_IP_DST_HOST, TEST_DST_PORT, TEST_GW_HOST, TEST_PORT_START) : + make_udp_pkt(TEST_IP_SRC_HOST, TEST_SRC_PORT, TEST_IP_DST_HOST, TEST_DST_PORT); + if (computed_zero) { + size_t ip_offset = ingress ? 16 : 12; + size_t port_offset = ingress ? 22 : 20; + uint32_t original_ip; memcpy(&original_ip, pkt + ip_offset, 4); + uint16_t original_port; memcpy(&original_port, pkt + port_offset, 2); + uint32_t target_ip = htobe32(ingress ? TEST_IP_SRC_HOST : TEST_GW_HOST); + uint16_t target_port = htobe16(ingress ? TEST_SRC_PORT : TEST_PORT_START); + memcpy(pkt + ip_offset, &target_ip, 4); memcpy(pkt + port_offset, &target_port, 2); + memset(pkt + 28, 0, 2); + uint16_t payload_word = htobe16(full_transport_checksum(pkt, len)); + memcpy(pkt + 28, &payload_word, 2); + ASSERT_EQ(full_transport_checksum(pkt, len), 0, "translated packet requires zero checksum"); + memcpy(pkt + ip_offset, &original_ip, 4); memcpy(pkt + port_offset, &original_port, 2); + set_packet_checksums(pkt, len, 26); + ASSERT_EQ(full_transport_checksum(pkt, len), 0, "valid nonzero input UDP checksum"); + } + struct eim_nat_entry* entry = NULL; + int r = ingress ? eim_nat_ingress(&ctx, pkt, len, &entry) : + eim_nat_egress(&ctx, pkt, len, 1, get_mock_conn()); + uint16_t checksum; memcpy(&checksum, pkt + 26, 2); + int valid = !computed_zero || full_transport_checksum(pkt, len) == 0; + free(pkt); + ASSERT_EQ(r, 0, "UDP checksum packet accepted"); + ASSERT(!ingress || entry == &ctx.table[TEST_PORT_START], "ingress used expected UDP mapping"); + ASSERT_EQ(checksum, computed_zero ? 0xffff : 0, "correct UDP checksum encoding"); + ASSERT(valid, "encoded UDP checksum remains valid"); + eim_nat_destroy_ctx(&ctx); + PASS(); + } +} + /* ================================================================ * Main * ================================================================ */ @@ -912,6 +957,8 @@ int main(void) { test_port_forward(); test_bypass_flows(); test_checksum_update(); + test_udp_checksum_encoding(0); + test_udp_checksum_encoding(1); printf("\n=== Results: %d run, %d passed, %d failed ===\n", stats.run, stats.passed, stats.failed);