diff --git a/Makefile b/Makefile index d0fef6d9..68b11cd6 100644 --- a/Makefile +++ b/Makefile @@ -1079,21 +1079,6 @@ test-wolfguard-loopback-ubsan: CFLAGS+=-fsanitize=undefined -fno-sanitize-recove test-wolfguard-loopback-ubsan: LDFLAGS+=-fsanitize=undefined $(UNIT_LIBS) test-wolfguard-loopback-ubsan: clean-test-wolfguard-loopback build/test/test-wolfguard-loopback -# wolfGuard benchmark -bench-wolfguard: build/test/bench-wolfguard - -build/test/bench-wolfguard: src/test/bench_wolfguard.c - @mkdir -p build/test/ - @echo "[CC] bench_wolfguard.c" - @$(CC) $(CFLAGS) -O2 $(WOLFGUARD_CFLAGS) \ - -c src/test/bench_wolfguard.c -o build/test/bench_wolfguard.o - @echo "[LD] $@" - @$(CC) build/test/bench_wolfguard.o -o $@ \ - $(LDFLAGS) -lwolfssl - -clean-bench-wolfguard: - @rm -f build/test/bench-wolfguard build/test/bench_wolfguard.o - # wolfGuard interop test (wolfIP <-> kernel wolfGuard via TUN) test-wolfguard-interop: build/test/test-wolfguard-interop @@ -1117,7 +1102,6 @@ clean-test-wolfguard-interop: unit-wolfguard unit-wolfguard-asan unit-wolfguard-ubsan clean-unit-wolfguard \ test-wolfguard-loopback test-wolfguard-loopback-asan test-wolfguard-loopback-ubsan \ clean-test-wolfguard-loopback \ - bench-wolfguard clean-bench-wolfguard \ test-wolfguard-interop clean-test-wolfguard-interop cppcheck: diff --git a/docs/wolfguard_howto.md b/docs/wolfguard_howto.md index a7cfb977..470cedf1 100644 --- a/docs/wolfguard_howto.md +++ b/docs/wolfguard_howto.md @@ -94,7 +94,6 @@ switch. The pre-wired Makefile targets build and exercise it: ```sh make unit-wolfguard # unit tests make test-wolfguard-loopback # two-stack loopback integration test -make bench-wolfguard # micro-benchmarks make test-wolfguard-interop # interop binary (driven by the script in §8) ``` @@ -189,10 +188,10 @@ void wolfguard_destroy(struct wg_device *dev); ``` - `wolfguard_init` zeroes `dev`, initialises its RNG, configures `wg_if_idx` as - the `wg0` L3 interface (and sets its MTU to `LINK_MTU - 60` to leave room for - the outer IP/UDP/WireGuard overhead), opens the outer UDP socket, binds it to - `listen_port`, and registers the RX callback. Returns `0` on success, `-1` on - failure. + the `wg0` L3 interface (and sets its MTU to `WG_IF_MTU`, derived so that a + full-size inner packet still fits in one outer UDP datagram after padding and + encapsulation), opens the outer UDP socket, binds it to `listen_port`, and + registers the RX callback. Returns `0` on success, `-1` on failure. - `wolfguard_set_private_key` stores the 32-byte private key and derives the device's 65-byte public key (`wg_pubkey_from_private`). It must be called before adding peers; calling it again rotates the identity and drops live @@ -407,6 +406,12 @@ wolfIP stacks. - **It will not talk to stock WireGuard.** Expected — wolfGuard uses the FIPS suite (P-256 / AES-GCM / SHA-256) and is interoperable only with other wolfGuard peers (kernel module or another wolfIP instance). -- **Inner MTU surprises.** `wolfguard_init` sets the `wg0` MTU to - `LINK_MTU - 60` to reserve the outer IP/UDP/WireGuard overhead; size inner - payloads accordingly. +- **Inner MTU surprises.** `wolfguard_init` sets the `wg0` MTU to `WG_IF_MTU` + (1454 with the usual `LINK_MTU` of 1536), leaving a 1440-byte inner IP budget + and 1412 bytes of UDP payload; size inner payloads accordingly. The + reservation is not a flat header sum: the plaintext is padded up to a 16-byte + multiple before encryption, the outer IP payload is capped at 1500 rather + than `LINK_MTU`, and wolfIP subtracts a 14-byte link header from every + interface MTU including this one. Raising the `wg0` MTU past `WG_IF_MTU` + black-holes the largest packets rather than failing loudly, since the outer + `sendto()` rejects them after wolfIP has already accepted them. diff --git a/src/test/test_wolfguard_loopback.c b/src/test/test_wolfguard_loopback.c index ba9eb031..0ce8da63 100644 --- a/src/test/test_wolfguard_loopback.c +++ b/src/test/test_wolfguard_loopback.c @@ -988,6 +988,297 @@ START_TEST(test_multi_peer) } END_TEST +/* + * MTU encapsulation budget + * + * Structural companion to the sweep below, this test doesn't send/receive any + * traffic, it just checks the arithmetic that + * wolfguard_init() has to get right. + */ +START_TEST(test_mtu_encap_fits_transport) +{ + uint64_t now; + int wg0_ip_mtu; /* largest inner IP packet wolfIP accepts on wg0 */ + int outer_udp_max; /* largest outer UDP payload the phys iface carries */ + int wg_overhead; /* WG data header + auth tag */ + int carry_cap; /* largest inner IP packet that survives encapsulation */ + + setup_loopback_stacks(&now); + + wg_overhead = (int)(sizeof(struct wg_msg_data) + WG_AUTHTAG_LEN); + wg0_ip_mtu = (int)wolfIP_ip_mtu(&stack_a, TEST_WG_IF); + outer_udp_max = (int)wolfIP_ip_mtu(&stack_a, TEST_PHYS_IF) + - (IP_HEADER_LEN + UDP_HEADER_LEN); + /* Padding rounds the inner length up to a 16-byte multiple, so the largest + * inner packet that still fits is the budget rounded *down* to one. */ + carry_cap = outer_udp_max - wg_overhead; + carry_cap -= carry_cap % 16; + + ck_assert_int_gt(carry_cap, 0); + ck_assert_msg(wg0_ip_mtu <= carry_cap, + "wg0 advertises a %d-byte IP MTU but only %d bytes of inner " + "IP survive encapsulation: pad16(%d) + %d = %d, over the " + "%d-byte outer UDP budget. Inner packets of %d..%d bytes " + "are accepted and then silently dropped.", + wg0_ip_mtu, carry_cap, + wg0_ip_mtu, wg_overhead, + (int)wg_pad_len((size_t)wg0_ip_mtu) + wg_overhead, + outer_udp_max, + carry_cap + 1, wg0_ip_mtu); + + teardown_stacks(); +} +END_TEST + +/* + * MTU boundary sweep + * + * Sweeps the inner UDP payload across the wg0 MTU in 1-byte steps. + * + * The contract under test is that wg0 must not advertise capacity the outer + * transport cannot carry: every payload wolfIP accepts on wg0 has to arrive + * intact at the far end, and nothing beyond that may arrive at all. Both + * halves matter. A one-sided "never exceeds the MTU" check passes happily + * while the tunnel black-holes the top of its own advertised range, which is + * precisely the failure this test exists to catch, so the boundary is pinned + * from both directions. + * + * Nothing here is hardcoded to a particular LINK_MTU: the accept cap is read + * back from wolfIP and the carry cap is recomputed from the wire format, so + * the test tracks whatever MTU wolfguard_init() actually installs. + */ +START_TEST(test_mtu_boundary_sweep) +{ + uint64_t now; + int app_sock_a, app_sock_b; + struct wolfIP_sockaddr_in bind_addr, dst_addr; + uint8_t sndbuf[LINK_MTU]; + const int anchor = 1000; /* known-deliverable (cf. flood) */ + /* Read back from the live stack once it is up, never hardcoded. */ + int accept_cap; /* largest app payload wolfIP accepts on wg0 */ + int top; /* sweep ceiling, just past the advertised MTU */ + int max_delivered = -1; + int anchor_delivered = 0; + int first_gap = -1; /* first accepted-but-lost size */ + int over_delivered = -1; /* first past-cap size delivered */ + int p, i, ret; + + setup_loopback_stacks(&now); + + accept_cap = (int)wolfIP_ip_mtu(&stack_a, TEST_WG_IF) + - (IP_HEADER_LEN + UDP_HEADER_LEN); + top = accept_cap + 16; + ck_assert_int_gt(accept_cap, anchor); + ck_assert_int_lt(top, (int)sizeof(sndbuf)); + + /* B listens on 7777, A binds a source port on 9999 */ + app_sock_b = wolfIP_sock_socket(&stack_b, AF_INET, SOCK_DGRAM, 0); + ck_assert_int_ge(app_sock_b, 0); + memset(&bind_addr, 0, sizeof(bind_addr)); + bind_addr.sin_family = AF_INET; + bind_addr.sin_port = ee16(7777); + bind_addr.sin_addr.s_addr = ee32(MAKE_IP4(10,0,0,2)); + ck_assert_int_ge(wolfIP_sock_bind(&stack_b, app_sock_b, + (struct wolfIP_sockaddr *)&bind_addr, sizeof(bind_addr)), 0); + wolfIP_register_callback(&stack_b, app_sock_b, app_udp_callback, &stack_b); + + app_sock_a = wolfIP_sock_socket(&stack_a, AF_INET, SOCK_DGRAM, 0); + ck_assert_int_ge(app_sock_a, 0); + memset(&bind_addr, 0, sizeof(bind_addr)); + bind_addr.sin_family = AF_INET; + bind_addr.sin_port = ee16(9999); + bind_addr.sin_addr.s_addr = ee32(MAKE_IP4(10,0,0,1)); + ck_assert_int_ge(wolfIP_sock_bind(&stack_a, app_sock_a, + (struct wolfIP_sockaddr *)&bind_addr, sizeof(bind_addr)), 0); + + memset(&dst_addr, 0, sizeof(dst_addr)); + dst_addr.sin_family = AF_INET; + dst_addr.sin_port = ee16(7777); + dst_addr.sin_addr.s_addr = ee32(MAKE_IP4(10,0,0,2)); + + /* Bring the session up with a small packet so the sweep tests + * the pure data-plane MTU path, not a handshake+size interaction. */ + for (i = 0; i < 64; i++) + sndbuf[i] = (uint8_t)(i & 0xff); + ret = wolfIP_sock_sendto(&stack_a, app_sock_a, sndbuf, 64, 0, + (const struct wolfIP_sockaddr *)&dst_addr, + sizeof(dst_addr)); + ck_assert_int_ge(ret, 0); + pump_stacks(&now, 200, 10); + ck_assert_int_gt(app_recv_count, 0); + ck_assert_ptr_nonnull(wg_dev_a.peers[0].keypairs.current); + + /* Sweep the inner payload up to just past the tunnel MTU, one byte at a + * time. Timers are frozen (step_ms = 0) so a single session persists, a + * live clock would trip the spec's stale-receive rekey, since B is a pure + * sink that never replies. */ + for (p = anchor; p <= top; p++) { + for (i = 0; i < p; i++) + sndbuf[i] = (uint8_t)((i * 31 + p) & 0xff); + + app_recv_count = 0; + app_recv_len = 0; + + ret = wolfIP_sock_sendto(&stack_a, app_sock_a, sndbuf, p, 0, + (const struct wolfIP_sockaddr *)&dst_addr, + sizeof(dst_addr)); + /* wolfIP does not fragment, so the accept/reject split has to land + * exactly on the wg0 MTU. */ + if (p <= accept_cap) + ck_assert_msg(ret >= 0, + "sendto rejected a %d-byte payload, at or below the " + "%d-byte wg0 cap", p, accept_cap); + else + ck_assert_msg(ret < 0, + "sendto accepted a %d-byte payload, above the " + "%d-byte wg0 cap", p, accept_cap); + + pump_stacks(&now, 20, 0); + + if (app_recv_count > 0) { + /* Any delivered packet must be intact: exact length and bytes. + * This is what catches padding / truncation / buffer bugs. */ + ck_assert_int_eq(app_recv_len, p); + for (i = 0; i < p; i++) + ck_assert_uint_eq(app_recv_buf[i], + (uint8_t)((i * 31 + p) & 0xff)); + if (p == anchor) + anchor_delivered = 1; + if (p > accept_cap && over_delivered < 0) + over_delivered = p; + max_delivered = p; + } + else if (p <= accept_cap && first_gap < 0) { + first_gap = p; + } + } + + /* Pin the boundary from both sides. + * + * Below the cap: everything wg0 accepted has to arrive. A gap means the + * interface is advertising an MTU its own transport cannot carry, and the + * packets in that band vanish with no error and no ICMP, the black hole a + * one-sided "never exceeds the MTU" check would sail straight past. + * + * Above the cap: nothing may arrive. wolfIP has no fragmentation, so an + * over-MTU delivery would mean truncation or a buffer overrun. */ + ck_assert_int_eq(anchor_delivered, 1); + ck_assert_msg(first_gap < 0, + "%d-byte payload accepted by wg0 (cap %d) but never " + "delivered: the top %d bytes of the advertised MTU are a " + "black hole", first_gap, accept_cap, + accept_cap - first_gap + 1); + ck_assert_msg(over_delivered < 0, + "%d-byte payload delivered above the %d-byte wg0 cap", + over_delivered, accept_cap); + ck_assert_int_eq(max_delivered, accept_cap); + + wolfIP_sock_close(&stack_a, app_sock_a); + wolfIP_sock_close(&stack_b, app_sock_b); + teardown_stacks(); +} +END_TEST + +/* + * Sustained flood + * This test floods 256 packets to stress the transport path. Each packet + * is uniquely tagged, exercising the replay-counter sliding window implemented + * by wg_counter_validate(). + * */ +START_TEST(test_sustained_flood) +{ + uint64_t now; + int app_sock_a, app_sock_b; + struct wolfIP_sockaddr_in bind_addr, dst_addr; + uint8_t sndbuf[1024]; + const int N = 256; /* crosses several replay-bitmap words */ + const int payload_len = 1000; + int i, j, ret, delivered = 0; + + setup_loopback_stacks(&now); + + app_sock_b = wolfIP_sock_socket(&stack_b, AF_INET, SOCK_DGRAM, 0); + ck_assert_int_ge(app_sock_b, 0); + memset(&bind_addr, 0, sizeof(bind_addr)); + bind_addr.sin_family = AF_INET; + bind_addr.sin_port = ee16(7777); + bind_addr.sin_addr.s_addr = ee32(MAKE_IP4(10,0,0,2)); + ck_assert_int_ge(wolfIP_sock_bind(&stack_b, app_sock_b, + (struct wolfIP_sockaddr *)&bind_addr, sizeof(bind_addr)), 0); + wolfIP_register_callback(&stack_b, app_sock_b, app_udp_callback, &stack_b); + + app_sock_a = wolfIP_sock_socket(&stack_a, AF_INET, SOCK_DGRAM, 0); + ck_assert_int_ge(app_sock_a, 0); + memset(&bind_addr, 0, sizeof(bind_addr)); + bind_addr.sin_family = AF_INET; + bind_addr.sin_port = ee16(9999); + bind_addr.sin_addr.s_addr = ee32(MAKE_IP4(10,0,0,1)); + ck_assert_int_ge(wolfIP_sock_bind(&stack_a, app_sock_a, + (struct wolfIP_sockaddr *)&bind_addr, sizeof(bind_addr)), 0); + + memset(&dst_addr, 0, sizeof(dst_addr)); + dst_addr.sin_family = AF_INET; + dst_addr.sin_port = ee16(7777); + dst_addr.sin_addr.s_addr = ee32(MAKE_IP4(10,0,0,2)); + + /* Establish the session before flooding. */ + for (j = 0; j < payload_len; j++) + sndbuf[j] = (uint8_t)(j & 0xff); + ret = wolfIP_sock_sendto(&stack_a, app_sock_a, sndbuf, payload_len, 0, + (const struct wolfIP_sockaddr *)&dst_addr, + sizeof(dst_addr)); + ck_assert_int_ge(ret, 0); + pump_stacks(&now, 200, 10); + ck_assert_int_gt(app_recv_count, 0); + ck_assert_ptr_nonnull(wg_dev_a.peers[0].keypairs.current); + + /* This is the flood logic, where each + * packet carries its sequence number in the first two bytes so + * delivery, ordering, and integrity are checked per packet. */ + for (i = 0; i < N; i++) { + for (j = 0; j < payload_len; j++) + sndbuf[j] = (uint8_t)((j + i) & 0xff); + sndbuf[0] = (uint8_t)(i & 0xff); + sndbuf[1] = (uint8_t)((i >> 8) & 0xff); + + app_recv_count = 0; + app_recv_len = 0; + + ret = wolfIP_sock_sendto(&stack_a, app_sock_a, sndbuf, payload_len, 0, + (const struct wolfIP_sockaddr *)&dst_addr, + sizeof(dst_addr)); + ck_assert_int_ge(ret, 0); + /* Freeze timers (step_ms = 0): keeps a single session for the whole + * flood so the replay counter advances monotonically. With a live + * clock the spec's stale-receive rekey would fire (B never replies), + * resetting the counter mid-flood. */ + pump_stacks(&now, 16, 0); + + if (app_recv_count > 0) { + ck_assert_int_eq(app_recv_len, payload_len); + ck_assert_uint_eq(app_recv_buf[0], (uint8_t)(i & 0xff)); + ck_assert_uint_eq(app_recv_buf[1], (uint8_t)((i >> 8) & 0xff)); + delivered++; + } + } + + /* Nearly all delivered (small slack for pump-timing stragglers). */ + ck_assert_int_ge(delivered, N - 4); + + /* Receiver's replay window advanced across the whole flood, crossing many + * 32-bit bitmap words in wg_counter_validate without false rejections. */ + ck_assert_ptr_nonnull(wg_dev_b.peers[0].keypairs.current); + ck_assert_uint_ge(wg_dev_b.peers[0].keypairs.current->receiving_counter_max, + (uint64_t)(N - 4)); + ck_assert_uint_gt(wg_dev_a.peers[0].tx_bytes, + (uint64_t)(N - 4) * (uint64_t)payload_len); + + wolfIP_sock_close(&stack_a, app_sock_a); + wolfIP_sock_close(&stack_b, app_sock_b); + teardown_stacks(); +} +END_TEST + /* * Test suite assembly * */ @@ -1027,6 +1318,20 @@ static Suite *wolfguard_integration_suite(void) tcase_add_test(tc, test_multi_peer); suite_add_tcase(s, tc); + /* MTU boundary: encapsulation budget, then a 1-byte payload sweep + * across the wg0 MTU */ + tc = tcase_create("mtu_boundary"); + tcase_set_timeout(tc, 120); + tcase_add_test(tc, test_mtu_encap_fits_transport); + tcase_add_test(tc, test_mtu_boundary_sweep); + suite_add_tcase(s, tc); + + /* Sustained flood: replay-window advance under volume */ + tc = tcase_create("flood"); + tcase_set_timeout(tc, 120); + tcase_add_test(tc, test_sustained_flood); + suite_add_tcase(s, tc); + return s; } diff --git a/src/wolfguard/wg_allowedips.c b/src/wolfguard/wg_allowedips.c index 8dd00780..71f67082 100644 --- a/src/wolfguard/wg_allowedips.c +++ b/src/wolfguard/wg_allowedips.c @@ -16,7 +16,6 @@ /* * Compute network mask from CIDR prefix length * */ - static uint32_t cidr_to_mask(uint8_t cidr) { if (cidr == 0) @@ -29,7 +28,6 @@ static uint32_t cidr_to_mask(uint8_t cidr) /* * Insert an allowed IP entry * */ - int wg_allowedips_insert(struct wg_device *dev, uint32_t ip, uint8_t cidr, uint8_t peer_idx) { @@ -67,7 +65,6 @@ int wg_allowedips_insert(struct wg_device *dev, uint32_t ip, uint8_t cidr, * * Returns peer_idx or -1 if no match. * */ - int wg_allowedips_lookup(struct wg_device *dev, uint32_t ip) { int i; @@ -94,7 +91,6 @@ int wg_allowedips_lookup(struct wg_device *dev, uint32_t ip) /* * Remove all entries for a given peer * */ - void wg_allowedips_remove_by_peer(struct wg_device *dev, uint8_t peer_idx) { int i; diff --git a/src/wolfguard/wg_cookie.c b/src/wolfguard/wg_cookie.c index 43d581d0..ef7a3102 100644 --- a/src/wolfguard/wg_cookie.c +++ b/src/wolfguard/wg_cookie.c @@ -20,7 +20,6 @@ * message_mac1_key = Hash("mac1----" || device_public_key) * cookie_encryption_key = Hash("cookie--" || device_public_key) * */ - void wg_cookie_checker_init(struct wg_cookie_checker *checker, const uint8_t *device_public_key) { @@ -43,7 +42,6 @@ void wg_cookie_checker_init(struct wg_cookie_checker *checker, * * Keys are derived from the remote peer's public key. * */ - void wg_cookie_init(struct wg_cookie *cookie, const uint8_t *peer_public_key) { @@ -64,7 +62,6 @@ void wg_cookie_init(struct wg_cookie *cookie, * mac1 = Mac(message_mac1_key, msg[0..mac_offset)) * mac2 = Mac(cookie, msg[0..mac_offset+16)) if cookie is valid * */ - int wg_cookie_add_macs(struct wg_peer *peer, void *msg, size_t msg_len, size_t mac_offset, uint64_t now) { @@ -107,7 +104,6 @@ int wg_cookie_add_macs(struct wg_peer *peer, void *msg, size_t msg_len, /* * Validate mac1 (and optionally mac2) on incoming handshake message * */ - enum wg_cookie_mac_state wg_cookie_validate( struct wg_cookie_checker *checker, void *msg, size_t msg_len, size_t mac_offset, uint32_t src_ip, uint16_t src_port, uint64_t now) @@ -167,7 +163,6 @@ enum wg_cookie_mac_state wg_cookie_validate( /* * Create cookie reply message * */ - int wg_cookie_create_reply(struct wg_device *dev, struct wg_msg_cookie *reply, const void *triggering_msg, size_t mac_offset, uint32_t sender_index, @@ -235,7 +230,6 @@ int wg_cookie_create_reply(struct wg_device *dev, struct wg_msg_cookie *reply, /* * Consume cookie reply message * */ - int wg_cookie_consume_reply(struct wg_peer *peer, struct wg_msg_cookie *msg, uint64_t now) { diff --git a/src/wolfguard/wg_noise.c b/src/wolfguard/wg_noise.c index a0e3aa37..44ad3e82 100644 --- a/src/wolfguard/wg_noise.c +++ b/src/wolfguard/wg_noise.c @@ -16,7 +16,7 @@ /* Helper: generate a new sender index. * Per spec 5.4.2: "Ii (sender index 4 bytes) is generated randomly (p4) - * p^n represents a random bitstring of length n bytes. + * p^n represents a random bitstring of length n bytes." * */ static uint32_t wg_new_index(struct wg_device *dev) { @@ -65,7 +65,7 @@ void wg_noise_handshake_init(struct wg_handshake *hs, const uint8_t *preshared_key, WC_RNG *rng) { - /* Save PSK before memset — preshared_key may alias hs->preshared_key */ + /* Save PSK before memset, preshared_key may alias hs->preshared_key */ uint8_t psk_buf[WG_SYMMETRIC_KEY_LEN]; if (preshared_key != NULL) memcpy(psk_buf, preshared_key, WG_SYMMETRIC_KEY_LEN); diff --git a/src/wolfguard/wg_packet.c b/src/wolfguard/wg_packet.c index 08b6cf9d..51a1b31e 100644 --- a/src/wolfguard/wg_packet.c +++ b/src/wolfguard/wg_packet.c @@ -49,7 +49,6 @@ static uint64_t wg_le64_decode(uint64_t v) /* * Replay counter validation (sliding window) * */ - int wg_counter_validate(struct wg_keypair *kp, uint64_t counter) { uint64_t diff; @@ -95,7 +94,6 @@ int wg_counter_validate(struct wg_keypair *kp, uint64_t counter) /* * Pad plaintext to 16-byte multiple (WireGuard spec requirement) * */ - static size_t wg_pad_len(size_t len) { size_t padded = len; @@ -105,9 +103,10 @@ static size_t wg_pad_len(size_t len) } /* - * Find keypair by receiver index (linear scan — fine for small N) + * Find keypair by receiver index. + * Linear scan, which is fine for small N (WOLFGUARD_MAX_PEERS + * defaults to 8). * */ - static struct wg_peer *wg_find_peer_by_index(struct wg_device *dev, uint32_t receiver_index, struct wg_keypair **kp_out) @@ -150,7 +149,6 @@ static struct wg_peer *wg_find_peer_by_index(struct wg_device *dev, * says "after queuing the packet"). Dropping new arrivals is simpler * and avoids memmove/ring-buffer overhead on an embedded target. * */ - static void wg_stage_packet(struct wg_peer *peer, const uint8_t *packet, size_t len) { @@ -168,7 +166,6 @@ static void wg_stage_packet(struct wg_peer *peer, /* * TX: encrypt and send a plaintext IP packet as WG data message * */ - int wg_packet_send(struct wg_device *dev, struct wg_peer *peer, const uint8_t *plaintext, size_t len) { @@ -182,7 +179,9 @@ int wg_packet_send(struct wg_device *dev, struct wg_peer *peer, /* Check for valid sending keypair */ if (kp == NULL || !kp->sending.is_valid) { - /* No valid session — stage packet and initiate handshake */ + /* No valid session, so we + * stage packet and initiate handshake + */ wg_stage_packet(peer, plaintext, len); if (peer->handshake.state == WG_HANDSHAKE_ZEROED) { struct wg_msg_initiation init_msg; @@ -283,7 +282,6 @@ int wg_packet_send(struct wg_device *dev, struct wg_peer *peer, /* * Send staged (queued) packets after handshake completes * */ - void wg_packet_send_staged(struct wg_device *dev, struct wg_peer *peer) { int i; @@ -315,7 +313,6 @@ void wg_packet_send_staged(struct wg_device *dev, struct wg_peer *peer) /* * Send keepalive (empty encrypted data message) * */ - int wg_packet_send_keepalive(struct wg_device *dev, struct wg_peer *peer) { struct wg_keypair *kp = peer->keypairs.current; @@ -370,7 +367,6 @@ int wg_packet_send_keepalive(struct wg_device *dev, struct wg_peer *peer) /* * RX: handle incoming data message (type 4) * */ - static void wg_handle_data(struct wg_device *dev, const uint8_t *data, size_t len, uint32_t src_ip, uint16_t src_port) { @@ -464,7 +460,6 @@ static void wg_handle_data(struct wg_device *dev, const uint8_t *data, /* * RX: handle incoming handshake initiation (type 1) * */ - static void wg_handle_initiation(struct wg_device *dev, const uint8_t *data, size_t len, uint32_t src_ip, uint16_t src_port) @@ -550,7 +545,6 @@ static void wg_handle_initiation(struct wg_device *dev, const uint8_t *data, /* * RX: handle incoming handshake response (type 2) * */ - static void wg_handle_response(struct wg_device *dev, const uint8_t *data, size_t len, uint32_t src_ip, uint16_t src_port) @@ -640,7 +634,6 @@ static void wg_handle_response(struct wg_device *dev, const uint8_t *data, /* * RX: handle incoming cookie reply (type 3) * */ - static void wg_handle_cookie(struct wg_device *dev, const uint8_t *data, size_t len) { @@ -674,7 +667,6 @@ static void wg_handle_cookie(struct wg_device *dev, const uint8_t *data, /* * RX: main dispatch, receive and dispatch incoming WG message * */ - void wg_packet_receive(struct wg_device *dev, const uint8_t *data, size_t len, uint32_t src_ip, uint16_t src_port) { diff --git a/src/wolfguard/wg_timers.c b/src/wolfguard/wg_timers.c index a2926bc0..2d9c770d 100644 --- a/src/wolfguard/wg_timers.c +++ b/src/wolfguard/wg_timers.c @@ -35,7 +35,6 @@ static void wg_regenerate_jitter(struct wg_peer *peer, WC_RNG *rng) /* * Timer event notifications (called from packet processing) * */ - void wg_timers_data_sent(struct wg_peer *peer, uint64_t now) { peer->timer_last_data_sent = now; @@ -59,220 +58,229 @@ void wg_timers_handshake_complete(struct wg_peer *peer, uint64_t now) } /* - * Main timer tick, this gets called called every wolfIP_poll() cycle + * Reset a peer's handshake to a clean, un-started state while keeping the + * long-term key material (remote static, precomputed static-static DH, PSK) + * needed to start a fresh handshake later. + * + * wg_noise_handshake_init() snapshots the PSK before it zeroes the handshake, + * so passing the peer's own (aliased) preshared_key pointer is safe. + * */ +static void wg_reset_handshake(struct wg_device *dev, struct wg_peer *peer) +{ + wg_noise_handshake_init(&peer->handshake, dev->static_private, + peer->public_key, peer->handshake.preshared_key, + &dev->rng); +} + +/* + * Start a fresh handshake with the peer: generate new ephemeral keys, build an + * initiation message, attach its mac1/mac2, send it to the peer's endpoint, and + * arm the retransmit/attempt timers. No-op on any construction failure. + * */ +static void wg_send_handshake_initiation(struct wg_device *dev, + struct wg_peer *peer, uint64_t now_ms) +{ + struct wg_msg_initiation msg; + struct wolfIP_sockaddr_in dst; + + wg_reset_handshake(dev, peer); /* fresh ephemeral keys */ + + if (wg_noise_create_initiation(dev, peer, &msg) != 0) + return; + + wg_cookie_add_macs(peer, &msg, sizeof(msg), + offsetof(struct wg_msg_initiation, macs), now_ms); + + memset(&dst, 0, sizeof(dst)); + dst.sin_family = AF_INET; + dst.sin_addr.s_addr = peer->endpoint_ip; + dst.sin_port = peer->endpoint_port; + + wolfIP_sock_sendto(dev->stack, dev->udp_sock_fd, &msg, sizeof(msg), 0, + (const struct wolfIP_sockaddr *)&dst, sizeof(dst)); + + wg_timers_handshake_initiated(peer, now_ms); +} + +/* + * Handshake retransmit / give-up. + * + * From the spec (Section 6.4): + * "if a handshake response message is not subsequently received after + * Rekey-Timeout seconds, a new handshake initiation message is constructed + * (with new random ephemeral keys) and sent. This reinitiation is attempted + * for Rekey-Attempt-Time seconds before giving up" + * + * We retransmit every REKEY_TIMEOUT (5s) with fresh ephemeral keys. After + * WG_MAX_HANDSHAKE_ATTEMPTS (18) retries (18 * 5s = 90s = REKEY_ATTEMPT_TIME) + * we give up and clear the handshake state. + * + * Note: the spec mentions "critically important future work includes adjusting + * the Rekey-Timeout value to use exponential backoff." The kernel WireGuard + * implementation still uses the fixed 5s interval, so we follow that. + * */ +static void wg_timer_handshake_retransmit(struct wg_device *dev, + struct wg_peer *peer, uint64_t now_ms) +{ + if (peer->handshake.state != WG_HANDSHAKE_CREATED_INITIATION || + peer->timer_handshake_initiated == 0) + return; + + if (peer->handshake_attempts >= WG_MAX_HANDSHAKE_ATTEMPTS) { + /* Gave up after REKEY_ATTEMPT_TIME of retries: drop the in-flight + * handshake but keep long-term keys so a later send can re-initiate. */ + wg_reset_handshake(dev, peer); + peer->handshake_attempts = 0; + peer->timer_handshake_initiated = 0; + } else if (now_ms - peer->timer_handshake_initiated >= MS(WG_REKEY_TIMEOUT)) { + wg_send_handshake_initiation(dev, peer, now_ms); + } +} + +/* + * Passive keepalive: we received data recently but have not sent anything + * back, so emit an empty keepalive to acknowledge the peer. * */ +static void wg_timer_passive_keepalive(struct wg_device *dev, + struct wg_peer *peer, uint64_t now_ms) +{ + struct wg_keypair *current = peer->keypairs.current; + + if (current == NULL || !current->sending.is_valid) + return; + /* Must have received data within the last KEEPALIVE_TIMEOUT */ + if (peer->timer_last_data_received == 0 || + now_ms - peer->timer_last_data_received >= MS(WG_KEEPALIVE_TIMEOUT)) + return; + /* ...and not have sent data recently */ + if (peer->timer_last_data_sent != 0 && + now_ms - peer->timer_last_data_sent < MS(WG_KEEPALIVE_TIMEOUT)) + return; + /* ...and not have sent a keepalive recently */ + if (peer->timer_last_keepalive_sent != 0 && + now_ms - peer->timer_last_keepalive_sent < MS(WG_KEEPALIVE_TIMEOUT)) + return; + + wg_packet_send_keepalive(dev, peer); + peer->timer_last_keepalive_sent = now_ms; +} + +/* + * Rekey after time (initiator only, with jitter): proactively start a new + * handshake once the current session reaches REKEY_AFTER_TIME, before it can + * expire at REJECT_AFTER_TIME. + * */ +static void wg_timer_rekey_after_time(struct wg_device *dev, + struct wg_peer *peer, uint64_t now_ms) +{ + struct wg_keypair *current = peer->keypairs.current; + + if (current == NULL || !current->sending.is_valid || + !current->i_am_initiator || + peer->handshake.state != WG_HANDSHAKE_ZEROED) + return; + if (now_ms - current->sending.birthdate < + MS(WG_REKEY_AFTER_TIME) + peer->rekey_jitter_ms) + return; + + wg_regenerate_jitter(peer, &dev->rng); + wg_send_handshake_initiation(dev, peer, now_ms); +} +/* + * New handshake on stale receive (with jitter): we sent data but have not heard + * back, so re-initiate to recover a possibly-dead session. + * */ +static void wg_timer_initiate_after_stale(struct wg_device *dev, + struct wg_peer *peer, uint64_t now_ms) +{ + struct wg_keypair *current = peer->keypairs.current; + + if (current == NULL || + peer->handshake.state != WG_HANDSHAKE_ZEROED || + peer->timer_last_data_sent == 0 || + now_ms - peer->timer_last_data_sent >= + MS(WG_KEEPALIVE_TIMEOUT + WG_REKEY_TIMEOUT)) + return; + /* Only when we sent more recently than we received (awaiting a reply) */ + if (peer->timer_last_data_received != 0 && + peer->timer_last_data_sent <= peer->timer_last_data_received) + return; + /* Don't re-initiate if we already did recently */ + if (peer->timer_handshake_initiated != 0 && + now_ms - peer->timer_handshake_initiated < + MS(WG_REKEY_TIMEOUT) + peer->rekey_jitter_ms) + return; + + wg_regenerate_jitter(peer, &dev->rng); + wg_send_handshake_initiation(dev, peer, now_ms); +} + +/* + * Zero key material after REJECT_AFTER_TIME * 3: the session is long dead and + * cannot be revived, so wipe the keypairs and reset the handshake. + * */ +static void wg_timer_zero_expired_keys(struct wg_device *dev, + struct wg_peer *peer, uint64_t now_ms) +{ + struct wg_keypair *current = peer->keypairs.current; + + if (current == NULL || + now_ms - current->sending.birthdate < MS(WG_REJECT_AFTER_TIME) * 3ULL) + return; + + wg_memzero(&peer->keypairs.keypair_slots, + sizeof(peer->keypairs.keypair_slots)); + peer->keypairs.current = NULL; + peer->keypairs.previous = NULL; + peer->keypairs.next = NULL; + + wg_reset_handshake(dev, peer); +} + +/* + * Persistent keepalive: if configured, send an empty keepalive whenever the + * link has been idle for the configured interval. + * */ +static void wg_timer_persistent_keepalive(struct wg_device *dev, + struct wg_peer *peer, uint64_t now_ms) +{ + struct wg_keypair *current = peer->keypairs.current; + + if (peer->persistent_keepalive_interval == 0 || + current == NULL || !current->sending.is_valid) + return; + if (peer->timer_last_data_sent != 0 && + now_ms - peer->timer_last_data_sent < + MS(peer->persistent_keepalive_interval)) + return; + + wg_packet_send_keepalive(dev, peer); + peer->timer_last_keepalive_sent = now_ms; +} + +/* + * Main timer tick: called every wolfIP_poll() cycle. Each active peer is run + * through the timer rules in order; a rule may change state that a later rule + * observes in the same tick (e.g. initiating a handshake sets the state that + * suppresses the stale-receive rule), so the ordering is significant. + * */ void wg_timers_tick(struct wg_device *dev, uint64_t now_ms) { int i; for (i = 0; i < WOLFGUARD_MAX_PEERS; i++) { struct wg_peer *peer = &dev->peers[i]; - struct wg_keypair *current; if (!peer->is_active) continue; - current = peer->keypairs.current; - - /* Handshake retransmit - * - * From the spec (Section 6.4): - * "if a handshake response message is not subsequently received - * after Rekey-Timeout seconds, a new handshake initiation message - * is constructed (with new random ephemeral keys) and sent. - * This reinitiation is attempted for Rekey-Attempt-Time seconds - * before giving up" - * - * We retransmit every REKEY_TIMEOUT (5s) with fresh ephemeral keys. - * After WG_MAX_HANDSHAKE_ATTEMPTS (18) retries (18 * 5s = 90s = - * REKEY_ATTEMPT_TIME), we give up and clear the handshake state. - * - * Note: the spec mentions "critically important future work includes - * adjusting the Rekey-Timeout value to use exponential backoff." - * The kernel WireGuard implementation still uses the fixed 5s interval, - * so we follow that. - * */ - if (peer->handshake.state == WG_HANDSHAKE_CREATED_INITIATION && - peer->timer_handshake_initiated > 0) { - - if (peer->handshake_attempts >= WG_MAX_HANDSHAKE_ATTEMPTS) { - /* Give up after REKEY_ATTEMPT_TIME worth of retries. - * Re-initialize handshake: zero ephemeral/session state - * but restore long-term keys so future sends can - * re-initiate a fresh handshake. */ - { - uint8_t psk[WG_SYMMETRIC_KEY_LEN]; - memcpy(psk, peer->handshake.preshared_key, - WG_SYMMETRIC_KEY_LEN); - wg_noise_handshake_init(&peer->handshake, - dev->static_private, - peer->public_key, - psk, &dev->rng); - wg_memzero(psk, sizeof(psk)); - } - peer->handshake_attempts = 0; - peer->timer_handshake_initiated = 0; - } else if (now_ms - peer->timer_handshake_initiated >= - MS(WG_REKEY_TIMEOUT)) { - /* Retransmit initiation */ - struct wg_msg_initiation msg; - struct wolfIP_sockaddr_in dst; - - /* Re-init handshake for fresh ephemeral */ - wg_noise_handshake_init(&peer->handshake, - dev->static_private, - peer->public_key, - peer->handshake.preshared_key, - &dev->rng); - - if (wg_noise_create_initiation(dev, peer, &msg) == 0) { - size_t mac_off = - offsetof(struct wg_msg_initiation, macs); - wg_cookie_add_macs(peer, &msg, sizeof(msg), mac_off, now_ms); - - memset(&dst, 0, sizeof(dst)); - dst.sin_family = AF_INET; - dst.sin_addr.s_addr = peer->endpoint_ip; - dst.sin_port = peer->endpoint_port; - - wolfIP_sock_sendto(dev->stack, dev->udp_sock_fd, - &msg, sizeof(msg), 0, - (const struct wolfIP_sockaddr *)&dst, sizeof(dst)); - - wg_timers_handshake_initiated(peer, now_ms); - } - } - } - - /* Passive keepalive: received data recently but haven't sent */ - if (current != NULL && current->sending.is_valid && - peer->timer_last_data_received > 0 && - now_ms - peer->timer_last_data_received < MS(WG_KEEPALIVE_TIMEOUT) && - (peer->timer_last_data_sent == 0 || - now_ms - peer->timer_last_data_sent >= - MS(WG_KEEPALIVE_TIMEOUT)) && - (peer->timer_last_keepalive_sent == 0 || - now_ms - peer->timer_last_keepalive_sent >= - MS(WG_KEEPALIVE_TIMEOUT))) { - - wg_packet_send_keepalive(dev, peer); - peer->timer_last_keepalive_sent = now_ms; - } - - /* Rekey after time (initiator only, with jitter) */ - if (current != NULL && current->sending.is_valid && - current->i_am_initiator && - now_ms - current->sending.birthdate >= - MS(WG_REKEY_AFTER_TIME) + peer->rekey_jitter_ms && - peer->handshake.state == WG_HANDSHAKE_ZEROED) { - - struct wg_msg_initiation msg; - struct wolfIP_sockaddr_in dst; - - wg_regenerate_jitter(peer, &dev->rng); - - wg_noise_handshake_init(&peer->handshake, - dev->static_private, - peer->public_key, - peer->handshake.preshared_key, - &dev->rng); - - if (wg_noise_create_initiation(dev, peer, &msg) == 0) { - size_t mac_off = offsetof(struct wg_msg_initiation, macs); - wg_cookie_add_macs(peer, &msg, sizeof(msg), mac_off, now_ms); - - memset(&dst, 0, sizeof(dst)); - dst.sin_family = AF_INET; - dst.sin_addr.s_addr = peer->endpoint_ip; - dst.sin_port = peer->endpoint_port; - - wolfIP_sock_sendto(dev->stack, dev->udp_sock_fd, - &msg, sizeof(msg), 0, - (const struct wolfIP_sockaddr *)&dst, sizeof(dst)); - - wg_timers_handshake_initiated(peer, now_ms); - } - } - - /* New handshake on stale receive (sent data but no reply, with jitter) */ - if (current != NULL && - peer->timer_last_data_sent > 0 && - now_ms - peer->timer_last_data_sent < - MS(WG_KEEPALIVE_TIMEOUT + WG_REKEY_TIMEOUT) && - (peer->timer_last_data_received == 0 || - peer->timer_last_data_sent > peer->timer_last_data_received) && - peer->handshake.state == WG_HANDSHAKE_ZEROED) { - - /* Don't re-initiate if we already did recently */ - if (peer->timer_handshake_initiated == 0 || - now_ms - peer->timer_handshake_initiated >= - MS(WG_REKEY_TIMEOUT) + peer->rekey_jitter_ms) { - struct wg_msg_initiation msg; - struct wolfIP_sockaddr_in dst; - - wg_regenerate_jitter(peer, &dev->rng); - - wg_noise_handshake_init(&peer->handshake, - dev->static_private, - peer->public_key, - peer->handshake.preshared_key, - &dev->rng); - - if (wg_noise_create_initiation(dev, peer, &msg) == 0) { - size_t mac_off = - offsetof(struct wg_msg_initiation, macs); - wg_cookie_add_macs(peer, &msg, sizeof(msg), mac_off, now_ms); - - memset(&dst, 0, sizeof(dst)); - dst.sin_family = AF_INET; - dst.sin_addr.s_addr = peer->endpoint_ip; - dst.sin_port = peer->endpoint_port; - - wolfIP_sock_sendto(dev->stack, dev->udp_sock_fd, - &msg, sizeof(msg), 0, - (const struct wolfIP_sockaddr *)&dst, sizeof(dst)); - - wg_timers_handshake_initiated(peer, now_ms); - } - } - } - - /* Zero key material after REJECT_AFTER_TIME * 3 */ - if (current != NULL && - now_ms - current->sending.birthdate >= - MS(WG_REJECT_AFTER_TIME) * 3ULL) { - - wg_memzero(&peer->keypairs.keypair_slots, - sizeof(peer->keypairs.keypair_slots)); - peer->keypairs.current = NULL; - peer->keypairs.previous = NULL; - peer->keypairs.next = NULL; - - /* Re-initialize handshake: zero ephemeral/session state but - * restore long-term keys so future handshakes can proceed */ - { - uint8_t psk[WG_SYMMETRIC_KEY_LEN]; - memcpy(psk, peer->handshake.preshared_key, - WG_SYMMETRIC_KEY_LEN); - wg_noise_handshake_init(&peer->handshake, - dev->static_private, - peer->public_key, - psk, &dev->rng); - wg_memzero(psk, sizeof(psk)); - } - } - - /* Persistent keepalive */ - if (peer->persistent_keepalive_interval > 0 && - current != NULL && current->sending.is_valid && - (peer->timer_last_data_sent == 0 || - now_ms - peer->timer_last_data_sent >= - MS(peer->persistent_keepalive_interval))) { - - wg_packet_send_keepalive(dev, peer); - peer->timer_last_keepalive_sent = now_ms; - } + wg_timer_handshake_retransmit(dev, peer, now_ms); + wg_timer_passive_keepalive(dev, peer, now_ms); + wg_timer_rekey_after_time(dev, peer, now_ms); + wg_timer_initiate_after_stale(dev, peer, now_ms); + wg_timer_zero_expired_keys(dev, peer, now_ms); + wg_timer_persistent_keepalive(dev, peer, now_ms); } } diff --git a/src/wolfguard/wolfguard.c b/src/wolfguard/wolfguard.c index d68f526a..4eac0c7e 100644 --- a/src/wolfguard/wolfguard.c +++ b/src/wolfguard/wolfguard.c @@ -48,10 +48,11 @@ static int wolfguard_ll_poll(struct wolfIP_ll_dev *ll, void *buf, uint32_t len) return 0; } + +/* This is called when wolfIP routes a packet out through wg0. + * We need to find the device from the ll_dev pointer and encrypt. */ static int wolfguard_ll_send(struct wolfIP_ll_dev *ll, void *buf, uint32_t len) { - /* This is called when wolfIP routes a packet out through wg0. - * We need to find the device from the ll_dev pointer and encrypt. */ struct wg_device *dev = (struct wg_device *)ll->priv; if (dev == NULL) @@ -61,7 +62,6 @@ static int wolfguard_ll_send(struct wolfIP_ll_dev *ll, void *buf, uint32_t len) } /* UDP socket callback for incoming WireGuard messages */ - static void wg_udp_callback(int sock_fd, uint16_t events, void *arg) { struct wg_device *dev = (struct wg_device *)arg; @@ -129,8 +129,12 @@ int wolfguard_init(struct wg_device *dev, struct wolfIP *stack, ll->priv = dev; strncpy(ll->ifname, "wg0", sizeof(ll->ifname) - 1); - /* Set wg0 MTU = outer MTU - 60 (IP + UDP + WG header overhead) */ - wolfIP_mtu_set(stack, wg_if_idx, LINK_MTU - 60); + /* Size wg0 so a full-MTU inner packet still fits in one outer UDP datagram + * once padded and encapsulated. A too-generous MTU here does not fail + * loudly: wolfIP accepts the oversized packet, the outer sendto() in + * wg_packet_send() rejects the encapsulated datagram, and it is dropped + * with no error to the sender and no ICMP to the peer. See WG_IF_MTU. */ + wolfIP_mtu_set(stack, wg_if_idx, WG_IF_MTU); /* Create UDP socket for outer transport */ dev->udp_sock_fd = wolfIP_sock_socket(stack, AF_INET, SOCK_DGRAM, 0); diff --git a/src/wolfguard/wolfguard.h b/src/wolfguard/wolfguard.h index ed6df503..6ada8a34 100644 --- a/src/wolfguard/wolfguard.h +++ b/src/wolfguard/wolfguard.h @@ -68,6 +68,54 @@ #define WG_HEADER_LEN 16 /* type(4) + receiver(4) + counter(8) */ #define WG_AEAD_NONCE_LEN 16 /* AES-GCM IV */ +/* + * Tunnel MTU + * + * wg0 carries plaintext IP packets. Each one is padded up to a 16-byte + * multiple, wrapped in a WG data header plus auth tag, and handed to the outer + * UDP socket as a single datagram. wolfIP does not fragment, so a datagram + * that overshoots the outer interface's budget is rejected by sendto() and the + * packet is lost, so wg0 must be sized by working backwards from that budget. + * + * Two wolfIP conventions drive the arithmetic, and both are easy to miss: + * - a per-interface MTU is a link-layer *frame* budget, from which the stack + * subtracts an ethernet header to reach the IP budget. It does this on + * non-ethernet interfaces, wg0 included, so the reservation has to be + * added back when calling wolfIP_mtu_set(); + * - that IP budget is then capped at 1500, the IPv4 payload maximum, + * however large the link frame is (LINK_MTU is commonly 1536). + */ +#define WG_LL_HEADER_LEN 14U /* per-frame reservation in wolfIP */ +#define WG_IP_PAYLOAD_MAX 1500U /* wolfIP's IP_MTU_MAX */ +#define WG_OUTER_IP_HEADER_LEN 20U /* IPv4, no options */ +#define WG_OUTER_UDP_HEADER_LEN 8U + +/* WG data message overhead: header + auth tag, i.e. sizeof(struct wg_msg_data) + * + WG_AUTHTAG_LEN, spelled out so it stays usable by the preprocessor. */ +#define WG_DATA_MSG_OVERHEAD (WG_HEADER_LEN + WG_AUTHTAG_LEN) + +/* Largest outer IP payload an interface in this build can emit. */ +#define WG_OUTER_IP_MTU \ + (((LINK_MTU - WG_LL_HEADER_LEN) < WG_IP_PAYLOAD_MAX) \ + ? (LINK_MTU - WG_LL_HEADER_LEN) : WG_IP_PAYLOAD_MAX) + +#define WG_OUTER_UDP_PAYLOAD_MAX \ + (WG_OUTER_IP_MTU - WG_OUTER_IP_HEADER_LEN - WG_OUTER_UDP_HEADER_LEN) + +/* Inner IP budget, rounded *down* to a 16-byte multiple: the plaintext is + * padded up to one before encryption, so a budget that is not a multiple of 16 + * cannot be filled to the byte anyway. */ +#define WG_INNER_IP_MTU \ + ((WG_OUTER_UDP_PAYLOAD_MAX - WG_DATA_MSG_OVERHEAD) & ~15U) + +/* ...and back into the frame budget wolfIP_mtu_set() expects. + * With the usual LINK_MTU of 1536 this works out to 1440 + 14 = 1454. */ +#define WG_IF_MTU (WG_INNER_IP_MTU + WG_LL_HEADER_LEN) + +#if WG_OUTER_UDP_PAYLOAD_MAX <= WG_DATA_MSG_OVERHEAD || WG_IF_MTU < LINK_MTU_MIN +#error "LINK_MTU is too small to carry a wolfGuard tunnel" +#endif + /* Message Types */ #define WG_MSG_INITIATION 1 /* starts the handshake process */ @@ -198,8 +246,8 @@ struct wg_keypair { struct wg_keypairs { struct wg_keypair *current; struct wg_keypair *previous; - struct wg_keypair *next; /* Unconfirmed session for responder */ - /* Static storage — no dynamic alloc */ + struct wg_keypair *next; /* Unconfirmed session for responder */ + /* Static storage, so no dynamic alloc */ struct wg_keypair keypair_slots[3]; };