494 lines
20 KiB
C
494 lines
20 KiB
C
/**
|
|
* @file test_nat_stress.c
|
|
* @brief Стресс-тесты NAT engine: множество сессий, заполнение таблицы, многопоточная нагрузка.
|
|
*
|
|
* Тестирует только eim_nat.c/h (чистый engine), без TUN/ETCP.
|
|
* Измеряет время операций и проверяет корректность при предельных нагрузках.
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/time.h>
|
|
#include "../lib/debug_config.h"
|
|
#include "../src/eim_nat.h"
|
|
#include "../src/etcp.h"
|
|
#include "../src/config_parser.h"
|
|
|
|
static struct {
|
|
int run, passed, failed;
|
|
} stats = {0};
|
|
|
|
#define TEST(name) do { \
|
|
printf("TEST: %-50s ", name); fflush(stdout); \
|
|
stats.run++; \
|
|
} while(0)
|
|
|
|
#define PASS() do { puts("PASS"); stats.passed++; } while(0)
|
|
#define FAIL(msg) do { printf("FAIL: %s\n", msg); stats.failed++; } while(0)
|
|
|
|
#define ASSERT(c, m) do { if (!(c)) { FAIL(m); return; } } while(0)
|
|
#define ASSERT_EQ(a,b,m) ASSERT((a)==(b),m)
|
|
|
|
static double now_ms(void) {
|
|
struct timeval tv; gettimeofday(&tv, NULL);
|
|
return tv.tv_sec * 1000.0 + tv.tv_usec / 1000.0;
|
|
}
|
|
|
|
static uint8_t* make_udp_pkt(uint32_t src_host, uint16_t src_port_host, uint32_t dst_host, uint16_t dst_port_host, size_t* out_len) {
|
|
const size_t len = 20 + 8 + 14;
|
|
uint8_t* pkt = calloc(1, len);
|
|
pkt[0] = 0x45; pkt[1] = 0x00;
|
|
uint16_t tot = htobe16((uint16_t)len); memcpy(pkt + 2, &tot, 2);
|
|
pkt[4] = 0x12; pkt[5] = 0x34;
|
|
memset(pkt + 6, 0, 2);
|
|
pkt[8] = 64; pkt[9] = IPPROTO_UDP_UINT8;
|
|
memset(pkt + 10, 0, 2);
|
|
uint32_t sn = htobe32(src_host), dn = htobe32(dst_host);
|
|
memcpy(pkt + 12, &sn, 4); memcpy(pkt + 16, &dn, 4);
|
|
uint16_t sp = htobe16(src_port_host), dp = htobe16(dst_port_host);
|
|
memcpy(pkt + 20, &sp, 2); memcpy(pkt + 22, &dp, 2);
|
|
uint16_t ul = htobe16(8 + 14); memcpy(pkt + 24, &ul, 2);
|
|
memset(pkt + 26, 0, 2);
|
|
memset(pkt + 28, 0xAB, 14);
|
|
uint32_t sum = 0;
|
|
for (int i = 0; i < 10; i++) { uint16_t w; memcpy(&w, pkt + i*2, 2); sum += w; }
|
|
sum = (sum & 0xFFFF) + (sum >> 16); sum += (sum >> 16);
|
|
uint16_t cs = (uint16_t)(~sum);
|
|
memcpy(pkt + 10, &cs, 2);
|
|
*out_len = len;
|
|
return pkt;
|
|
}
|
|
|
|
static int verify_ip_checksum(const uint8_t* ip) {
|
|
uint32_t sum = 0;
|
|
for (int i = 0; i < 10; i++) { uint16_t w; memcpy(&w, ip + i*2, 2); sum += w; }
|
|
sum = (sum & 0xFFFF) + (sum >> 16); sum += (sum >> 16);
|
|
return (uint16_t)(~sum) == 0;
|
|
}
|
|
|
|
static struct ETCP_CONN mock_conn;
|
|
static struct ETCP_CONN* get_mock_conn(void) {
|
|
static int once = 0;
|
|
if (!once) { memset(&mock_conn, 0, sizeof(mock_conn)); mock_conn.peer_node_id = 1; once = 1; }
|
|
return &mock_conn;
|
|
}
|
|
|
|
static struct global_config make_config(uint16_t port_start, uint16_t port_end) {
|
|
struct global_config g;
|
|
memset(&g, 0, sizeof(g));
|
|
g.nat_enabled = 1;
|
|
g.nat_port_start = port_start;
|
|
g.nat_port_end = port_end;
|
|
g.nat_tun_ip.family = AF_INET;
|
|
g.nat_tun_ip.addr.v4.s_addr = htobe32(0x0A000001); // 10.0.0.1
|
|
return g;
|
|
}
|
|
|
|
// ==================== Stress tests ====================
|
|
|
|
static void stress_many_sessions(void) {
|
|
/* 10000 sequential egress operations */
|
|
TEST("stress_many_sessions_10k");
|
|
{
|
|
struct global_config g = make_config(10000, 20000); // 10001 ports
|
|
struct eim_nat_ctx ctx;
|
|
ASSERT_EQ(eim_nat_init_ctx(&ctx, &g), 0, "init");
|
|
|
|
double t0 = now_ms();
|
|
const int N = 10000;
|
|
for (int i = 0; i < N; i++) {
|
|
uint16_t sport = 50000 + (uint16_t)i;
|
|
size_t len;
|
|
uint8_t* pkt = make_udp_pkt(0x0A000002, sport, 0x08080808, 53, &len);
|
|
int r = eim_nat_egress(&ctx, pkt, len, (uint64_t)(i+1), get_mock_conn());
|
|
ASSERT_EQ(r, 0, "egress");
|
|
free(pkt);
|
|
}
|
|
double t1 = now_ms();
|
|
|
|
ASSERT_EQ(ctx.table[10000].state, EIM_NAT_ENTRY_ACTIVE, "first active");
|
|
ASSERT_EQ(ctx.table[10000+N-1].state, EIM_NAT_ENTRY_ACTIVE, "last active");
|
|
ASSERT_EQ(ctx.table[10000+N].state, EIM_NAT_ENTRY_FREE, "next free");
|
|
|
|
printf("%d egress in %.1fms (%.0f/sec) ", N, t1 - t0, N / ((t1 - t0) / 1000.0));
|
|
eim_nat_destroy_ctx(&ctx);
|
|
}
|
|
PASS();
|
|
}
|
|
|
|
static void stress_table_full_and_lookup(void) {
|
|
/* Fill entire port range, then ingress lookup */
|
|
TEST("stress_table_full_lookup");
|
|
{
|
|
int n_ports = 1000;
|
|
struct global_config g = make_config(1000, 1000 + n_ports - 1);
|
|
struct eim_nat_ctx ctx;
|
|
ASSERT_EQ(eim_nat_init_ctx(&ctx, &g), 0, "init");
|
|
|
|
// Fill all ports
|
|
for (int i = 0; i < n_ports; i++) {
|
|
uint16_t sport = 40000 + (uint16_t)i;
|
|
size_t len;
|
|
uint8_t* pkt = make_udp_pkt(0x0A000002, sport, 0x08080808, 53, &len);
|
|
eim_nat_egress(&ctx, pkt, len, (uint64_t)i, get_mock_conn());
|
|
free(pkt);
|
|
}
|
|
ASSERT_EQ(ctx.next_port, 1000, "wrapped to start");
|
|
|
|
// Verify exhaustion
|
|
{
|
|
size_t len;
|
|
uint8_t* pkt = make_udp_pkt(0x0A000002, 40000 + n_ports, 0x08080808, 53, &len);
|
|
int r = eim_nat_egress(&ctx, pkt, len, 9999, get_mock_conn());
|
|
ASSERT_EQ(r, -1, "exhaustion");
|
|
free(pkt);
|
|
}
|
|
|
|
// Ingress lookup: verify ALL entries work correctly
|
|
double t0 = now_ms();
|
|
for (int i = 0; i < n_ports; i++) {
|
|
uint16_t ext_port = 1000 + i;
|
|
size_t len;
|
|
uint8_t* resp = make_udp_pkt(0x08080808, 53, 0x0A000001, ext_port, &len);
|
|
struct eim_nat_entry* entry = NULL;
|
|
int r = eim_nat_ingress(&ctx, resp, len, &entry);
|
|
ASSERT_EQ(r, 0, "ingress");
|
|
ASSERT(entry != NULL, "entry non-null");
|
|
ASSERT_EQ(entry - ctx.table, ext_port, "entry index");
|
|
ASSERT_EQ(entry->internal_ip, 0x0A000002, "internal IP");
|
|
uint16_t expected_port_net = htobe16(40000 + i);
|
|
ASSERT_EQ(entry->internal_port, expected_port_net, "internal port");
|
|
ASSERT(verify_ip_checksum(resp), "IP checksum");
|
|
free(resp);
|
|
}
|
|
double t1 = now_ms();
|
|
printf("%d ingress lookups in %.1fms (%.0f/sec) ", n_ports, t1 - t0, n_ports / ((t1 - t0) / 1000.0));
|
|
|
|
eim_nat_destroy_ctx(&ctx);
|
|
}
|
|
PASS();
|
|
}
|
|
|
|
static void stress_egress_ingress_cycle(void) {
|
|
/* Repeated allocate-free cycles: egress → ingress → free entry → repeat */
|
|
TEST("stress_cyclic_allocate_free");
|
|
{
|
|
struct global_config g = make_config(1000, 1001); // 2 ports
|
|
struct eim_nat_ctx ctx;
|
|
ASSERT_EQ(eim_nat_init_ctx(&ctx, &g), 0, "init");
|
|
|
|
const int CYCLES = 5000;
|
|
size_t len;
|
|
for (int cycle = 0; cycle < CYCLES; cycle++) {
|
|
uint8_t* pkt = make_udp_pkt(0x0A000002, 40000, 0x08080808, 53, &len);
|
|
int r = eim_nat_egress(&ctx, pkt, len, 0x1111, get_mock_conn());
|
|
ASSERT_EQ(r, 0, "egress");
|
|
free(pkt);
|
|
|
|
// Ingress with response
|
|
uint16_t ext_port = ctx.next_port == 1000 ? 1001 : 1000; // find the allocated port
|
|
// Actually the alloc function uses next_port. First alloc gets 1000, next_port becomes 1001.
|
|
// But the cycle reuses the same internal ip:port, so it finds existing entry in eim_nat_find_egress.
|
|
// Let's use a different approach: free the entry, then re-egress.
|
|
// Actually, same (ip:port:proto) always reuses same port. Let's clean entry manually.
|
|
struct eim_nat_entry* e = &ctx.table[1000];
|
|
if (e->state != EIM_NAT_ENTRY_ACTIVE) { FAIL("entry not active"); eim_nat_destroy_ctx(&ctx); return; }
|
|
|
|
uint8_t* resp = make_udp_pkt(0x08080808, 53, 0x0A000001, 1000, &len);
|
|
struct eim_nat_entry* entry = NULL;
|
|
r = eim_nat_ingress(&ctx, resp, len, &entry);
|
|
ASSERT_EQ(r, 0, "ingress");
|
|
ASSERT(entry == e, "same entry");
|
|
ASSERT(verify_ip_checksum(resp), "IP checksum after ingress");
|
|
free(resp);
|
|
|
|
// Free entry for next cycle
|
|
memset(e, 0, sizeof(*e));
|
|
ctx.next_port = 1000;
|
|
}
|
|
|
|
eim_nat_destroy_ctx(&ctx);
|
|
printf("%d cycles OK ", CYCLES);
|
|
}
|
|
PASS();
|
|
}
|
|
|
|
static void stress_concurrent_different_flows(void) {
|
|
/* Many different internal IP:port combos, verify each gets unique external port */
|
|
TEST("stress_unique_port_mapping");
|
|
{
|
|
struct global_config g = make_config(20000, 20099); // 100 ports
|
|
struct eim_nat_ctx ctx;
|
|
ASSERT_EQ(eim_nat_init_ctx(&ctx, &g), 0, "init");
|
|
|
|
const int N = 100;
|
|
for (int i = 0; i < N; i++) {
|
|
uint32_t int_ip = 0x0A000002 + (uint32_t)(i / 10); // 10 IPs, 10 ports each
|
|
uint16_t int_port = 40000 + (uint16_t)(i % 10);
|
|
size_t len;
|
|
uint8_t* pkt = make_udp_pkt(int_ip, int_port, 0x08080808, 53, &len);
|
|
int r = eim_nat_egress(&ctx, pkt, len, (uint64_t)i, get_mock_conn());
|
|
ASSERT_EQ(r, 0, "egress");
|
|
free(pkt);
|
|
}
|
|
|
|
// Verify all ports are used
|
|
for (int i = 0; i < N; i++) {
|
|
ASSERT(ctx.table[20000 + i].state == EIM_NAT_ENTRY_ACTIVE, "port active");
|
|
}
|
|
|
|
// Verify unique (internal_ip, internal_port) → unique external_port
|
|
for (int i = 0; i < N; i++) {
|
|
struct eim_nat_entry* ei = &ctx.table[20000 + i];
|
|
for (int j = i + 1; j < N; j++) {
|
|
struct eim_nat_entry* ej = &ctx.table[20000 + j];
|
|
// Different entries should NOT map the same (internal_ip, internal_port)
|
|
if (ei->internal_ip == ej->internal_ip && ei->internal_port == ej->internal_port) {
|
|
FAIL("duplicate flow mapping");
|
|
eim_nat_destroy_ctx(&ctx);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Verify ingress: each flow responds correctly
|
|
for (int i = 0; i < N; i++) {
|
|
uint16_t ext_port = 20000 + i;
|
|
size_t len;
|
|
uint8_t* resp = make_udp_pkt(0x08080808, 53, 0x0A000001, ext_port, &len);
|
|
struct eim_nat_entry* entry = NULL;
|
|
int r = eim_nat_ingress(&ctx, resp, len, &entry);
|
|
ASSERT_EQ(r, 0, "ingress");
|
|
ASSERT(entry == &ctx.table[ext_port], "entry match");
|
|
ASSERT(verify_ip_checksum(resp), "IP checksum");
|
|
free(resp);
|
|
}
|
|
|
|
eim_nat_destroy_ctx(&ctx);
|
|
printf("%d unique flows OK ", N);
|
|
}
|
|
PASS();
|
|
}
|
|
|
|
static void stress_tcp_mixed_with_udp(void) {
|
|
/* Mix TCP and UDP flows on same NAT */
|
|
TEST("stress_tcp_udp_mixed");
|
|
{
|
|
struct global_config g = make_config(30000, 30099);
|
|
struct eim_nat_ctx ctx;
|
|
ASSERT_EQ(eim_nat_init_ctx(&ctx, &g), 0, "init");
|
|
|
|
// 50 UDP flows + 50 TCP flows
|
|
for (int i = 0; i < 50; i++) {
|
|
uint16_t sport = 40000 + i;
|
|
size_t len;
|
|
uint8_t* pkt = make_udp_pkt(0x0A000002, sport, 0x08080808, 53, &len);
|
|
eim_nat_egress(&ctx, pkt, len, i, get_mock_conn());
|
|
free(pkt);
|
|
}
|
|
for (int i = 0; i < 50; i++) {
|
|
uint16_t sport = 40000 + i;
|
|
const size_t tcp_len = 20 + 20 + 14;
|
|
uint8_t* pkt = calloc(1, tcp_len);
|
|
pkt[0] = 0x45; pkt[1] = 0x00;
|
|
uint16_t tot = htobe16((uint16_t)tcp_len); memcpy(pkt + 2, &tot, 2);
|
|
pkt[4] = 0x12; pkt[5] = 0x34;
|
|
memset(pkt + 6, 0, 2);
|
|
pkt[8] = 64; pkt[9] = IPPROTO_TCP_UINT8;
|
|
memset(pkt + 10, 0, 2);
|
|
uint32_t sn = htobe32(0x0A000002), dn = htobe32(0x08080808);
|
|
memcpy(pkt + 12, &sn, 4); memcpy(pkt + 16, &dn, 4);
|
|
uint16_t sp = htobe16(sport), dp = htobe16(80);
|
|
memcpy(pkt + 20, &sp, 2); memcpy(pkt + 22, &dp, 2);
|
|
pkt[32] = 0x50;
|
|
memset(pkt + 36, 0, 2);
|
|
memset(pkt + 40, 0xCC, 14);
|
|
uint32_t sum = 0;
|
|
for (int k = 0; k < 10; k++) { uint16_t w; memcpy(&w, pkt + k*2, 2); sum += w; }
|
|
sum = (sum & 0xFFFF) + (sum >> 16); sum += (sum >> 16);
|
|
uint16_t cs = (uint16_t)(~sum); memcpy(pkt + 10, &cs, 2);
|
|
eim_nat_egress(&ctx, pkt, tcp_len, 100 + i, get_mock_conn());
|
|
free(pkt);
|
|
}
|
|
|
|
// Verify: 100 entries total, same internal_port with different proto OK
|
|
int udp_count = 0, tcp_count = 0;
|
|
for (uint16_t p = 30000; p <= 30099; p++) {
|
|
if (ctx.table[p].state == EIM_NAT_ENTRY_ACTIVE) {
|
|
if (ctx.table[p].proto == IPPROTO_UDP_UINT8) udp_count++;
|
|
else if (ctx.table[p].proto == IPPROTO_TCP_UINT8) tcp_count++;
|
|
}
|
|
}
|
|
ASSERT_EQ(udp_count, 50, "50 UDP entries");
|
|
ASSERT_EQ(tcp_count, 50, "50 TCP entries");
|
|
|
|
// Ingress TCP: use same source internal_ip:port but want response
|
|
// The TCP entry for internal port 40000 is at some external port. Let's find it.
|
|
uint16_t tcp_ext_port = 0;
|
|
for (uint16_t p = 30000; p <= 30099; p++) {
|
|
if (ctx.table[p].state == EIM_NAT_ENTRY_ACTIVE && ctx.table[p].proto == IPPROTO_TCP_UINT8) {
|
|
tcp_ext_port = p; break;
|
|
}
|
|
}
|
|
ASSERT(tcp_ext_port > 0, "found TCP port");
|
|
|
|
// Send ingress TCP to that port
|
|
{
|
|
const size_t tcp_len = 20 + 20 + 14;
|
|
uint8_t* resp = calloc(1, tcp_len);
|
|
resp[0] = 0x45; resp[1] = 0x00;
|
|
uint16_t tot = htobe16((uint16_t)tcp_len); memcpy(resp + 2, &tot, 2);
|
|
resp[4] = 0x12; resp[5] = 0x34;
|
|
memset(resp + 6, 0, 2);
|
|
resp[8] = 64; resp[9] = IPPROTO_TCP_UINT8;
|
|
memset(resp + 10, 0, 2);
|
|
uint32_t sn = htobe32(0x08080808), dn = htobe32(0x0A000001);
|
|
memcpy(resp + 12, &sn, 4); memcpy(resp + 16, &dn, 4);
|
|
uint16_t sp = htobe16(80), dp = htobe16(tcp_ext_port);
|
|
memcpy(resp + 20, &sp, 2); memcpy(resp + 22, &dp, 2);
|
|
resp[32] = 0x50;
|
|
memset(resp + 36, 0, 2);
|
|
memset(resp + 40, 0xCC, 14);
|
|
uint32_t sum = 0;
|
|
for (int k = 0; k < 10; k++) { uint16_t w; memcpy(&w, resp + k*2, 2); sum += w; }
|
|
sum = (sum & 0xFFFF) + (sum >> 16); sum += (sum >> 16);
|
|
uint16_t cs = (uint16_t)(~sum); memcpy(resp + 10, &cs, 2);
|
|
struct eim_nat_entry* entry = NULL;
|
|
int r = eim_nat_ingress(&ctx, resp, tcp_len, &entry);
|
|
ASSERT_EQ(r, 0, "TCP ingress");
|
|
ASSERT(entry != NULL, "TCP entry non-null");
|
|
ASSERT_EQ(entry->proto, IPPROTO_TCP_UINT8, "proto=TCP");
|
|
ASSERT(verify_ip_checksum(resp), "IP checksum");
|
|
free(resp);
|
|
}
|
|
|
|
eim_nat_destroy_ctx(&ctx);
|
|
}
|
|
PASS();
|
|
}
|
|
|
|
static void stress_icmp_mixed(void) {
|
|
/* ICMP Echo + UDP mix in same NAT */
|
|
TEST("stress_icmp_udp_mixed");
|
|
{
|
|
struct global_config g = make_config(40000, 40099);
|
|
struct eim_nat_ctx ctx;
|
|
ASSERT_EQ(eim_nat_init_ctx(&ctx, &g), 0, "init");
|
|
|
|
// 10 ICMP Echo + 10 UDP flows
|
|
for (int i = 0; i < 10; i++) {
|
|
uint16_t icmp_id = 0x1000 + i;
|
|
const size_t len = 20 + 8 + 14;
|
|
uint8_t* pkt = calloc(1, len);
|
|
pkt[0] = 0x45; pkt[1] = 0x00;
|
|
uint16_t tot = htobe16((uint16_t)len); memcpy(pkt + 2, &tot, 2);
|
|
pkt[4] = 0x12; pkt[5] = 0x34;
|
|
memset(pkt + 6, 0, 2);
|
|
pkt[8] = 64; pkt[9] = IPPROTO_ICMP_UINT8;
|
|
memset(pkt + 10, 0, 2);
|
|
uint32_t sn = htobe32(0x0A000002), dn = htobe32(0x08080808);
|
|
memcpy(pkt + 12, &sn, 4); memcpy(pkt + 16, &dn, 4);
|
|
pkt[20] = 8; pkt[21] = 0;
|
|
memset(pkt + 22, 0, 2);
|
|
uint16_t id_n = htobe16(icmp_id), seq_n = htobe16(1);
|
|
memcpy(pkt + 24, &id_n, 2); memcpy(pkt + 26, &seq_n, 2);
|
|
memset(pkt + 28, 0xDD, 14);
|
|
uint32_t sum = 0;
|
|
for (int k = 0; k < 10; k++) { uint16_t w; memcpy(&w, pkt + k*2, 2); sum += w; }
|
|
sum = (sum & 0xFFFF) + (sum >> 16); sum += (sum >> 16);
|
|
uint16_t cs = (uint16_t)(~sum); memcpy(pkt + 10, &cs, 2);
|
|
eim_nat_egress(&ctx, pkt, len, i, get_mock_conn());
|
|
free(pkt);
|
|
}
|
|
for (int i = 0; i < 10; i++) {
|
|
size_t len;
|
|
uint8_t* pkt = make_udp_pkt(0x0A000002, 50000 + i, 0x08080808, 53, &len);
|
|
eim_nat_egress(&ctx, pkt, len, 100 + i, get_mock_conn());
|
|
free(pkt);
|
|
}
|
|
|
|
int icmp_ct = 0, udp_ct = 0;
|
|
for (uint16_t p = 40000; p <= 40099; p++) {
|
|
if (ctx.table[p].state == EIM_NAT_ENTRY_ACTIVE) {
|
|
if (ctx.table[p].proto == IPPROTO_ICMP_UINT8) icmp_ct++;
|
|
else if (ctx.table[p].proto == IPPROTO_UDP_UINT8) udp_ct++;
|
|
}
|
|
}
|
|
ASSERT_EQ(icmp_ct, 10, "10 ICMP");
|
|
ASSERT_EQ(udp_ct, 10, "10 UDP");
|
|
|
|
// ICMP ingress: reply to first ICMP entry
|
|
uint16_t icmp_ext = 0;
|
|
for (uint16_t p = 40000; p <= 40099; p++) {
|
|
if (ctx.table[p].state == EIM_NAT_ENTRY_ACTIVE && ctx.table[p].proto == IPPROTO_ICMP_UINT8) { icmp_ext = p; break; }
|
|
}
|
|
{
|
|
const size_t len = 20 + 8 + 14;
|
|
uint8_t* reply = calloc(1, len);
|
|
reply[0] = 0x45; reply[1] = 0x00;
|
|
uint16_t tot = htobe16((uint16_t)len); memcpy(reply + 2, &tot, 2);
|
|
reply[4] = 0x12; reply[5] = 0x34;
|
|
memset(reply + 6, 0, 2);
|
|
reply[8] = 64; reply[9] = IPPROTO_ICMP_UINT8;
|
|
memset(reply + 10, 0, 2);
|
|
uint32_t sn = htobe32(0x08080808), dn = htobe32(0x0A000001);
|
|
memcpy(reply + 12, &sn, 4); memcpy(reply + 16, &dn, 4);
|
|
reply[20] = 0; reply[21] = 0;
|
|
memset(reply + 22, 0, 2);
|
|
uint16_t ext_id_net = htobe16(icmp_ext), seq_n = htobe16(1);
|
|
memcpy(reply + 24, &ext_id_net, 2); memcpy(reply + 26, &seq_n, 2);
|
|
memset(reply + 28, 0xDD, 14);
|
|
uint32_t sum = 0;
|
|
for (int k = 0; k < 10; k++) { uint16_t w; memcpy(&w, reply + k*2, 2); sum += w; }
|
|
sum = (sum & 0xFFFF) + (sum >> 16); sum += (sum >> 16);
|
|
uint16_t cs = (uint16_t)(~sum); memcpy(reply + 10, &cs, 2);
|
|
struct eim_nat_entry* entry = NULL;
|
|
int r = eim_nat_ingress(&ctx, reply, len, &entry);
|
|
ASSERT_EQ(r, 0, "ICMP ingress");
|
|
ASSERT(entry != NULL, "ICMP entry");
|
|
ASSERT(verify_ip_checksum(reply), "IP checksum");
|
|
free(reply);
|
|
}
|
|
|
|
eim_nat_destroy_ctx(&ctx);
|
|
}
|
|
PASS();
|
|
}
|
|
|
|
static void stress_large_table_init_free(void) {
|
|
/* Verify 65536-entry table alloc/free is fast and doesn't leak */
|
|
TEST("stress_table_alloc_free_65k");
|
|
{
|
|
struct global_config g = make_config(10000, 20000);
|
|
double t0 = now_ms();
|
|
for (int i = 0; i < 100; i++) {
|
|
struct eim_nat_ctx ctx;
|
|
int r = eim_nat_init_ctx(&ctx, &g);
|
|
ASSERT_EQ(r, 0, "init");
|
|
eim_nat_destroy_ctx(&ctx);
|
|
}
|
|
double t1 = now_ms();
|
|
printf("100 alloc/free in %.1fms (%.0f/sec) ", t1 - t0, 100.0 / ((t1 - t0) / 1000.0));
|
|
}
|
|
PASS();
|
|
}
|
|
|
|
// ==================== Main ====================
|
|
int main(void) {
|
|
debug_config_init();
|
|
debug_set_level(DEBUG_LEVEL_ERROR);
|
|
|
|
stress_many_sessions();
|
|
stress_table_full_and_lookup();
|
|
stress_egress_ingress_cycle();
|
|
stress_concurrent_different_flows();
|
|
stress_tcp_mixed_with_udp();
|
|
stress_icmp_mixed();
|
|
stress_large_table_init_free();
|
|
|
|
printf("\n=== Stress Results: %d run, %d passed, %d failed ===\n",
|
|
stats.run, stats.passed, stats.failed);
|
|
return stats.failed ? 1 : 0;
|
|
}
|