Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .github/workflows/stm32h563-m33mu-freertos.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,14 @@ jobs:

- name: Run m33mu + DHCP + TCP echo FreeRTOS test
run: /bin/bash tools/scripts/run-m33mu-ci-in-container.sh stm32h563-m33mu-freertos stm32h563_m33mu_echo_freertos

- name: Upload tap0 capture + board logs (DEBUG)
if: always()
uses: actions/upload-artifact@v4
with:
name: m33mu-echo-debug
path: |
/tmp/echo.pcap
/tmp/m33mu.log
/tmp/echo.log
if-no-files-found: warn
6 changes: 3 additions & 3 deletions src/test/test_wolfguard_loopback.c
Original file line number Diff line number Diff line change
Expand Up @@ -540,7 +540,7 @@ START_TEST(test_dos_cookie_mechanism)
memset(&init_msg, 0xAA, sizeof(init_msg));
mac_off = offsetof(struct wg_msg_initiation, macs);

ret = wg_cookie_add_macs(&peer, &init_msg, sizeof(init_msg), mac_off);
ret = wg_cookie_add_macs(&peer, &init_msg, sizeof(init_msg), mac_off, dev.now);
ck_assert_int_eq(ret, 0);

/* Verify mac2 is zero (no cookie available) */
Expand All @@ -560,13 +560,13 @@ START_TEST(test_dos_cookie_mechanism)
ck_assert_int_eq(ret, 0);

/* Step 4: Peer consumes cookie reply */
ret = wg_cookie_consume_reply(&peer, &cookie_reply);
ret = wg_cookie_consume_reply(&peer, &cookie_reply, dev.now);
ck_assert_int_eq(ret, 0);
ck_assert_int_eq(peer.cookie.is_valid, 1);

/* Step 5: Re-create initiation with mac1 + mac2 (using cookie) */
memset(&init_msg, 0xBB, sizeof(init_msg));
ret = wg_cookie_add_macs(&peer, &init_msg, sizeof(init_msg), mac_off);
ret = wg_cookie_add_macs(&peer, &init_msg, sizeof(init_msg), mac_off, dev.now);
ck_assert_int_eq(ret, 0);

/* Verify mac2 is NOT zero anymore */
Expand Down
12 changes: 11 additions & 1 deletion src/test/unit/unit.c
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,7 @@ Suite *wolf_suite(void)
tcase_add_test(tc_utils, test_multicast_udp_receive_requires_join);
tcase_add_test(tc_utils, test_multicast_udp_send_mac_ttl_loop_and_options);
tcase_add_test(tc_utils, test_multicast_igmp_query_refreshes_report);
tcase_add_test(tc_utils, test_multicast_igmp_query_flood_coalesced);
tcase_add_test(tc_utils, test_multicast_igmp_query_bad_checksum_dropped);
tcase_add_test(tc_utils, test_multicast_igmp_query_spoofed_dropped);
tcase_add_test(tc_utils, test_multicast_join_requires_configured_ip);
Expand Down Expand Up @@ -410,6 +411,8 @@ Suite *wolf_suite(void)
tcase_add_test(tc_utils, test_poll_icmp_send_on_arp_hit);
tcase_add_test(tc_utils, test_poll_icmp_send_on_arp_miss_requests_arp_and_retains_queue);
tcase_add_test(tc_utils, test_dhcp_timer_cb_paths);
tcase_add_test(tc_utils, test_dhcp_discover_retransmit_backoff);
tcase_add_test(tc_utils, test_dhcp_request_retransmit_backoff);
tcase_add_test(tc_utils, test_regression_dhcp_lease_expiry_deconfigures_address);
tcase_add_test(tc_utils, test_dhcp_request_retry_exhaustion_deconfigures_lease);
tcase_add_test(tc_utils, test_dhcp_timer_cb_send_failure_does_not_consume_retry_budget);
Expand Down Expand Up @@ -849,6 +852,7 @@ Suite *wolf_suite(void)
tcase_add_test(tc_proto, test_arp_flush_pending_ttl_expired);
tcase_add_test(tc_proto, test_wolfip_forwarding_basic);
tcase_add_test(tc_proto, test_wolfip_forwarding_ttl_expired);
tcase_add_test(tc_proto, test_regression_forwarding_no_ttl_exceeded_for_icmp_error);
tcase_add_test(tc_proto, test_forward_packet_ip_filter_drop);
tcase_add_test(tc_proto, test_forward_packet_eth_filter_drop);
tcase_add_test(tc_proto, test_loopback_dest_not_forwarded);
Expand Down Expand Up @@ -1190,7 +1194,7 @@ Suite *wolf_suite(void)
tcase_add_test(tc_core, test_notify_loopback_null_stack_no_crash);

/* === Branch-coverage tests from fleet ===*/
/* --- unit_tests_tcp_state.c (62 tests) --- */
/* --- unit_tests_tcp_state.c (65 tests) --- */
tcase_add_test(tc_core, test_tcp_send_reset_reply_ignores_rst_input);
tcase_add_test(tc_core, test_tcp_send_reset_reply_ack_in_uses_ack_seq);
tcase_add_test(tc_core, test_tcp_send_reset_reply_syn_no_ack_sets_rst_ack);
Expand All @@ -1201,10 +1205,12 @@ Suite *wolf_suite(void)
tcase_add_test(tc_core, test_tcp_parse_options_timestamp_parsed);
tcase_add_test(tc_core, test_tcp_parse_options_timestamp_overlong_ignored);
tcase_add_test(tc_core, test_tcp_parse_options_mss_zero_ignored);
tcase_add_test(tc_core, test_tcp_parse_options_mss_below_floor_clamped);
tcase_add_test(tc_core, test_tcp_parse_options_sack_permitted_parsed);
tcase_add_test(tc_core, test_tcp_input_syn_rcvd_rst_bad_seq_ignored);
tcase_add_test(tc_core, test_tcp_input_syn_rcvd_rst_good_seq_reverts_to_listen);
tcase_add_test(tc_core, test_tcp_input_syn_rcvd_rst_good_seq_nonlistener_closes);
tcase_add_test(tc_core, test_tcp_input_syn_rcvd_rst_nullcb_recv_reports_eof);
tcase_add_test(tc_core, test_tcp_input_time_wait_sends_ack_on_any_segment);
tcase_add_test(tc_core, test_tcp_input_last_ack_unacceptable_sends_ack);
tcase_add_test(tc_core, test_tcp_input_last_ack_syn_sends_challenge_ack);
Expand Down Expand Up @@ -1258,6 +1264,10 @@ Suite *wolf_suite(void)
tcase_add_test(tc_core, test_tcp_input_fin_wait_1_fin_enters_closing);
tcase_add_test(tc_core, test_tcp_input_fin_wait_2_fin_enters_time_wait);
tcase_add_test(tc_core, test_tcp_input_rst_in_window_not_exact_sends_ack);
tcase_add_test(tc_core, test_sock_close_established_disarms_callback);
tcase_add_test(tc_core, test_sock_close_close_wait_disarms_callback);
tcase_add_test(tc_core, test_rst_in_fin_wait_1_delivers_close_event);
tcase_add_test(tc_core, test_last_ack_final_ack_delivers_close_event);
/* --- unit_tests_poll_dispatcher.c (47 tests) --- */
tcase_add_test(tc_core, test_poll_device_poll_returns_zero_exits_loop);
tcase_add_test(tc_core, test_poll_device_poll_returns_negative_exits_loop);
Expand Down
2 changes: 2 additions & 0 deletions src/test/unit/unit_shared.c
Original file line number Diff line number Diff line change
Expand Up @@ -79,12 +79,14 @@ static uint32_t memsz = 8 * 1024;
static const uint8_t ifmac[] = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55};
static uint8_t last_frame_sent[LINK_MTU];
static uint32_t last_frame_sent_size = 0;
static uint32_t last_frame_sent_count = 0;

static int mock_send(struct wolfIP_ll_dev *dev, void *frame, uint32_t len)
{
(void)dev;
memcpy(last_frame_sent, frame, len);
last_frame_sent_size = len;
last_frame_sent_count++;
return 0;
}

Expand Down
85 changes: 85 additions & 0 deletions src/test/unit/unit_tests_dns_dhcp.c
Original file line number Diff line number Diff line change
Expand Up @@ -4310,6 +4310,91 @@ START_TEST(test_dhcp_timer_cb_paths)
}
END_TEST

/* RFC 2131 §4.1: DHCPDISCOVER retransmissions must back off exponentially
* (doubling each attempt) instead of repeating on a fixed ~2s cadence.
* dhcp_timer_cb() reschedules using the current dhcp_timeout_count, so drive it
* at successive counts and capture the scheduled delay; with the fixed-interval
* scheme all three are equal, which the growth assertions below reject. */
START_TEST(test_dhcp_discover_retransmit_backoff)
{
struct wolfIP s;
uint64_t delay0, delay1, delay2;
const uint64_t now = 100000U;

wolfIP_init(&s);
mock_link_init(&s);
s.dhcp_udp_sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_UDP);
ck_assert_int_gt(s.dhcp_udp_sd, 0);
s.dhcp_xid = 1;

s.dhcp_state = DHCP_DISCOVER_SENT;
s.last_tick = now;
s.dhcp_timeout_count = 0;
dhcp_timer_cb(&s);
ck_assert_uint_eq(s.dhcp_timeout_count, 1U); /* retransmit queued */
delay0 = find_timer_expiry(&s, s.dhcp_timer) - now;

s.dhcp_state = DHCP_DISCOVER_SENT;
s.last_tick = now;
s.dhcp_timeout_count = 1;
dhcp_timer_cb(&s);
delay1 = find_timer_expiry(&s, s.dhcp_timer) - now;

s.dhcp_state = DHCP_DISCOVER_SENT;
s.last_tick = now;
s.dhcp_timeout_count = 2;
dhcp_timer_cb(&s);
delay2 = find_timer_expiry(&s, s.dhcp_timer) - now;

/* Fixed-cadence (current) scheme makes all three equal. */
ck_assert_uint_gt(delay1, delay0);
ck_assert_uint_gt(delay2, delay1);
/* The test RNG is deterministic, so the jitter is identical on every call
* and cancels in the deltas: exponential backoff means each step is twice
* the previous one. */
ck_assert_uint_eq(delay2 - delay1, 2U * (delay1 - delay0));
}
END_TEST

/* Same exponential-backoff requirement for DHCPREQUEST retransmissions, which
* are scheduled through dhcp_schedule_retry_timer() rather than inline. */
START_TEST(test_dhcp_request_retransmit_backoff)
{
struct wolfIP s;
uint64_t delay0, delay1, delay2;
const uint64_t now = 100000U;

wolfIP_init(&s);
mock_link_init(&s);
s.dhcp_udp_sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_UDP);
ck_assert_int_gt(s.dhcp_udp_sd, 0);
s.dhcp_xid = 1;

s.dhcp_state = DHCP_REQUEST_SENT;
s.last_tick = now;
s.dhcp_timeout_count = 0;
dhcp_timer_cb(&s);
ck_assert_uint_eq(s.dhcp_timeout_count, 1U); /* retransmit queued */
delay0 = find_timer_expiry(&s, s.dhcp_timer) - now;

s.dhcp_state = DHCP_REQUEST_SENT;
s.last_tick = now;
s.dhcp_timeout_count = 1;
dhcp_timer_cb(&s);
delay1 = find_timer_expiry(&s, s.dhcp_timer) - now;

s.dhcp_state = DHCP_REQUEST_SENT;
s.last_tick = now;
s.dhcp_timeout_count = 2;
dhcp_timer_cb(&s);
delay2 = find_timer_expiry(&s, s.dhcp_timer) - now;

ck_assert_uint_gt(delay1, delay0);
ck_assert_uint_gt(delay2, delay1);
ck_assert_uint_eq(delay2 - delay1, 2U * (delay1 - delay0));
}
END_TEST

START_TEST(test_regression_dhcp_lease_expiry_deconfigures_address)
{
struct wolfIP s;
Expand Down
73 changes: 71 additions & 2 deletions src/test/unit/unit_tests_multicast.c
Original file line number Diff line number Diff line change
Expand Up @@ -266,8 +266,74 @@ START_TEST(test_multicast_igmp_query_refreshes_report)
put_be16(igmp + 2, ip_checksum_buf(igmp, IGMPV3_QUERY_MIN_LEN));
fix_ip_checksum(ip);

/* RFC 3376 §5.2: the report is deferred to a timer, not emitted from the
* receive path. Max Resp Code 0 floors the delay to 1 ms, so it fires on
* the next poll. */
last_frame_sent_size = 0;
wolfIP_recv_ex(&s, TEST_PRIMARY_IF, frame, sizeof(frame));
ck_assert_uint_eq(last_frame_sent_size, 0);
wolfIP_poll(&s, 2);
ck_assert_uint_gt(last_frame_sent_size, 0);
ck_assert_uint_eq(last_igmp_payload()[8], IGMPV3_REC_MODE_IS_EXCLUDE);
}
END_TEST

/* A Membership Query must not draw an immediate, undelayed report. RFC 3376
* §5.2 requires a host to defer its Current-State Report by a delay chosen
* uniformly in (0, Max Resp Time], and §5.2 rule 1 says a query that arrives
* while a response is already pending must not schedule another. Together this
* coalesces a query flood into a single deferred report: an on-link attacker
* spraying General Queries (TTL 1 -> 224.0.0.1) can otherwise force one report
* per query, draining a constrained host. Max Resp Code 100 -> 10 s window. */
START_TEST(test_multicast_igmp_query_flood_coalesced)
{
struct wolfIP s;
int sd;
struct wolfIP_ip_mreq mreq;
uint8_t frame[ETH_HEADER_LEN + IP_HEADER_LEN + IGMPV3_QUERY_MIN_LEN];
struct wolfIP_ip_packet *ip = (struct wolfIP_ip_packet *)frame;
uint8_t *igmp = frame + ETH_HEADER_LEN + IP_HEADER_LEN;
ip4 group = 0xE9010207U;
unsigned int q;

wolfIP_init(&s);
mock_link_init(&s);
wolfIP_ipconfig_set(&s, 0x0A000002U, 0xFFFFFF00U, 0);
sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_UDP);
ck_assert_int_gt(sd, 0);
multicast_mreq(&mreq, group, IPADDR_ANY);
ck_assert_int_eq(wolfIP_sock_setsockopt(&s, sd, WOLFIP_SOL_IP,
WOLFIP_IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)), 0);

/* General query, Max Resp Code 100 (= 10 s). */
memset(frame, 0, sizeof(frame));
memcpy(ip->eth.dst, "\x01\x00\x5e\x00\x00\x01", 6);
memcpy(ip->eth.src, "\x02\x00\x00\x00\x00\x01", 6);
ip->eth.type = ee16(ETH_TYPE_IP);
ip->ver_ihl = 0x45;
ip->ttl = 1;
ip->proto = WI_IPPROTO_IGMP;
ip->len = ee16(IP_HEADER_LEN + IGMPV3_QUERY_MIN_LEN);
ip->src = ee32(0x0A000001U);
ip->dst = ee32(IGMP_ALL_HOSTS);
igmp[0] = IGMP_TYPE_MEMBERSHIP_QUERY;
igmp[1] = 100;
put_be32(igmp + 4, group);
put_be16(igmp + 2, ip_checksum_buf(igmp, IGMPV3_QUERY_MIN_LEN));
fix_ip_checksum(ip);

/* Feed a burst of identical queries without polling. None may be answered
* synchronously from the receive path; the report is deferred to a timer. */
last_frame_sent_count = 0;
for (q = 0; q < 5; q++)
wolfIP_recv_ex(&s, TEST_PRIMARY_IF, frame, sizeof(frame));
ck_assert_uint_eq(last_frame_sent_count, 0);

/* Poll past the 10 s window: the whole burst collapses to exactly one
* deferred Current-State Report (mode IS_EXCLUDE). */
last_frame_sent_size = 0;
wolfIP_poll(&s, 10001);
ck_assert_uint_eq(last_frame_sent_count, 1);
ck_assert_uint_gt(last_frame_sent_size, 0);
ck_assert_uint_eq(last_igmp_payload()[8], IGMPV3_REC_MODE_IS_EXCLUDE);
}
Expand Down Expand Up @@ -380,8 +446,9 @@ START_TEST(test_multicast_igmp_query_spoofed_dropped)
wolfIP_recv_ex(&s, TEST_PRIMARY_IF, frame, sizeof(frame));
ck_assert_uint_eq(last_frame_sent_size, 0);

/* Sanity: a compliant query (TTL 1, all-hosts dst) still solicits a report,
* so the guards did not over-block. */
/* Sanity: a compliant query (TTL 1, all-hosts dst) still solicits a report
* (deferred per RFC 3376 §5.2, then emitted on poll), so the guards did not
* over-block. */
memset(frame, 0, sizeof(frame));
memcpy(ip->eth.dst, "\x01\x00\x5e\x00\x00\x01", 6);
memcpy(ip->eth.src, "\x02\x00\x00\x00\x00\x01", 6);
Expand All @@ -398,6 +465,8 @@ START_TEST(test_multicast_igmp_query_spoofed_dropped)
fix_ip_checksum(ip);
last_frame_sent_size = 0;
wolfIP_recv_ex(&s, TEST_PRIMARY_IF, frame, sizeof(frame));
ck_assert_uint_eq(last_frame_sent_size, 0);
wolfIP_poll(&s, 2);
ck_assert_uint_gt(last_frame_sent_size, 0);
}
END_TEST
Expand Down
87 changes: 87 additions & 0 deletions src/test/unit/unit_tests_proto.c
Original file line number Diff line number Diff line change
Expand Up @@ -3865,6 +3865,93 @@ START_TEST(test_wolfip_forwarding_ttl_expired)
}
END_TEST

/* Regression: an ICMP error message MUST NOT trigger another ICMP error
* (RFC 1812 sec 4.3.2.7, RFC 1122 sec 3.2.2). When forwarding an ICMP error
* datagram (type 3, 4, 5, 11, 12) whose TTL has reached 1, wolfIP must
* silently drop it instead of emitting a Time Exceeded reply to the source. */
START_TEST(test_regression_forwarding_no_ttl_exceeded_for_icmp_error)
{
struct wolfIP s;
uint8_t frame_buf[64];
struct wolfIP_ip_packet *frame = (struct wolfIP_ip_packet *)frame_buf;
uint8_t src_mac[6] = {0x52, 0x54, 0x00, 0xAA, 0xBB, 0xCC};
uint8_t iface1_mac[6] = {0x02, 0x00, 0x00, 0x00, 0x00, 0x03};
uint32_t dest_ip = 0xC0A80110;
/* The ICMP error types that must not provoke a Time Exceeded reply. */
uint8_t icmp_error_types[5] = {ICMP_DEST_UNREACH, 4 /* Source Quench */,
5 /* Redirect */, ICMP_TTL_EXCEEDED, 12 /* Parameter Problem */};
unsigned int t;

for (t = 0; t < 5; t++) {
wolfIP_init(&s);
mock_link_init(&s);
mock_link_init_idx(&s, TEST_SECOND_IF, iface1_mac);
wolfIP_ipconfig_set(&s, 0xC0A80001, 0xFFFFFF00, 0);
wolfIP_ipconfig_set_ex(&s, TEST_SECOND_IF, 0xC0A80101, 0xFFFFFF00, 0);

memset(frame_buf, 0, sizeof(frame_buf));
memcpy(frame->eth.dst, s.ll_dev[TEST_PRIMARY_IF].mac, 6);
memcpy(frame->eth.src, src_mac, 6);
frame->eth.type = ee16(ETH_TYPE_IP);
frame->ver_ihl = 0x45;
frame->ttl = 1;
frame->proto = WI_IPPROTO_ICMP;
frame->len = ee16(IP_HEADER_LEN + 8);
frame->src = ee32(0xC0A800AA);
frame->dst = ee32(dest_ip);
/* Embedded ICMP message: type is the first byte of the IP payload. */
frame->data[0] = icmp_error_types[t];
frame->csum = 0;
iphdr_set_checksum(frame);

memset(last_frame_sent, 0, sizeof(last_frame_sent));
last_frame_sent_size = 0;

wolfIP_recv_ex(&s, TEST_PRIMARY_IF, frame,
ETH_HEADER_LEN + IP_HEADER_LEN + 8);

/* No Time Exceeded (or any other frame) may be emitted. */
ck_assert_uint_eq(last_frame_sent_size, 0);
}

/* Positive control: a non-error ICMP message (Echo Request, type 8) is a
* query, not an error, so a TTL-expired forward of it must STILL produce a
* Time Exceeded. This guards against an over-broad fix that suppresses the
* reply for every ICMP packet rather than only ICMP error messages. */
wolfIP_init(&s);
mock_link_init(&s);
mock_link_init_idx(&s, TEST_SECOND_IF, iface1_mac);
wolfIP_ipconfig_set(&s, 0xC0A80001, 0xFFFFFF00, 0);
wolfIP_ipconfig_set_ex(&s, TEST_SECOND_IF, 0xC0A80101, 0xFFFFFF00, 0);

memset(frame_buf, 0, sizeof(frame_buf));
memcpy(frame->eth.dst, s.ll_dev[TEST_PRIMARY_IF].mac, 6);
memcpy(frame->eth.src, src_mac, 6);
frame->eth.type = ee16(ETH_TYPE_IP);
frame->ver_ihl = 0x45;
frame->ttl = 1;
frame->proto = WI_IPPROTO_ICMP;
frame->len = ee16(IP_HEADER_LEN + 8);
frame->src = ee32(0xC0A800AA);
frame->dst = ee32(dest_ip);
frame->data[0] = ICMP_ECHO_REQUEST;
frame->csum = 0;
iphdr_set_checksum(frame);

memset(last_frame_sent, 0, sizeof(last_frame_sent));
last_frame_sent_size = 0;

wolfIP_recv_ex(&s, TEST_PRIMARY_IF, frame,
ETH_HEADER_LEN + IP_HEADER_LEN + 8);

ck_assert_uint_eq(last_frame_sent_size,
(uint32_t)(ETH_HEADER_LEN + IP_HEADER_LEN + ICMP_TTL_EXCEEDED_SIZE));
ck_assert_uint_eq(
((struct wolfIP_icmp_ttl_exceeded_packet *)last_frame_sent)->type,
ICMP_TTL_EXCEEDED);
}
END_TEST

START_TEST(test_loopback_dest_not_forwarded)
{
struct wolfIP s;
Expand Down
Loading
Loading