Cover NAT forward owners, full port range and invalid rules
This commit is contained in:
1 parent
ca471d777e
commit
ddc163a2b7
3 files changed
+95
-1
No files matched your search
@@ -71,7 +71,7 @@ free_config(cfg);
|
||||
| `[firewall]` | `parse_firewall_rule()` | `allow=IPv4[:port]` или `allow=all`; порт 0–65535, 0 разрешает весь IP; CIDR запрещён |
|
||||
| `[control]` | `parse_control()` | `ip`, `port`, `allow` (подсеть) |
|
||||
| `[allowed_keys]` | inline | `allow_all`, `key` (64 hex) |
|
||||
| `[nat]` | `parse_nat()` | `tun_ifname`, `tun_ip`, `nat_via`, `port_start`, `port_end`, `forward` (proto:ip:in_port:ext_port) |
|
||||
| `[nat]` | `parse_nat()` | `tun_ifname`, `tun_ip`, `nat_via`, `port_start`, `port_end`, `forward` (`proto:node_id_hex:IPv4:in_port:ext_port`) |
|
||||
| `[tcp_proxy_client]` | `parse_tcp_proxy_client()` | `enabled`, `tun_name`, `tun_ip`, `mtu`, `via_node`, `socks_*`, `http_proxy_*`, `forward` (port -> ip:port) |
|
||||
| `[tcp_proxy_server]` | inline | `tcp_recv_buf` |
|
||||
| `[ntp]` | inline | `enabled`, `server`, `interval` |
|
||||
|
||||
@@ -973,6 +973,74 @@ static void test_node_mapping_isolation(void) {
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* Пробросы вне/внутри динамического пула работают до первого исходящего пакета и сохраняют владельца. */
|
||||
static void test_static_forward_owners(void) {
|
||||
const uint16_t external_ports[] = {80, TEST_PORT_START, 65535};
|
||||
for (size_t i = 0; i < sizeof(external_ports) / sizeof(external_ports[0]); i++) {
|
||||
TEST("static_forward_owner_and_port_range");
|
||||
struct global_config g = make_global_config();
|
||||
struct eim_nat_ctx ctx;
|
||||
ASSERT_EQ(eim_nat_init_ctx(&ctx, &g), 0, "forward context initialized");
|
||||
uint16_t external_port = external_ports[i];
|
||||
ASSERT_EQ(eim_nat_add_forward(&ctx, IPPROTO_UDP_UINT8, TEST_IP_SRC_HOST, htobe16(TEST_SRC_PORT), external_port, 1),
|
||||
0, "forward with explicit owner accepted");
|
||||
const size_t len = 28 + TEST_PAYLOAD_LEN;
|
||||
uint8_t* pkt = make_udp_pkt(TEST_IP_DST_HOST, TEST_DST_PORT, TEST_GW_HOST, external_port);
|
||||
struct eim_nat_entry* entry = NULL;
|
||||
int r = eim_nat_ingress(&ctx, pkt, len, &entry);
|
||||
free(pkt);
|
||||
ASSERT_EQ(r, 0, "unsolicited forward accepted");
|
||||
ASSERT(entry && entry->src_node_id == 1, "unsolicited packet has target node");
|
||||
for (uint64_t node = 1; node <= 2; node++) {
|
||||
pkt = make_udp_pkt(TEST_IP_SRC_HOST, TEST_SRC_PORT, TEST_IP_DST_HOST, TEST_DST_PORT);
|
||||
r = eim_nat_egress(&ctx, pkt, len, node, NULL);
|
||||
uint16_t port; memcpy(&port, pkt + 20, 2);
|
||||
free(pkt);
|
||||
ASSERT_EQ(r, 0, "forward response accepted");
|
||||
ASSERT(node == 1 ? be16toh(port) == external_port : be16toh(port) != external_port,
|
||||
"only forward owner reuses its static port");
|
||||
}
|
||||
ASSERT_EQ(ctx.table[external_port].state, EIM_NAT_ENTRY_STATIC, "static mapping preserved");
|
||||
ASSERT_EQ(eim_nat_add_forward(&ctx, IPPROTO_UDP_UINT8, TEST_IP_SRC_HOST, htobe16(TEST_SRC_PORT), 81, 1),
|
||||
-1, "duplicate internal endpoint rejected");
|
||||
ASSERT_EQ(eim_nat_add_forward(&ctx, IPPROTO_UDP_UINT8, TEST_IP_SRC_HOST, htobe16(8080), 81, 0),
|
||||
-1, "forward without target node rejected");
|
||||
eim_nat_destroy_ctx(&ctx);
|
||||
PASS();
|
||||
}
|
||||
TEST("static_forward_config_and_rollback");
|
||||
const char* text = "[nat]\nforward=udp:1234:10.0.0.2:40000:80\n";
|
||||
struct utun_config* config = parse_config_from_buf(text, strlen(text), "nat-forward-valid");
|
||||
ASSERT(config && config->global.nat_forward_count == 1, "forward config parsed");
|
||||
struct eim_nat_ctx ctx;
|
||||
ASSERT_EQ(eim_nat_init_ctx(&ctx, &config->global), 0, "forward config initialized");
|
||||
ASSERT_EQ(ctx.table[80].src_node_id, 0x1234, "HEX target loaded from config");
|
||||
eim_nat_destroy_ctx(&ctx);
|
||||
config->global.nat_forward_count = 2;
|
||||
config->global.nat_forwards[1] = config->global.nat_forwards[0];
|
||||
int r = eim_nat_init_ctx(&ctx, &config->global);
|
||||
free_config(config);
|
||||
ASSERT_EQ(r, -1, "duplicate config fails initialization");
|
||||
ASSERT(!ctx.initialized && !ctx.table, "failed init releases NAT table");
|
||||
PASS();
|
||||
TEST("static_forward_invalid_config");
|
||||
const char* invalid[] = {
|
||||
"forward=udp:10.0.0.2:40000:80", "forward=udp:0:10.0.0.2:40000:80",
|
||||
"forward=udp::10.0.0.2:40000:80", "forward=udp:1234:10.0.0.2:40000:80:extra",
|
||||
"forward=udp:10000000000000000:10.0.0.2:40000:80", "forward=udp:1234:10.0.0.2:65536:80",
|
||||
"forward=udp:1234:10.0.0.2:40000:0", "forward=udp:1234:10.0.0.2:40000:-1",
|
||||
"forward=udp:1234:10.0.0.2:40000:80x", "port_start=65536", "port_end=-1", "nat_via=1234x"
|
||||
};
|
||||
for (size_t i = 0; i < sizeof(invalid) / sizeof(invalid[0]); i++) {
|
||||
char buf[160]; snprintf(buf, sizeof(buf), "[nat]\n%s\n", invalid[i]);
|
||||
config = parse_config_from_buf(buf, strlen(buf), "nat-forward-invalid");
|
||||
int rejected = !config;
|
||||
if (config) free_config(config);
|
||||
ASSERT(rejected, "invalid NAT config rejected");
|
||||
}
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* ================================================================
|
||||
* Main
|
||||
* ================================================================ */
|
||||
@@ -996,6 +1064,7 @@ int main(void) {
|
||||
test_udp_checksum_encoding(0);
|
||||
test_udp_checksum_encoding(1);
|
||||
test_node_mapping_isolation();
|
||||
test_static_forward_owners();
|
||||
|
||||
printf("\n=== Results: %d run, %d passed, %d failed ===\n",
|
||||
stats.run, stats.passed, stats.failed);
|
||||
|
||||
@@ -496,6 +496,30 @@ static int test_send_failure_ownership(int provider) {
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Первый входящий пакет по статическому пробросу ниже динамического диапазона доходит до заданного узла. */
|
||||
static int test_static_forward_delivery(void) {
|
||||
if (eim_nat_add_forward(&inst_provider->nat, IPPROTO_UDP_UINT8, 0x0a0000ce, htobe16(8081), 8080, g_client_node_id)) return 0;
|
||||
size_t len;
|
||||
uint8_t* packet = build_udp_pkt(0x08080808, 53, inst_provider->nat.gateway_ip, 8080, &len);
|
||||
test_state.capture_count = 0; test_state.captured_len = 0;
|
||||
int r = tun_inject_packet(inst_provider->nat_tr.nat_tun, packet, len);
|
||||
free(packet);
|
||||
if (r) { DEBUG_ERROR(DEBUG_CATEGORY_NAT, "Static forward injection failed"); return 0; }
|
||||
for (int i = 0; i < 500 && !test_timed_out && !test_state.capture_count; i++) {
|
||||
uasync_poll(ua, 10); capture_tun_read(inst_client->nat_tr.nat_tun);
|
||||
}
|
||||
uint32_t dst = 0; uint16_t port = 0;
|
||||
if (test_state.captured_len >= 28) {
|
||||
memcpy(&dst, test_state.captured + 16, 4); memcpy(&port, test_state.captured + 22, 2);
|
||||
}
|
||||
int ok = test_state.captured_len == len && dst == htobe32(0x0a0000ce) && port == htobe16(8081) &&
|
||||
verify_ip_checksum(test_state.captured);
|
||||
if (ok) DEBUG_INFO(DEBUG_CATEGORY_NAT, "PASS static forward: :8080 -> node=%016llx 10.0.0.206:8081", (unsigned long long)g_client_node_id);
|
||||
else DEBUG_ERROR(DEBUG_CATEGORY_NAT, "FAIL static forward: bytes=%zu destination=%08x port=%u",
|
||||
test_state.captured_len, be32toh(dst), be16toh(port));
|
||||
return ok;
|
||||
}
|
||||
|
||||
static int test_full_roundtrip(void) {
|
||||
/* Полный путь через client socketpair → ETCP → provider TUN и обратно. */
|
||||
DEBUG_INFO(DEBUG_CATEGORY_NAT, "=== test_full_roundtrip ===");
|
||||
@@ -634,6 +658,7 @@ int main(void) {
|
||||
#endif
|
||||
// Реальная доставка после инъекций проверяет восстановление обеих веток без перезапуска NAT.
|
||||
total++; if (test_full_roundtrip()) passed++; else DEBUG_ERROR(DEBUG_CATEGORY_NAT, "FAIL: full_roundtrip");
|
||||
total++; if (test_static_forward_delivery()) passed++; else DEBUG_ERROR(DEBUG_CATEGORY_NAT, "FAIL: static_forward");
|
||||
|
||||
printf("\n=== NAT Transport Tests: %d/%d passed ===\n", passed, total);
|
||||
test_result = (passed == total) ? 0 : 1;
|
||||
|
||||
Reference in new issue
Block a user