diff --git a/include/n2n_typedefs.h b/include/n2n_typedefs.h index 360b340d..36daa73f 100644 --- a/include/n2n_typedefs.h +++ b/include/n2n_typedefs.h @@ -456,7 +456,7 @@ typedef struct n2n_edge_conf { devstr_t tuntap_dev_name; struct n2n_ip_subnet tuntap_v4; uint8_t tuntap_ip_mode; /**< Interface IP address allocated mode, eg. DHCP. */ - + bool clamp_mss; /**< Clamp TCP MSS to MTU. */ uint32_t test_benchmark_seconds; int test_output_format; diff --git a/include/n3n/edge.h b/include/n3n/edge.h index f898d52c..f8c81a92 100644 --- a/include/n3n/edge.h +++ b/include/n3n/edge.h @@ -29,4 +29,8 @@ void send_register_super (struct n3n_runtime_data *eee); void send_query_peer (struct n3n_runtime_data *eee, const n2n_mac_t dst_mac); void supernode_connect (struct n3n_runtime_data *eee); +/* TCP MSS clamping */ +uint16_t tcp_csum_update (uint16_t old_csum, uint16_t old_val, uint16_t new_val); +void clamp_mss (struct n3n_runtime_data *eee, uint8_t *tap_pkt, size_t len); + #endif diff --git a/src/conffile_defs.c b/src/conffile_defs.c index 15f2c0fc..0a65c328 100644 --- a/src/conffile_defs.c +++ b/src/conffile_defs.c @@ -463,6 +463,15 @@ static struct n3n_conf_option section_tuntap[] = { "address is set by the edge and an external process is " "expected to set it.", }, + { + .name = "clamp_mss", + .type = n3n_conf_bool, + .offset = offsetof(n2n_edge_conf_t, clamp_mss), + .desc = "Clamp TCP MSS to MTU", + .help = "When enabled, the MSS value in TCP SYN packets is adjusted " + "to prevent fragmentation issues when the underlying MTU is " + "smaller than the standard 1500 bytes.", + }, { .name = "macaddr", .type = n3n_conf_strncpy, diff --git a/src/edge_utils.c b/src/edge_utils.c index fdb0257d..241eaea1 100644 --- a/src/edge_utils.c +++ b/src/edge_utils.c @@ -2056,6 +2056,11 @@ static int handle_PACKET (struct n3n_runtime_data * eee, } } + /* Clamp TCP MSS on incoming VPN packets before writing to TAP. */ + if(eee->conf.clamp_mss) { + clamp_mss(eee, eth_payload, eth_size); + } + /* Write ethernet packet to tap device. */ traceEvent(TRACE_DEBUG, "sending data of size %u to TAP", (unsigned int)eth_size); data_sent_len = tuntap_write(&(eee->device), eth_payload, eth_size); @@ -2193,6 +2198,188 @@ static int find_peer_destination (struct n3n_runtime_data * eee, /* ***************************************************** */ +/** + * Incremental TCP checksum update (RFC 1624). + * + * When a 16-bit field in the TCP header is modified, the checksum can be + * updated incrementally rather than recalculated from scratch: + * HC' = ~(~HC + ~m + m') + * + * All values are in host byte order. + */ +uint16_t tcp_csum_update (uint16_t old_csum, uint16_t old_val, uint16_t new_val) { + uint32_t sum = (uint32_t)(~old_csum & 0xFFFF) + + (uint32_t)(~old_val & 0xFFFF) + + (uint32_t) new_val; + sum = (sum & 0xFFFF) + (sum >> 16); + sum += sum >> 16; + return (uint16_t)(~sum & 0xFFFF); +} + +/** + * Clamp the TCP MSS value in SYN/SYN-ACK packets to prevent fragmentation. + * Supports IPv4/IPv6, VLAN/QinQ, IP-in-IP, and IPv6 extension headers. + * Uses byte-shift reads for alignment safety on ARM/MIPS. + * Inspired by tinc's clamp_mss; expanded with full protocol support. + * TCP checksum update follows RFC 1624 incremental one's-complement method. + */ +void clamp_mss (struct n3n_runtime_data *eee, uint8_t *tap_pkt, size_t len) { + + if(eee->conf.mtu <= 0) { + return; + } + + uint16_t mtu = eee->conf.mtu; + + /* ---- Ethernet ---- */ + if(len < sizeof(ether_hdr_t)) { + return; + } + + size_t offset = sizeof(ether_hdr_t); + uint16_t ethertype = (tap_pkt[12] << 8) | tap_pkt[13]; + + /* Skip stacked VLAN tags (802.1Q / 802.1ad QinQ) */ + while(ethertype == 0x8100 || ethertype == 0x88A8 || ethertype == 0x9100) { + if(len < offset + 4) { + return; + } + ethertype = (tap_pkt[offset + 2] << 8) | tap_pkt[offset + 3]; + offset += 4; + } + + /* ---- IP ---- */ + uint8_t ip_proto = 0; + + if(ethertype == 0x0800) { + /* --- IPv4 --- */ + if(len < offset + 20 || (tap_pkt[offset] >> 4) != 4) { + return; + } + + /* Check for fragmentation: offset != 0 OR More Fragments flag set */ + if(((tap_pkt[offset + 6] << 8) | tap_pkt[offset + 7]) & 0x3FFF) { + return; + } + + uint16_t ihl_bytes = (tap_pkt[offset] & 0x0F) * 4; + ip_proto = tap_pkt[offset + 9]; + + /* Support for IP-in-IP encapsulation (RFC 2003) */ + if(ip_proto == 4) { + offset += ihl_bytes; + if(len < offset + 20 || (tap_pkt[offset] >> 4) != 4) { + return; + } + if(((tap_pkt[offset + 6] << 8) | tap_pkt[offset + 7]) & 0x3FFF) { + return; + } + ihl_bytes = (tap_pkt[offset] & 0x0F) * 4; + ip_proto = tap_pkt[offset + 9]; + } + offset += ihl_bytes; + + } else if(ethertype == 0x86DD) { + /* --- IPv6 --- */ + if(len < offset + 40 || (tap_pkt[offset] >> 4) != 6) { + return; + } + uint8_t next_hdr = tap_pkt[offset + 6]; + offset += 40; + + /* Walk through Extension Headers */ + while(next_hdr != IPPROTO_TCP && next_hdr != IPPROTO_NONE) { + if(len < offset + 2) { + return; + } + if(next_hdr == 0 || next_hdr == 43 || next_hdr == 60) { + uint16_t ext_len = (tap_pkt[offset + 1] + 1) * 8; + if(len < offset + ext_len) { + return; + } + next_hdr = tap_pkt[offset]; + offset += ext_len; + } else if(next_hdr == 44 || next_hdr == 51) { + /* Abort on Fragment (44) or Auth Header (51) */ + return; + } else { + break; + } + } + ip_proto = next_hdr; + } else { + return; /* not IP */ + } + + /* ---- TCP ---- */ + if(ip_proto != IPPROTO_TCP || len < offset + 20) { + return; + } + + /* Only process SYN or SYN-ACK packets (Flag bit 2) */ + if(!(tap_pkt[offset + 13] & 0x02)) { + return; + } + + uint8_t tcp_doff = tap_pkt[offset + 12] >> 4; + size_t tcp_hdr_len = tcp_doff * 4; + if(tcp_doff < 5 || len < offset + tcp_hdr_len) { + return; + } + + /* Calculate dynamic Max MSS based on total IP overhead found */ + size_t ip_overhead = offset - 14; + if(mtu <= (ip_overhead + 20)) { + return; + } + uint16_t max_mss = mtu - (uint16_t)ip_overhead - 20; + if(max_mss == 0) { + return; + } + + /* ---- Walk TCP options for MSS (kind=2, len=4) ---- */ + uint8_t *opts = tap_pkt + offset + 20; + size_t opts_len = tcp_hdr_len - 20; + + for(size_t i = 0; i < opts_len; ) { + uint8_t kind = opts[i]; + + if(kind == 0) break; /* End of Options */ + if(kind == 1) { + i++; continue; + } /* NOP */ + + if(i + 1 >= opts_len) break; + uint8_t olen = opts[i + 1]; + if(olen < 2 || i + olen > opts_len) break; + + if(kind == 2 && olen == 4) { + uint16_t old_mss = (opts[i + 2] << 8) | opts[i + 3]; + if(old_mss > max_mss) { + uint16_t new_mss = max_mss; + + traceEvent(TRACE_INFO, "Clamping MSS from %u to %u", old_mss, new_mss); + + /* Update MSS value in place using byte-writes for alignment safety */ + opts[i + 2] = (uint8_t)(new_mss >> 8); + opts[i + 3] = (uint8_t)(new_mss & 0xFF); + + /* Recalculate TCP Checksum incrementally */ + uint16_t old_cs = (tap_pkt[offset + 16] << 8) | tap_pkt[offset + 17]; + uint16_t new_cs = tcp_csum_update(old_cs, old_mss, new_mss); + + tap_pkt[offset + 16] = (uint8_t)(new_cs >> 8); + tap_pkt[offset + 17] = (uint8_t)(new_cs & 0xFF); + } + break; + } + + i += olen; + } +} + +/* ***************************************************** */ + /** Send an ecapsulated ethernet PACKET to a destination edge or broadcast MAC * address. */ static int send_packet (struct n3n_runtime_data * eee, @@ -2250,6 +2437,11 @@ size_t edge_encode_packet (struct n3n_runtime_data *eee, uint8_t *pktbuf, size_t pktbuf_size, n2n_mac_t out_destMac) { + /* Clamp TCP MSS if enabled */ + if(eee->conf.clamp_mss) { + clamp_mss(eee, tap_pkt, len); + } + ipstr_t ip_buf; n2n_common_t cmn; n2n_PACKET_t pkt; diff --git a/tests/test_builtin_edge.sh.expected b/tests/test_builtin_edge.sh.expected index 07901fde..10819629 100644 --- a/tests/test_builtin_edge.sh.expected +++ b/tests/test_builtin_edge.sh.expected @@ -52,6 +52,7 @@ output_format=pretty [tuntap] address=0.0.0.0/0 address_mode=auto +clamp_mss=false metric=0 mtu=0 diff --git a/tests/tests-mss.expected b/tests/tests-mss.expected new file mode 100644 index 00000000..7e77c574 --- /dev/null +++ b/tests/tests-mss.expected @@ -0,0 +1,33 @@ +PASS: incremental matches full recalculation +PASS: carry wrapping correct +PASS: MSS clamped 1500 -> 1460 +PASS: new checksum valid (0x02d8) +PASS: non-SYN packet unchanged +PASS: MSS=1000 not clamped (<= 1460) +PASS: clamp_mss=0 ignored by function (check at caller level) +PASS: IPv6 MSS clamped 1500 -> 1440 +PASS: VLAN-tagged MSS clamped 1500 -> 1456 +PASS: QinQ-tagged MSS clamped 1500 -> 1452 +PASS: IP-in-IP MSS clamped 1500 -> 1440 +PASS: fragmented packets skipped +PASS: IPv6 fragment extension header skips clamp +PASS: TAP-style MSS 1500->1460, checksum valid +PASS: UDP-edge-style MSS 1500->1460, checksum valid +PASS: mtu=0 skips clamping +PASS: custom MTU=1400, MSS clamped to 1360 +PASS: SYN-ACK also clamped 1500 -> 1460 +PASS: short packet unchanged (safe early return) +PASS: non-IP ethertype (ARP) skipped +PASS: IHL=6 IP options, MSS clamped to 1456 +PASS: TCP with no options handled without crash +PASS: 0x9100 VLAN MSS clamped 1500 -> 1456 +PASS: IPv6 Hop-by-Hop ext, MSS clamped to 1432 +PASS: IPv6 Auth Header (51) skips clamp +PASS: IPv6 Routing ext, MSS clamped to 1432 +PASS: IPv6 Destination Options ext, MSS clamped to 1432 +PASS: first MSS option processed, packet valid +PASS: MSS=1460 (== max_mss) not modified +PASS: csum_update identity (same value) returns original +PASS: malformed TCP options handled without crash +PASS: MSS=0 left unchanged (not > max_mss) +=== All MSS tests complete === diff --git a/tools/Makefile b/tools/Makefile index aad6970f..2cf661f3 100644 --- a/tools/Makefile +++ b/tools/Makefile @@ -18,6 +18,7 @@ TESTS+=tests-elliptic TESTS+=tests-transform TESTS+=tests-wire TESTS+=tests-auth +TESTS+=tests-mss .PHONY: all clean install all: $(TOOLS) $(TESTS) diff --git a/tools/tests-mss.c b/tools/tests-mss.c new file mode 100644 index 00000000..1383510f --- /dev/null +++ b/tools/tests-mss.c @@ -0,0 +1,1389 @@ +/* + * Copyright (C) catoc + * SPDX-License-Identifier: GPL-3.0-only + * + * Unit tests for TCP MSS clamping (clamp_mss / tcp_csum_update) + */ + +#ifdef _WIN32 +#include +#else +#include +#include +#endif + +#include +#include +#include +#include +#include + +#include "n2n.h" +#include "n2n_typedefs.h" +#include "n2n_define.h" +#include "n3n/edge.h" + +#define ETH_HDR_SIZE 14 +#define ETH_P_IP 0x0800 +#define ETH_P_IPV6 0x86DD +#define ETH_P_8021Q 0x8100 +#define ETH_P_8021AD 0x88A8 +#define ETH_P_8021Q_9100 0x9100 +#define ETH_P_ARP 0x0806 + +#define TCP_PORT_SYN 50000 +#define TCP_PORT_HTTP 80 +#define TCP_PORT_TEST 12345 + +#define IP4_HDR_SIZE 20 +#define IP6_HDR_SIZE 40 +#define TCP_HDR_BASE 20 +#define TCP_HDR_WITH_OPTS 24 +#define TCP_OPT_MSS 2 +#define TCP_OPT_MSS_LEN 4 +#define TCP_OPT_NOP 1 +#define TCP_OPT_EOL 0 + +#define TCP_FLAG_SYN 0x02 +#define TCP_FLAG_ACK 0x10 +#define TCP_FLAG_SYNACK (TCP_FLAG_SYN | TCP_FLAG_ACK) + +/* ---- Full TCP checksum using raw byte arithmetic (no endianness issues) ---- */ + +static void sum_word (uint32_t *sum, const uint8_t *p) { + *sum += (uint32_t)p[0] << 8 | p[1]; +} + +static uint16_t full_tcp_checksum_ipv4 (const uint8_t *ip, uint8_t *tcp, size_t tcp_len) { + uint32_t sum = 0; + + /* Pseudo-header: src IP (4) + dst IP (4) + proto+len (2) */ + sum_word(&sum, ip + 12); + sum_word(&sum, ip + 14); + sum_word(&sum, ip + 16); + sum_word(&sum, ip + 18); + sum += (uint32_t)ip[9] << 8 | (tcp_len >> 8 & 0xff); + sum += tcp_len & 0xff; + + /* TCP header + options, checksum field zeroed */ + uint8_t tmp[2]; + memcpy(tmp, tcp + 16, 2); + ((uint8_t *)tcp)[16] = 0; + ((uint8_t *)tcp)[17] = 0; + + for(size_t i = 0; i + 1 < tcp_len; i += 2) + sum_word(&sum, tcp + i); + if(tcp_len & 1) + sum += (uint32_t)tcp[tcp_len - 1] << 8; + + ((uint8_t *)tcp)[16] = tmp[0]; + ((uint8_t *)tcp)[17] = tmp[1]; + + /* Fold + negate */ + sum = (sum >> 16) + (sum & 0xffff); + sum += sum >> 16; + return (uint16_t)(~sum & 0xffff); +} + +/* ---- Build IPv4 TCP SYN packet with MSS option, correct checksum ---- */ + +static size_t build_ipv4_tcp_syn (uint8_t *buf, size_t buf_size, uint16_t mss) { + if(buf_size < ETH_HDR_SIZE + IP4_HDR_SIZE + TCP_HDR_WITH_OPTS) return 0; + memset(buf, 0, buf_size); + + /* Ethernet */ + buf[12] = ETH_P_IP >> 8; buf[13] = ETH_P_IP & 0xff; + + /* IPv4: ver=4, ihl=5, proto=TCP, ttl=64 */ + uint8_t *ip = buf + ETH_HDR_SIZE; + ip[0] = 0x45; ip[8] = 64; ip[9] = IPPROTO_TCP; + ip[2] = 0; ip[3] = IP4_HDR_SIZE + TCP_HDR_WITH_OPTS; /* total len */ + ip[12] = 192; ip[13] = 168; ip[14] = 1; ip[15] = 10; + ip[16] = 10; ip[17] = 0; ip[18] = 0; ip[19] = 1; + + /* TCP: src=50000, dst=80, SYN, doff=6 (24 bytes), win=65535 */ + uint8_t *tcp = ip + IP4_HDR_SIZE; + uint16_t src_port = htons(TCP_PORT_SYN); + uint16_t dst_port = htons(TCP_PORT_HTTP); + memcpy(tcp, &src_port, 2); + memcpy(tcp + 2, &dst_port, 2); + tcp[7] = 0x01; /* seq = 1 */ + tcp[12] = 0x60; /* data offset = 6 */ + tcp[13] = TCP_FLAG_SYN; + tcp[14] = 0xff; tcp[15] = 0xff; /* window */ + /* checksum [16-17] left zero */ + + /* TCP options: MSS */ + tcp[20] = TCP_OPT_MSS; tcp[21] = TCP_OPT_MSS_LEN; + tcp[22] = mss >> 8; tcp[23] = mss & 0xff; + tcp[24] = TCP_OPT_NOP; + + /* Compute and write checksum (network byte order) */ + uint16_t csum = full_tcp_checksum_ipv4(ip, tcp, TCP_HDR_WITH_OPTS); + tcp[16] = csum >> 8; + tcp[17] = csum & 0xff; + + return ETH_HDR_SIZE + IP4_HDR_SIZE + TCP_HDR_WITH_OPTS; +} + +/* ---- Read MSS from packet (network byte order value) ---- */ +static uint16_t get_mss (const uint8_t *tcp) { + return (uint16_t)tcp[22] << 8 | tcp[23]; +} +static uint16_t get_csum (const uint8_t *tcp) { + return (uint16_t)tcp[16] << 8 | tcp[17]; +} +static void set_mss (uint8_t *tcp, uint16_t mss) { + tcp[22] = mss >> 8; tcp[23] = mss & 0xff; +} + +/* ============================================================ */ + +static void test_tcp_csum_update (void) { + fprintf(stderr, "=== test_tcp_csum_update ===\n"); + + /* Build a real packet with valid checksum, then verify incremental update */ + uint8_t pkt[256]; + uint16_t old_mss = 1460; + uint16_t new_mss = 1250; + size_t len = build_ipv4_tcp_syn(pkt, sizeof(pkt), old_mss); + uint8_t *tcp = pkt + ETH_HDR_SIZE + IP4_HDR_SIZE; + + (void)len; /* build used only to create packet with correct checksum */ + + uint16_t old_csum = get_csum(tcp); + fprintf(stderr, "old_csum (from packet) = 0x%04x\n", old_csum); + fprintf(stderr, "old_mss = %u\n", old_mss); + fprintf(stderr, "new_mss = %u\n", new_mss); + + /* Incremental update */ + uint16_t new_csum = tcp_csum_update(old_csum, old_mss, new_mss); + fprintf(stderr, "incremental result = 0x%04x\n", new_csum); + + /* Verify: set new MSS in packet and compute full checksum */ + set_mss(tcp, new_mss); + uint16_t expected = full_tcp_checksum_ipv4(pkt + ETH_HDR_SIZE, tcp, TCP_HDR_WITH_OPTS); + set_mss(tcp, old_mss); /* restore */ + + fprintf(stderr, "full recompute = 0x%04x\n", expected); + + if(new_csum == expected) { + printf("PASS: incremental matches full recalculation\n"); + } else { + printf("FAIL: 0x%04x != 0x%04x\n", new_csum, expected); + } + + /* Edge case: carry wrapping with values that actually wrap */ + old_csum = 0xFFFE; + old_mss = 1; + new_mss = 2; + new_csum = tcp_csum_update(old_csum, old_mss, new_mss); + + /* Verify with math: ~(~0xFFFE + ~1 + 2) = ~(1 + 0xFFFE + 2) = ~(0x10001) = ~(1) = 0xFFFE */ + uint32_t s = (~old_csum & 0xffff) + (~old_mss & 0xffff) + new_mss; + s = (s & 0xffff) + (s >> 16); + uint16_t math_expected = (~s) & 0xffff; + fprintf(stderr, "\ncarry: old_csum=0xFFFE, mss 1->2, result=0x%04x, expect=0x%04x\n", + new_csum, math_expected); + + if(new_csum == math_expected) + printf("PASS: carry wrapping correct\n"); + else + printf("FAIL: 0x%04x != 0x%04x\n", new_csum, math_expected); +} + +/* ============================================================ */ + +static void test_clamp_ipv4_syn (void) { + fprintf(stderr, "=== test_clamp_ipv4_syn ===\n"); + + uint8_t pkt[1500]; + size_t len = build_ipv4_tcp_syn(pkt, sizeof(pkt), 1500); + uint8_t *tcp = pkt + ETH_HDR_SIZE + IP4_HDR_SIZE; + + fprintf(stderr, "Packet before clamp (%zu bytes)\n", len); + + uint16_t old_mss = get_mss(tcp); + uint16_t old_cksum = get_csum(tcp); + fprintf(stderr, "Original MSS = %u, checksum = 0x%04x\n", old_mss, old_cksum); + + /* Verify original checksum */ + uint16_t v = full_tcp_checksum_ipv4(pkt + ETH_HDR_SIZE, tcp, TCP_HDR_WITH_OPTS); + fprintf(stderr, "Original checksum valid: %s\n", old_cksum == v ? "YES" : "NO"); + + /* Clamp */ + struct n3n_runtime_data eee; + memset(&eee, 0, sizeof(eee)); + eee.conf.clamp_mss = 1; + eee.conf.mtu = 1500; + + clamp_mss(&eee, pkt, len); + + uint16_t new_mss = get_mss(tcp); + uint16_t new_cksum = get_csum(tcp); + fprintf(stderr, "After clamp: MSS = %u, checksum = 0x%04x\n", new_mss, new_cksum); + + if(new_mss == 1460) + printf("PASS: MSS clamped 1500 -> 1460\n"); + else + printf("FAIL: expected MSS=1460, got %u\n", new_mss); + + v = full_tcp_checksum_ipv4(pkt + ETH_HDR_SIZE, tcp, TCP_HDR_WITH_OPTS); + if(new_cksum == v) + printf("PASS: new checksum valid (0x%04x)\n", v); + else + printf("FAIL: checksum mismatch got=0x%04x expect=0x%04x\n", new_cksum, v); + + fprintf(stderr, "Packet after clamp (%zu bytes)\n", len); +} + +/* ============================================================ */ + +static void test_no_clamp_non_syn (void) { + fprintf(stderr, "=== test_no_clamp_non_syn ===\n"); + uint8_t pkt[1500]; + size_t len = build_ipv4_tcp_syn(pkt, sizeof(pkt), 1500); + uint8_t *tcp = pkt + ETH_HDR_SIZE + IP4_HDR_SIZE; + tcp[13] = TCP_FLAG_ACK; /* ACK (not SYN) */ + + uint16_t mss0 = get_mss(tcp), csum0 = get_csum(tcp); + + struct n3n_runtime_data eee; + memset(&eee, 0, sizeof(eee)); + eee.conf.clamp_mss = 1; + eee.conf.mtu = 1500; + clamp_mss(&eee, pkt, len); + + if(get_mss(tcp) == mss0 && get_csum(tcp) == csum0) + printf("PASS: non-SYN packet unchanged\n"); + else + printf("FAIL: non-SYN packet modified\n"); +} + +/* ============================================================ */ + +static void test_no_clamp_when_small (void) { + fprintf(stderr, "=== test_no_clamp_when_small ===\n"); + uint8_t pkt[1500]; + size_t len = build_ipv4_tcp_syn(pkt, sizeof(pkt), 1000); + uint8_t *tcp = pkt + ETH_HDR_SIZE + IP4_HDR_SIZE; + uint16_t mss0 = get_mss(tcp); + + struct n3n_runtime_data eee; + memset(&eee, 0, sizeof(eee)); + eee.conf.clamp_mss = 1; + eee.conf.mtu = 1500; + clamp_mss(&eee, pkt, len); + + if(get_mss(tcp) == mss0) + printf("PASS: MSS=%u not clamped (<= 1460)\n", mss0); + else + printf("FAIL: MSS changed %u -> %u\n", mss0, get_mss(tcp)); +} + +/* ============================================================ */ + +static void test_clamp_disabled (void) { + fprintf(stderr, "=== test_clamp_disabled ===\n"); + uint8_t pkt[1500]; + size_t len = build_ipv4_tcp_syn(pkt, sizeof(pkt), 1500); + uint8_t *tcp = pkt + ETH_HDR_SIZE + IP4_HDR_SIZE; + uint16_t mss0 = get_mss(tcp); + + /* clamp_mss config check moved to caller (PR review feedback). + * This test verifies the function NO LONGER checks clamp_mss internally. + * If called directly, MSS gets clamped regardless of clamp_mss=0. */ + struct n3n_runtime_data eee; + memset(&eee, 0, sizeof(eee)); + eee.conf.clamp_mss = 0; + eee.conf.mtu = 1500; + + /* Call directly to prove function doesn't check clamp_mss */ + clamp_mss(&eee, pkt, len); + + uint16_t new_mss = get_mss(tcp); + if(new_mss != mss0) { + /* Expected: MSS was clamped, proving no internal check */ + printf("PASS: clamp_mss=0 ignored by function (check at caller level)\n"); + } else { + printf("FAIL: clamp_mss=0 still checked inside function\n"); + } +} + +/* ============================================================ */ + +/* ---- Build IPv6 TCP SYN packet with MSS option ---- */ +static size_t build_ipv6_tcp_syn (uint8_t *buf, size_t buf_size, uint16_t mss) { + if(buf_size < ETH_HDR_SIZE + IP6_HDR_SIZE + TCP_HDR_WITH_OPTS) return 0; + memset(buf, 0, buf_size); + + /* Ethernet */ + buf[12] = ETH_P_IPV6 >> 8; buf[13] = ETH_P_IPV6 & 0xff; + + /* IPv6: ver=6, Next Header=TCP, Payload Len=24, Hop Limit=64 */ + uint8_t *ip = buf + ETH_HDR_SIZE; + ip[0] = 0x60; ip[6] = IPPROTO_TCP; ip[7] = TCP_HDR_WITH_OPTS; + /* src: fd00::1, dst: fd00::2 */ + ip[8] = 0xfd; ip[9] = 0x00; ip[38] = 0x00; ip[39] = 0x01; + ip[40] = 0xfd; ip[41] = 0x00; ip[54] = 0x00; ip[55] = 0x02; + + /* TCP: src=50000, dst=80, SYN, doff=6 (24 bytes), win=65535 */ + uint8_t *tcp = ip + IP6_HDR_SIZE; + uint16_t src_port = htons(TCP_PORT_SYN); + uint16_t dst_port = htons(TCP_PORT_HTTP); + memcpy(tcp, &src_port, 2); + memcpy(tcp + 2, &dst_port, 2); + tcp[7] = 0x01; + tcp[12] = 0x60; + tcp[13] = TCP_FLAG_SYN; + tcp[14] = 0xff; tcp[15] = 0xff; + tcp[20] = TCP_OPT_MSS; tcp[21] = TCP_OPT_MSS_LEN; + tcp[22] = mss >> 8; tcp[23] = mss & 0xff; + + return ETH_HDR_SIZE + IP6_HDR_SIZE + TCP_HDR_WITH_OPTS; +} + +/* ---- Build IPv4 TCP SYN with 802.1Q VLAN tag ---- */ +static size_t build_vlan_ipv4_tcp_syn (uint8_t *buf, size_t buf_size, uint16_t mss) { + if(buf_size < ETH_HDR_SIZE + 4 + IP4_HDR_SIZE + TCP_HDR_WITH_OPTS) return 0; + memset(buf, 0, buf_size); + + /* Ethernet: EtherType = 0x8100 */ + buf[12] = ETH_P_8021Q >> 8; buf[13] = ETH_P_8021Q & 0xff; + /* 802.1Q: PCP=0, CFI=0, VID=100 */ + buf[14] = 0x00; buf[15] = 0x64; + /* Inner EtherType = 0x0800 */ + buf[16] = ETH_P_IP >> 8; buf[17] = ETH_P_IP & 0xff; + + uint8_t *ip = buf + ETH_HDR_SIZE + 4; + ip[0] = 0x45; ip[8] = 64; ip[9] = IPPROTO_TCP; + ip[2] = 0; ip[3] = IP4_HDR_SIZE + TCP_HDR_WITH_OPTS; + ip[12] = 192; ip[13] = 168; ip[14] = 1; ip[15] = 10; + ip[16] = 10; ip[17] = 0; ip[18] = 0; ip[19] = 1; + + uint8_t *tcp = ip + IP4_HDR_SIZE; + uint16_t src_port = htons(TCP_PORT_SYN); + uint16_t dst_port = htons(TCP_PORT_HTTP); + memcpy(tcp, &src_port, 2); + memcpy(tcp + 2, &dst_port, 2); + tcp[7] = 0x01; + tcp[12] = 0x60; tcp[13] = TCP_FLAG_SYN; + tcp[14] = 0xff; tcp[15] = 0xff; + tcp[20] = TCP_OPT_MSS; tcp[21] = TCP_OPT_MSS_LEN; + tcp[22] = mss >> 8; tcp[23] = mss & 0xff; + + uint16_t csum = full_tcp_checksum_ipv4(ip, tcp, TCP_HDR_WITH_OPTS); + tcp[16] = csum >> 8; tcp[17] = csum & 0xff; + + return ETH_HDR_SIZE + 4 + IP4_HDR_SIZE + TCP_HDR_WITH_OPTS; +} + +/* ---- Build IPv4 TCP SYN with QinQ (double VLAN) ---- */ +static size_t build_qinq_ipv4_tcp_syn (uint8_t *buf, size_t buf_size, uint16_t mss) { + if(buf_size < ETH_HDR_SIZE + 4 + 4 + IP4_HDR_SIZE + TCP_HDR_WITH_OPTS) return 0; + memset(buf, 0, buf_size); + + /* Outer EtherType = 0x88A8 (802.1ad) */ + buf[12] = ETH_P_8021AD >> 8; buf[13] = ETH_P_8021AD & 0xff; + buf[14] = 0x00; buf[15] = 0x10; + /* Inner EtherType = 0x8100 */ + buf[16] = ETH_P_8021Q >> 8; buf[17] = ETH_P_8021Q & 0xff; + buf[18] = 0x00; buf[19] = 0x64; + /* EtherType = 0x0800 */ + buf[20] = ETH_P_IP >> 8; buf[21] = ETH_P_IP & 0xff; + + uint8_t *ip = buf + ETH_HDR_SIZE + 8; + ip[0] = 0x45; ip[8] = 64; ip[9] = IPPROTO_TCP; + ip[2] = 0; ip[3] = IP4_HDR_SIZE + TCP_HDR_WITH_OPTS; + ip[12] = 10; ip[13] = 0; ip[14] = 0; ip[15] = 1; + ip[16] = 10; ip[17] = 0; ip[18] = 0; ip[19] = 2; + + uint8_t *tcp = ip + IP4_HDR_SIZE; + uint16_t src_port = htons(TCP_PORT_SYN); + uint16_t dst_port = htons(TCP_PORT_HTTP); + memcpy(tcp, &src_port, 2); + memcpy(tcp + 2, &dst_port, 2); + tcp[7] = 0x01; + tcp[12] = 0x60; tcp[13] = TCP_FLAG_SYN; + tcp[14] = 0xff; tcp[15] = 0xff; + tcp[20] = TCP_OPT_MSS; tcp[21] = TCP_OPT_MSS_LEN; + tcp[22] = mss >> 8; tcp[23] = mss & 0xff; + + uint16_t csum = full_tcp_checksum_ipv4(ip, tcp, TCP_HDR_WITH_OPTS); + tcp[16] = csum >> 8; tcp[17] = csum & 0xff; + + return ETH_HDR_SIZE + 8 + IP4_HDR_SIZE + TCP_HDR_WITH_OPTS; +} + +/* ---- Build IP-in-IP (RFC 2003) IPv4 TCP SYN ---- */ +static size_t build_ipip_ipv4_tcp_syn (uint8_t *buf, size_t buf_size, uint16_t mss) { + if(buf_size < ETH_HDR_SIZE + IP4_HDR_SIZE + IP4_HDR_SIZE + TCP_HDR_WITH_OPTS) return 0; + memset(buf, 0, buf_size); + + /* Ethernet */ + buf[12] = ETH_P_IP >> 8; buf[13] = ETH_P_IP & 0xff; + + /* Outer IPv4: proto=4 (IP-in-IP), IHL=5 */ + uint8_t *outer_ip = buf + ETH_HDR_SIZE; + outer_ip[0] = 0x45; outer_ip[8] = 64; outer_ip[9] = 4; /* proto=4 */ + outer_ip[2] = 0; outer_ip[3] = IP4_HDR_SIZE + IP4_HDR_SIZE + TCP_HDR_WITH_OPTS; + outer_ip[12] = 1; outer_ip[13] = 1; outer_ip[14] = 1; outer_ip[15] = 1; + outer_ip[16] = 2; outer_ip[17] = 2; outer_ip[18] = 2; outer_ip[19] = 2; + + /* Inner IPv4 */ + uint8_t *inner_ip = outer_ip + IP4_HDR_SIZE; + inner_ip[0] = 0x45; inner_ip[8] = 64; inner_ip[9] = IPPROTO_TCP; + inner_ip[2] = 0; inner_ip[3] = IP4_HDR_SIZE + TCP_HDR_WITH_OPTS; + inner_ip[12] = 10; inner_ip[13] = 0; inner_ip[14] = 0; inner_ip[15] = 1; + inner_ip[16] = 10; inner_ip[17] = 0; inner_ip[18] = 0; inner_ip[19] = 2; + + /* TCP */ + uint8_t *tcp = inner_ip + IP4_HDR_SIZE; + uint16_t src_port = htons(TCP_PORT_SYN); + uint16_t dst_port = htons(TCP_PORT_HTTP); + memcpy(tcp, &src_port, 2); + memcpy(tcp + 2, &dst_port, 2); + tcp[7] = 0x01; + tcp[12] = 0x60; tcp[13] = TCP_FLAG_SYN; + tcp[14] = 0xff; tcp[15] = 0xff; + tcp[20] = TCP_OPT_MSS; tcp[21] = TCP_OPT_MSS_LEN; + tcp[22] = mss >> 8; tcp[23] = mss & 0xff; + + uint16_t csum = full_tcp_checksum_ipv4(inner_ip, tcp, TCP_HDR_WITH_OPTS); + tcp[16] = csum >> 8; tcp[17] = csum & 0xff; + + return ETH_HDR_SIZE + IP4_HDR_SIZE + IP4_HDR_SIZE + TCP_HDR_WITH_OPTS; +} + +/* ============================================================ */ + +static void test_clamp_ipv6_syn (void) { + fprintf(stderr, "=== test_clamp_ipv6_syn ===\n"); + + uint8_t pkt[1500]; + size_t len = build_ipv6_tcp_syn(pkt, sizeof(pkt), 1500); + uint8_t *tcp = pkt + ETH_HDR_SIZE + IP6_HDR_SIZE; + + uint16_t old_mss = get_mss(tcp); + fprintf(stderr, "Original IPv6 MSS = %u\n", old_mss); + + struct n3n_runtime_data eee; + memset(&eee, 0, sizeof(eee)); + eee.conf.clamp_mss = 1; + eee.conf.mtu = 1500; + clamp_mss(&eee, pkt, len); + + uint16_t new_mss = get_mss(tcp); + /* IPv6: max_mss = 1500 - 40 - 20 = 1440 */ + if(new_mss == 1440) + printf("PASS: IPv6 MSS clamped 1500 -> 1440\n"); + else + printf("FAIL: expected 1440, got %u\n", new_mss); +} + +static void test_clamp_vlan_syn (void) { + fprintf(stderr, "=== test_clamp_vlan_syn ===\n"); + + uint8_t pkt[1500]; + size_t len = build_vlan_ipv4_tcp_syn(pkt, sizeof(pkt), 1500); + uint8_t *tcp = pkt + ETH_HDR_SIZE + 4 + IP4_HDR_SIZE; + + uint16_t old_mss = get_mss(tcp); + fprintf(stderr, "Original VLAN-tagged MSS = %u\n", old_mss); + + struct n3n_runtime_data eee; + memset(&eee, 0, sizeof(eee)); + eee.conf.clamp_mss = 1; + eee.conf.mtu = 1500; + clamp_mss(&eee, pkt, len); + + uint16_t new_mss = get_mss(tcp); + /* VLAN: max_mss = 1500 - 24 - 20 = 1456 */ + if(new_mss == 1456) + printf("PASS: VLAN-tagged MSS clamped 1500 -> 1456\n"); + else + printf("FAIL: expected 1456, got %u\n", new_mss); +} + +static void test_clamp_qinq_syn (void) { + fprintf(stderr, "=== test_clamp_qinq_syn ===\n"); + + uint8_t pkt[1500]; + size_t len = build_qinq_ipv4_tcp_syn(pkt, sizeof(pkt), 1500); + uint8_t *tcp = pkt + ETH_HDR_SIZE + 8 + IP4_HDR_SIZE; + + uint16_t old_mss = get_mss(tcp); + fprintf(stderr, "Original QinQ-tagged MSS = %u\n", old_mss); + + struct n3n_runtime_data eee; + memset(&eee, 0, sizeof(eee)); + eee.conf.clamp_mss = 1; + eee.conf.mtu = 1500; + clamp_mss(&eee, pkt, len); + + uint16_t new_mss = get_mss(tcp); + /* QinQ: max_mss = 1500 - 28 - 20 = 1452 */ + if(new_mss == 1452) + printf("PASS: QinQ-tagged MSS clamped 1500 -> 1452\n"); + else + printf("FAIL: expected 1452, got %u\n", new_mss); +} + +static void test_clamp_ipip_syn (void) { + fprintf(stderr, "=== test_clamp_ipip_syn ===\n"); + + uint8_t pkt[1500]; + size_t len = build_ipip_ipv4_tcp_syn(pkt, sizeof(pkt), 1500); + uint8_t *tcp = pkt + ETH_HDR_SIZE + IP4_HDR_SIZE + IP4_HDR_SIZE; + + uint16_t old_mss = get_mss(tcp); + fprintf(stderr, "Original IP-in-IP MSS = %u\n", old_mss); + + struct n3n_runtime_data eee; + memset(&eee, 0, sizeof(eee)); + eee.conf.clamp_mss = 1; + eee.conf.mtu = 1500; + clamp_mss(&eee, pkt, len); + + uint16_t new_mss = get_mss(tcp); + /* IP-in-IP: max_mss = 1500 - 40 - 20 = 1440 */ + if(new_mss == 1440) + printf("PASS: IP-in-IP MSS clamped 1500 -> 1440\n"); + else + printf("FAIL: expected 1440, got %u\n", new_mss); +} + +static void test_skip_fragmented (void) { + fprintf(stderr, "=== test_skip_fragmented ===\n"); + + uint8_t pkt[1500]; + size_t len = build_ipv4_tcp_syn(pkt, sizeof(pkt), 1500); + uint8_t *ip = pkt + ETH_HDR_SIZE; + uint8_t *tcp = ip + IP4_HDR_SIZE; + + struct n3n_runtime_data eee; + memset(&eee, 0, sizeof(eee)); + eee.conf.clamp_mss = 1; + eee.conf.mtu = 1500; + + /* Test MF flag set */ + ip[6] = 0x00; ip[7] = 0x20; /* MF=1, offset=0 */ + uint16_t mss_mf = get_mss(tcp); + clamp_mss(&eee, pkt, len); + bool mf_ok = (get_mss(tcp) == mss_mf); + + /* Test Fragment Offset > 0 */ + ip[6] = 0x00; ip[7] = 0x08; /* MF=0, offset=1 (8 bytes) */ + uint16_t mss_off = get_mss(tcp); + clamp_mss(&eee, pkt, len); + bool off_ok = (get_mss(tcp) == mss_off); + + if(mf_ok && off_ok) + printf("PASS: fragmented packets skipped\n"); + else + printf("FAIL: fragmented packet was modified (MF=%d, Off=%d)\n", !mf_ok, !off_ok); +} + +static void test_skip_ipv6_frag_ext (void) { + fprintf(stderr, "=== test_skip_ipv6_frag_ext ===\n"); + + uint8_t pkt[1500]; + memset(pkt, 0, sizeof(pkt)); + + /* Rebuild with Fragment extension header (44) before TCP */ + pkt[12] = ETH_P_IPV6 >> 8; pkt[13] = ETH_P_IPV6 & 0xff; + uint8_t *ip = pkt + ETH_HDR_SIZE; + ip[0] = 0x60; ip[6] = 44; ip[7] = 8 + TCP_HDR_WITH_OPTS; /* Next Header = 44 */ + + uint8_t *frag = ip + IP6_HDR_SIZE; + frag[0] = IPPROTO_TCP; /* Next Header = TCP */ + frag[1] = 0; /* Reserved */ + frag[2] = 0; frag[3] = 0x00; /* Offset=0, M=0 */ + frag[4] = 0; frag[5] = 0; frag[6] = 0; frag[7] = 0; /* Identification */ + + uint8_t *tcp = frag + 8; + uint16_t src_port = htons(TCP_PORT_SYN); + uint16_t dst_port = htons(TCP_PORT_HTTP); + memcpy(tcp, &src_port, 2); + memcpy(tcp + 2, &dst_port, 2); + tcp[7] = 0x01; + tcp[12] = 0x60; tcp[13] = TCP_FLAG_SYN; + tcp[14] = 0xff; tcp[15] = 0xff; + tcp[20] = TCP_OPT_MSS; tcp[21] = TCP_OPT_MSS_LEN; + tcp[22] = 1500 >> 8; tcp[23] = 1500 & 0xff; + + size_t total_len = ETH_HDR_SIZE + IP6_HDR_SIZE + 8 + TCP_HDR_WITH_OPTS; + uint16_t old_mss = get_mss(tcp); + + struct n3n_runtime_data eee; + memset(&eee, 0, sizeof(eee)); + eee.conf.clamp_mss = 1; + eee.conf.mtu = 1500; + clamp_mss(&eee, pkt, total_len); + + if(get_mss(tcp) == old_mss) + printf("PASS: IPv6 fragment extension header skips clamp\n"); + else + printf("FAIL: IPv6 fragment packet was modified\n"); +} + +/* ============================================================ */ + +/* ---- Packet construction from mss_test/test_mss_clamp.c ---- */ + +static size_t build_tap_style_tcp_syn (uint8_t *buf, size_t buf_size, uint16_t mss) { + /* Mimics test_mss_clamp.c: uses 10.0.0.1->10.0.0.2, ports 12345->80 */ + if(buf_size < ETH_HDR_SIZE + IP4_HDR_SIZE + TCP_HDR_WITH_OPTS) return 0; + memset(buf, 0, buf_size); + + /* Ethernet */ + buf[12] = ETH_P_IP >> 8; buf[13] = ETH_P_IP & 0xff; + + /* IPv4: src=10.0.0.1, dst=10.0.0.2 */ + uint8_t *ip = buf + ETH_HDR_SIZE; + ip[0] = 0x45; ip[8] = 64; ip[9] = IPPROTO_TCP; + ip[2] = 0; ip[3] = IP4_HDR_SIZE + TCP_HDR_WITH_OPTS; + ip[12] = 10; ip[13] = 0; ip[14] = 0; ip[15] = 1; + ip[16] = 10; ip[17] = 0; ip[18] = 0; ip[19] = 2; + + uint8_t *tcp = ip + IP4_HDR_SIZE; + uint16_t src_port = htons(TCP_PORT_TEST); + uint16_t dst_port = htons(TCP_PORT_HTTP); + memcpy(tcp, &src_port, 2); + memcpy(tcp + 2, &dst_port, 2); + tcp[4] = 0; tcp[5] = 0; tcp[6] = 0; tcp[7] = 0x03; /* seq=3 */ + tcp[12] = 0x60; /* doff=6 */ + tcp[13] = TCP_FLAG_SYN; + tcp[14] = 0xff; tcp[15] = 0xff; + tcp[20] = TCP_OPT_MSS; tcp[21] = TCP_OPT_MSS_LEN; + tcp[22] = mss >> 8; tcp[23] = mss & 0xff; + + uint16_t csum = full_tcp_checksum_ipv4(ip, tcp, TCP_HDR_WITH_OPTS); + tcp[16] = csum >> 8; tcp[17] = csum & 0xff; + + return ETH_HDR_SIZE + IP4_HDR_SIZE + TCP_HDR_WITH_OPTS; +} + +/* ---- Packet construction from mss_test/test_udp_to_edge.c ---- */ + +static size_t build_udp_edge_style_tcp_syn (uint8_t *buf, size_t buf_size, uint16_t mss) { + /* Mimics test_udp_to_edge.c: src=10.0.0.2, dst=10.0.0.100, ports 12345->80 */ + if(buf_size < ETH_HDR_SIZE + IP4_HDR_SIZE + TCP_HDR_WITH_OPTS) return 0; + memset(buf, 0, buf_size); + + /* Ethernet */ + buf[12] = ETH_P_IP >> 8; buf[13] = ETH_P_IP & 0xff; + + /* IPv4: src=10.0.0.2, dst=10.0.0.100 */ + uint8_t *ip = buf + ETH_HDR_SIZE; + ip[0] = 0x45; ip[8] = 64; ip[9] = IPPROTO_TCP; + ip[2] = 0; ip[3] = IP4_HDR_SIZE + TCP_HDR_WITH_OPTS; + ip[4] = 0x30; ip[5] = 0x39; /* ID=12345 */ + ip[12] = 10; ip[13] = 0; ip[14] = 0; ip[15] = 2; + ip[16] = 10; ip[17] = 0; ip[18] = 0; ip[19] = 100; + + uint16_t ip_csum = 0; + uint32_t s = 0; + for(int i = 0; i < 20; i += 2) + s += (uint32_t)ip[i] << 8 | ip[i+1]; + s = (s >> 16) + (s & 0xffff); + s += s >> 16; + ip_csum = (~s) & 0xffff; + ip[10] = ip_csum >> 8; ip[11] = ip_csum & 0xff; + + uint8_t *tcp = ip + IP4_HDR_SIZE; + uint16_t src_port = htons(TCP_PORT_TEST); + uint16_t dst_port = htons(TCP_PORT_HTTP); + memcpy(tcp, &src_port, 2); + memcpy(tcp + 2, &dst_port, 2); + tcp[7] = 0x03; /* seq = 1000 (simplified) */ + tcp[12] = 0x60; /* doff=6 */ + tcp[13] = TCP_FLAG_SYN; + tcp[14] = 0xff; tcp[15] = 0xff; + tcp[20] = TCP_OPT_MSS; tcp[21] = TCP_OPT_MSS_LEN; + tcp[22] = mss >> 8; tcp[23] = mss & 0xff; + + uint16_t csum = full_tcp_checksum_ipv4(ip, tcp, TCP_HDR_WITH_OPTS); + tcp[16] = csum >> 8; tcp[17] = csum & 0xff; + + return ETH_HDR_SIZE + IP4_HDR_SIZE + TCP_HDR_WITH_OPTS; +} + +/* ============================================================ */ + +static void test_clamp_tap_style (void) { + fprintf(stderr, "=== test_clamp_tap_style ===\n"); + + uint8_t pkt[1500]; + size_t len = build_tap_style_tcp_syn(pkt, sizeof(pkt), 1500); + uint8_t *tcp = pkt + ETH_HDR_SIZE + IP4_HDR_SIZE; + + uint16_t old_mss = get_mss(tcp); + uint16_t old_cksum = get_csum(tcp); + fprintf(stderr, "TAP-style: MSS=%u, csum=0x%04x\n", old_mss, old_cksum); + + /* Verify original */ + uint16_t v = full_tcp_checksum_ipv4(pkt + ETH_HDR_SIZE, tcp, TCP_HDR_WITH_OPTS); + if(old_cksum != v) { + printf("FAIL: original checksum invalid\n"); return; + } + + struct n3n_runtime_data eee; + memset(&eee, 0, sizeof(eee)); + eee.conf.clamp_mss = 1; + eee.conf.mtu = 1500; + clamp_mss(&eee, pkt, len); + + uint16_t new_mss = get_mss(tcp); + uint16_t new_cksum = get_csum(tcp); + fprintf(stderr, "After clamp: MSS=%u, csum=0x%04x\n", new_mss, new_cksum); + + v = full_tcp_checksum_ipv4(pkt + ETH_HDR_SIZE, tcp, TCP_HDR_WITH_OPTS); + if(new_mss == 1460 && new_cksum == v) + printf("PASS: TAP-style MSS 1500->1460, checksum valid\n"); + else + printf("FAIL: MSS=%u (expected 1460), csum valid=%d\n", new_mss, new_cksum == v); +} + +static void test_clamp_udp_edge_style (void) { + fprintf(stderr, "=== test_clamp_udp_edge_style ===\n"); + + uint8_t pkt[1500]; + size_t len = build_udp_edge_style_tcp_syn(pkt, sizeof(pkt), 1500); + uint8_t *tcp = pkt + ETH_HDR_SIZE + IP4_HDR_SIZE; + + uint16_t old_mss = get_mss(tcp); + uint16_t old_cksum = get_csum(tcp); + fprintf(stderr, "UDP-edge-style: MSS=%u, csum=0x%04x\n", old_mss, old_cksum); + + uint16_t v = full_tcp_checksum_ipv4(pkt + ETH_HDR_SIZE, tcp, TCP_HDR_WITH_OPTS); + if(old_cksum != v) { + printf("FAIL: original checksum invalid\n"); return; + } + + struct n3n_runtime_data eee; + memset(&eee, 0, sizeof(eee)); + eee.conf.clamp_mss = 1; + eee.conf.mtu = 1500; + clamp_mss(&eee, pkt, len); + + uint16_t new_mss = get_mss(tcp); + uint16_t new_cksum = get_csum(tcp); + fprintf(stderr, "After clamp: MSS=%u, csum=0x%04x\n", new_mss, new_cksum); + + v = full_tcp_checksum_ipv4(pkt + ETH_HDR_SIZE, tcp, TCP_HDR_WITH_OPTS); + if(new_mss == 1460 && new_cksum == v) + printf("PASS: UDP-edge-style MSS 1500->1460, checksum valid\n"); + else + printf("FAIL: MSS=%u (expected 1460), csum valid=%d\n", new_mss, new_cksum == v); +} + +/* ============================================================ */ + +/* ---- Build IPv4 TCP SYN with IP options (IHL=6, 24-byte IP header) ---- */ +static size_t build_ipv4_ipopt_tcp_syn (uint8_t *buf, size_t buf_size, uint16_t mss) { + /* IHL=6 means 24-byte IP header with 4 bytes of options */ + if(buf_size < ETH_HDR_SIZE + 24 + TCP_HDR_WITH_OPTS) return 0; + memset(buf, 0, buf_size); + + buf[12] = ETH_P_IP >> 8; buf[13] = ETH_P_IP & 0xff; + + uint8_t *ip = buf + ETH_HDR_SIZE; + ip[0] = 0x46; /* ver=4, ihl=6 */ + ip[8] = 64; ip[9] = IPPROTO_TCP; + ip[2] = 0; ip[3] = 24 + TCP_HDR_WITH_OPTS; + ip[12] = 192; ip[13] = 168; ip[14] = 1; ip[15] = 10; + ip[16] = 10; ip[17] = 0; ip[18] = 0; ip[19] = 1; + /* IP option: NOP (1 byte) + EOL (3 bytes padding) */ + ip[20] = TCP_OPT_NOP; + ip[21] = TCP_OPT_EOL; + ip[22] = 0; ip[23] = 0; + + uint8_t *tcp = ip + 24; + uint16_t src_port = htons(TCP_PORT_SYN); + uint16_t dst_port = htons(TCP_PORT_HTTP); + memcpy(tcp, &src_port, 2); + memcpy(tcp + 2, &dst_port, 2); + tcp[7] = 0x01; + tcp[12] = 0x60; + tcp[13] = TCP_FLAG_SYN; + tcp[14] = 0xff; tcp[15] = 0xff; + tcp[20] = TCP_OPT_MSS; tcp[21] = TCP_OPT_MSS_LEN; + tcp[22] = mss >> 8; tcp[23] = mss & 0xff; + + uint16_t csum = full_tcp_checksum_ipv4(ip, tcp, TCP_HDR_WITH_OPTS); + tcp[16] = csum >> 8; tcp[17] = csum & 0xff; + + return ETH_HDR_SIZE + 24 + TCP_HDR_WITH_OPTS; +} + +/* ---- Build IPv4 TCP SYN with doff=5 (no options, 20-byte TCP) ---- */ +static size_t build_ipv4_tcp_syn_no_opts (uint8_t *buf, size_t buf_size) { + if(buf_size < ETH_HDR_SIZE + IP4_HDR_SIZE + TCP_HDR_BASE) return 0; + memset(buf, 0, buf_size); + + buf[12] = ETH_P_IP >> 8; buf[13] = ETH_P_IP & 0xff; + + uint8_t *ip = buf + ETH_HDR_SIZE; + ip[0] = 0x45; ip[8] = 64; ip[9] = IPPROTO_TCP; + ip[2] = 0; ip[3] = IP4_HDR_SIZE + TCP_HDR_BASE; + ip[12] = 192; ip[13] = 168; ip[14] = 1; ip[15] = 10; + ip[16] = 10; ip[17] = 0; ip[18] = 0; ip[19] = 1; + + uint8_t *tcp = ip + IP4_HDR_SIZE; + uint16_t src_port = htons(TCP_PORT_SYN); + uint16_t dst_port = htons(TCP_PORT_HTTP); + memcpy(tcp, &src_port, 2); + memcpy(tcp + 2, &dst_port, 2); + tcp[7] = 0x01; + tcp[12] = 0x50; /* doff=5 */ + tcp[13] = TCP_FLAG_SYN; + tcp[14] = 0xff; tcp[15] = 0xff; + + uint16_t csum = full_tcp_checksum_ipv4(ip, tcp, TCP_HDR_BASE); + tcp[16] = csum >> 8; tcp[17] = csum & 0xff; + + return ETH_HDR_SIZE + IP4_HDR_SIZE + TCP_HDR_BASE; +} + +/* ---- Build IPv6 TCP SYN with Hop-by-Hop extension header ---- */ +static size_t build_ipv6_tcp_syn_hbh (uint8_t *buf, size_t buf_size, uint16_t mss) { + /* IPv6 + Hop-by-Hop (8 bytes) + TCP */ + if(buf_size < ETH_HDR_SIZE + IP6_HDR_SIZE + 8 + TCP_HDR_WITH_OPTS) return 0; + memset(buf, 0, buf_size); + + buf[12] = ETH_P_IPV6 >> 8; buf[13] = ETH_P_IPV6 & 0xff; + + uint8_t *ip = buf + ETH_HDR_SIZE; + ip[0] = 0x60; + ip[6] = 0; /* Next Header = Hop-by-Hop (0) */ + ip[7] = (8 + TCP_HDR_WITH_OPTS) & 0xff; /* Payload len */ + + uint8_t *hbh = ip + IP6_HDR_SIZE; + hbh[0] = IPPROTO_TCP; /* Next Header = TCP */ + hbh[1] = 0; /* Hdr Ext Len = 0 (8 bytes total) */ + + uint8_t *tcp = hbh + 8; + uint16_t src_port = htons(TCP_PORT_SYN); + uint16_t dst_port = htons(TCP_PORT_HTTP); + memcpy(tcp, &src_port, 2); + memcpy(tcp + 2, &dst_port, 2); + tcp[7] = 0x01; + tcp[12] = 0x60; + tcp[13] = TCP_FLAG_SYN; + tcp[14] = 0xff; tcp[15] = 0xff; + tcp[20] = TCP_OPT_MSS; tcp[21] = TCP_OPT_MSS_LEN; + tcp[22] = mss >> 8; tcp[23] = mss & 0xff; + + return ETH_HDR_SIZE + IP6_HDR_SIZE + 8 + TCP_HDR_WITH_OPTS; +} + +/* ---- Build IPv6 TCP SYN with Auth Header (protocol 51) ---- */ +static size_t build_ipv6_tcp_syn_ah (uint8_t *buf, size_t buf_size, uint16_t mss) { + /* IPv6 + Auth Header (8 bytes) + TCP */ + if(buf_size < ETH_HDR_SIZE + IP6_HDR_SIZE + 8 + TCP_HDR_WITH_OPTS) return 0; + memset(buf, 0, buf_size); + + buf[12] = ETH_P_IPV6 >> 8; buf[13] = ETH_P_IPV6 & 0xff; + + uint8_t *ip = buf + ETH_HDR_SIZE; + ip[0] = 0x60; + ip[6] = 51; /* Next Header = Auth Header */ + ip[7] = (8 + TCP_HDR_WITH_OPTS) & 0xff; + + uint8_t *ah = ip + IP6_HDR_SIZE; + ah[0] = IPPROTO_TCP; /* Next Header = TCP */ + ah[1] = 0; /* Payload Len = 0 (8 bytes) */ + + uint8_t *tcp = ah + 8; + uint16_t src_port = htons(TCP_PORT_SYN); + uint16_t dst_port = htons(TCP_PORT_HTTP); + memcpy(tcp, &src_port, 2); + memcpy(tcp + 2, &dst_port, 2); + tcp[7] = 0x01; + tcp[12] = 0x60; + tcp[13] = TCP_FLAG_SYN; + tcp[14] = 0xff; tcp[15] = 0xff; + tcp[20] = TCP_OPT_MSS; tcp[21] = TCP_OPT_MSS_LEN; + tcp[22] = mss >> 8; tcp[23] = mss & 0xff; + + return ETH_HDR_SIZE + IP6_HDR_SIZE + 8 + TCP_HDR_WITH_OPTS; +} + +/* ---- Build IPv4 TCP SYN with 0x9100 VLAN tag ---- */ +static size_t build_vlan9100_ipv4_tcp_syn (uint8_t *buf, size_t buf_size, uint16_t mss) { + if(buf_size < ETH_HDR_SIZE + 4 + IP4_HDR_SIZE + TCP_HDR_WITH_OPTS) return 0; + memset(buf, 0, buf_size); + + buf[12] = ETH_P_8021Q_9100 >> 8; buf[13] = ETH_P_8021Q_9100 & 0xff; + buf[14] = 0x00; buf[15] = 0x64; /* VID=100 */ + buf[16] = ETH_P_IP >> 8; buf[17] = ETH_P_IP & 0xff; + + uint8_t *ip = buf + ETH_HDR_SIZE + 4; + ip[0] = 0x45; ip[8] = 64; ip[9] = IPPROTO_TCP; + ip[2] = 0; ip[3] = IP4_HDR_SIZE + TCP_HDR_WITH_OPTS; + ip[12] = 192; ip[13] = 168; ip[14] = 1; ip[15] = 10; + ip[16] = 10; ip[17] = 0; ip[18] = 0; ip[19] = 1; + + uint8_t *tcp = ip + IP4_HDR_SIZE; + uint16_t src_port = htons(TCP_PORT_SYN); + uint16_t dst_port = htons(TCP_PORT_HTTP); + memcpy(tcp, &src_port, 2); + memcpy(tcp + 2, &dst_port, 2); + tcp[7] = 0x01; + tcp[12] = 0x60; tcp[13] = TCP_FLAG_SYN; + tcp[14] = 0xff; tcp[15] = 0xff; + tcp[20] = TCP_OPT_MSS; tcp[21] = TCP_OPT_MSS_LEN; + tcp[22] = mss >> 8; tcp[23] = mss & 0xff; + + uint16_t csum = full_tcp_checksum_ipv4(ip, tcp, TCP_HDR_WITH_OPTS); + tcp[16] = csum >> 8; tcp[17] = csum & 0xff; + + return ETH_HDR_SIZE + 4 + IP4_HDR_SIZE + TCP_HDR_WITH_OPTS; +} + +/* ============================================================ */ + +static void test_mtu_zero (void) { + fprintf(stderr, "=== test_mtu_zero ===\n"); + uint8_t pkt[1500]; + size_t len = build_ipv4_tcp_syn(pkt, sizeof(pkt), 1500); + uint8_t *tcp = pkt + ETH_HDR_SIZE + IP4_HDR_SIZE; + uint16_t mss0 = get_mss(tcp); + + struct n3n_runtime_data eee; + memset(&eee, 0, sizeof(eee)); + eee.conf.clamp_mss = 1; + eee.conf.mtu = 0; + clamp_mss(&eee, pkt, len); + + if(get_mss(tcp) == mss0) + printf("PASS: mtu=0 skips clamping\n"); + else + printf("FAIL: mtu=0 modified packet\n"); +} + +static void test_custom_mtu (void) { + fprintf(stderr, "=== test_custom_mtu ===\n"); + uint8_t pkt[1500]; + size_t len = build_ipv4_tcp_syn(pkt, sizeof(pkt), 1500); + uint8_t *tcp = pkt + ETH_HDR_SIZE + IP4_HDR_SIZE; + + struct n3n_runtime_data eee; + memset(&eee, 0, sizeof(eee)); + eee.conf.clamp_mss = 1; + eee.conf.mtu = 1400; + clamp_mss(&eee, pkt, len); + + /* mtu=1400, overhead=20, max_mss=1400-20-20=1360 */ + uint16_t new_mss = get_mss(tcp); + if(new_mss == 1360) + printf("PASS: custom MTU=1400, MSS clamped to 1360\n"); + else + printf("FAIL: expected 1360, got %u\n", new_mss); +} + +static void test_clamp_synack (void) { + fprintf(stderr, "=== test_clamp_synack ===\n"); + uint8_t pkt[1500]; + size_t len = build_ipv4_tcp_syn(pkt, sizeof(pkt), 1500); + uint8_t *tcp = pkt + ETH_HDR_SIZE + IP4_HDR_SIZE; + tcp[13] = TCP_FLAG_SYNACK; /* SYN+ACK */ + + uint16_t old_mss = get_mss(tcp); + struct n3n_runtime_data eee; + memset(&eee, 0, sizeof(eee)); + eee.conf.clamp_mss = 1; + eee.conf.mtu = 1500; + clamp_mss(&eee, pkt, len); + + uint16_t new_mss = get_mss(tcp); + if(new_mss < old_mss && new_mss == 1460) + printf("PASS: SYN-ACK also clamped 1500 -> 1460\n"); + else + printf("FAIL: SYN-ACK not clamped, MSS=%u\n", new_mss); +} + +static void test_pkt_too_short (void) { + fprintf(stderr, "=== test_pkt_too_short ===\n"); + uint8_t pkt[10]; /* Less than Ethernet header */ + uint8_t original[10]; + memset(pkt, 0, sizeof(pkt)); + memcpy(original, pkt, sizeof(pkt)); + + struct n3n_runtime_data eee; + memset(&eee, 0, sizeof(eee)); + eee.conf.clamp_mss = 1; + eee.conf.mtu = 1500; + clamp_mss(&eee, pkt, sizeof(pkt)); + + if(memcmp(original, pkt, sizeof(pkt)) == 0) + printf("PASS: short packet unchanged (safe early return)\n"); + else + printf("FAIL: short packet was modified\n"); +} + +static void test_non_ip_ethertype (void) { + fprintf(stderr, "=== test_non_ip_ethertype ===\n"); + uint8_t pkt[1500]; + size_t len = build_ipv4_tcp_syn(pkt, sizeof(pkt), 1500); + /* Change ethertype to ARP */ + pkt[12] = ETH_P_ARP >> 8; pkt[13] = ETH_P_ARP & 0xff; + + uint8_t *tcp = pkt + ETH_HDR_SIZE + IP4_HDR_SIZE; + uint16_t mss0 = get_mss(tcp); + + struct n3n_runtime_data eee; + memset(&eee, 0, sizeof(eee)); + eee.conf.clamp_mss = 1; + eee.conf.mtu = 1500; + clamp_mss(&eee, pkt, len); + + if(get_mss(tcp) == mss0) + printf("PASS: non-IP ethertype (ARP) skipped\n"); + else + printf("FAIL: ARP packet was modified\n"); +} + +static void test_ipv4_ihl_options (void) { + fprintf(stderr, "=== test_ipv4_ihl_options ===\n"); + uint8_t pkt[1500]; + size_t len = build_ipv4_ipopt_tcp_syn(pkt, sizeof(pkt), 1500); + uint8_t *tcp = pkt + ETH_HDR_SIZE + 24; /* IHL=6 -> 24 byte IP hdr */ + + uint16_t old_mss = get_mss(tcp); + struct n3n_runtime_data eee; + memset(&eee, 0, sizeof(eee)); + eee.conf.clamp_mss = 1; + eee.conf.mtu = 1500; + clamp_mss(&eee, pkt, len); + + uint16_t new_mss = get_mss(tcp); + /* IHL=6: ip_overhead = (14+24) - 14 = 24, max_mss = 1500 - 24 - 20 = 1456 */ + if(new_mss == 1456) + printf("PASS: IHL=6 IP options, MSS clamped to 1456\n"); + else + printf("FAIL: expected 1456, got %u (old=%u)\n", new_mss, old_mss); +} + +static void test_tcp_no_options (void) { + fprintf(stderr, "=== test_tcp_no_options ===\n"); + uint8_t pkt[1500]; + size_t len = build_ipv4_tcp_syn_no_opts(pkt, sizeof(pkt)); + + struct n3n_runtime_data eee; + memset(&eee, 0, sizeof(eee)); + eee.conf.clamp_mss = 1; + eee.conf.mtu = 1500; + /* Should not crash — no MSS option to find */ + clamp_mss(&eee, pkt, len); + printf("PASS: TCP with no options handled without crash\n"); +} + +static void test_vlan9100_syn (void) { + fprintf(stderr, "=== test_vlan9100_syn ===\n"); + uint8_t pkt[1500]; + size_t len = build_vlan9100_ipv4_tcp_syn(pkt, sizeof(pkt), 1500); + uint8_t *tcp = pkt + ETH_HDR_SIZE + 4 + IP4_HDR_SIZE; + + uint16_t old_mss = get_mss(tcp); + fprintf(stderr, "Original 0x9100 VLAN MSS = %u\n", old_mss); + + struct n3n_runtime_data eee; + memset(&eee, 0, sizeof(eee)); + eee.conf.clamp_mss = 1; + eee.conf.mtu = 1500; + clamp_mss(&eee, pkt, len); + + uint16_t new_mss = get_mss(tcp); + /* Same as 802.1Q: overhead = 14+4 = 18, max_mss = 1500 - 24 - 20 = 1456 */ + if(new_mss == 1456) + printf("PASS: 0x9100 VLAN MSS clamped 1500 -> 1456\n"); + else + printf("FAIL: expected 1456, got %u\n", new_mss); +} + +static void test_ipv6_hbh_ext (void) { + fprintf(stderr, "=== test_ipv6_hbh_ext ===\n"); + uint8_t pkt[1500]; + size_t len = build_ipv6_tcp_syn_hbh(pkt, sizeof(pkt), 1500); + uint8_t *tcp = pkt + ETH_HDR_SIZE + IP6_HDR_SIZE + 8; + + uint16_t old_mss = get_mss(tcp); + struct n3n_runtime_data eee; + memset(&eee, 0, sizeof(eee)); + eee.conf.clamp_mss = 1; + eee.conf.mtu = 1500; + clamp_mss(&eee, pkt, len); + + uint16_t new_mss = get_mss(tcp); + /* Hop-by-Hop: ip_overhead = (14+40+8) - 14 = 48, max_mss = 1500 - 48 - 20 = 1432 */ + if(new_mss == 1432) + printf("PASS: IPv6 Hop-by-Hop ext, MSS clamped to 1432\n"); + else + printf("FAIL: expected 1432, got %u (old=%u)\n", new_mss, old_mss); +} + +static void test_ipv6_auth_header (void) { + fprintf(stderr, "=== test_ipv6_auth_header ===\n"); + uint8_t pkt[1500]; + size_t len = build_ipv6_tcp_syn_ah(pkt, sizeof(pkt), 1500); + uint8_t *tcp = pkt + ETH_HDR_SIZE + IP6_HDR_SIZE + 8; + + uint16_t old_mss = get_mss(tcp); + struct n3n_runtime_data eee; + memset(&eee, 0, sizeof(eee)); + eee.conf.clamp_mss = 1; + eee.conf.mtu = 1500; + clamp_mss(&eee, pkt, len); + + if(get_mss(tcp) == old_mss) + printf("PASS: IPv6 Auth Header (51) skips clamp\n"); + else + printf("FAIL: IPv6 Auth Header packet was modified\n"); +} + +/* ---- Build IPv6 TCP SYN with Routing extension header (43) ---- */ +static size_t build_ipv6_tcp_syn_routing (uint8_t *buf, size_t buf_size, uint16_t mss) { + /* IPv6 + Routing (8 bytes) + TCP */ + if(buf_size < ETH_HDR_SIZE + IP6_HDR_SIZE + 8 + TCP_HDR_WITH_OPTS) return 0; + memset(buf, 0, buf_size); + + buf[12] = ETH_P_IPV6 >> 8; buf[13] = ETH_P_IPV6 & 0xff; + + uint8_t *ip = buf + ETH_HDR_SIZE; + ip[0] = 0x60; + ip[6] = 43; /* Next Header = Routing (43) */ + ip[7] = (8 + TCP_HDR_WITH_OPTS) & 0xff; + + uint8_t *rth = ip + IP6_HDR_SIZE; + rth[0] = IPPROTO_TCP; /* Next Header = TCP */ + rth[1] = 0; /* Hdr Ext Len = 0 (8 bytes total) */ + + uint8_t *tcp = rth + 8; + uint16_t src_port = htons(TCP_PORT_SYN); + uint16_t dst_port = htons(TCP_PORT_HTTP); + memcpy(tcp, &src_port, 2); + memcpy(tcp + 2, &dst_port, 2); + tcp[7] = 0x01; + tcp[12] = 0x60; + tcp[13] = TCP_FLAG_SYN; + tcp[14] = 0xff; tcp[15] = 0xff; + tcp[20] = TCP_OPT_MSS; tcp[21] = TCP_OPT_MSS_LEN; + tcp[22] = mss >> 8; tcp[23] = mss & 0xff; + + return ETH_HDR_SIZE + IP6_HDR_SIZE + 8 + TCP_HDR_WITH_OPTS; +} + +/* ---- Build IPv6 TCP SYN with Destination Options header (60) ---- */ +static size_t build_ipv6_tcp_syn_destopt (uint8_t *buf, size_t buf_size, uint16_t mss) { + /* IPv6 + Destination Options (8 bytes) + TCP */ + if(buf_size < ETH_HDR_SIZE + IP6_HDR_SIZE + 8 + TCP_HDR_WITH_OPTS) return 0; + memset(buf, 0, buf_size); + + buf[12] = ETH_P_IPV6 >> 8; buf[13] = ETH_P_IPV6 & 0xff; + + uint8_t *ip = buf + ETH_HDR_SIZE; + ip[0] = 0x60; + ip[6] = 60; /* Next Header = Destination Options (60) */ + ip[7] = (8 + TCP_HDR_WITH_OPTS) & 0xff; + + uint8_t *dst = ip + IP6_HDR_SIZE; + dst[0] = IPPROTO_TCP; /* Next Header = TCP */ + dst[1] = 0; /* Hdr Ext Len = 0 (8 bytes total) */ + + uint8_t *tcp = dst + 8; + uint16_t src_port = htons(TCP_PORT_SYN); + uint16_t dst_port = htons(TCP_PORT_HTTP); + memcpy(tcp, &src_port, 2); + memcpy(tcp + 2, &dst_port, 2); + tcp[7] = 0x01; + tcp[12] = 0x60; + tcp[13] = TCP_FLAG_SYN; + tcp[14] = 0xff; tcp[15] = 0xff; + tcp[20] = TCP_OPT_MSS; tcp[21] = TCP_OPT_MSS_LEN; + tcp[22] = mss >> 8; tcp[23] = mss & 0xff; + + return ETH_HDR_SIZE + IP6_HDR_SIZE + 8 + TCP_HDR_WITH_OPTS; +} + +static void test_ipv6_routing_ext (void) { + fprintf(stderr, "=== test_ipv6_routing_ext ===\n"); + uint8_t pkt[1500]; + size_t len = build_ipv6_tcp_syn_routing(pkt, sizeof(pkt), 1500); + uint8_t *tcp = pkt + ETH_HDR_SIZE + IP6_HDR_SIZE + 8; + + uint16_t old_mss = get_mss(tcp); + struct n3n_runtime_data eee; + memset(&eee, 0, sizeof(eee)); + eee.conf.clamp_mss = 1; + eee.conf.mtu = 1500; + clamp_mss(&eee, pkt, len); + + uint16_t new_mss = get_mss(tcp); + /* Routing: ip_overhead = (14+40+8) - 14 = 48, max_mss = 1500 - 48 - 20 = 1432 */ + if(new_mss == 1432) + printf("PASS: IPv6 Routing ext, MSS clamped to 1432\n"); + else + printf("FAIL: expected 1432, got %u (old=%u)\n", new_mss, old_mss); +} + +static void test_ipv6_destopt_ext (void) { + fprintf(stderr, "=== test_ipv6_destopt_ext ===\n"); + uint8_t pkt[1500]; + size_t len = build_ipv6_tcp_syn_destopt(pkt, sizeof(pkt), 1500); + uint8_t *tcp = pkt + ETH_HDR_SIZE + IP6_HDR_SIZE + 8; + + uint16_t old_mss = get_mss(tcp); + struct n3n_runtime_data eee; + memset(&eee, 0, sizeof(eee)); + eee.conf.clamp_mss = 1; + eee.conf.mtu = 1500; + clamp_mss(&eee, pkt, len); + + uint16_t new_mss = get_mss(tcp); + /* Dest Opt: ip_overhead = (14+40+8) - 14 = 48, max_mss = 1500 - 48 - 20 = 1432 */ + if(new_mss == 1432) + printf("PASS: IPv6 Destination Options ext, MSS clamped to 1432\n"); + else + printf("FAIL: expected 1432, got %u (old=%u)\n", new_mss, old_mss); +} + +static void test_multiple_mss_options (void) { + fprintf(stderr, "=== test_multiple_mss_options ===\n"); + uint8_t pkt[1500]; + size_t len = build_ipv4_tcp_syn(pkt, sizeof(pkt), 1500); + uint8_t *tcp = pkt + ETH_HDR_SIZE + IP4_HDR_SIZE; + + /* Insert a second MSS option before the real one */ + /* Original: NOP(1), MSS(4 bytes) at offset 20-24 */ + /* Change: MSS at offset 20-23, NOP at 24, new MSS at 25-28 */ + /* But we need to extend TCP header for this test */ + /* Simpler: just verify only first MSS is processed and clamped */ + /* Current implementation breaks after first MSS found - this is correct behavior */ + + uint16_t old_mss = get_mss(tcp); + struct n3n_runtime_data eee; + memset(&eee, 0, sizeof(eee)); + eee.conf.clamp_mss = 1; + eee.conf.mtu = 1500; + clamp_mss(&eee, pkt, len); + + /* Should clamp first MSS and stop (correct per RFC) */ + if(get_mss(tcp) == 1460 && old_mss == 1500) + printf("PASS: first MSS option processed, packet valid\n"); + else + printf("FAIL: expected 1460, got %u\n", get_mss(tcp)); +} + +static void test_mss_at_boundary (void) { + fprintf(stderr, "=== test_mss_at_boundary ===\n"); + /* MSS = max_mss exactly (1460 for standard IPv4 MTU=1500) — should NOT change */ + uint8_t pkt[1500]; + size_t len = build_ipv4_tcp_syn(pkt, sizeof(pkt), 1460); + uint8_t *tcp = pkt + ETH_HDR_SIZE + IP4_HDR_SIZE; + uint16_t mss0 = get_mss(tcp); + + struct n3n_runtime_data eee; + memset(&eee, 0, sizeof(eee)); + eee.conf.clamp_mss = 1; + eee.conf.mtu = 1500; + clamp_mss(&eee, pkt, len); + + if(get_mss(tcp) == mss0) + printf("PASS: MSS=1460 (== max_mss) not modified\n"); + else + printf("FAIL: boundary MSS changed %u -> %u\n", mss0, get_mss(tcp)); +} + +static void test_csum_update_identity (void) { + fprintf(stderr, "=== test_csum_update_identity ===\n"); + /* Same old and new value — checksum should be unchanged */ + uint16_t csum = 0xABCD; + uint16_t val = 500; + uint16_t result = tcp_csum_update(csum, val, val); + + if(result == csum) + printf("PASS: csum_update identity (same value) returns original\n"); + else + printf("FAIL: expected 0x%04x, got 0x%04x\n", csum, result); +} + +static void test_malformed_tcp_options (void) { + fprintf(stderr, "=== test_malformed_tcp_options ===\n"); + uint8_t pkt[1500]; + size_t len = build_ipv4_tcp_syn(pkt, sizeof(pkt), 1500); + uint8_t *tcp = pkt + ETH_HDR_SIZE + IP4_HDR_SIZE; + + /* Corrupt MSS option length to 255 (extends past TCP header) */ + tcp[21] = 255; + + struct n3n_runtime_data eee; + memset(&eee, 0, sizeof(eee)); + eee.conf.clamp_mss = 1; + eee.conf.mtu = 1500; + clamp_mss(&eee, pkt, len); + printf("PASS: malformed TCP options handled without crash\n"); +} + +static void test_mss_zero_in_packet (void) { + fprintf(stderr, "=== test_mss_zero_in_packet ===\n"); + uint8_t pkt[1500]; + size_t len = build_ipv4_tcp_syn(pkt, sizeof(pkt), 0); /* MSS=0 */ + uint8_t *tcp = pkt + ETH_HDR_SIZE + IP4_HDR_SIZE; + + struct n3n_runtime_data eee; + memset(&eee, 0, sizeof(eee)); + eee.conf.clamp_mss = 1; + eee.conf.mtu = 1500; + clamp_mss(&eee, pkt, len); + + /* MSS=0 is not > max_mss, should remain unchanged */ + if(get_mss(tcp) == 0) + printf("PASS: MSS=0 left unchanged (not > max_mss)\n"); + else + printf("FAIL: MSS=0 modified to %u\n", get_mss(tcp)); +} + +/* ============================================================ */ +int main (int argc, char *argv[]) { + (void)argc; (void)argv; + + test_tcp_csum_update(); + test_clamp_ipv4_syn(); + test_no_clamp_non_syn(); + test_no_clamp_when_small(); + test_clamp_disabled(); + + test_clamp_ipv6_syn(); + test_clamp_vlan_syn(); + test_clamp_qinq_syn(); + test_clamp_ipip_syn(); + test_skip_fragmented(); + test_skip_ipv6_frag_ext(); + + test_clamp_tap_style(); + test_clamp_udp_edge_style(); + +/* New edge-case tests */ + test_mtu_zero(); + test_custom_mtu(); + test_clamp_synack(); + test_pkt_too_short(); + test_non_ip_ethertype(); + test_ipv4_ihl_options(); + test_tcp_no_options(); + test_vlan9100_syn(); + test_ipv6_hbh_ext(); + test_ipv6_auth_header(); + test_ipv6_routing_ext(); + test_ipv6_destopt_ext(); + test_multiple_mss_options(); + test_mss_at_boundary(); + test_csum_update_identity(); + test_malformed_tcp_options(); + test_mss_zero_in_packet(); + + printf("=== All MSS tests complete ===\n"); + return 0; +}