diff --git a/src/test/unit/unit.c b/src/test/unit/unit.c index 7124598..70898d0 100644 --- a/src/test/unit/unit.c +++ b/src/test/unit/unit.c @@ -13853,7 +13853,100 @@ START_TEST(test_icmp_socket_send_recv) } END_TEST +START_TEST(test_regression_snd_una_initialized_on_syn_rcvd) +{ + struct wolfIP s; + int sd; + struct wolfIP_sockaddr_in sin; + struct tsocket *ts; + ip4 local_ip = 0x0A000001U; + uint16_t local_port = 8080; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, local_ip, 0xFFFFFF00U, 0); + + sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_STREAM, WI_IPPROTO_TCP); + ck_assert_int_gt(sd, 0); + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_port = ee16(local_port); + sin.sin_addr.s_addr = ee32(local_ip); + wolfIP_sock_bind(&s, sd, (struct wolfIP_sockaddr *)&sin, sizeof(sin)); + wolfIP_sock_listen(&s, sd, 1); + + /* inject_tcp_syn uses a deterministic PRNG seed so seq is reproducible */ + inject_tcp_syn(&s, TEST_PRIMARY_IF, local_ip, local_port); + + ts = &s.tcpsockets[SOCKET_UNMARK(sd)]; + ck_assert_int_eq(ts->sock.tcp.state, TCP_SYN_RCVD); + + /* snd_una must equal the ISN that was placed in seq, not 0 */ + ck_assert_uint_eq(ts->sock.tcp.snd_una, ts->sock.tcp.seq); + + /* The wrap-aware ordering invariant: snd_una <= seq */ + ck_assert_int_eq(tcp_seq_leq(ts->sock.tcp.snd_una, ts->sock.tcp.seq), 1); +} +END_TEST + +START_TEST(test_regression_duplicate_syn_rejected_on_established) +{ + struct wolfIP s; + int sd, sd2; + struct wolfIP_sockaddr_in sin; + struct tsocket *listener, *established; + ip4 local_ip = 0x0A000001U; + ip4 remote_ip = 0x0A0000A1U; /* hardcoded in inject_tcp_syn */ + uint16_t local_port = 8080; + uint16_t remote_port = 40000; /* hardcoded in inject_tcp_syn */ + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, local_ip, 0xFFFFFF00U, 0); + + /* Create a listening socket that must stay in LISTEN throughout */ + sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_STREAM, WI_IPPROTO_TCP); + ck_assert_int_gt(sd, 0); + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_port = ee16(local_port); + sin.sin_addr.s_addr = ee32(local_ip); + wolfIP_sock_bind(&s, sd, (struct wolfIP_sockaddr *)&sin, sizeof(sin)); + wolfIP_sock_listen(&s, sd, 4); + listener = &s.tcpsockets[SOCKET_UNMARK(sd)]; + ck_assert_int_eq(listener->sock.tcp.state, TCP_LISTEN); + + /* Allocate a second socket slot and wire it as ESTABLISHED with the + * same 4-tuple that inject_tcp_syn will use. This simulates a live + * connection that was established by a previous handshake. */ + sd2 = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_STREAM, WI_IPPROTO_TCP); + ck_assert_int_gt(sd2, 0); + established = &s.tcpsockets[SOCKET_UNMARK(sd2)]; + established->sock.tcp.state = TCP_ESTABLISHED; + established->local_ip = local_ip; + established->remote_ip = remote_ip; + established->if_idx = TEST_PRIMARY_IF; + established->src_port = local_port; + established->dst_port = remote_port; + established->sock.tcp.seq = 1000; + established->sock.tcp.snd_una = 1000; + established->sock.tcp.ack = 2; + established->sock.tcp.cwnd = TCP_MSS; + established->sock.tcp.peer_rwnd = TCP_MSS; + queue_init(&established->sock.tcp.rxbuf, established->rxmem, RXBUF_SIZE, 0); + fifo_init(&established->sock.tcp.txbuf, established->txmem, TXBUF_SIZE); + + /* Send a SYN whose tuple matches the established connection above. + * Without the fix (da5c792): listener → TCP_SYN_RCVD. + * With the fix: listener stays in TCP_LISTEN. */ + inject_tcp_syn(&s, TEST_PRIMARY_IF, local_ip, local_port); + + ck_assert_int_eq(listener->sock.tcp.state, TCP_LISTEN); +} +END_TEST + +/* ----------------------------------------------------------------------- */ Suite *wolf_suite(void) { @@ -14749,7 +14842,13 @@ Suite *wolf_suite(void) suite_add_tcase(s, tc_proto); tcase_add_test(tc_proto, test_tcp_handshake_and_fin_close_wait); suite_add_tcase(s, tc_proto); - + + /* Regression tests for fixes-4 branch */ + tcase_add_test(tc_proto, test_regression_snd_una_initialized_on_syn_rcvd); + suite_add_tcase(s, tc_proto); + tcase_add_test(tc_proto, test_regression_duplicate_syn_rejected_on_established); + suite_add_tcase(s, tc_proto); + tcase_add_test(tc_utils, test_transport_checksum); suite_add_tcase(s, tc_proto); tcase_add_test(tc_utils, test_iphdr_set_checksum); diff --git a/src/wolfip.c b/src/wolfip.c index 534c3a2..7f1f2e0 100644 --- a/src/wolfip.c +++ b/src/wolfip.c @@ -119,10 +119,10 @@ struct wolfIP_icmp_packet; /* Macros */ #define IS_IP_BCAST(ip) (ip == 0xFFFFFFFF) -#define PKT_FLAG_SENT 0x01 -#define PKT_FLAG_ACKED 0x02 -#define PKT_FLAG_FIN 0x04 -#define PKT_FLAG_RETRANS 0x08 +#define PKT_FLAG_SENT 0x01U +#define PKT_FLAG_ACKED 0x02U +#define PKT_FLAG_FIN 0x04U +#define PKT_FLAG_RETRANS 0x08U #define TX_WRITABLE_THRESHOLD 1 #define TCP_SACK_MAX_BLOCKS 4 @@ -504,6 +504,16 @@ static uint32_t queue_len(struct queue *q) return (q->size - 1) - queue_space(q); } + +/* Subtract two TCP sequence numbers: a - b (wraps at 2^32) */ +static inline uint32_t tcp_seq_diff(uint32_t a, uint32_t b) +{ + if (a >= b) + return a - b; + return UINT32_MAX - (b - a - 1); +} + + /* Insert data into the queue */ static int queue_insert(struct queue *q, void *data, uint32_t seq, uint32_t len) { @@ -526,7 +536,7 @@ static int queue_insert(struct queue *q, void *data, uint32_t seq, uint32_t len) /* Sequence arithmetic is modulo 2^32. Use signed relative distance * so contiguous inserts across wrap are accepted and old data behind * seq_base is rejected. */ - rel = (int32_t)(seq - q->seq_base); + rel = tcp_seq_diff(seq, q->seq_base); if (rel < 0) { /* Old data that is behind the current receive base. */ return -1; @@ -1707,7 +1717,7 @@ static void tcp_parse_options(const struct wolfIP_tcp_seg *tcp, uint32_t frame_l { const uint8_t *opt = tcp->data; int claimed_opt_len = (tcp->hlen >> 2) - TCP_HEADER_LEN; - int available_bytes = (int)(frame_len - sizeof(struct wolfIP_tcp_seg)); + int available_bytes = (int)frame_len - (int)sizeof(struct wolfIP_tcp_seg); int opt_len; const uint8_t *opt_end; @@ -1922,7 +1932,7 @@ static void tcp_consume_ooo(struct tsocket *t) break; } else { /* Keep only the still-unacknowledged suffix. */ - uint32_t trim = t->sock.tcp.ack - t->sock.tcp.ooo[i].seq; + uint32_t trim = tcp_seq_diff(t->sock.tcp.ack, t->sock.tcp.ooo[i].seq); memmove(t->sock.tcp.ooo[i].data, t->sock.tcp.ooo[i].data + trim, t->sock.tcp.ooo[i].len - trim); @@ -2117,14 +2127,14 @@ static void tcp_recv(struct tsocket *t, struct wolfIP_tcp_seg *seg) if (seg_len == 0) return; if (tcp_seq_lt(seq, t->sock.tcp.ack)) { - uint32_t consumed = t->sock.tcp.ack - seq; + uint32_t consumed = tcp_seq_diff(t->sock.tcp.ack, seq); /* Retransmitted/overlapping data below ACK is already delivered. * Trim it so only bytes above ACK participate in hole handling. */ if (consumed >= seg_len) { tcp_send_ack(t); return; } - seq += consumed; + seq = tcp_seq_inc(seq, consumed); payload += consumed; seg_len -= consumed; } @@ -2821,12 +2831,34 @@ static void tcp_input(struct wolfIP *S, unsigned int if_idx, ip4 syn_dst = ee32(tcp->ip.dst); int dst_match = 0; unsigned int dst_if; + int dup_found = 0; if (syn_dst == IPADDR_ANY) continue; if (t->bound_local_ip != IPADDR_ANY && t->bound_local_ip != syn_dst) continue; + /* Reject SYNs that match an already-active connection + * with the same tuple (local_port, remote_port, remote_ip), that is already + * in the established state. + * */ + for (int k = 0; k < MAX_TCPSOCKETS; k++) { + struct tsocket *tk = &S->tcpsockets[k]; + if (tk == t) + continue; + if (tk->sock.tcp.state <= TCP_LISTEN || + tk->sock.tcp.state == TCP_CLOSED) + continue; + if (tk->src_port == t->src_port && + tk->dst_port == ee16(tcp->src_port) && + tk->remote_ip == ee32(tcp->ip.src)) { + dup_found = 1; + break; + } + } + if (dup_found) + continue; + dst_if = wolfIP_if_for_local_ip(S, syn_dst, &dst_match); if (!dst_match) continue; @@ -2836,6 +2868,7 @@ static void tcp_input(struct wolfIP *S, unsigned int if_idx, t->sock.tcp.state = TCP_SYN_RCVD; t->sock.tcp.ack = tcp_seq_inc(ee32(tcp->seq), 1); t->sock.tcp.seq = wolfIP_getrandom(); + t->sock.tcp.snd_una = t->sock.tcp.seq; t->dst_port = ee16(tcp->src_port); t->remote_ip = ee32(tcp->ip.src); t->events |= CB_EVENT_READABLE; /* Keep flag until application calls accept */