diff options
Diffstat (limited to 'test/unit/tcp')
-rw-r--r-- | test/unit/tcp/tcp_helper.c | 325 | ||||
-rw-r--r-- | test/unit/tcp/tcp_helper.h | 58 | ||||
-rw-r--r-- | test/unit/tcp/test_tcp.c | 1700 | ||||
-rw-r--r-- | test/unit/tcp/test_tcp.h | 8 | ||||
-rw-r--r-- | test/unit/tcp/test_tcp_oos.c | 1018 | ||||
-rw-r--r-- | test/unit/tcp/test_tcp_oos.h | 8 | ||||
-rw-r--r-- | test/unit/tcp/test_tcp_state.c | 665 | ||||
-rw-r--r-- | test/unit/tcp/test_tcp_state.h | 8 |
8 files changed, 3790 insertions, 0 deletions
diff --git a/test/unit/tcp/tcp_helper.c b/test/unit/tcp/tcp_helper.c new file mode 100644 index 00000000000..61d6e5625e1 --- /dev/null +++ b/test/unit/tcp/tcp_helper.c @@ -0,0 +1,325 @@ +#include "tcp_helper.h" + +#include "lwip/priv/tcp_priv.h" +#include "lwip/stats.h" +#include "lwip/pbuf.h" +#include "lwip/inet.h" +#include "lwip/inet_chksum.h" +#include "lwip/ip_addr.h" + +#if !LWIP_STATS || !TCP_STATS || !MEMP_STATS +#error "This tests needs TCP- and MEMP-statistics enabled" +#endif + +const ip_addr_t test_local_ip = IPADDR4_INIT_BYTES(192, 168, 1, 1); +const ip_addr_t test_remote_ip = IPADDR4_INIT_BYTES(192, 168, 1, 2); +const ip_addr_t test_netmask = IPADDR4_INIT_BYTES(255, 255, 255, 0); + +/** Remove all pcbs on the given list. */ +static void +tcp_remove(struct tcp_pcb* pcb_list) +{ + struct tcp_pcb *pcb = pcb_list; + struct tcp_pcb *pcb2; + + while(pcb != NULL) { + pcb2 = pcb; + pcb = pcb->next; + if (pcb2->state == LISTEN) { + tcp_close(pcb2); + } else { + tcp_abort(pcb2); + } + } +} + +/** Remove all pcbs on listen-, active- and time-wait-list (bound- isn't exported). */ +void +tcp_remove_all(void) +{ + tcp_remove(tcp_listen_pcbs.pcbs); + tcp_remove(tcp_bound_pcbs); + tcp_remove(tcp_active_pcbs); + tcp_remove(tcp_tw_pcbs); + fail_unless(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 0); + fail_unless(MEMP_STATS_GET(used, MEMP_TCP_PCB_LISTEN) == 0); + fail_unless(MEMP_STATS_GET(used, MEMP_TCP_SEG) == 0); + fail_unless(MEMP_STATS_GET(used, MEMP_PBUF_POOL) == 0); +} + +/** Create a TCP segment usable for passing to tcp_input */ +static struct pbuf* +tcp_create_segment_wnd(ip_addr_t* src_ip, ip_addr_t* dst_ip, + u16_t src_port, u16_t dst_port, void* data, size_t data_len, + u32_t seqno, u32_t ackno, u8_t headerflags, u16_t wnd) +{ + struct pbuf *p, *q; + struct ip_hdr* iphdr; + struct tcp_hdr* tcphdr; + u16_t pbuf_len = (u16_t)(sizeof(struct ip_hdr) + sizeof(struct tcp_hdr) + data_len); + LWIP_ASSERT("data_len too big", data_len <= 0xFFFF); + + p = pbuf_alloc(PBUF_RAW, pbuf_len, PBUF_POOL); + EXPECT_RETNULL(p != NULL); + /* first pbuf must be big enough to hold the headers */ + EXPECT_RETNULL(p->len >= (sizeof(struct ip_hdr) + sizeof(struct tcp_hdr))); + if (data_len > 0) { + /* first pbuf must be big enough to hold at least 1 data byte, too */ + EXPECT_RETNULL(p->len > (sizeof(struct ip_hdr) + sizeof(struct tcp_hdr))); + } + + for(q = p; q != NULL; q = q->next) { + memset(q->payload, 0, q->len); + } + + iphdr = (struct ip_hdr*)p->payload; + /* fill IP header */ + iphdr->dest.addr = ip_2_ip4(dst_ip)->addr; + iphdr->src.addr = ip_2_ip4(src_ip)->addr; + IPH_VHL_SET(iphdr, 4, IP_HLEN / 4); + IPH_TOS_SET(iphdr, 0); + IPH_LEN_SET(iphdr, htons(p->tot_len)); + IPH_CHKSUM_SET(iphdr, inet_chksum(iphdr, IP_HLEN)); + + /* let p point to TCP header */ + pbuf_header(p, -(s16_t)sizeof(struct ip_hdr)); + + tcphdr = (struct tcp_hdr*)p->payload; + tcphdr->src = htons(src_port); + tcphdr->dest = htons(dst_port); + tcphdr->seqno = htonl(seqno); + tcphdr->ackno = htonl(ackno); + TCPH_HDRLEN_SET(tcphdr, sizeof(struct tcp_hdr)/4); + TCPH_FLAGS_SET(tcphdr, headerflags); + tcphdr->wnd = htons(wnd); + + if (data_len > 0) { + /* let p point to TCP data */ + pbuf_header(p, -(s16_t)sizeof(struct tcp_hdr)); + /* copy data */ + pbuf_take(p, data, (u16_t)data_len); + /* let p point to TCP header again */ + pbuf_header(p, sizeof(struct tcp_hdr)); + } + + /* calculate checksum */ + + tcphdr->chksum = ip_chksum_pseudo(p, + IP_PROTO_TCP, p->tot_len, src_ip, dst_ip); + + pbuf_header(p, sizeof(struct ip_hdr)); + + return p; +} + +/** Create a TCP segment usable for passing to tcp_input */ +struct pbuf* +tcp_create_segment(ip_addr_t* src_ip, ip_addr_t* dst_ip, + u16_t src_port, u16_t dst_port, void* data, size_t data_len, + u32_t seqno, u32_t ackno, u8_t headerflags) +{ + return tcp_create_segment_wnd(src_ip, dst_ip, src_port, dst_port, data, + data_len, seqno, ackno, headerflags, TCP_WND); +} + +/** Create a TCP segment usable for passing to tcp_input + * - IP-addresses, ports, seqno and ackno are taken from pcb + * - seqno and ackno can be altered with an offset + */ +struct pbuf* +tcp_create_rx_segment(struct tcp_pcb* pcb, void* data, size_t data_len, u32_t seqno_offset, + u32_t ackno_offset, u8_t headerflags) +{ + return tcp_create_segment(&pcb->remote_ip, &pcb->local_ip, pcb->remote_port, pcb->local_port, + data, data_len, pcb->rcv_nxt + seqno_offset, pcb->lastack + ackno_offset, headerflags); +} + +/** Create a TCP segment usable for passing to tcp_input + * - IP-addresses, ports, seqno and ackno are taken from pcb + * - seqno and ackno can be altered with an offset + * - TCP window can be adjusted + */ +struct pbuf* tcp_create_rx_segment_wnd(struct tcp_pcb* pcb, void* data, size_t data_len, + u32_t seqno_offset, u32_t ackno_offset, u8_t headerflags, u16_t wnd) +{ + return tcp_create_segment_wnd(&pcb->remote_ip, &pcb->local_ip, pcb->remote_port, pcb->local_port, + data, data_len, pcb->rcv_nxt + seqno_offset, pcb->lastack + ackno_offset, headerflags, wnd); +} + +/** Safely bring a tcp_pcb into the requested state */ +void +tcp_set_state(struct tcp_pcb* pcb, enum tcp_state state, const ip_addr_t* local_ip, + const ip_addr_t* remote_ip, u16_t local_port, u16_t remote_port) +{ + u32_t iss; + + /* @todo: are these all states? */ + /* @todo: remove from previous list */ + pcb->state = state; + + iss = tcp_next_iss(pcb); + pcb->snd_wl2 = iss; + pcb->snd_nxt = iss; + pcb->lastack = iss; + pcb->snd_lbb = iss; + + if (state == ESTABLISHED) { + TCP_REG(&tcp_active_pcbs, pcb); + ip_addr_copy(pcb->local_ip, *local_ip); + pcb->local_port = local_port; + ip_addr_copy(pcb->remote_ip, *remote_ip); + pcb->remote_port = remote_port; + } else if(state == LISTEN) { + TCP_REG(&tcp_listen_pcbs.pcbs, pcb); + ip_addr_copy(pcb->local_ip, *local_ip); + pcb->local_port = local_port; + } else if(state == TIME_WAIT) { + TCP_REG(&tcp_tw_pcbs, pcb); + ip_addr_copy(pcb->local_ip, *local_ip); + pcb->local_port = local_port; + ip_addr_copy(pcb->remote_ip, *remote_ip); + pcb->remote_port = remote_port; + } else { + fail(); + } +} + +void +test_tcp_counters_err(void* arg, err_t err) +{ + struct test_tcp_counters* counters = (struct test_tcp_counters*)arg; + EXPECT_RET(arg != NULL); + counters->err_calls++; + counters->last_err = err; +} + +static void +test_tcp_counters_check_rxdata(struct test_tcp_counters* counters, struct pbuf* p) +{ + struct pbuf* q; + u32_t i, received; + if(counters->expected_data == NULL) { + /* no data to compare */ + return; + } + EXPECT_RET(counters->recved_bytes + p->tot_len <= counters->expected_data_len); + received = counters->recved_bytes; + for(q = p; q != NULL; q = q->next) { + char *data = (char*)q->payload; + for(i = 0; i < q->len; i++) { + EXPECT_RET(data[i] == counters->expected_data[received]); + received++; + } + } + EXPECT(received == counters->recved_bytes + p->tot_len); +} + +err_t +test_tcp_counters_recv(void* arg, struct tcp_pcb* pcb, struct pbuf* p, err_t err) +{ + struct test_tcp_counters* counters = (struct test_tcp_counters*)arg; + EXPECT_RETX(arg != NULL, ERR_OK); + EXPECT_RETX(pcb != NULL, ERR_OK); + EXPECT_RETX(err == ERR_OK, ERR_OK); + + if (p != NULL) { + if (counters->close_calls == 0) { + counters->recv_calls++; + test_tcp_counters_check_rxdata(counters, p); + counters->recved_bytes += p->tot_len; + } else { + counters->recv_calls_after_close++; + counters->recved_bytes_after_close += p->tot_len; + } + pbuf_free(p); + } else { + counters->close_calls++; + } + EXPECT(counters->recv_calls_after_close == 0 && counters->recved_bytes_after_close == 0); + return ERR_OK; +} + +/** Allocate a pcb and set up the test_tcp_counters_* callbacks */ +struct tcp_pcb* +test_tcp_new_counters_pcb(struct test_tcp_counters* counters) +{ + struct tcp_pcb* pcb = tcp_new(); + if (pcb != NULL) { + /* set up args and callbacks */ + tcp_arg(pcb, counters); + tcp_recv(pcb, test_tcp_counters_recv); + tcp_err(pcb, test_tcp_counters_err); + pcb->snd_wnd = TCP_WND; + pcb->snd_wnd_max = TCP_WND; + } + return pcb; +} + +/** Calls tcp_input() after adjusting current_iphdr_dest */ +void test_tcp_input(struct pbuf *p, struct netif *inp) +{ + struct ip_hdr *iphdr = (struct ip_hdr*)p->payload; + /* these lines are a hack, don't use them as an example :-) */ + ip_addr_copy_from_ip4(*ip_current_dest_addr(), iphdr->dest); + ip_addr_copy_from_ip4(*ip_current_src_addr(), iphdr->src); + ip_current_netif() = inp; + ip_data.current_ip4_header = iphdr; + ip_data.current_input_netif = inp; + + /* since adding IPv6, p->payload must point to tcp header, not ip header */ + pbuf_header(p, -(s16_t)sizeof(struct ip_hdr)); + + tcp_input(p, inp); + + ip_addr_set_zero(ip_current_dest_addr()); + ip_addr_set_zero(ip_current_src_addr()); + ip_current_netif() = NULL; + ip_data.current_ip4_header = NULL; +} + +static err_t test_tcp_netif_output(struct netif *netif, struct pbuf *p, + const ip4_addr_t *ipaddr) +{ + struct test_tcp_txcounters *txcounters = (struct test_tcp_txcounters*)netif->state; + LWIP_UNUSED_ARG(ipaddr); + if (txcounters != NULL) + { + txcounters->num_tx_calls++; + txcounters->num_tx_bytes += p->tot_len; + if (txcounters->copy_tx_packets) { + struct pbuf *p_copy = pbuf_alloc(PBUF_LINK, p->tot_len, PBUF_RAM); + err_t err; + EXPECT(p_copy != NULL); + err = pbuf_copy(p_copy, p); + EXPECT(err == ERR_OK); + if (txcounters->tx_packets == NULL) { + txcounters->tx_packets = p_copy; + } else { + pbuf_cat(txcounters->tx_packets, p_copy); + } + } + } + return ERR_OK; +} + +void test_tcp_init_netif(struct netif *netif, struct test_tcp_txcounters *txcounters, + const ip_addr_t *ip_addr, const ip_addr_t *netmask) +{ + struct netif *n; + memset(netif, 0, sizeof(struct netif)); + if (txcounters != NULL) { + memset(txcounters, 0, sizeof(struct test_tcp_txcounters)); + netif->state = txcounters; + } + netif->output = test_tcp_netif_output; + netif->flags |= NETIF_FLAG_UP | NETIF_FLAG_LINK_UP; + ip_addr_copy_from_ip4(netif->netmask, *ip_2_ip4(netmask)); + ip_addr_copy_from_ip4(netif->ip_addr, *ip_2_ip4(ip_addr)); + for (n = netif_list; n != NULL; n = n->next) { + if (n == netif) { + return; + } + } + netif->next = NULL; + netif_list = netif; +} diff --git a/test/unit/tcp/tcp_helper.h b/test/unit/tcp/tcp_helper.h new file mode 100644 index 00000000000..cc72e2aba90 --- /dev/null +++ b/test/unit/tcp/tcp_helper.h @@ -0,0 +1,58 @@ +#ifndef LWIP_HDR_TCP_HELPER_H +#define LWIP_HDR_TCP_HELPER_H + +#include "../lwip_check.h" +#include "lwip/arch.h" +#include "lwip/tcp.h" +#include "lwip/netif.h" + +/* counters used for test_tcp_counters_* callback functions */ +struct test_tcp_counters { + u32_t recv_calls; + u32_t recved_bytes; + u32_t recv_calls_after_close; + u32_t recved_bytes_after_close; + u32_t close_calls; + u32_t err_calls; + err_t last_err; + char* expected_data; + u32_t expected_data_len; +}; + +struct test_tcp_txcounters { + u32_t num_tx_calls; + u32_t num_tx_bytes; + u8_t copy_tx_packets; + struct pbuf *tx_packets; +}; + +extern const ip_addr_t test_local_ip; +extern const ip_addr_t test_remote_ip; +extern const ip_addr_t test_netmask; +#define TEST_REMOTE_PORT 0x100 +#define TEST_LOCAL_PORT 0x101 + +/* Helper functions */ +void tcp_remove_all(void); + +struct pbuf* tcp_create_segment(ip_addr_t* src_ip, ip_addr_t* dst_ip, + u16_t src_port, u16_t dst_port, void* data, size_t data_len, + u32_t seqno, u32_t ackno, u8_t headerflags); +struct pbuf* tcp_create_rx_segment(struct tcp_pcb* pcb, void* data, size_t data_len, + u32_t seqno_offset, u32_t ackno_offset, u8_t headerflags); +struct pbuf* tcp_create_rx_segment_wnd(struct tcp_pcb* pcb, void* data, size_t data_len, + u32_t seqno_offset, u32_t ackno_offset, u8_t headerflags, u16_t wnd); +void tcp_set_state(struct tcp_pcb* pcb, enum tcp_state state, const ip_addr_t* local_ip, + const ip_addr_t* remote_ip, u16_t local_port, u16_t remote_port); +void test_tcp_counters_err(void* arg, err_t err); +err_t test_tcp_counters_recv(void* arg, struct tcp_pcb* pcb, struct pbuf* p, err_t err); + +struct tcp_pcb* test_tcp_new_counters_pcb(struct test_tcp_counters* counters); + +void test_tcp_input(struct pbuf *p, struct netif *inp); + +void test_tcp_init_netif(struct netif *netif, struct test_tcp_txcounters *txcounters, + const ip_addr_t *ip_addr, const ip_addr_t *netmask); + + +#endif diff --git a/test/unit/tcp/test_tcp.c b/test/unit/tcp/test_tcp.c new file mode 100644 index 00000000000..42324d47d6c --- /dev/null +++ b/test/unit/tcp/test_tcp.c @@ -0,0 +1,1700 @@ +#include "test_tcp.h" + +#include "lwip/priv/tcp_priv.h" +#include "lwip/stats.h" +#include "lwip/inet.h" +#include "tcp_helper.h" +#include "lwip/inet_chksum.h" + +#ifdef _MSC_VER +#pragma warning(disable: 4307) /* we explicitly wrap around TCP seqnos */ +#endif + +#if !LWIP_STATS || !TCP_STATS || !MEMP_STATS +#error "This tests needs TCP- and MEMP-statistics enabled" +#endif +#if TCP_SND_BUF <= TCP_WND +#error "This tests needs TCP_SND_BUF to be > TCP_WND" +#endif + +/* used with check_seqnos() */ +#define SEQNO1 (0xFFFFFF00 - TCP_MSS) +#define ISS 6510 +static u32_t seqnos[] = { + SEQNO1, + SEQNO1 + (1 * TCP_MSS), + SEQNO1 + (2 * TCP_MSS), + SEQNO1 + (3 * TCP_MSS), + SEQNO1 + (4 * TCP_MSS), + SEQNO1 + (5 * TCP_MSS) }; + +static u8_t test_tcp_timer; + +/* our own version of tcp_tmr so we can reset fast/slow timer state */ +static void +test_tcp_tmr(void) +{ + tcp_fasttmr(); + if (++test_tcp_timer & 1) { + tcp_slowtmr(); + } +} + +/* Setups/teardown functions */ +static struct netif *old_netif_list; +static struct netif *old_netif_default; + +static void +tcp_setup(void) +{ + struct tcp_pcb dummy_pcb; /* we need this for tcp_next_iss() only */ + + old_netif_list = netif_list; + old_netif_default = netif_default; + netif_list = NULL; + netif_default = NULL; + /* reset iss to default (6510) */ + tcp_ticks = 0; + tcp_ticks = 0 - (tcp_next_iss(&dummy_pcb) - 6510); + tcp_next_iss(&dummy_pcb); + tcp_ticks = 0; + + test_tcp_timer = 0; + tcp_remove_all(); + lwip_check_ensure_no_alloc(SKIP_POOL(MEMP_SYS_TIMEOUT)); +} + +static void +tcp_teardown(void) +{ + netif_list = NULL; + netif_default = NULL; + tcp_remove_all(); + /* restore netif_list for next tests (e.g. loopif) */ + netif_list = old_netif_list; + netif_default = old_netif_default; + lwip_check_ensure_no_alloc(SKIP_POOL(MEMP_SYS_TIMEOUT)); +} + + +/* Test functions */ + +/** Call tcp_new() and tcp_abort() and test memp stats */ +START_TEST(test_tcp_new_abort) +{ + struct tcp_pcb* pcb; + LWIP_UNUSED_ARG(_i); + + fail_unless(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 0); + + pcb = tcp_new(); + fail_unless(pcb != NULL); + if (pcb != NULL) { + fail_unless(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 1); + tcp_abort(pcb); + fail_unless(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 0); + } +} +END_TEST + +/** Call tcp_new() and tcp_abort() and test memp stats */ +START_TEST(test_tcp_listen_passive_open) +{ + struct tcp_pcb *pcb, *pcbl; + struct tcp_pcb_listen *lpcb; + struct netif netif; + struct test_tcp_txcounters txcounters; + struct test_tcp_counters counters; + struct pbuf *p; + ip_addr_t src_addr; + err_t err; + LWIP_UNUSED_ARG(_i); + + fail_unless(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 0); + + test_tcp_init_netif(&netif, &txcounters, &test_local_ip, &test_netmask); + /* initialize counter struct */ + memset(&counters, 0, sizeof(counters)); + + pcb = tcp_new(); + EXPECT_RET(pcb != NULL); + err = tcp_bind(pcb, &netif.ip_addr, 1234); + EXPECT(err == ERR_OK); + pcbl = tcp_listen(pcb); + EXPECT_RET(pcbl != NULL); + EXPECT_RET(pcbl != pcb); + lpcb = (struct tcp_pcb_listen *)pcbl; + + ip_addr_set_ip4_u32_val(src_addr, lwip_htonl(lwip_ntohl(ip_addr_get_ip4_u32(&lpcb->local_ip)) + 1)); + + /* check correct syn packet */ + p = tcp_create_segment(&src_addr, &lpcb->local_ip, 12345, + lpcb->local_port, NULL, 0, 12345, 54321, TCP_SYN); + EXPECT(p != NULL); + if (p != NULL) { + /* pass the segment to tcp_input */ + test_tcp_input(p, &netif); + /* check if counters are as expected */ + EXPECT(txcounters.num_tx_calls == 1); + } + + /* check syn packet with short length */ + p = tcp_create_segment(&src_addr, &lpcb->local_ip, 12345, + lpcb->local_port, NULL, 0, 12345, 54321, TCP_SYN); + EXPECT(p != NULL); + EXPECT(p->next == NULL); + if ((p != NULL) && (p->next == NULL)) { + p->len -= 2; + p->tot_len -= 2; + /* pass the segment to tcp_input */ + test_tcp_input(p, &netif); + /* check if counters are as expected */ + EXPECT(txcounters.num_tx_calls == 1); + } + + tcp_close(pcbl); +} +END_TEST + +/** Create an ESTABLISHED pcb and check if receive callback is called */ +START_TEST(test_tcp_recv_inseq) +{ + struct test_tcp_counters counters; + struct tcp_pcb* pcb; + struct pbuf* p; + char data[] = {1, 2, 3, 4}; + u16_t data_len; + struct netif netif; + struct test_tcp_txcounters txcounters; + LWIP_UNUSED_ARG(_i); + + /* initialize local vars */ + test_tcp_init_netif(&netif, &txcounters, &test_local_ip, &test_netmask); + data_len = sizeof(data); + /* initialize counter struct */ + memset(&counters, 0, sizeof(counters)); + counters.expected_data_len = data_len; + counters.expected_data = data; + + /* create and initialize the pcb */ + pcb = test_tcp_new_counters_pcb(&counters); + EXPECT_RET(pcb != NULL); + tcp_set_state(pcb, ESTABLISHED, &test_local_ip, &test_remote_ip, TEST_LOCAL_PORT, TEST_REMOTE_PORT); + + /* create a segment */ + p = tcp_create_rx_segment(pcb, counters.expected_data, data_len, 0, 0, 0); + EXPECT(p != NULL); + if (p != NULL) { + /* pass the segment to tcp_input */ + test_tcp_input(p, &netif); + /* check if counters are as expected */ + EXPECT(counters.close_calls == 0); + EXPECT(counters.recv_calls == 1); + EXPECT(counters.recved_bytes == data_len); + EXPECT(counters.err_calls == 0); + } + + /* make sure the pcb is freed */ + EXPECT(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 1); + tcp_abort(pcb); + EXPECT(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 0); +} +END_TEST + +/** Create an ESTABLISHED pcb and check if receive callback is called if a segment + * overlapping rcv_nxt is received */ +START_TEST(test_tcp_recv_inseq_trim) +{ + struct test_tcp_counters counters; + struct tcp_pcb* pcb; + struct pbuf* p; + char data[PBUF_POOL_BUFSIZE*2]; + u16_t data_len; + struct netif netif; + struct test_tcp_txcounters txcounters; + const u32_t new_data_len = 40; + LWIP_UNUSED_ARG(_i); + + /* initialize local vars */ + test_tcp_init_netif(&netif, &txcounters, &test_local_ip, &test_netmask); + data_len = sizeof(data); + memset(data, 0, sizeof(data)); + /* initialize counter struct */ + memset(&counters, 0, sizeof(counters)); + counters.expected_data_len = data_len; + counters.expected_data = data; + + /* create and initialize the pcb */ + pcb = test_tcp_new_counters_pcb(&counters); + EXPECT_RET(pcb != NULL); + tcp_set_state(pcb, ESTABLISHED, &test_local_ip, &test_remote_ip, TEST_LOCAL_PORT, TEST_REMOTE_PORT); + + /* create a segment (with an overlapping/old seqno so that the new data begins in the 2nd pbuf) */ + p = tcp_create_rx_segment(pcb, counters.expected_data, data_len, (u32_t)(0-(data_len-new_data_len)), 0, 0); + EXPECT(p != NULL); + if (p != NULL) { + EXPECT(p->next != NULL); + if (p->next != NULL) { + EXPECT(p->next->next != NULL); + } + } + if ((p != NULL) && (p->next != NULL) && (p->next->next != NULL)) { + /* pass the segment to tcp_input */ + test_tcp_input(p, &netif); + /* check if counters are as expected */ + EXPECT(counters.close_calls == 0); + EXPECT(counters.recv_calls == 1); + EXPECT(counters.recved_bytes == new_data_len); + EXPECT(counters.err_calls == 0); + } + + /* make sure the pcb is freed */ + EXPECT(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 1); + tcp_abort(pcb); + EXPECT(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 0); +} +END_TEST + +static err_t test_tcp_recv_expect1byte(void* arg, struct tcp_pcb* pcb, struct pbuf* p, err_t err); + +static err_t +test_tcp_recv_expectclose(void* arg, struct tcp_pcb* pcb, struct pbuf* p, err_t err) +{ + EXPECT_RETX(pcb != NULL, ERR_OK); + EXPECT_RETX(err == ERR_OK, ERR_OK); + LWIP_UNUSED_ARG(arg); + + if (p != NULL) { + fail(); + } else { + /* correct: FIN received; close our end, too */ + err_t err2 = tcp_close(pcb); + fail_unless(err2 == ERR_OK); + /* set back to some other rx function, just to not get here again */ + tcp_recv(pcb, test_tcp_recv_expect1byte); + } + return ERR_OK; +} + +static err_t +test_tcp_recv_expect1byte(void* arg, struct tcp_pcb* pcb, struct pbuf* p, err_t err) +{ + EXPECT_RETX(pcb != NULL, ERR_OK); + EXPECT_RETX(err == ERR_OK, ERR_OK); + LWIP_UNUSED_ARG(arg); + + if (p != NULL) { + if ((p->len == 1) && (p->tot_len == 1)) { + tcp_recv(pcb, test_tcp_recv_expectclose); + } else { + fail(); + } + pbuf_free(p); + } else { + fail(); + } + return ERR_OK; +} + +START_TEST(test_tcp_passive_close) +{ + struct test_tcp_counters counters; + struct tcp_pcb* pcb; + struct pbuf* p; + char data = 0x0f; + struct netif netif; + struct test_tcp_txcounters txcounters; + LWIP_UNUSED_ARG(_i); + + /* initialize local vars */ + test_tcp_init_netif(&netif, &txcounters, &test_local_ip, &test_netmask); + + /* initialize counter struct */ + memset(&counters, 0, sizeof(counters)); + counters.expected_data_len = 1; + counters.expected_data = &data; + + /* create and initialize the pcb */ + pcb = test_tcp_new_counters_pcb(&counters); + EXPECT_RET(pcb != NULL); + tcp_set_state(pcb, ESTABLISHED, &test_local_ip, &test_remote_ip, TEST_LOCAL_PORT, TEST_REMOTE_PORT); + + /* create a segment without data */ + p = tcp_create_rx_segment(pcb, &data, 1, 0, 0, TCP_FIN); + EXPECT(p != NULL); + if (p != NULL) { + tcp_recv(pcb, test_tcp_recv_expect1byte); + /* pass the segment to tcp_input */ + test_tcp_input(p, &netif); + } + /* don't free the pcb here (part of the test!) */ +} +END_TEST + +START_TEST(test_tcp_active_abort) +{ + struct test_tcp_counters counters; + struct tcp_pcb* pcb; + char data = 0x0f; + struct netif netif; + struct test_tcp_txcounters txcounters; + LWIP_UNUSED_ARG(_i); + + memset(&txcounters, 0, sizeof(txcounters)); + + /* initialize local vars */ + test_tcp_init_netif(&netif, &txcounters, &test_local_ip, &test_netmask); + + /* initialize counter struct */ + memset(&counters, 0, sizeof(counters)); + counters.expected_data_len = 1; + counters.expected_data = &data; + + /* create and initialize the pcb */ + pcb = test_tcp_new_counters_pcb(&counters); + EXPECT_RET(pcb != NULL); + tcp_set_state(pcb, ESTABLISHED, &test_local_ip, &test_remote_ip, TEST_LOCAL_PORT, TEST_REMOTE_PORT); + + /* abort the pcb */ + EXPECT_RET(txcounters.num_tx_calls == 0); + txcounters.copy_tx_packets = 1; + tcp_abort(pcb); + txcounters.copy_tx_packets = 0; + EXPECT(txcounters.num_tx_calls == 1); + EXPECT(txcounters.num_tx_bytes == 40U); + EXPECT(txcounters.tx_packets != NULL); + if (txcounters.tx_packets != NULL) { + u16_t ret; + struct tcp_hdr tcphdr; + ret = pbuf_copy_partial(txcounters.tx_packets, &tcphdr, 20, 20); + EXPECT(ret == 20); + EXPECT(tcphdr.dest == PP_HTONS(TEST_REMOTE_PORT)); + EXPECT(tcphdr.src == PP_HTONS(TEST_LOCAL_PORT)); + pbuf_free(txcounters.tx_packets); + txcounters.tx_packets = NULL; + } + + /* don't free the pcb here (part of the test!) */ +} +END_TEST + +/** Check that we handle malformed tcp headers, and discard the pbuf(s) */ +START_TEST(test_tcp_malformed_header) +{ + struct test_tcp_counters counters; + struct tcp_pcb* pcb; + struct pbuf* p; + char data[] = {1, 2, 3, 4}; + u16_t data_len, chksum; + struct netif netif; + struct test_tcp_txcounters txcounters; + struct tcp_hdr *hdr; + LWIP_UNUSED_ARG(_i); + + /* initialize local vars */ + test_tcp_init_netif(&netif, &txcounters, &test_local_ip, &test_netmask); + data_len = sizeof(data); + /* initialize counter struct */ + memset(&counters, 0, sizeof(counters)); + counters.expected_data_len = data_len; + counters.expected_data = data; + + /* create and initialize the pcb */ + pcb = test_tcp_new_counters_pcb(&counters); + EXPECT_RET(pcb != NULL); + tcp_set_state(pcb, ESTABLISHED, &test_local_ip, &test_remote_ip, TEST_LOCAL_PORT, TEST_REMOTE_PORT); + + /* create a segment */ + p = tcp_create_rx_segment(pcb, counters.expected_data, data_len, 0, 0, 0); + + pbuf_header(p, -(s16_t)sizeof(struct ip_hdr)); + + hdr = (struct tcp_hdr *)p->payload; + TCPH_HDRLEN_FLAGS_SET(hdr, 15, 0x3d1); + + hdr->chksum = 0; + + chksum = ip_chksum_pseudo(p, IP_PROTO_TCP, p->tot_len, + &test_remote_ip, &test_local_ip); + + hdr->chksum = chksum; + + pbuf_header(p, sizeof(struct ip_hdr)); + + EXPECT(p != NULL); + EXPECT(p->next == NULL); + if (p != NULL) { + /* pass the segment to tcp_input */ + test_tcp_input(p, &netif); + /* check if counters are as expected */ + EXPECT(counters.close_calls == 0); + EXPECT(counters.recv_calls == 0); + EXPECT(counters.recved_bytes == 0); + EXPECT(counters.err_calls == 0); + } + + /* make sure the pcb is freed */ + EXPECT(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 1); + tcp_abort(pcb); + EXPECT(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 0); +} +END_TEST + + +/** Provoke fast retransmission by duplicate ACKs and then recover by ACKing all sent data. + * At the end, send more data. */ +START_TEST(test_tcp_fast_retx_recover) +{ + struct netif netif; + struct test_tcp_txcounters txcounters; + struct test_tcp_counters counters; + struct tcp_pcb* pcb; + struct pbuf* p; + char data1[] = { 1, 2, 3, 4}; + char data2[] = { 5, 6, 7, 8}; + char data3[] = { 9, 10, 11, 12}; + char data4[] = {13, 14, 15, 16}; + char data5[] = {17, 18, 19, 20}; + char data6[TCP_MSS] = {21, 22, 23, 24}; + err_t err; + LWIP_UNUSED_ARG(_i); + + /* initialize local vars */ + test_tcp_init_netif(&netif, &txcounters, &test_local_ip, &test_netmask); + memset(&counters, 0, sizeof(counters)); + + /* create and initialize the pcb */ + pcb = test_tcp_new_counters_pcb(&counters); + EXPECT_RET(pcb != NULL); + tcp_set_state(pcb, ESTABLISHED, &test_local_ip, &test_remote_ip, TEST_LOCAL_PORT, TEST_REMOTE_PORT); + pcb->mss = TCP_MSS; + /* disable initial congestion window (we don't send a SYN here...) */ + pcb->cwnd = pcb->snd_wnd; + + /* send data1 */ + err = tcp_write(pcb, data1, sizeof(data1), TCP_WRITE_FLAG_COPY); + EXPECT_RET(err == ERR_OK); + err = tcp_output(pcb); + EXPECT_RET(err == ERR_OK); + EXPECT_RET(txcounters.num_tx_calls == 1); + EXPECT_RET(txcounters.num_tx_bytes == sizeof(data1) + sizeof(struct tcp_hdr) + sizeof(struct ip_hdr)); + memset(&txcounters, 0, sizeof(txcounters)); + /* "recv" ACK for data1 */ + p = tcp_create_rx_segment(pcb, NULL, 0, 0, 4, TCP_ACK); + EXPECT_RET(p != NULL); + test_tcp_input(p, &netif); + EXPECT_RET(txcounters.num_tx_calls == 0); + EXPECT_RET(pcb->unacked == NULL); + /* send data2 */ + err = tcp_write(pcb, data2, sizeof(data2), TCP_WRITE_FLAG_COPY); + EXPECT_RET(err == ERR_OK); + err = tcp_output(pcb); + EXPECT_RET(err == ERR_OK); + EXPECT_RET(txcounters.num_tx_calls == 1); + EXPECT_RET(txcounters.num_tx_bytes == sizeof(data2) + sizeof(struct tcp_hdr) + sizeof(struct ip_hdr)); + memset(&txcounters, 0, sizeof(txcounters)); + /* duplicate ACK for data1 (data2 is lost) */ + p = tcp_create_rx_segment(pcb, NULL, 0, 0, 0, TCP_ACK); + EXPECT_RET(p != NULL); + test_tcp_input(p, &netif); + EXPECT_RET(txcounters.num_tx_calls == 0); + EXPECT_RET(pcb->dupacks == 1); + /* send data3 */ + err = tcp_write(pcb, data3, sizeof(data3), TCP_WRITE_FLAG_COPY); + EXPECT_RET(err == ERR_OK); + err = tcp_output(pcb); + EXPECT_RET(err == ERR_OK); + /* nagle enabled, no tx calls */ + EXPECT_RET(txcounters.num_tx_calls == 0); + EXPECT_RET(txcounters.num_tx_bytes == 0); + memset(&txcounters, 0, sizeof(txcounters)); + /* 2nd duplicate ACK for data1 (data2 and data3 are lost) */ + p = tcp_create_rx_segment(pcb, NULL, 0, 0, 0, TCP_ACK); + EXPECT_RET(p != NULL); + test_tcp_input(p, &netif); + EXPECT_RET(txcounters.num_tx_calls == 0); + EXPECT_RET(pcb->dupacks == 2); + /* queue data4, don't send it (unsent-oversize is != 0) */ + err = tcp_write(pcb, data4, sizeof(data4), TCP_WRITE_FLAG_COPY); + EXPECT_RET(err == ERR_OK); + /* 3nd duplicate ACK for data1 (data2 and data3 are lost) -> fast retransmission */ + p = tcp_create_rx_segment(pcb, NULL, 0, 0, 0, TCP_ACK); + EXPECT_RET(p != NULL); + test_tcp_input(p, &netif); + /*EXPECT_RET(txcounters.num_tx_calls == 1);*/ + EXPECT_RET(pcb->dupacks == 3); + memset(&txcounters, 0, sizeof(txcounters)); + /* @todo: check expected data?*/ + + /* send data5, not output yet */ + err = tcp_write(pcb, data5, sizeof(data5), TCP_WRITE_FLAG_COPY); + EXPECT_RET(err == ERR_OK); + /*err = tcp_output(pcb); + EXPECT_RET(err == ERR_OK);*/ + EXPECT_RET(txcounters.num_tx_calls == 0); + EXPECT_RET(txcounters.num_tx_bytes == 0); + memset(&txcounters, 0, sizeof(txcounters)); + { + int i = 0; + do + { + err = tcp_write(pcb, data6, TCP_MSS, TCP_WRITE_FLAG_COPY); + i++; + }while(err == ERR_OK); + EXPECT_RET(err != ERR_OK); + } + err = tcp_output(pcb); + EXPECT_RET(err == ERR_OK); + /*EXPECT_RET(txcounters.num_tx_calls == 0); + EXPECT_RET(txcounters.num_tx_bytes == 0);*/ + memset(&txcounters, 0, sizeof(txcounters)); + + /* send even more data */ + err = tcp_write(pcb, data5, sizeof(data5), TCP_WRITE_FLAG_COPY); + EXPECT_RET(err == ERR_OK); + err = tcp_output(pcb); + EXPECT_RET(err == ERR_OK); + /* ...and even more data */ + err = tcp_write(pcb, data5, sizeof(data5), TCP_WRITE_FLAG_COPY); + EXPECT_RET(err == ERR_OK); + err = tcp_output(pcb); + EXPECT_RET(err == ERR_OK); + /* ...and even more data */ + err = tcp_write(pcb, data5, sizeof(data5), TCP_WRITE_FLAG_COPY); + EXPECT_RET(err == ERR_OK); + err = tcp_output(pcb); + EXPECT_RET(err == ERR_OK); + /* ...and even more data */ + err = tcp_write(pcb, data5, sizeof(data5), TCP_WRITE_FLAG_COPY); + EXPECT_RET(err == ERR_OK); + err = tcp_output(pcb); + EXPECT_RET(err == ERR_OK); + + /* send ACKs for data2 and data3 */ + p = tcp_create_rx_segment(pcb, NULL, 0, 0, 12, TCP_ACK); + EXPECT_RET(p != NULL); + test_tcp_input(p, &netif); + /*EXPECT_RET(txcounters.num_tx_calls == 0);*/ + + /* ...and even more data */ + err = tcp_write(pcb, data5, sizeof(data5), TCP_WRITE_FLAG_COPY); + EXPECT_RET(err == ERR_OK); + err = tcp_output(pcb); + EXPECT_RET(err == ERR_OK); + /* ...and even more data */ + err = tcp_write(pcb, data5, sizeof(data5), TCP_WRITE_FLAG_COPY); + EXPECT_RET(err == ERR_OK); + err = tcp_output(pcb); + EXPECT_RET(err == ERR_OK); + +#if 0 + /* create expected segment */ + p1 = tcp_create_rx_segment(pcb, counters.expected_data, data_len, 0, 0, 0); + EXPECT_RET(p != NULL); + if (p != NULL) { + /* pass the segment to tcp_input */ + test_tcp_input(p, &netif); + /* check if counters are as expected */ + EXPECT_RET(counters.close_calls == 0); + EXPECT_RET(counters.recv_calls == 1); + EXPECT_RET(counters.recved_bytes == data_len); + EXPECT_RET(counters.err_calls == 0); + } +#endif + /* make sure the pcb is freed */ + EXPECT_RET(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 1); + tcp_abort(pcb); + EXPECT_RET(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 0); +} +END_TEST + +static u8_t tx_data[TCP_WND*2]; + +static void +check_seqnos(struct tcp_seg *segs, int num_expected, u32_t *seqnos_expected) +{ + struct tcp_seg *s = segs; + int i; + for (i = 0; i < num_expected; i++, s = s->next) { + EXPECT_RET(s != NULL); + EXPECT(s->tcphdr->seqno == htonl(seqnos_expected[i])); + } + EXPECT(s == NULL); +} + +/** Send data with sequence numbers that wrap around the u32_t range. + * Then, provoke fast retransmission by duplicate ACKs and check that all + * segment lists are still properly sorted. */ +START_TEST(test_tcp_fast_rexmit_wraparound) +{ + struct netif netif; + struct test_tcp_txcounters txcounters; + struct test_tcp_counters counters; + struct tcp_pcb* pcb; + struct pbuf* p; + err_t err; + size_t i; + u16_t sent_total = 0; + LWIP_UNUSED_ARG(_i); + + for (i = 0; i < sizeof(tx_data); i++) { + tx_data[i] = (u8_t)i; + } + + /* initialize local vars */ + test_tcp_init_netif(&netif, &txcounters, &test_local_ip, &test_netmask); + memset(&counters, 0, sizeof(counters)); + + /* create and initialize the pcb */ + tcp_ticks = SEQNO1 - ISS; + pcb = test_tcp_new_counters_pcb(&counters); + EXPECT_RET(pcb != NULL); + tcp_set_state(pcb, ESTABLISHED, &test_local_ip, &test_remote_ip, TEST_LOCAL_PORT, TEST_REMOTE_PORT); + pcb->mss = TCP_MSS; + /* disable initial congestion window (we don't send a SYN here...) */ + pcb->cwnd = 2*TCP_MSS; + /* start in congestion advoidance */ + pcb->ssthresh = pcb->cwnd; + + /* send 6 mss-sized segments */ + for (i = 0; i < 6; i++) { + err = tcp_write(pcb, &tx_data[sent_total], TCP_MSS, TCP_WRITE_FLAG_COPY); + EXPECT_RET(err == ERR_OK); + sent_total += TCP_MSS; + } + check_seqnos(pcb->unsent, 6, seqnos); + EXPECT(pcb->unacked == NULL); + err = tcp_output(pcb); + EXPECT_RET(err == ERR_OK); + EXPECT(txcounters.num_tx_calls == 2); + EXPECT(txcounters.num_tx_bytes == 2 * (TCP_MSS + 40U)); + memset(&txcounters, 0, sizeof(txcounters)); + + check_seqnos(pcb->unacked, 2, seqnos); + check_seqnos(pcb->unsent, 4, &seqnos[2]); + + /* ACK the first segment */ + p = tcp_create_rx_segment(pcb, NULL, 0, 0, TCP_MSS, TCP_ACK); + test_tcp_input(p, &netif); + /* ensure this didn't trigger a retransmission. Only one + segment should be transmitted because cwnd opened up by + TCP_MSS and a fraction since we are in congestion avoidance */ + EXPECT(txcounters.num_tx_calls == 1); + EXPECT(txcounters.num_tx_bytes == TCP_MSS + 40U); + memset(&txcounters, 0, sizeof(txcounters)); + check_seqnos(pcb->unacked, 2, &seqnos[1]); + check_seqnos(pcb->unsent, 3, &seqnos[3]); + + /* 3 dupacks */ + EXPECT(pcb->dupacks == 0); + p = tcp_create_rx_segment(pcb, NULL, 0, 0, 0, TCP_ACK); + test_tcp_input(p, &netif); + EXPECT(txcounters.num_tx_calls == 0); + EXPECT(pcb->dupacks == 1); + p = tcp_create_rx_segment(pcb, NULL, 0, 0, 0, TCP_ACK); + test_tcp_input(p, &netif); + EXPECT(txcounters.num_tx_calls == 0); + EXPECT(pcb->dupacks == 2); + /* 3rd dupack -> fast rexmit */ + p = tcp_create_rx_segment(pcb, NULL, 0, 0, 0, TCP_ACK); + test_tcp_input(p, &netif); + EXPECT(pcb->dupacks == 3); + EXPECT(txcounters.num_tx_calls == 4); + memset(&txcounters, 0, sizeof(txcounters)); + EXPECT(pcb->unsent == NULL); + check_seqnos(pcb->unacked, 5, &seqnos[1]); + + /* make sure the pcb is freed */ + EXPECT_RET(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 1); + tcp_abort(pcb); + EXPECT_RET(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 0); +} +END_TEST + +/** Send data with sequence numbers that wrap around the u32_t range. + * Then, provoke RTO retransmission and check that all + * segment lists are still properly sorted. */ +START_TEST(test_tcp_rto_rexmit_wraparound) +{ + struct netif netif; + struct test_tcp_txcounters txcounters; + struct test_tcp_counters counters; + struct tcp_pcb* pcb; + struct tcp_pcb dummy_pcb_for_iss; /* we need this for tcp_next_iss() only */ + err_t err; + size_t i; + u16_t sent_total = 0; + LWIP_UNUSED_ARG(_i); + + for (i = 0; i < sizeof(tx_data); i++) { + tx_data[i] = (u8_t)i; + } + + /* initialize local vars */ + test_tcp_init_netif(&netif, &txcounters, &test_local_ip, &test_netmask); + memset(&counters, 0, sizeof(counters)); + + /* create and initialize the pcb */ + tcp_ticks = 0; + tcp_ticks = 0 - tcp_next_iss(&dummy_pcb_for_iss); + tcp_ticks = SEQNO1 - tcp_next_iss(&dummy_pcb_for_iss); + pcb = test_tcp_new_counters_pcb(&counters); + EXPECT_RET(pcb != NULL); + tcp_set_state(pcb, ESTABLISHED, &test_local_ip, &test_remote_ip, TEST_LOCAL_PORT, TEST_REMOTE_PORT); + pcb->mss = TCP_MSS; + /* disable initial congestion window (we don't send a SYN here...) */ + pcb->cwnd = 2*TCP_MSS; + + /* send 6 mss-sized segments */ + for (i = 0; i < 6; i++) { + err = tcp_write(pcb, &tx_data[sent_total], TCP_MSS, TCP_WRITE_FLAG_COPY); + EXPECT_RET(err == ERR_OK); + sent_total += TCP_MSS; + } + check_seqnos(pcb->unsent, 6, seqnos); + EXPECT(pcb->unacked == NULL); + err = tcp_output(pcb); + EXPECT_RET(err == ERR_OK); + EXPECT(txcounters.num_tx_calls == 2); + EXPECT(txcounters.num_tx_bytes == 2 * (TCP_MSS + 40U)); + memset(&txcounters, 0, sizeof(txcounters)); + + check_seqnos(pcb->unacked, 2, seqnos); + check_seqnos(pcb->unsent, 4, &seqnos[2]); + + /* call the tcp timer some times */ + for (i = 0; i < 10; i++) { + test_tcp_tmr(); + EXPECT(txcounters.num_tx_calls == 0); + } + /* 11th call to tcp_tmr: RTO rexmit fires */ + test_tcp_tmr(); + EXPECT(txcounters.num_tx_calls == 1); + check_seqnos(pcb->unacked, 1, seqnos); + check_seqnos(pcb->unsent, 5, &seqnos[1]); + + /* fake greater cwnd */ + pcb->cwnd = pcb->snd_wnd; + /* send more data */ + err = tcp_output(pcb); + EXPECT(err == ERR_OK); + /* check queues are sorted */ + EXPECT(pcb->unsent == NULL); + check_seqnos(pcb->unacked, 6, seqnos); + + /* make sure the pcb is freed */ + EXPECT_RET(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 1); + tcp_abort(pcb); + EXPECT_RET(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 0); +} +END_TEST + +/** Provoke fast retransmission by duplicate ACKs and then recover by ACKing all sent data. + * At the end, send more data. */ +static void test_tcp_tx_full_window_lost(u8_t zero_window_probe_from_unsent) +{ + struct netif netif; + struct test_tcp_txcounters txcounters; + struct test_tcp_counters counters; + struct tcp_pcb* pcb; + struct pbuf *p; + err_t err; + size_t i; + u16_t sent_total; + u8_t expected = 0xFE; + + for (i = 0; i < sizeof(tx_data); i++) { + u8_t d = (u8_t)i; + if (d == 0xFE) { + d = 0xF0; + } + tx_data[i] = d; + } + if (zero_window_probe_from_unsent) { + tx_data[TCP_WND] = expected; + } else { + tx_data[0] = expected; + } + + /* initialize local vars */ + test_tcp_init_netif(&netif, &txcounters, &test_local_ip, &test_netmask); + memset(&counters, 0, sizeof(counters)); + + /* create and initialize the pcb */ + pcb = test_tcp_new_counters_pcb(&counters); + EXPECT_RET(pcb != NULL); + tcp_set_state(pcb, ESTABLISHED, &test_local_ip, &test_remote_ip, TEST_LOCAL_PORT, TEST_REMOTE_PORT); + pcb->mss = TCP_MSS; + /* disable initial congestion window (we don't send a SYN here...) */ + pcb->cwnd = pcb->snd_wnd; + + /* send a full window (minus 1 packets) of TCP data in MSS-sized chunks */ + sent_total = 0; + if ((TCP_WND - TCP_MSS) % TCP_MSS != 0) { + u16_t initial_data_len = (TCP_WND - TCP_MSS) % TCP_MSS; + err = tcp_write(pcb, &tx_data[sent_total], initial_data_len, TCP_WRITE_FLAG_COPY); + EXPECT_RET(err == ERR_OK); + err = tcp_output(pcb); + EXPECT_RET(err == ERR_OK); + EXPECT(txcounters.num_tx_calls == 1); + EXPECT(txcounters.num_tx_bytes == initial_data_len + 40U); + memset(&txcounters, 0, sizeof(txcounters)); + sent_total += initial_data_len; + } + for (; sent_total < (TCP_WND - TCP_MSS); sent_total += TCP_MSS) { + err = tcp_write(pcb, &tx_data[sent_total], TCP_MSS, TCP_WRITE_FLAG_COPY); + EXPECT_RET(err == ERR_OK); + err = tcp_output(pcb); + EXPECT_RET(err == ERR_OK); + EXPECT(txcounters.num_tx_calls == 1); + EXPECT(txcounters.num_tx_bytes == TCP_MSS + 40U); + memset(&txcounters, 0, sizeof(txcounters)); + } + EXPECT(sent_total == (TCP_WND - TCP_MSS)); + + /* now ACK the packet before the first */ + p = tcp_create_rx_segment(pcb, NULL, 0, 0, 0, TCP_ACK); + test_tcp_input(p, &netif); + /* ensure this didn't trigger a retransmission */ + EXPECT(txcounters.num_tx_calls == 0); + EXPECT(txcounters.num_tx_bytes == 0); + + EXPECT(pcb->persist_backoff == 0); + /* send the last packet, now a complete window has been sent */ + err = tcp_write(pcb, &tx_data[sent_total], TCP_MSS, TCP_WRITE_FLAG_COPY); + sent_total += TCP_MSS; + EXPECT_RET(err == ERR_OK); + err = tcp_output(pcb); + EXPECT_RET(err == ERR_OK); + EXPECT(txcounters.num_tx_calls == 1); + EXPECT(txcounters.num_tx_bytes == TCP_MSS + 40U); + memset(&txcounters, 0, sizeof(txcounters)); + EXPECT(pcb->persist_backoff == 0); + + if (zero_window_probe_from_unsent) { + /* ACK all data but close the TX window */ + p = tcp_create_rx_segment_wnd(pcb, NULL, 0, 0, TCP_WND, TCP_ACK, 0); + test_tcp_input(p, &netif); + /* ensure this didn't trigger any transmission */ + EXPECT(txcounters.num_tx_calls == 0); + EXPECT(txcounters.num_tx_bytes == 0); + /* window is completely full, but persist timer is off since send buffer is empty */ + EXPECT(pcb->snd_wnd == 0); + EXPECT(pcb->persist_backoff == 0); + } + + /* send one byte more (out of window) -> persist timer starts */ + err = tcp_write(pcb, &tx_data[sent_total], 1, TCP_WRITE_FLAG_COPY); + EXPECT_RET(err == ERR_OK); + err = tcp_output(pcb); + EXPECT_RET(err == ERR_OK); + EXPECT(txcounters.num_tx_calls == 0); + EXPECT(txcounters.num_tx_bytes == 0); + memset(&txcounters, 0, sizeof(txcounters)); + if (!zero_window_probe_from_unsent) { + /* no persist timer unless a zero window announcement has been received */ + EXPECT(pcb->persist_backoff == 0); + } else { + EXPECT(pcb->persist_backoff == 1); + + /* call tcp_timer some more times to let persist timer count up */ + for (i = 0; i < 4; i++) { + test_tcp_tmr(); + EXPECT(txcounters.num_tx_calls == 0); + EXPECT(txcounters.num_tx_bytes == 0); + } + + /* this should trigger the zero-window-probe */ + txcounters.copy_tx_packets = 1; + test_tcp_tmr(); + txcounters.copy_tx_packets = 0; + EXPECT(txcounters.num_tx_calls == 1); + EXPECT(txcounters.num_tx_bytes == 1 + 40U); + EXPECT(txcounters.tx_packets != NULL); + if (txcounters.tx_packets != NULL) { + u8_t sent; + u16_t ret; + ret = pbuf_copy_partial(txcounters.tx_packets, &sent, 1, 40U); + EXPECT(ret == 1); + EXPECT(sent == expected); + } + if (txcounters.tx_packets != NULL) { + pbuf_free(txcounters.tx_packets); + txcounters.tx_packets = NULL; + } + } + + /* make sure the pcb is freed */ + EXPECT_RET(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 1); + tcp_abort(pcb); + EXPECT_RET(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 0); +} + +START_TEST(test_tcp_tx_full_window_lost_from_unsent) +{ + LWIP_UNUSED_ARG(_i); + test_tcp_tx_full_window_lost(1); +} +END_TEST + +START_TEST(test_tcp_tx_full_window_lost_from_unacked) +{ + LWIP_UNUSED_ARG(_i); + test_tcp_tx_full_window_lost(0); +} +END_TEST + +/** Send data, provoke retransmission and then add data to a segment + * that already has been sent before. */ +START_TEST(test_tcp_retx_add_to_sent) +{ + struct netif netif; + struct test_tcp_txcounters txcounters; + struct test_tcp_counters counters; + struct tcp_pcb* pcb; + struct pbuf* p; + char data1a[] = { 1, 2, 3}; + char data1b[] = { 4}; + char data2a[] = { 5, 6, 7, 8}; + char data2b[] = { 5, 6, 7}; + char data3[] = { 9, 10, 11, 12, 12}; + char data4[] = { 13, 14, 15, 16,17}; + err_t err; + int i; + LWIP_UNUSED_ARG(_i); + + /* initialize local vars */ + test_tcp_init_netif(&netif, &txcounters, &test_local_ip, &test_netmask); + memset(&counters, 0, sizeof(counters)); + + /* create and initialize the pcb */ + pcb = test_tcp_new_counters_pcb(&counters); + EXPECT_RET(pcb != NULL); + tcp_set_state(pcb, ESTABLISHED, &test_local_ip, &test_remote_ip, TEST_LOCAL_PORT, TEST_REMOTE_PORT); + pcb->mss = TCP_MSS; + /* disable initial congestion window (we don't send a SYN here...) */ + pcb->cwnd = pcb->snd_wnd; + + /* send data1 */ + err = tcp_write(pcb, data1a, sizeof(data1a), TCP_WRITE_FLAG_COPY); + EXPECT_RET(err == ERR_OK); + err = tcp_write(pcb, data1b, sizeof(data1b), TCP_WRITE_FLAG_COPY); + EXPECT_RET(err == ERR_OK); + err = tcp_output(pcb); + EXPECT_RET(err == ERR_OK); + EXPECT_RET(txcounters.num_tx_calls == 1); + EXPECT_RET(txcounters.num_tx_bytes == sizeof(data1a) + sizeof(data1b) + sizeof(struct tcp_hdr) + sizeof(struct ip_hdr)); + memset(&txcounters, 0, sizeof(txcounters)); + /* "recv" ACK for data1 */ + p = tcp_create_rx_segment(pcb, NULL, 0, 0, 4, TCP_ACK); + EXPECT_RET(p != NULL); + test_tcp_input(p, &netif); + EXPECT_RET(txcounters.num_tx_calls == 0); + EXPECT_RET(pcb->unacked == NULL); + /* send data2 */ + err = tcp_write(pcb, data2a, sizeof(data2a), TCP_WRITE_FLAG_COPY); + EXPECT_RET(err == ERR_OK); + err = tcp_write(pcb, data2b, sizeof(data2b), TCP_WRITE_FLAG_COPY); + EXPECT_RET(err == ERR_OK); + err = tcp_output(pcb); + EXPECT_RET(err == ERR_OK); + EXPECT_RET(txcounters.num_tx_calls == 1); + EXPECT_RET(txcounters.num_tx_bytes == sizeof(data2a) + sizeof(data2b) + sizeof(struct tcp_hdr) + sizeof(struct ip_hdr)); + memset(&txcounters, 0, sizeof(txcounters)); + /* send data3 */ + err = tcp_write(pcb, data3, sizeof(data3), TCP_WRITE_FLAG_COPY); + EXPECT_RET(err == ERR_OK); + err = tcp_output(pcb); + EXPECT_RET(err == ERR_OK); + EXPECT_RET(txcounters.num_tx_calls == 0); + EXPECT_RET(txcounters.num_tx_bytes == 0); + memset(&txcounters, 0, sizeof(txcounters)); + + /* data3 not sent yet (nagle) */ + EXPECT_RET(pcb->unacked != NULL); + EXPECT_RET(pcb->unsent != NULL); + + /* disable nagle for this test so data to sent segment can be added below... */ + tcp_nagle_disable(pcb); + + /* call the tcp timer some times */ + for (i = 0; i < 20; i++) { + test_tcp_tmr(); + if (txcounters.num_tx_calls != 0) { + break; + } + } + /* data3 sent */ + EXPECT_RET(txcounters.num_tx_calls == 1); + EXPECT_RET(txcounters.num_tx_bytes == sizeof(data3) + sizeof(struct tcp_hdr) + sizeof(struct ip_hdr)); + EXPECT_RET(pcb->unacked != NULL); + EXPECT_RET(pcb->unsent == NULL); + memset(&txcounters, 0, sizeof(txcounters)); + + tcp_nagle_enable(pcb); + + /* call the tcp timer some times */ + for (i = 0; i < 20; i++) { + test_tcp_tmr(); + if (txcounters.num_tx_calls != 0) { + break; + } + } + /* RTO: rexmit of data2 */ + EXPECT_RET(txcounters.num_tx_calls == 1); + EXPECT_RET(txcounters.num_tx_bytes == sizeof(data2a) + sizeof(data2b) + sizeof(struct tcp_hdr) + sizeof(struct ip_hdr)); + EXPECT_RET(pcb->unacked != NULL); + EXPECT_RET(pcb->unsent != NULL); + memset(&txcounters, 0, sizeof(txcounters)); + + /* send data4 */ + err = tcp_write(pcb, data4, sizeof(data4), TCP_WRITE_FLAG_COPY); + EXPECT_RET(err == ERR_OK); + /* disable nagle for this test so data to transmit without further ACKs... */ + tcp_nagle_disable(pcb); + err = tcp_output(pcb); + EXPECT_RET(err == ERR_OK); + /* nagle enabled, no tx calls */ + EXPECT_RET(txcounters.num_tx_calls == 1); + EXPECT_RET(txcounters.num_tx_bytes == sizeof(data3) + sizeof(data4) + sizeof(struct tcp_hdr) + sizeof(struct ip_hdr)); + memset(&txcounters, 0, sizeof(txcounters)); + /* make sure the pcb is freed */ + EXPECT_RET(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 1); + tcp_abort(pcb); + EXPECT_RET(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 0); +} +END_TEST + +START_TEST(test_tcp_rto_tracking) +{ + struct netif netif; + struct test_tcp_txcounters txcounters; + struct test_tcp_counters counters; + struct tcp_pcb* pcb; + struct pbuf* p; + err_t err; + size_t i; + u16_t sent_total = 0; + LWIP_UNUSED_ARG(_i); + + for (i = 0; i < sizeof(tx_data); i++) { + tx_data[i] = (u8_t)i; + } + + /* initialize local vars */ + test_tcp_init_netif(&netif, &txcounters, &test_local_ip, &test_netmask); + memset(&counters, 0, sizeof(counters)); + + /* create and initialize the pcb */ + tcp_ticks = SEQNO1 - ISS; + pcb = test_tcp_new_counters_pcb(&counters); + EXPECT_RET(pcb != NULL); + tcp_set_state(pcb, ESTABLISHED, &test_local_ip, &test_remote_ip, TEST_LOCAL_PORT, TEST_REMOTE_PORT); + pcb->mss = TCP_MSS; + /* Set congestion window large enough to send all our segments */ + pcb->cwnd = 5*TCP_MSS; + + /* send 5 mss-sized segments */ + for (i = 0; i < 5; i++) { + err = tcp_write(pcb, &tx_data[sent_total], TCP_MSS, TCP_WRITE_FLAG_COPY); + EXPECT_RET(err == ERR_OK); + sent_total += TCP_MSS; + } + check_seqnos(pcb->unsent, 5, seqnos); + EXPECT(pcb->unacked == NULL); + err = tcp_output(pcb); + EXPECT_RET(err == ERR_OK); + EXPECT(txcounters.num_tx_calls == 5); + EXPECT(txcounters.num_tx_bytes == 5 * (TCP_MSS + 40U)); + memset(&txcounters, 0, sizeof(txcounters)); + /* Check all 5 are in-flight */ + EXPECT(pcb->unsent == NULL); + check_seqnos(pcb->unacked, 5, seqnos); + + /* Force us into retransmisson timeout */ + while (!(pcb->flags & TF_RTO)) { + test_tcp_tmr(); + } + /* Ensure 4 remaining segments are back on unsent, ready for retransmission */ + check_seqnos(pcb->unsent, 4, &seqnos[1]); + /* Ensure 1st segment is on unacked (already retransmitted) */ + check_seqnos(pcb->unacked, 1, seqnos); + EXPECT(txcounters.num_tx_calls == 1); + EXPECT(txcounters.num_tx_bytes == TCP_MSS + 40U); + memset(&txcounters, 0, sizeof(txcounters)); + /* Ensure rto_end points to next byte */ + EXPECT(pcb->rto_end == seqnos[5]); + EXPECT(pcb->rto_end == pcb->snd_nxt); + /* Check cwnd was reset */ + EXPECT(pcb->cwnd == pcb->mss); + + /* Add another segment to send buffer which is outside of RTO */ + err = tcp_write(pcb, &tx_data[sent_total], TCP_MSS, TCP_WRITE_FLAG_COPY); + EXPECT_RET(err == ERR_OK); + sent_total += TCP_MSS; + check_seqnos(pcb->unsent, 5, &seqnos[1]); + /* Ensure no new data was sent */ + EXPECT(txcounters.num_tx_calls == 0); + EXPECT(txcounters.num_tx_bytes == 0); + EXPECT(pcb->rto_end == pcb->snd_nxt); + + /* ACK first segment */ + p = tcp_create_rx_segment(pcb, NULL, 0, 0, TCP_MSS, TCP_ACK); + test_tcp_input(p, &netif); + /* Next two retranmissions should go out, due to cwnd in slow start */ + EXPECT(txcounters.num_tx_calls == 2); + EXPECT(txcounters.num_tx_bytes == 2 * (TCP_MSS + 40U)); + memset(&txcounters, 0, sizeof(txcounters)); + check_seqnos(pcb->unacked, 2, &seqnos[1]); + check_seqnos(pcb->unsent, 3, &seqnos[3]); + /* RTO should still be marked */ + EXPECT(pcb->flags & TF_RTO); + /* cwnd should have only grown by 1 MSS */ + EXPECT(pcb->cwnd == (tcpwnd_size_t)(2 * pcb->mss)); + /* Ensure no new data was sent */ + EXPECT(pcb->rto_end == pcb->snd_nxt); + + /* ACK the next two segments */ + p = tcp_create_rx_segment(pcb, NULL, 0, 0, 2*TCP_MSS, TCP_ACK); + test_tcp_input(p, &netif); + /* Final 2 retransmissions and 1 new data should go out */ + EXPECT(txcounters.num_tx_calls == 3); + EXPECT(txcounters.num_tx_bytes == 3 * (TCP_MSS + 40U)); + memset(&txcounters, 0, sizeof(txcounters)); + check_seqnos(pcb->unacked, 3, &seqnos[3]); + EXPECT(pcb->unsent == NULL); + /* RTO should still be marked */ + EXPECT(pcb->flags & TF_RTO); + /* cwnd should have only grown by 1 MSS */ + EXPECT(pcb->cwnd == (tcpwnd_size_t)(3 * pcb->mss)); + /* snd_nxt should have been advanced past rto_end */ + EXPECT(TCP_SEQ_GT(pcb->snd_nxt, pcb->rto_end)); + + /* ACK the next two segments, finishing our RTO, leaving new segment unacked */ + p = tcp_create_rx_segment(pcb, NULL, 0, 0, 2*TCP_MSS, TCP_ACK); + test_tcp_input(p, &netif); + EXPECT(!(pcb->flags & TF_RTO)); + check_seqnos(pcb->unacked, 1, &seqnos[5]); + /* We should be in ABC congestion avoidance, so no change in cwnd */ + EXPECT(pcb->cwnd == (tcpwnd_size_t)(3 * pcb->mss)); + EXPECT(pcb->cwnd >= pcb->ssthresh); + /* Ensure ABC congestion avoidance is tracking bytes acked */ + EXPECT(pcb->bytes_acked == (tcpwnd_size_t)(2 * pcb->mss)); + + /* make sure the pcb is freed */ + EXPECT_RET(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 1); + tcp_abort(pcb); + EXPECT_RET(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 0); +} +END_TEST + +static void test_tcp_rto_timeout_impl(int link_down) +{ + struct netif netif; + struct test_tcp_txcounters txcounters; + struct test_tcp_counters counters; + struct tcp_pcb *pcb, *cur; + err_t err; + size_t i; + const size_t max_wait_ctr = 1024 * 1024; + + /* Setup data for a single segment */ + for (i = 0; i < TCP_MSS; i++) { + tx_data[i] = (u8_t)i; + } + + /* initialize local vars */ + test_tcp_init_netif(&netif, &txcounters, &test_local_ip, &test_netmask); + memset(&counters, 0, sizeof(counters)); + + /* create and initialize the pcb */ + tcp_ticks = SEQNO1 - ISS; + pcb = test_tcp_new_counters_pcb(&counters); + EXPECT_RET(pcb != NULL); + tcp_set_state(pcb, ESTABLISHED, &test_local_ip, &test_remote_ip, TEST_LOCAL_PORT, TEST_REMOTE_PORT); + pcb->mss = TCP_MSS; + pcb->cwnd = TCP_MSS; + + /* send our segment */ + err = tcp_write(pcb, &tx_data[0], TCP_MSS, TCP_WRITE_FLAG_COPY); + EXPECT_RET(err == ERR_OK); + err = tcp_output(pcb); + EXPECT_RET(err == ERR_OK); + EXPECT(txcounters.num_tx_calls == 1); + EXPECT(txcounters.num_tx_bytes == 1 * (TCP_MSS + 40U)); + memset(&txcounters, 0, sizeof(txcounters)); + + /* ensure no errors have been recorded */ + EXPECT(counters.err_calls == 0); + EXPECT(counters.last_err == ERR_OK); + + /* Force us into retransmisson timeout */ + for (i = 0; !(pcb->flags & TF_RTO) && i < max_wait_ctr; i++) { + test_tcp_tmr(); + } + EXPECT(i < max_wait_ctr); + + /* check first rexmit */ + EXPECT(pcb->nrtx == 1); + EXPECT(txcounters.num_tx_calls == 1); + EXPECT(txcounters.num_tx_bytes == 1 * (TCP_MSS + 40U)); + + /* still no error expected */ + EXPECT(counters.err_calls == 0); + EXPECT(counters.last_err == ERR_OK); + + if (link_down) { + netif_set_link_down(&netif); + } + + /* keep running the timer till we hit our maximum RTO */ + for (i = 0; counters.last_err == ERR_OK && i < max_wait_ctr; i++) { + test_tcp_tmr(); + } + EXPECT(i < max_wait_ctr); + + /* check number of retransmissions */ + if (link_down) { + EXPECT(txcounters.num_tx_calls == 1); + EXPECT(txcounters.num_tx_bytes == 1 * (TCP_MSS + 40U)); + } else { + EXPECT(txcounters.num_tx_calls == TCP_MAXRTX); + EXPECT(txcounters.num_tx_bytes == TCP_MAXRTX * (TCP_MSS + 40U)); + } + + /* check the connection (pcb) has been aborted */ + EXPECT(counters.err_calls == 1); + EXPECT(counters.last_err == ERR_ABRT); + /* check our pcb is no longer active */ + for (cur = tcp_active_pcbs; cur != NULL; cur = cur->next) { + EXPECT(cur != pcb); + } + EXPECT_RET(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 0); +} + +START_TEST(test_tcp_rto_timeout) +{ + LWIP_UNUSED_ARG(_i); + test_tcp_rto_timeout_impl(0); +} +END_TEST + +START_TEST(test_tcp_rto_timeout_link_down) +{ + LWIP_UNUSED_ARG(_i); + test_tcp_rto_timeout_impl(1); +} +END_TEST + +static void test_tcp_rto_timeout_syn_sent_impl(int link_down) +{ + struct netif netif; + struct test_tcp_txcounters txcounters; + struct test_tcp_counters counters; + struct tcp_pcb *pcb, *cur; + err_t err; + size_t i; + const size_t max_wait_ctr = 1024 * 1024; + const u16_t tcp_syn_opts_len = LWIP_TCP_OPT_LENGTH(TF_SEG_OPTS_MSS|TF_SEG_OPTS_WND_SCALE|TF_SEG_OPTS_SACK_PERM|TF_SEG_OPTS_TS); + + /* Setup data for a single segment */ + for (i = 0; i < TCP_MSS; i++) { + tx_data[i] = (u8_t)i; + } + + /* initialize local vars */ + test_tcp_init_netif(&netif, &txcounters, &test_local_ip, &test_netmask); + memset(&counters, 0, sizeof(counters)); + + /* create and initialize the pcb */ + tcp_ticks = SEQNO1 - ISS; + pcb = test_tcp_new_counters_pcb(&counters); + EXPECT_RET(pcb != NULL); + err = tcp_connect(pcb, &netif.gw, 123, NULL); + EXPECT_RET(err == ERR_OK); + EXPECT_RET(pcb->state == SYN_SENT); + EXPECT(txcounters.num_tx_calls == 1); + EXPECT(txcounters.num_tx_bytes == 40U + tcp_syn_opts_len); + + /* ensure no errors have been recorded */ + EXPECT(counters.err_calls == 0); + EXPECT(counters.last_err == ERR_OK); + + txcounters.num_tx_calls = 0; + txcounters.num_tx_bytes = 0; + + /* Force us into retransmisson timeout */ + for (i = 0; !(pcb->flags & TF_RTO) && i < max_wait_ctr; i++) { + test_tcp_tmr(); + } + EXPECT(i < max_wait_ctr); + + /* check first rexmit */ + EXPECT(pcb->nrtx == 1); + EXPECT(txcounters.num_tx_calls == 1); + EXPECT(txcounters.num_tx_bytes == 40U + tcp_syn_opts_len); /* 40: headers; >=: options */ + + /* still no error expected */ + EXPECT(counters.err_calls == 0); + EXPECT(counters.last_err == ERR_OK); + + if (link_down) { + /* set link down and check what happens to the RTO counter */ + netif_set_link_down(&netif); + } + + /* keep running the timer till we hit our maximum RTO */ + for (i = 0; counters.last_err == ERR_OK && i < max_wait_ctr; i++) { + test_tcp_tmr(); + } + EXPECT(i < max_wait_ctr); + + /* check number of retransmissions */ + if (link_down) { + EXPECT(txcounters.num_tx_calls == 1); + EXPECT(txcounters.num_tx_bytes == 40U + tcp_syn_opts_len); + } else { + EXPECT(txcounters.num_tx_calls == TCP_SYNMAXRTX); + EXPECT(txcounters.num_tx_bytes == TCP_SYNMAXRTX * (tcp_syn_opts_len + 40U)); + } + + /* check the connection (pcb) has been aborted */ + EXPECT(counters.err_calls == 1); + EXPECT(counters.last_err == ERR_ABRT); + /* check our pcb is no longer active */ + for (cur = tcp_active_pcbs; cur != NULL; cur = cur->next) { + EXPECT(cur != pcb); + } + EXPECT_RET(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 0); +} + +START_TEST(test_tcp_rto_timeout_syn_sent) +{ + LWIP_UNUSED_ARG(_i); + test_tcp_rto_timeout_syn_sent_impl(0); +} +END_TEST + +START_TEST(test_tcp_rto_timeout_syn_sent_link_down) +{ + LWIP_UNUSED_ARG(_i); + test_tcp_rto_timeout_syn_sent_impl(1); +} +END_TEST + +static void test_tcp_zwp_timeout_impl(int link_down) +{ + struct netif netif; + struct test_tcp_txcounters txcounters; + struct test_tcp_counters counters; + struct tcp_pcb *pcb, *cur; + struct pbuf* p; + err_t err; + size_t i; + + /* Setup data for two segments */ + for (i = 0; i < 2*TCP_MSS; i++) { + tx_data[i] = (u8_t)i; + } + + /* initialize local vars */ + test_tcp_init_netif(&netif, &txcounters, &test_local_ip, &test_netmask); + memset(&counters, 0, sizeof(counters)); + + /* create and initialize the pcb */ + tcp_ticks = SEQNO1 - ISS; + pcb = test_tcp_new_counters_pcb(&counters); + EXPECT_RET(pcb != NULL); + tcp_set_state(pcb, ESTABLISHED, &test_local_ip, &test_remote_ip, TEST_LOCAL_PORT, TEST_REMOTE_PORT); + pcb->mss = TCP_MSS; + pcb->cwnd = TCP_MSS; + + /* send first segment */ + err = tcp_write(pcb, &tx_data[0], TCP_MSS, TCP_WRITE_FLAG_COPY); + EXPECT(err == ERR_OK); + err = tcp_output(pcb); + EXPECT(err == ERR_OK); + + /* verify segment is in-flight */ + EXPECT(pcb->unsent == NULL); + check_seqnos(pcb->unacked, 1, seqnos); + EXPECT(txcounters.num_tx_calls == 1); + EXPECT(txcounters.num_tx_bytes == 1 * (TCP_MSS + 40U)); + memset(&txcounters, 0, sizeof(txcounters)); + + /* ACK the segment and close the TX window */ + p = tcp_create_rx_segment_wnd(pcb, NULL, 0, 0, TCP_MSS, TCP_ACK, 0); + test_tcp_input(p, &netif); + EXPECT(pcb->unacked == NULL); + EXPECT(pcb->unsent == NULL); + /* send buffer empty, persist should be off */ + EXPECT(pcb->persist_backoff == 0); + EXPECT(pcb->snd_wnd == 0); + + /* send second segment, should be buffered */ + err = tcp_write(pcb, &tx_data[TCP_MSS], TCP_MSS, TCP_WRITE_FLAG_COPY); + EXPECT(err == ERR_OK); + err = tcp_output(pcb); + EXPECT(err == ERR_OK); + + /* ensure it is buffered and persist timer started */ + EXPECT(pcb->unacked == NULL); + check_seqnos(pcb->unsent, 1, &seqnos[1]); + EXPECT(txcounters.num_tx_calls == 0); + EXPECT(txcounters.num_tx_bytes == 0); + EXPECT(pcb->persist_backoff == 1); + + /* ensure no errors have been recorded */ + EXPECT(counters.err_calls == 0); + EXPECT(counters.last_err == ERR_OK); + + /* run timer till first probe */ + EXPECT(pcb->persist_probe == 0); + while (pcb->persist_probe == 0) { + test_tcp_tmr(); + } + EXPECT(txcounters.num_tx_calls == 1); + EXPECT(txcounters.num_tx_bytes == (1 + 40U)); + memset(&txcounters, 0, sizeof(txcounters)); + + /* respond to probe with remote's current SEQ, ACK, and zero-window */ + p = tcp_create_rx_segment_wnd(pcb, NULL, 0, 0, 0, TCP_ACK, 0); + test_tcp_input(p, &netif); + /* ensure zero-window is still active, but probe count reset */ + EXPECT(pcb->persist_backoff > 1); + EXPECT(pcb->persist_probe == 0); + EXPECT(pcb->snd_wnd == 0); + + /* ensure no errors have been recorded */ + EXPECT(counters.err_calls == 0); + EXPECT(counters.last_err == ERR_OK); + + if (link_down) { + netif_set_link_down(&netif); + } + + /* now run the timer till we hit our maximum probe count */ + while (counters.last_err == ERR_OK) { + test_tcp_tmr(); + } + + if (link_down) { + EXPECT(txcounters.num_tx_calls == 0); + EXPECT(txcounters.num_tx_bytes == 0); + } else { + /* check maximum number of 1 byte probes were sent */ + EXPECT(txcounters.num_tx_calls == TCP_MAXRTX); + EXPECT(txcounters.num_tx_bytes == TCP_MAXRTX * (1 + 40U)); + } + + /* check the connection (pcb) has been aborted */ + EXPECT(counters.err_calls == 1); + EXPECT(counters.last_err == ERR_ABRT); + /* check our pcb is no longer active */ + for (cur = tcp_active_pcbs; cur != NULL; cur = cur->next) { + EXPECT(cur != pcb); + } + EXPECT_RET(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 0); +} + +START_TEST(test_tcp_zwp_timeout) +{ + LWIP_UNUSED_ARG(_i); + test_tcp_zwp_timeout_impl(0); +} +END_TEST + +START_TEST(test_tcp_zwp_timeout_link_down) +{ + LWIP_UNUSED_ARG(_i); + test_tcp_zwp_timeout_impl(1); +} +END_TEST + +START_TEST(test_tcp_persist_split) +{ + struct netif netif; + struct test_tcp_txcounters txcounters; + struct test_tcp_counters counters; + struct tcp_pcb *pcb; + struct pbuf* p; + err_t err; + size_t i; + LWIP_UNUSED_ARG(_i); + + /* Setup data for four segments */ + for (i = 0; i < 4 * TCP_MSS; i++) { + tx_data[i] = (u8_t)i; + } + + /* initialize local vars */ + test_tcp_init_netif(&netif, &txcounters, &test_local_ip, &test_netmask); + memset(&counters, 0, sizeof(counters)); + + /* create and initialize the pcb */ + tcp_ticks = SEQNO1 - ISS; + pcb = test_tcp_new_counters_pcb(&counters); + EXPECT_RET(pcb != NULL); + tcp_set_state(pcb, ESTABLISHED, &test_local_ip, &test_remote_ip, TEST_LOCAL_PORT, TEST_REMOTE_PORT); + pcb->mss = TCP_MSS; + /* set window to three segments */ + pcb->cwnd = 3 * TCP_MSS; + pcb->snd_wnd = 3 * TCP_MSS; + pcb->snd_wnd_max = 3 * TCP_MSS; + + /* send four segments. Fourth should stay buffered and is a 3/4 MSS segment to + get coverage on the oversized segment case */ + err = tcp_write(pcb, &tx_data[0], (3 * TCP_MSS) + (TCP_MSS - (TCP_MSS / 4)), TCP_WRITE_FLAG_COPY); + EXPECT(err == ERR_OK); + err = tcp_output(pcb); + EXPECT(err == ERR_OK); + + /* verify 3 segments are in-flight */ + EXPECT(pcb->unacked != NULL); + check_seqnos(pcb->unacked, 3, seqnos); + EXPECT(txcounters.num_tx_calls == 3); + EXPECT(txcounters.num_tx_bytes == 3 * (TCP_MSS + 40U)); + memset(&txcounters, 0, sizeof(txcounters)); + /* verify 4th segment is on unsent */ + EXPECT(pcb->unsent != NULL); + EXPECT(pcb->unsent->len == TCP_MSS - (TCP_MSS / 4)); + check_seqnos(pcb->unsent, 1, &seqnos[3]); +#if TCP_OVERSIZE + EXPECT(pcb->unsent_oversize == TCP_MSS / 4); +#if TCP_OVERSIZE_DBGCHECK + EXPECT(pcb->unsent->oversize_left == pcb->unsent_oversize); +#endif /* TCP_OVERSIZE_DBGCHECK */ +#endif /* TCP_OVERSIZE */ + + /* ACK the 3 segments and update the window to only 1/2 TCP_MSS. + 4th segment should stay on unsent because it's bigger than 1/2 MSS */ + p = tcp_create_rx_segment_wnd(pcb, NULL, 0, 0, 3 * TCP_MSS, TCP_ACK, TCP_MSS / 2); + test_tcp_input(p, &netif); + EXPECT(pcb->unacked == NULL); + EXPECT(pcb->snd_wnd == TCP_MSS / 2); + EXPECT(pcb->unsent != NULL); + check_seqnos(pcb->unsent, 1, &seqnos[3]); + EXPECT(txcounters.num_tx_calls == 0); + EXPECT(txcounters.num_tx_bytes == 0); + /* persist timer should be started since 4th segment is stuck waiting on snd_wnd */ + EXPECT(pcb->persist_backoff == 1); + + /* ensure no errors have been recorded */ + EXPECT(counters.err_calls == 0); + EXPECT(counters.last_err == ERR_OK); + + /* call tcp_timer some more times to let persist timer count up */ + for (i = 0; i < 4; i++) { + test_tcp_tmr(); + EXPECT(txcounters.num_tx_calls == 0); + EXPECT(txcounters.num_tx_bytes == 0); + } + + /* this should be the first timer shot, which should split the + * segment and send a runt (of the remaining window size) */ + txcounters.copy_tx_packets = 1; + test_tcp_tmr(); + txcounters.copy_tx_packets = 0; + /* persist will be disabled as RTO timer takes over */ + EXPECT(pcb->persist_backoff == 0); + EXPECT(txcounters.num_tx_calls == 1); + EXPECT(txcounters.num_tx_bytes == ((TCP_MSS /2) + 40U)); + /* verify 1/2 MSS segment sent, 1/4 MSS still buffered */ + EXPECT(pcb->unsent != NULL); + EXPECT(pcb->unsent->len == TCP_MSS / 4); + EXPECT(pcb->unacked != NULL); + EXPECT(pcb->unacked->len == TCP_MSS / 2); +#if TCP_OVERSIZE + /* verify there is no oversized remaining since during the + segment split, the remainder pbuf is always the exact length */ + EXPECT(pcb->unsent_oversize == 0); +#if TCP_OVERSIZE_DBGCHECK + /* Split segment already transmitted, should be at 0 */ + EXPECT(pcb->unacked->oversize_left == 0); + /* Remainder segment should match pcb value (which is 0) */ + EXPECT(pcb->unsent->oversize_left == pcb->unsent_oversize); +#endif /* TCP_OVERSIZE_DBGCHECK */ +#endif /* TCP_OVERSIZE */ + + /* verify first half segment */ + EXPECT(txcounters.tx_packets != NULL); + if (txcounters.tx_packets != NULL) { + u8_t sent[TCP_MSS / 2]; + u16_t ret; + ret = pbuf_copy_partial(txcounters.tx_packets, &sent, TCP_MSS / 2, 40U); + EXPECT(ret == TCP_MSS / 2); + EXPECT(memcmp(sent, &tx_data[3 * TCP_MSS], TCP_MSS / 2) == 0); + } + if (txcounters.tx_packets != NULL) { + pbuf_free(txcounters.tx_packets); + txcounters.tx_packets = NULL; + } + memset(&txcounters, 0, sizeof(txcounters)); + + /* ACK the half segment, leave window at half segment */ + p = tcp_create_rx_segment_wnd(pcb, NULL, 0, 0, TCP_MSS / 2, TCP_ACK, TCP_MSS / 2); + txcounters.copy_tx_packets = 1; + test_tcp_input(p, &netif); + txcounters.copy_tx_packets = 0; + /* ensure remaining segment was sent */ + EXPECT(txcounters.num_tx_calls == 1); + EXPECT(txcounters.num_tx_bytes == ((TCP_MSS / 4) + 40U)); + EXPECT(pcb->unsent == NULL); + EXPECT(pcb->unacked != NULL); + EXPECT(pcb->unacked->len == TCP_MSS / 4); + EXPECT(pcb->snd_wnd == TCP_MSS / 2); + + /* verify remainder segment */ + EXPECT(txcounters.tx_packets != NULL); + if (txcounters.tx_packets != NULL) { + u8_t sent[TCP_MSS / 4]; + u16_t ret; + ret = pbuf_copy_partial(txcounters.tx_packets, &sent, TCP_MSS / 4, 40U); + EXPECT(ret == TCP_MSS / 4); + EXPECT(memcmp(sent, &tx_data[(3 * TCP_MSS) + TCP_MSS / 2], TCP_MSS / 4) == 0); + } + if (txcounters.tx_packets != NULL) { + pbuf_free(txcounters.tx_packets); + txcounters.tx_packets = NULL; + } + + /* ensure no errors have been recorded */ + EXPECT(counters.err_calls == 0); + EXPECT(counters.last_err == ERR_OK); + + /* make sure the pcb is freed */ + EXPECT_RET(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 1); + tcp_abort(pcb); + EXPECT_RET(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 0); +} +END_TEST + +/** Create the suite including all tests for this module */ +Suite * +tcp_suite(void) +{ + testfunc tests[] = { + TESTFUNC(test_tcp_new_abort), + TESTFUNC(test_tcp_listen_passive_open), + TESTFUNC(test_tcp_recv_inseq), + TESTFUNC(test_tcp_recv_inseq_trim), + TESTFUNC(test_tcp_passive_close), + TESTFUNC(test_tcp_active_abort), + TESTFUNC(test_tcp_malformed_header), + TESTFUNC(test_tcp_fast_retx_recover), + TESTFUNC(test_tcp_fast_rexmit_wraparound), + TESTFUNC(test_tcp_rto_rexmit_wraparound), + TESTFUNC(test_tcp_tx_full_window_lost_from_unacked), + TESTFUNC(test_tcp_tx_full_window_lost_from_unsent), + TESTFUNC(test_tcp_retx_add_to_sent), + TESTFUNC(test_tcp_rto_tracking), + TESTFUNC(test_tcp_rto_timeout), + TESTFUNC(test_tcp_rto_timeout_link_down), + TESTFUNC(test_tcp_rto_timeout_syn_sent), + TESTFUNC(test_tcp_rto_timeout_syn_sent_link_down), + TESTFUNC(test_tcp_zwp_timeout), + TESTFUNC(test_tcp_zwp_timeout_link_down), + TESTFUNC(test_tcp_persist_split) + }; + return create_suite("TCP", tests, sizeof(tests)/sizeof(testfunc), tcp_setup, tcp_teardown); +} diff --git a/test/unit/tcp/test_tcp.h b/test/unit/tcp/test_tcp.h new file mode 100644 index 00000000000..f28ee56530e --- /dev/null +++ b/test/unit/tcp/test_tcp.h @@ -0,0 +1,8 @@ +#ifndef LWIP_HDR_TEST_TCP_H +#define LWIP_HDR_TEST_TCP_H + +#include "../lwip_check.h" + +Suite *tcp_suite(void); + +#endif diff --git a/test/unit/tcp/test_tcp_oos.c b/test/unit/tcp/test_tcp_oos.c new file mode 100644 index 00000000000..a190e7dca3e --- /dev/null +++ b/test/unit/tcp/test_tcp_oos.c @@ -0,0 +1,1018 @@ +#include "test_tcp_oos.h" + +#include "lwip/priv/tcp_priv.h" +#include "lwip/stats.h" +#include "tcp_helper.h" + +#if !LWIP_STATS || !TCP_STATS || !MEMP_STATS +#error "This tests needs TCP- and MEMP-statistics enabled" +#endif +#if !TCP_QUEUE_OOSEQ +#error "This tests needs TCP_QUEUE_OOSEQ enabled" +#endif + +/** CHECK_SEGMENTS_ON_OOSEQ: + * 1: check count, seqno and len of segments on pcb->ooseq (strict) + * 0: only check that bytes are received in correct order (less strict) */ +#define CHECK_SEGMENTS_ON_OOSEQ 1 + +#if CHECK_SEGMENTS_ON_OOSEQ +#define EXPECT_OOSEQ(x) EXPECT(x) +#else +#define EXPECT_OOSEQ(x) +#endif + +/* helper functions */ + +/** Get the numbers of segments on the ooseq list */ +static int tcp_oos_count(struct tcp_pcb* pcb) +{ + int num = 0; + struct tcp_seg* seg = pcb->ooseq; + while(seg != NULL) { + num++; + seg = seg->next; + } + return num; +} + +#if TCP_OOSEQ_MAX_PBUFS && (TCP_OOSEQ_MAX_PBUFS < ((TCP_WND / TCP_MSS) + 1)) && (PBUF_POOL_BUFSIZE >= (TCP_MSS + PBUF_LINK_ENCAPSULATION_HLEN + PBUF_LINK_HLEN + PBUF_IP_HLEN + PBUF_TRANSPORT_HLEN)) +/** Get the numbers of pbufs on the ooseq list */ +static int tcp_oos_pbuf_count(struct tcp_pcb* pcb) +{ + int num = 0; + struct tcp_seg* seg = pcb->ooseq; + while(seg != NULL) { + num += pbuf_clen(seg->p); + seg = seg->next; + } + return num; +} +#endif + +/** Get the seqno of a segment (by index) on the ooseq list + * + * @param pcb the pcb to check for ooseq segments + * @param seg_index index of the segment on the ooseq list + * @return seqno of the segment + */ +static u32_t +tcp_oos_seg_seqno(struct tcp_pcb* pcb, int seg_index) +{ + int num = 0; + struct tcp_seg* seg = pcb->ooseq; + + /* then check the actual segment */ + while(seg != NULL) { + if(num == seg_index) { + return seg->tcphdr->seqno; + } + num++; + seg = seg->next; + } + fail(); + return 0; +} + +/** Get the tcplen (datalen + SYN/FIN) of a segment (by index) on the ooseq list + * + * @param pcb the pcb to check for ooseq segments + * @param seg_index index of the segment on the ooseq list + * @return tcplen of the segment + */ +static int +tcp_oos_seg_tcplen(struct tcp_pcb* pcb, int seg_index) +{ + int num = 0; + struct tcp_seg* seg = pcb->ooseq; + + /* then check the actual segment */ + while(seg != NULL) { + if(num == seg_index) { + return TCP_TCPLEN(seg); + } + num++; + seg = seg->next; + } + fail(); + return -1; +} + +/** Get the tcplen (datalen + SYN/FIN) of all segments on the ooseq list + * + * @param pcb the pcb to check for ooseq segments + * @return tcplen of all segment + */ +static int +tcp_oos_tcplen(struct tcp_pcb* pcb) +{ + int len = 0; + struct tcp_seg* seg = pcb->ooseq; + + /* then check the actual segment */ + while(seg != NULL) { + len += TCP_TCPLEN(seg); + seg = seg->next; + } + return len; +} + +/* Setup/teardown functions */ +static struct netif *old_netif_list; +static struct netif *old_netif_default; + +static void +tcp_oos_setup(void) +{ + old_netif_list = netif_list; + old_netif_default = netif_default; + netif_list = NULL; + netif_default = NULL; + tcp_remove_all(); + lwip_check_ensure_no_alloc(SKIP_POOL(MEMP_SYS_TIMEOUT)); +} + +static void +tcp_oos_teardown(void) +{ + netif_list = NULL; + netif_default = NULL; + tcp_remove_all(); + /* restore netif_list for next tests (e.g. loopif) */ + netif_list = old_netif_list; + netif_default = old_netif_default; + lwip_check_ensure_no_alloc(SKIP_POOL(MEMP_SYS_TIMEOUT)); +} + + + +/* Test functions */ + +/** create multiple segments and pass them to tcp_input in a wrong + * order to see if ooseq-caching works correctly + * FIN is received in out-of-sequence segments only */ +START_TEST(test_tcp_recv_ooseq_FIN_OOSEQ) +{ + struct test_tcp_counters counters; + struct tcp_pcb* pcb; + struct pbuf *p_8_9, *p_4_8, *p_4_10, *p_2_14, *p_fin, *pinseq; + char data[] = { + 1, 2, 3, 4, + 5, 6, 7, 8, + 9, 10, 11, 12, + 13, 14, 15, 16}; + u16_t data_len; + struct netif netif; + LWIP_UNUSED_ARG(_i); + + /* initialize local vars */ + test_tcp_init_netif(&netif, NULL, &test_local_ip, &test_netmask); + data_len = sizeof(data); + /* initialize counter struct */ + memset(&counters, 0, sizeof(counters)); + counters.expected_data_len = data_len; + counters.expected_data = data; + + /* create and initialize the pcb */ + pcb = test_tcp_new_counters_pcb(&counters); + EXPECT_RET(pcb != NULL); + tcp_set_state(pcb, ESTABLISHED, &test_local_ip, &test_remote_ip, TEST_LOCAL_PORT, TEST_REMOTE_PORT); + + /* create segments */ + /* pinseq is sent as last segment! */ + pinseq = tcp_create_rx_segment(pcb, &data[0], 4, 0, 0, TCP_ACK); + /* p1: 8 bytes before FIN */ + /* seqno: 8..16 */ + p_8_9 = tcp_create_rx_segment(pcb, &data[8], 8, 8, 0, TCP_ACK|TCP_FIN); + /* p2: 4 bytes before p1, including the first 4 bytes of p1 (partly duplicate) */ + /* seqno: 4..11 */ + p_4_8 = tcp_create_rx_segment(pcb, &data[4], 8, 4, 0, TCP_ACK); + /* p3: same as p2 but 2 bytes longer */ + /* seqno: 4..13 */ + p_4_10 = tcp_create_rx_segment(pcb, &data[4], 10, 4, 0, TCP_ACK); + /* p4: 14 bytes before FIN, includes data from p1 and p2, plus partly from pinseq */ + /* seqno: 2..15 */ + p_2_14 = tcp_create_rx_segment(pcb, &data[2], 14, 2, 0, TCP_ACK); + /* FIN, seqno 16 */ + p_fin = tcp_create_rx_segment(pcb, NULL, 0,16, 0, TCP_ACK|TCP_FIN); + EXPECT(pinseq != NULL); + EXPECT(p_8_9 != NULL); + EXPECT(p_4_8 != NULL); + EXPECT(p_4_10 != NULL); + EXPECT(p_2_14 != NULL); + EXPECT(p_fin != NULL); + if ((pinseq != NULL) && (p_8_9 != NULL) && (p_4_8 != NULL) && (p_4_10 != NULL) && (p_2_14 != NULL) && (p_fin != NULL)) { + /* pass the segment to tcp_input */ + test_tcp_input(p_8_9, &netif); + /* check if counters are as expected */ + EXPECT(counters.close_calls == 0); + EXPECT(counters.recv_calls == 0); + EXPECT(counters.recved_bytes == 0); + EXPECT(counters.err_calls == 0); + /* check ooseq queue */ + EXPECT_OOSEQ(tcp_oos_count(pcb) == 1); + EXPECT_OOSEQ(tcp_oos_seg_seqno(pcb, 0) == 8); + EXPECT_OOSEQ(tcp_oos_seg_tcplen(pcb, 0) == 9); /* includes FIN */ + + /* pass the segment to tcp_input */ + test_tcp_input(p_4_8, &netif); + /* check if counters are as expected */ + EXPECT(counters.close_calls == 0); + EXPECT(counters.recv_calls == 0); + EXPECT(counters.recved_bytes == 0); + EXPECT(counters.err_calls == 0); + /* check ooseq queue */ + EXPECT_OOSEQ(tcp_oos_count(pcb) == 2); + EXPECT_OOSEQ(tcp_oos_seg_seqno(pcb, 0) == 4); + EXPECT_OOSEQ(tcp_oos_seg_tcplen(pcb, 0) == 4); + EXPECT_OOSEQ(tcp_oos_seg_seqno(pcb, 1) == 8); + EXPECT_OOSEQ(tcp_oos_seg_tcplen(pcb, 1) == 9); /* includes FIN */ + + /* pass the segment to tcp_input */ + test_tcp_input(p_4_10, &netif); + /* check if counters are as expected */ + EXPECT(counters.close_calls == 0); + EXPECT(counters.recv_calls == 0); + EXPECT(counters.recved_bytes == 0); + EXPECT(counters.err_calls == 0); + /* ooseq queue: unchanged */ + EXPECT_OOSEQ(tcp_oos_count(pcb) == 2); + EXPECT_OOSEQ(tcp_oos_seg_seqno(pcb, 0) == 4); + EXPECT_OOSEQ(tcp_oos_seg_tcplen(pcb, 0) == 4); + EXPECT_OOSEQ(tcp_oos_seg_seqno(pcb, 1) == 8); + EXPECT_OOSEQ(tcp_oos_seg_tcplen(pcb, 1) == 9); /* includes FIN */ + + /* pass the segment to tcp_input */ + test_tcp_input(p_2_14, &netif); + /* check if counters are as expected */ + EXPECT(counters.close_calls == 0); + EXPECT(counters.recv_calls == 0); + EXPECT(counters.recved_bytes == 0); + EXPECT(counters.err_calls == 0); + /* check ooseq queue */ + EXPECT_OOSEQ(tcp_oos_count(pcb) == 1); + EXPECT_OOSEQ(tcp_oos_seg_seqno(pcb, 0) == 2); + EXPECT_OOSEQ(tcp_oos_seg_tcplen(pcb, 0) == 15); /* includes FIN */ + + /* pass the segment to tcp_input */ + test_tcp_input(p_fin, &netif); + /* check if counters are as expected */ + EXPECT(counters.close_calls == 0); + EXPECT(counters.recv_calls == 0); + EXPECT(counters.recved_bytes == 0); + EXPECT(counters.err_calls == 0); + /* ooseq queue: unchanged */ + EXPECT_OOSEQ(tcp_oos_count(pcb) == 1); + EXPECT_OOSEQ(tcp_oos_seg_seqno(pcb, 0) == 2); + EXPECT_OOSEQ(tcp_oos_seg_tcplen(pcb, 0) == 15); /* includes FIN */ + + /* pass the segment to tcp_input */ + test_tcp_input(pinseq, &netif); + /* check if counters are as expected */ + EXPECT(counters.close_calls == 1); + EXPECT(counters.recv_calls == 1); + EXPECT(counters.recved_bytes == data_len); + EXPECT(counters.err_calls == 0); + EXPECT(pcb->ooseq == NULL); + } + + /* make sure the pcb is freed */ + EXPECT(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 1); + tcp_abort(pcb); + EXPECT(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 0); +} +END_TEST + + +/** create multiple segments and pass them to tcp_input in a wrong + * order to see if ooseq-caching works correctly + * FIN is received IN-SEQUENCE at the end */ +START_TEST(test_tcp_recv_ooseq_FIN_INSEQ) +{ + struct test_tcp_counters counters; + struct tcp_pcb* pcb; + struct pbuf *p_1_2, *p_4_8, *p_3_11, *p_2_12, *p_15_1, *p_15_1a, *pinseq, *pinseqFIN; + char data[] = { + 1, 2, 3, 4, + 5, 6, 7, 8, + 9, 10, 11, 12, + 13, 14, 15, 16}; + u16_t data_len; + struct netif netif; + LWIP_UNUSED_ARG(_i); + + /* initialize local vars */ + test_tcp_init_netif(&netif, NULL, &test_local_ip, &test_netmask); + data_len = sizeof(data); + /* initialize counter struct */ + memset(&counters, 0, sizeof(counters)); + counters.expected_data_len = data_len; + counters.expected_data = data; + + /* create and initialize the pcb */ + pcb = test_tcp_new_counters_pcb(&counters); + EXPECT_RET(pcb != NULL); + tcp_set_state(pcb, ESTABLISHED, &test_local_ip, &test_remote_ip, TEST_LOCAL_PORT, TEST_REMOTE_PORT); + + /* create segments */ + /* p1: 7 bytes - 2 before FIN */ + /* seqno: 1..2 */ + p_1_2 = tcp_create_rx_segment(pcb, &data[1], 2, 1, 0, TCP_ACK); + /* p2: 4 bytes before p1, including the first 4 bytes of p1 (partly duplicate) */ + /* seqno: 4..11 */ + p_4_8 = tcp_create_rx_segment(pcb, &data[4], 8, 4, 0, TCP_ACK); + /* p3: same as p2 but 2 bytes longer and one byte more at the front */ + /* seqno: 3..13 */ + p_3_11 = tcp_create_rx_segment(pcb, &data[3], 11, 3, 0, TCP_ACK); + /* p4: 13 bytes - 2 before FIN - should be ignored as contained in p1 and p3 */ + /* seqno: 2..13 */ + p_2_12 = tcp_create_rx_segment(pcb, &data[2], 12, 2, 0, TCP_ACK); + /* pinseq is the first segment that is held back to create ooseq! */ + /* seqno: 0..3 */ + pinseq = tcp_create_rx_segment(pcb, &data[0], 4, 0, 0, TCP_ACK); + /* p5: last byte before FIN */ + /* seqno: 15 */ + p_15_1 = tcp_create_rx_segment(pcb, &data[15], 1, 15, 0, TCP_ACK); + /* p6: same as p5, should be ignored */ + p_15_1a= tcp_create_rx_segment(pcb, &data[15], 1, 15, 0, TCP_ACK); + /* pinseqFIN: last 2 bytes plus FIN */ + /* only segment containing seqno 14 and FIN */ + pinseqFIN = tcp_create_rx_segment(pcb, &data[14], 2, 14, 0, TCP_ACK|TCP_FIN); + EXPECT(pinseq != NULL); + EXPECT(p_1_2 != NULL); + EXPECT(p_4_8 != NULL); + EXPECT(p_3_11 != NULL); + EXPECT(p_2_12 != NULL); + EXPECT(p_15_1 != NULL); + EXPECT(p_15_1a != NULL); + EXPECT(pinseqFIN != NULL); + if ((pinseq != NULL) && (p_1_2 != NULL) && (p_4_8 != NULL) && (p_3_11 != NULL) && (p_2_12 != NULL) + && (p_15_1 != NULL) && (p_15_1a != NULL) && (pinseqFIN != NULL)) { + /* pass the segment to tcp_input */ + test_tcp_input(p_1_2, &netif); + /* check if counters are as expected */ + EXPECT(counters.close_calls == 0); + EXPECT(counters.recv_calls == 0); + EXPECT(counters.recved_bytes == 0); + EXPECT(counters.err_calls == 0); + /* check ooseq queue */ + EXPECT_OOSEQ(tcp_oos_count(pcb) == 1); + EXPECT_OOSEQ(tcp_oos_seg_seqno(pcb, 0) == 1); + EXPECT_OOSEQ(tcp_oos_seg_tcplen(pcb, 0) == 2); + + /* pass the segment to tcp_input */ + test_tcp_input(p_4_8, &netif); + /* check if counters are as expected */ + EXPECT(counters.close_calls == 0); + EXPECT(counters.recv_calls == 0); + EXPECT(counters.recved_bytes == 0); + EXPECT(counters.err_calls == 0); + /* check ooseq queue */ + EXPECT_OOSEQ(tcp_oos_count(pcb) == 2); + EXPECT_OOSEQ(tcp_oos_seg_seqno(pcb, 0) == 1); + EXPECT_OOSEQ(tcp_oos_seg_tcplen(pcb, 0) == 2); + EXPECT_OOSEQ(tcp_oos_seg_seqno(pcb, 1) == 4); + EXPECT_OOSEQ(tcp_oos_seg_tcplen(pcb, 1) == 8); + + /* pass the segment to tcp_input */ + test_tcp_input(p_3_11, &netif); + /* check if counters are as expected */ + EXPECT(counters.close_calls == 0); + EXPECT(counters.recv_calls == 0); + EXPECT(counters.recved_bytes == 0); + EXPECT(counters.err_calls == 0); + /* check ooseq queue */ + EXPECT_OOSEQ(tcp_oos_count(pcb) == 2); + EXPECT_OOSEQ(tcp_oos_seg_seqno(pcb, 0) == 1); + EXPECT_OOSEQ(tcp_oos_seg_tcplen(pcb, 0) == 2); + /* p_3_11 has removed p_4_8 from ooseq */ + EXPECT_OOSEQ(tcp_oos_seg_seqno(pcb, 1) == 3); + EXPECT_OOSEQ(tcp_oos_seg_tcplen(pcb, 1) == 11); + + /* pass the segment to tcp_input */ + test_tcp_input(p_2_12, &netif); + /* check if counters are as expected */ + EXPECT(counters.close_calls == 0); + EXPECT(counters.recv_calls == 0); + EXPECT(counters.recved_bytes == 0); + EXPECT(counters.err_calls == 0); + /* check ooseq queue */ + EXPECT_OOSEQ(tcp_oos_count(pcb) == 2); + EXPECT_OOSEQ(tcp_oos_seg_seqno(pcb, 0) == 1); + EXPECT_OOSEQ(tcp_oos_seg_tcplen(pcb, 0) == 1); + EXPECT_OOSEQ(tcp_oos_seg_seqno(pcb, 1) == 2); + EXPECT_OOSEQ(tcp_oos_seg_tcplen(pcb, 1) == 12); + + /* pass the segment to tcp_input */ + test_tcp_input(pinseq, &netif); + /* check if counters are as expected */ + EXPECT(counters.close_calls == 0); + EXPECT(counters.recv_calls == 1); + EXPECT(counters.recved_bytes == 14); + EXPECT(counters.err_calls == 0); + EXPECT(pcb->ooseq == NULL); + + /* pass the segment to tcp_input */ + test_tcp_input(p_15_1, &netif); + /* check if counters are as expected */ + EXPECT(counters.close_calls == 0); + EXPECT(counters.recv_calls == 1); + EXPECT(counters.recved_bytes == 14); + EXPECT(counters.err_calls == 0); + /* check ooseq queue */ + EXPECT_OOSEQ(tcp_oos_count(pcb) == 1); + EXPECT_OOSEQ(tcp_oos_seg_seqno(pcb, 0) == 15); + EXPECT_OOSEQ(tcp_oos_seg_tcplen(pcb, 0) == 1); + + /* pass the segment to tcp_input */ + test_tcp_input(p_15_1a, &netif); + /* check if counters are as expected */ + EXPECT(counters.close_calls == 0); + EXPECT(counters.recv_calls == 1); + EXPECT(counters.recved_bytes == 14); + EXPECT(counters.err_calls == 0); + /* check ooseq queue: unchanged */ + EXPECT_OOSEQ(tcp_oos_count(pcb) == 1); + EXPECT_OOSEQ(tcp_oos_seg_seqno(pcb, 0) == 15); + EXPECT_OOSEQ(tcp_oos_seg_tcplen(pcb, 0) == 1); + + /* pass the segment to tcp_input */ + test_tcp_input(pinseqFIN, &netif); + /* check if counters are as expected */ + EXPECT(counters.close_calls == 1); + EXPECT(counters.recv_calls == 2); + EXPECT(counters.recved_bytes == data_len); + EXPECT(counters.err_calls == 0); + EXPECT(pcb->ooseq == NULL); + } + + /* make sure the pcb is freed */ + EXPECT(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 1); + tcp_abort(pcb); + EXPECT(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 0); +} +END_TEST + +static char data_full_wnd[TCP_WND + TCP_MSS]; + +/** create multiple segments and pass them to tcp_input with the first segment missing + * to simulate overruning the rxwin with ooseq queueing enabled */ +START_TEST(test_tcp_recv_ooseq_overrun_rxwin) +{ +#if !TCP_OOSEQ_MAX_BYTES && !TCP_OOSEQ_MAX_PBUFS + int i, k; + struct test_tcp_counters counters; + struct tcp_pcb* pcb; + struct pbuf *pinseq, *p_ovr; + struct netif netif; + int datalen = 0; + int datalen2; + + for(i = 0; i < (int)sizeof(data_full_wnd); i++) { + data_full_wnd[i] = (char)i; + } + + /* initialize local vars */ + test_tcp_init_netif(&netif, NULL, &test_local_ip, &test_netmask); + /* initialize counter struct */ + memset(&counters, 0, sizeof(counters)); + counters.expected_data_len = TCP_WND; + counters.expected_data = data_full_wnd; + + /* create and initialize the pcb */ + pcb = test_tcp_new_counters_pcb(&counters); + EXPECT_RET(pcb != NULL); + tcp_set_state(pcb, ESTABLISHED, &test_local_ip, &test_remote_ip, TEST_LOCAL_PORT, TEST_REMOTE_PORT); + pcb->rcv_nxt = 0x8000; + + /* create segments */ + /* pinseq is sent as last segment! */ + pinseq = tcp_create_rx_segment(pcb, &data_full_wnd[0], TCP_MSS, 0, 0, TCP_ACK); + + for(i = TCP_MSS, k = 0; i < TCP_WND; i += TCP_MSS, k++) { + int count, expected_datalen; + struct pbuf *p = tcp_create_rx_segment(pcb, &data_full_wnd[TCP_MSS*(k+1)], + TCP_MSS, TCP_MSS*(k+1), 0, TCP_ACK); + EXPECT_RET(p != NULL); + /* pass the segment to tcp_input */ + test_tcp_input(p, &netif); + /* check if counters are as expected */ + EXPECT(counters.close_calls == 0); + EXPECT(counters.recv_calls == 0); + EXPECT(counters.recved_bytes == 0); + EXPECT(counters.err_calls == 0); + /* check ooseq queue */ + count = tcp_oos_count(pcb); + EXPECT_OOSEQ(count == k+1); + datalen = tcp_oos_tcplen(pcb); + if (i + TCP_MSS < TCP_WND) { + expected_datalen = (k+1)*TCP_MSS; + } else { + expected_datalen = TCP_WND - TCP_MSS; + } + if (datalen != expected_datalen) { + EXPECT_OOSEQ(datalen == expected_datalen); + } + } + + /* pass in one more segment, cleary overrunning the rxwin */ + p_ovr = tcp_create_rx_segment(pcb, &data_full_wnd[TCP_MSS*(k+1)], TCP_MSS, TCP_MSS*(k+1), 0, TCP_ACK); + EXPECT_RET(p_ovr != NULL); + /* pass the segment to tcp_input */ + test_tcp_input(p_ovr, &netif); + /* check if counters are as expected */ + EXPECT(counters.close_calls == 0); + EXPECT(counters.recv_calls == 0); + EXPECT(counters.recved_bytes == 0); + EXPECT(counters.err_calls == 0); + /* check ooseq queue */ + EXPECT_OOSEQ(tcp_oos_count(pcb) == k); + datalen2 = tcp_oos_tcplen(pcb); + EXPECT_OOSEQ(datalen == datalen2); + + /* now pass inseq */ + test_tcp_input(pinseq, &netif); + EXPECT(pcb->ooseq == NULL); + + /* make sure the pcb is freed */ + EXPECT(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 1); + tcp_abort(pcb); + EXPECT(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 0); +#endif /* !TCP_OOSEQ_MAX_BYTES && !TCP_OOSEQ_MAX_PBUFS */ + LWIP_UNUSED_ARG(_i); +} +END_TEST + +/** similar to above test, except seqno starts near the max rxwin */ +START_TEST(test_tcp_recv_ooseq_overrun_rxwin_edge) +{ +#if !TCP_OOSEQ_MAX_BYTES && !TCP_OOSEQ_MAX_PBUFS + int i, k; + struct test_tcp_counters counters; + struct tcp_pcb* pcb; + struct pbuf *pinseq, *p_ovr; + struct netif netif; + int datalen = 0; + int datalen2; + + for(i = 0; i < (int)sizeof(data_full_wnd); i++) { + data_full_wnd[i] = (char)i; + } + + /* initialize local vars */ + test_tcp_init_netif(&netif, NULL, &test_local_ip, &test_netmask); + /* initialize counter struct */ + memset(&counters, 0, sizeof(counters)); + counters.expected_data_len = TCP_WND; + counters.expected_data = data_full_wnd; + + /* create and initialize the pcb */ + pcb = test_tcp_new_counters_pcb(&counters); + EXPECT_RET(pcb != NULL); + tcp_set_state(pcb, ESTABLISHED, &test_local_ip, &test_remote_ip, TEST_LOCAL_PORT, TEST_REMOTE_PORT); + pcb->rcv_nxt = 0xffffffff - (TCP_WND / 2); + + /* create segments */ + /* pinseq is sent as last segment! */ + pinseq = tcp_create_rx_segment(pcb, &data_full_wnd[0], TCP_MSS, 0, 0, TCP_ACK); + + for(i = TCP_MSS, k = 0; i < TCP_WND; i += TCP_MSS, k++) { + int count, expected_datalen; + struct pbuf *p = tcp_create_rx_segment(pcb, &data_full_wnd[TCP_MSS*(k+1)], + TCP_MSS, TCP_MSS*(k+1), 0, TCP_ACK); + EXPECT_RET(p != NULL); + /* pass the segment to tcp_input */ + test_tcp_input(p, &netif); + /* check if counters are as expected */ + EXPECT(counters.close_calls == 0); + EXPECT(counters.recv_calls == 0); + EXPECT(counters.recved_bytes == 0); + EXPECT(counters.err_calls == 0); + /* check ooseq queue */ + count = tcp_oos_count(pcb); + EXPECT_OOSEQ(count == k+1); + datalen = tcp_oos_tcplen(pcb); + if (i + TCP_MSS < TCP_WND) { + expected_datalen = (k+1)*TCP_MSS; + } else { + expected_datalen = TCP_WND - TCP_MSS; + } + if (datalen != expected_datalen) { + EXPECT_OOSEQ(datalen == expected_datalen); + } + } + + /* pass in one more segment, cleary overrunning the rxwin */ + p_ovr = tcp_create_rx_segment(pcb, &data_full_wnd[TCP_MSS*(k+1)], TCP_MSS, TCP_MSS*(k+1), 0, TCP_ACK); + EXPECT_RET(p_ovr != NULL); + /* pass the segment to tcp_input */ + test_tcp_input(p_ovr, &netif); + /* check if counters are as expected */ + EXPECT(counters.close_calls == 0); + EXPECT(counters.recv_calls == 0); + EXPECT(counters.recved_bytes == 0); + EXPECT(counters.err_calls == 0); + /* check ooseq queue */ + EXPECT_OOSEQ(tcp_oos_count(pcb) == k); + datalen2 = tcp_oos_tcplen(pcb); + EXPECT_OOSEQ(datalen == datalen2); + + /* now pass inseq */ + test_tcp_input(pinseq, &netif); + EXPECT(pcb->ooseq == NULL); + + /* make sure the pcb is freed */ + EXPECT(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 1); + tcp_abort(pcb); + EXPECT(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 0); +#endif /* !TCP_OOSEQ_MAX_BYTES && !TCP_OOSEQ_MAX_PBUFS */ + LWIP_UNUSED_ARG(_i); +} +END_TEST + +START_TEST(test_tcp_recv_ooseq_max_bytes) +{ +#if TCP_OOSEQ_MAX_BYTES && (TCP_OOSEQ_MAX_BYTES < (TCP_WND + 1)) && (PBUF_POOL_BUFSIZE >= (TCP_MSS + PBUF_LINK_ENCAPSULATION_HLEN + PBUF_LINK_HLEN + PBUF_IP_HLEN + PBUF_TRANSPORT_HLEN)) + int i, k; + struct test_tcp_counters counters; + struct tcp_pcb* pcb; + struct pbuf *p_ovr; + struct netif netif; + int datalen = 0; + int datalen2; + + for(i = 0; i < sizeof(data_full_wnd); i++) { + data_full_wnd[i] = (char)i; + } + + /* initialize local vars */ + test_tcp_init_netif(&netif, NULL, &test_local_ip, &test_netmask); + /* initialize counter struct */ + memset(&counters, 0, sizeof(counters)); + counters.expected_data_len = TCP_WND; + counters.expected_data = data_full_wnd; + + /* create and initialize the pcb */ + pcb = test_tcp_new_counters_pcb(&counters); + EXPECT_RET(pcb != NULL); + tcp_set_state(pcb, ESTABLISHED, &test_local_ip, &test_remote_ip, TEST_LOCAL_PORT, TEST_REMOTE_PORT); + pcb->rcv_nxt = 0x8000; + + /* don't 'recv' the first segment (1 byte) so that all other segments will be ooseq */ + + /* create segments and 'recv' them */ + for(k = 1, i = 1; k < TCP_OOSEQ_MAX_BYTES; k += TCP_MSS, i++) { + int count; + struct pbuf *p = tcp_create_rx_segment(pcb, &data_full_wnd[k], + TCP_MSS, k, 0, TCP_ACK); + EXPECT_RET(p != NULL); + EXPECT_RET(p->next == NULL); + /* pass the segment to tcp_input */ + test_tcp_input(p, &netif); + /* check if counters are as expected */ + EXPECT(counters.close_calls == 0); + EXPECT(counters.recv_calls == 0); + EXPECT(counters.recved_bytes == 0); + EXPECT(counters.err_calls == 0); + /* check ooseq queue */ + count = tcp_oos_pbuf_count(pcb); + EXPECT_OOSEQ(count == i); + datalen = tcp_oos_tcplen(pcb); + EXPECT_OOSEQ(datalen == (i * TCP_MSS)); + } + + /* pass in one more segment, overrunning the limit */ + p_ovr = tcp_create_rx_segment(pcb, &data_full_wnd[k+1], 1, k+1, 0, TCP_ACK); + EXPECT_RET(p_ovr != NULL); + /* pass the segment to tcp_input */ + test_tcp_input(p_ovr, &netif); + /* check if counters are as expected */ + EXPECT(counters.close_calls == 0); + EXPECT(counters.recv_calls == 0); + EXPECT(counters.recved_bytes == 0); + EXPECT(counters.err_calls == 0); + /* check ooseq queue (ensure the new segment was not accepted) */ + EXPECT_OOSEQ(tcp_oos_count(pcb) == (i-1)); + datalen2 = tcp_oos_tcplen(pcb); + EXPECT_OOSEQ(datalen2 == ((i-1) * TCP_MSS)); + + /* make sure the pcb is freed */ + EXPECT(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 1); + tcp_abort(pcb); + EXPECT(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 0); +#endif /* TCP_OOSEQ_MAX_BYTES && (TCP_OOSEQ_MAX_BYTES < (TCP_WND + 1)) && (PBUF_POOL_BUFSIZE >= (TCP_MSS + PBUF_LINK_ENCAPSULATION_HLEN + PBUF_LINK_HLEN + PBUF_IP_HLEN + PBUF_TRANSPORT_HLEN)) */ + LWIP_UNUSED_ARG(_i); +} +END_TEST + +START_TEST(test_tcp_recv_ooseq_max_pbufs) +{ +#if TCP_OOSEQ_MAX_PBUFS && (TCP_OOSEQ_MAX_PBUFS < ((TCP_WND / TCP_MSS) + 1)) && (PBUF_POOL_BUFSIZE >= (TCP_MSS + PBUF_LINK_ENCAPSULATION_HLEN + PBUF_LINK_HLEN + PBUF_IP_HLEN + PBUF_TRANSPORT_HLEN)) + int i; + struct test_tcp_counters counters; + struct tcp_pcb* pcb; + struct pbuf *p_ovr; + struct netif netif; + int datalen = 0; + int datalen2; + + for(i = 0; i < sizeof(data_full_wnd); i++) { + data_full_wnd[i] = (char)i; + } + + /* initialize local vars */ + test_tcp_init_netif(&netif, NULL, &test_local_ip, &test_netmask); + /* initialize counter struct */ + memset(&counters, 0, sizeof(counters)); + counters.expected_data_len = TCP_WND; + counters.expected_data = data_full_wnd; + + /* create and initialize the pcb */ + pcb = test_tcp_new_counters_pcb(&counters); + EXPECT_RET(pcb != NULL); + tcp_set_state(pcb, ESTABLISHED, &local_ip, &remote_ip, TEST_LOCAL_PORT, TEST_REMOTE_PORT); + pcb->rcv_nxt = 0x8000; + + /* don't 'recv' the first segment (1 byte) so that all other segments will be ooseq */ + + /* create segments and 'recv' them */ + for(i = 1; i <= TCP_OOSEQ_MAX_PBUFS; i++) { + int count; + struct pbuf *p = tcp_create_rx_segment(pcb, &data_full_wnd[i], + 1, i, 0, TCP_ACK); + EXPECT_RET(p != NULL); + EXPECT_RET(p->next == NULL); + /* pass the segment to tcp_input */ + test_tcp_input(p, &netif); + /* check if counters are as expected */ + EXPECT(counters.close_calls == 0); + EXPECT(counters.recv_calls == 0); + EXPECT(counters.recved_bytes == 0); + EXPECT(counters.err_calls == 0); + /* check ooseq queue */ + count = tcp_oos_pbuf_count(pcb); + EXPECT_OOSEQ(count == i); + datalen = tcp_oos_tcplen(pcb); + EXPECT_OOSEQ(datalen == i); + } + + /* pass in one more segment, overrunning the limit */ + p_ovr = tcp_create_rx_segment(pcb, &data_full_wnd[i+1], 1, i+1, 0, TCP_ACK); + EXPECT_RET(p_ovr != NULL); + /* pass the segment to tcp_input */ + test_tcp_input(p_ovr, &netif); + /* check if counters are as expected */ + EXPECT(counters.close_calls == 0); + EXPECT(counters.recv_calls == 0); + EXPECT(counters.recved_bytes == 0); + EXPECT(counters.err_calls == 0); + /* check ooseq queue (ensure the new segment was not accepted) */ + EXPECT_OOSEQ(tcp_oos_count(pcb) == (i-1)); + datalen2 = tcp_oos_tcplen(pcb); + EXPECT_OOSEQ(datalen2 == (i-1)); + + /* make sure the pcb is freed */ + EXPECT(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 1); + tcp_abort(pcb); + EXPECT(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 0); +#endif /* TCP_OOSEQ_MAX_PBUFS && (TCP_OOSEQ_MAX_BYTES < (TCP_WND + 1)) && (PBUF_POOL_BUFSIZE >= (TCP_MSS + PBUF_LINK_ENCAPSULATION_HLEN + PBUF_LINK_HLEN + PBUF_IP_HLEN + PBUF_TRANSPORT_HLEN)) */ + LWIP_UNUSED_ARG(_i); +} +END_TEST + +static void +check_rx_counters(struct tcp_pcb *pcb, struct test_tcp_counters *counters, u32_t exp_close_calls, u32_t exp_rx_calls, + u32_t exp_rx_bytes, u32_t exp_err_calls, int exp_oos_count, int exp_oos_len) +{ + int oos_len; + EXPECT(counters->close_calls == exp_close_calls); + EXPECT(counters->recv_calls == exp_rx_calls); + EXPECT(counters->recved_bytes == exp_rx_bytes); + EXPECT(counters->err_calls == exp_err_calls); + /* check that pbuf is queued in ooseq */ + EXPECT_OOSEQ(tcp_oos_count(pcb) == exp_oos_count); + oos_len = tcp_oos_tcplen(pcb); + EXPECT_OOSEQ(exp_oos_len == oos_len); +} + +/* this test uses 4 packets: + * - data (len=TCP_MSS) + * - FIN + * - data after FIN (len=1) (invalid) + * - 2nd FIN (invalid) + * + * the parameter 'delay_packet' is a bitmask that choses which on these packets is ooseq + */ +static void test_tcp_recv_ooseq_double_FINs(int delay_packet) +{ + int i, k; + struct test_tcp_counters counters; + struct tcp_pcb* pcb; + struct pbuf *p_normal_fin, *p_data_after_fin, *p, *p_2nd_fin_ooseq; + struct netif netif; + u32_t exp_rx_calls = 0, exp_rx_bytes = 0, exp_close_calls = 0, exp_oos_pbufs = 0, exp_oos_tcplen = 0; + int first_dropped = 0xff; + + for(i = 0; i < (int)sizeof(data_full_wnd); i++) { + data_full_wnd[i] = (char)i; + } + + /* initialize local vars */ + test_tcp_init_netif(&netif, NULL, &test_local_ip, &test_netmask); + /* initialize counter struct */ + memset(&counters, 0, sizeof(counters)); + counters.expected_data_len = TCP_WND; + counters.expected_data = data_full_wnd; + + /* create and initialize the pcb */ + pcb = test_tcp_new_counters_pcb(&counters); + EXPECT_RET(pcb != NULL); + tcp_set_state(pcb, ESTABLISHED, &test_local_ip, &test_remote_ip, TEST_LOCAL_PORT, TEST_REMOTE_PORT); + pcb->rcv_nxt = 0x8000; + + /* create segments */ + p = tcp_create_rx_segment(pcb, &data_full_wnd[0], TCP_MSS, 0, 0, TCP_ACK); + p_normal_fin = tcp_create_rx_segment(pcb, NULL, 0, TCP_MSS, 0, TCP_ACK|TCP_FIN); + k = 1; + p_data_after_fin = tcp_create_rx_segment(pcb, &data_full_wnd[TCP_MSS+1], k, TCP_MSS+1, 0, TCP_ACK); + p_2nd_fin_ooseq = tcp_create_rx_segment(pcb, NULL, 0, TCP_MSS+1+k, 0, TCP_ACK|TCP_FIN); + + if(delay_packet & 1) { + /* drop normal data */ + first_dropped = 1; + } else { + /* send normal data */ + test_tcp_input(p, &netif); + exp_rx_calls++; + exp_rx_bytes += TCP_MSS; + } + /* check if counters are as expected */ + check_rx_counters(pcb, &counters, exp_close_calls, exp_rx_calls, exp_rx_bytes, 0, exp_oos_pbufs, exp_oos_tcplen); + + if(delay_packet & 2) { + /* drop FIN */ + if(first_dropped > 2) { + first_dropped = 2; + } + } else { + /* send FIN */ + test_tcp_input(p_normal_fin, &netif); + if (first_dropped < 2) { + /* already dropped packets, this one is ooseq */ + exp_oos_pbufs++; + exp_oos_tcplen++; + } else { + /* inseq */ + exp_close_calls++; + } + } + /* check if counters are as expected */ + check_rx_counters(pcb, &counters, exp_close_calls, exp_rx_calls, exp_rx_bytes, 0, exp_oos_pbufs, exp_oos_tcplen); + + if(delay_packet & 4) { + /* drop data-after-FIN */ + if(first_dropped > 3) { + first_dropped = 3; + } + } else { + /* send data-after-FIN */ + test_tcp_input(p_data_after_fin, &netif); + if (first_dropped < 3) { + /* already dropped packets, this one is ooseq */ + if (delay_packet & 2) { + /* correct FIN was ooseq */ + exp_oos_pbufs++; + exp_oos_tcplen += k; + } + } else { + /* inseq: no change */ + } + } + /* check if counters are as expected */ + check_rx_counters(pcb, &counters, exp_close_calls, exp_rx_calls, exp_rx_bytes, 0, exp_oos_pbufs, exp_oos_tcplen); + + if(delay_packet & 8) { + /* drop 2nd-FIN */ + if(first_dropped > 4) { + first_dropped = 4; + } + } else { + /* send 2nd-FIN */ + test_tcp_input(p_2nd_fin_ooseq, &netif); + if (first_dropped < 3) { + /* already dropped packets, this one is ooseq */ + if (delay_packet & 2) { + /* correct FIN was ooseq */ + exp_oos_pbufs++; + exp_oos_tcplen++; + } + } else { + /* inseq: no change */ + } + } + /* check if counters are as expected */ + check_rx_counters(pcb, &counters, exp_close_calls, exp_rx_calls, exp_rx_bytes, 0, exp_oos_pbufs, exp_oos_tcplen); + + if(delay_packet & 1) { + /* dropped normal data before */ + test_tcp_input(p, &netif); + exp_rx_calls++; + exp_rx_bytes += TCP_MSS; + if((delay_packet & 2) == 0) { + /* normal FIN was NOT delayed */ + exp_close_calls++; + exp_oos_pbufs = exp_oos_tcplen = 0; + } + } + /* check if counters are as expected */ + check_rx_counters(pcb, &counters, exp_close_calls, exp_rx_calls, exp_rx_bytes, 0, exp_oos_pbufs, exp_oos_tcplen); + + if(delay_packet & 2) { + /* dropped normal FIN before */ + test_tcp_input(p_normal_fin, &netif); + exp_close_calls++; + exp_oos_pbufs = exp_oos_tcplen = 0; + } + /* check if counters are as expected */ + check_rx_counters(pcb, &counters, exp_close_calls, exp_rx_calls, exp_rx_bytes, 0, exp_oos_pbufs, exp_oos_tcplen); + + if(delay_packet & 4) { + /* dropped data-after-FIN before */ + test_tcp_input(p_data_after_fin, &netif); + } + /* check if counters are as expected */ + check_rx_counters(pcb, &counters, exp_close_calls, exp_rx_calls, exp_rx_bytes, 0, exp_oos_pbufs, exp_oos_tcplen); + + if(delay_packet & 8) { + /* dropped 2nd-FIN before */ + test_tcp_input(p_2nd_fin_ooseq, &netif); + } + /* check if counters are as expected */ + check_rx_counters(pcb, &counters, exp_close_calls, exp_rx_calls, exp_rx_bytes, 0, exp_oos_pbufs, exp_oos_tcplen); + + /* check that ooseq data has been dumped */ + EXPECT(pcb->ooseq == NULL); + + /* make sure the pcb is freed */ + EXPECT(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 1); + tcp_abort(pcb); + EXPECT(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 0); +} + +/** create multiple segments and pass them to tcp_input with the first segment missing + * to simulate overruning the rxwin with ooseq queueing enabled */ +#define FIN_TEST(name, num) \ + START_TEST(name) \ + { \ + LWIP_UNUSED_ARG(_i); \ + test_tcp_recv_ooseq_double_FINs(num); \ + } \ + END_TEST +FIN_TEST(test_tcp_recv_ooseq_double_FIN_0, 0) +FIN_TEST(test_tcp_recv_ooseq_double_FIN_1, 1) +FIN_TEST(test_tcp_recv_ooseq_double_FIN_2, 2) +FIN_TEST(test_tcp_recv_ooseq_double_FIN_3, 3) +FIN_TEST(test_tcp_recv_ooseq_double_FIN_4, 4) +FIN_TEST(test_tcp_recv_ooseq_double_FIN_5, 5) +FIN_TEST(test_tcp_recv_ooseq_double_FIN_6, 6) +FIN_TEST(test_tcp_recv_ooseq_double_FIN_7, 7) +FIN_TEST(test_tcp_recv_ooseq_double_FIN_8, 8) +FIN_TEST(test_tcp_recv_ooseq_double_FIN_9, 9) +FIN_TEST(test_tcp_recv_ooseq_double_FIN_10, 10) +FIN_TEST(test_tcp_recv_ooseq_double_FIN_11, 11) +FIN_TEST(test_tcp_recv_ooseq_double_FIN_12, 12) +FIN_TEST(test_tcp_recv_ooseq_double_FIN_13, 13) +FIN_TEST(test_tcp_recv_ooseq_double_FIN_14, 14) +FIN_TEST(test_tcp_recv_ooseq_double_FIN_15, 15) + + +/** Create the suite including all tests for this module */ +Suite * +tcp_oos_suite(void) +{ + testfunc tests[] = { + TESTFUNC(test_tcp_recv_ooseq_FIN_OOSEQ), + TESTFUNC(test_tcp_recv_ooseq_FIN_INSEQ), + TESTFUNC(test_tcp_recv_ooseq_overrun_rxwin), + TESTFUNC(test_tcp_recv_ooseq_overrun_rxwin_edge), + TESTFUNC(test_tcp_recv_ooseq_max_bytes), + TESTFUNC(test_tcp_recv_ooseq_max_pbufs), + TESTFUNC(test_tcp_recv_ooseq_double_FIN_0), + TESTFUNC(test_tcp_recv_ooseq_double_FIN_1), + TESTFUNC(test_tcp_recv_ooseq_double_FIN_2), + TESTFUNC(test_tcp_recv_ooseq_double_FIN_3), + TESTFUNC(test_tcp_recv_ooseq_double_FIN_4), + TESTFUNC(test_tcp_recv_ooseq_double_FIN_5), + TESTFUNC(test_tcp_recv_ooseq_double_FIN_6), + TESTFUNC(test_tcp_recv_ooseq_double_FIN_7), + TESTFUNC(test_tcp_recv_ooseq_double_FIN_8), + TESTFUNC(test_tcp_recv_ooseq_double_FIN_9), + TESTFUNC(test_tcp_recv_ooseq_double_FIN_10), + TESTFUNC(test_tcp_recv_ooseq_double_FIN_11), + TESTFUNC(test_tcp_recv_ooseq_double_FIN_12), + TESTFUNC(test_tcp_recv_ooseq_double_FIN_13), + TESTFUNC(test_tcp_recv_ooseq_double_FIN_14), + TESTFUNC(test_tcp_recv_ooseq_double_FIN_15) + }; + return create_suite("TCP_OOS", tests, sizeof(tests)/sizeof(testfunc), tcp_oos_setup, tcp_oos_teardown); +} diff --git a/test/unit/tcp/test_tcp_oos.h b/test/unit/tcp/test_tcp_oos.h new file mode 100644 index 00000000000..5b82013b73b --- /dev/null +++ b/test/unit/tcp/test_tcp_oos.h @@ -0,0 +1,8 @@ +#ifndef LWIP_HDR_TEST_TCP_OOS_H +#define LWIP_HDR_TEST_TCP_OOS_H + +#include "../lwip_check.h" + +Suite *tcp_oos_suite(void); + +#endif diff --git a/test/unit/tcp/test_tcp_state.c b/test/unit/tcp/test_tcp_state.c new file mode 100644 index 00000000000..afd21fce626 --- /dev/null +++ b/test/unit/tcp/test_tcp_state.c @@ -0,0 +1,665 @@ +#include "test_tcp_state.h" + +#include "lwip/priv/tcp_priv.h" +#include "lwip/stats.h" +#include "tcp_helper.h" +#include "lwip/inet_chksum.h" + +#ifdef _MSC_VER +#pragma warning(disable: 4307) /* we explicitly wrap around TCP seqnos */ +#endif + +#if !LWIP_STATS || !TCP_STATS || !MEMP_STATS +#error "This tests needs TCP- and MEMP-statistics enabled" +#endif + +static struct netif test_netif = {0}; +static struct test_tcp_txcounters test_txcounters = {0}; + +#define SEQNO1 (0xFFFFFF00 - TCP_MSS) +#define ISS 6510 +static u8_t test_tcp_timer; + +/* our own version of tcp_tmr so we can reset fast/slow timer state */ +static void +test_tcp_tmr(void) +{ + tcp_fasttmr(); + if (++test_tcp_timer & 1) { + tcp_slowtmr(); + } +} + +/* Get TCP flags from packets */ +static u8_t +get_tcp_flags_from_packet(struct pbuf *p, u16_t tcp_hdr_offset) +{ + struct tcp_hdr tcphdr; + u16_t ret; + EXPECT_RETX(p != NULL, 0); + EXPECT_RETX(p->len >= tcp_hdr_offset + sizeof(struct tcp_hdr), 0); + ret = pbuf_copy_partial(p, &tcphdr, sizeof(struct tcp_hdr), tcp_hdr_offset); + EXPECT(ret == sizeof(struct tcp_hdr)); + return TCPH_FLAGS(&tcphdr); +} + +/* Create listening tcp_pcb */ +static struct tcp_pcb_listen * +create_listening_pcb(u16_t local_port, struct test_tcp_counters *counters) +{ + struct tcp_pcb *pcb; + struct tcp_pcb_listen *lpcb=NULL; + err_t err; + u16_t port = local_port?local_port:1234; + + if (counters) { + pcb = test_tcp_new_counters_pcb(counters); + } else { + pcb = tcp_new(); + } + EXPECT(pcb != NULL); + + if (pcb) { + err = tcp_bind(pcb, &test_netif.ip_addr, port); + EXPECT(err == ERR_OK); + lpcb = (struct tcp_pcb_listen *)tcp_listen(pcb); + } + + return lpcb; +} + +/* Setup/teardown functions */ +static struct netif* old_netif_list; +static struct netif* old_netif_default; + +static void +tcp_state_setup(void) +{ + struct tcp_pcb dummy_pcb; /* we need this for tcp_next_iss() only */ + + /* reset iss to default (6510) */ + tcp_ticks = 0; + tcp_ticks = 0 - (tcp_next_iss(&dummy_pcb) - 6510); + tcp_next_iss(&dummy_pcb); + tcp_ticks = 0; + + test_tcp_timer = 0; + + old_netif_list = netif_list; + old_netif_default = netif_default; + netif_list = NULL; + netif_default = NULL; + tcp_remove_all(); + test_tcp_init_netif(&test_netif, &test_txcounters, &test_local_ip, &test_netmask); + lwip_check_ensure_no_alloc(SKIP_POOL(MEMP_SYS_TIMEOUT)); +} + +static void +tcp_state_teardown(void) +{ + netif_list = NULL; + netif_default = NULL; + tcp_remove_all(); + /* restore netif_list for next tests (e.g. loopif) */ + netif_list = old_netif_list; + netif_default = old_netif_default; + lwip_check_ensure_no_alloc(SKIP_POOL(MEMP_SYS_TIMEOUT)); +} + +/* helper functions */ + +static void +test_rst_generation_with_incoming_packet(struct pbuf *p, + struct netif *netif, struct test_tcp_txcounters *tx_counters) +{ + u16_t tcp_flags; + EXPECT_RET(p != NULL); + memset(tx_counters, 0, sizeof(struct test_tcp_txcounters)); + /* pass the segment to tcp_input */ + tx_counters->copy_tx_packets = 1; + test_tcp_input(p, netif); + tx_counters->copy_tx_packets = 0; + /* check if packets are as expected */ + EXPECT(tx_counters->tx_packets != NULL); + if (tx_counters->tx_packets) { + tcp_flags = get_tcp_flags_from_packet(tx_counters->tx_packets, 20); + EXPECT(tcp_flags & TCP_RST); + pbuf_free(tx_counters->tx_packets); + tx_counters->tx_packets = NULL; + } +} + +/* Test functions */ + +/* Call tcp_new() and test memp stats (max number) */ +START_TEST(test_tcp_new_max_num) +{ + struct tcp_pcb* pcb[MEMP_NUM_TCP_PCB + 1]; + int i; + LWIP_UNUSED_ARG(_i); + + fail_unless(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 0); + + for(i = 0;i < MEMP_NUM_TCP_PCB; i++) { + pcb[i] = tcp_new(); + fail_unless(MEMP_STATS_GET(used, MEMP_TCP_PCB) == (i + 1)); + } + fail_unless(MEMP_STATS_GET(used, MEMP_TCP_PCB) == MEMP_NUM_TCP_PCB); + /* Trying to remove the oldest pcb in TIME_WAIT,LAST_ACK,CLOSING state when pcb full */ + pcb[MEMP_NUM_TCP_PCB] = tcp_new(); + fail_unless(pcb[MEMP_NUM_TCP_PCB] == NULL); + tcp_set_state(pcb[0], TIME_WAIT, &test_local_ip, &test_remote_ip, TEST_LOCAL_PORT, TEST_REMOTE_PORT); + pcb[MEMP_NUM_TCP_PCB] = tcp_new(); + fail_unless(pcb[MEMP_NUM_TCP_PCB] != NULL); + fail_unless(MEMP_STATS_GET(used, MEMP_TCP_PCB) == MEMP_NUM_TCP_PCB); + + for (i = 1; i <= MEMP_NUM_TCP_PCB; i++) + { + tcp_abort(pcb[i]); + } + fail_unless(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 0); +} +END_TEST + + +/* pcbs in TIME_WAIT state will be deleted when creating new pcb reach the max number */ +START_TEST(test_tcp_new_max_num_remove_TIME_WAIT) +{ + struct tcp_pcb* pcb; + struct tcp_pcb* pcb_list[MEMP_NUM_TCP_PCB + 1]; + int i; + LWIP_UNUSED_ARG(_i); + + /* create a pcb in TIME_WAIT state */ + pcb = tcp_new(); + EXPECT_RET(pcb != NULL); + tcp_set_state(pcb, TIME_WAIT, &test_local_ip, &test_remote_ip, TEST_LOCAL_PORT, TEST_REMOTE_PORT); + EXPECT_RET(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 1); + EXPECT_RET(pcb->state == TIME_WAIT); + + /* Create max number pcbs */ + for(i = 0;i < MEMP_NUM_TCP_PCB-1; i++) { + pcb_list[i] = tcp_new(); + EXPECT(MEMP_STATS_GET(used, MEMP_TCP_PCB) == (i + 2)); + } + EXPECT(MEMP_STATS_GET(used, MEMP_TCP_PCB) == MEMP_NUM_TCP_PCB); + + /* Create one more pcb, and expect that the pcb in the TIME_WAIT state is deleted */ + pcb_list[MEMP_NUM_TCP_PCB-1] = tcp_new(); + EXPECT_RET(pcb_list[MEMP_NUM_TCP_PCB-1] != NULL); + EXPECT_RET(MEMP_STATS_GET(used, MEMP_TCP_PCB) == MEMP_NUM_TCP_PCB); + + for (i = 0; i <= MEMP_NUM_TCP_PCB-1; i++) + { + tcp_abort(pcb_list[i]); + } + EXPECT(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 0); + +} +END_TEST + + +/* Call tcp_connect to check active open */ +START_TEST(test_tcp_connect_active_open) +{ + struct test_tcp_counters counters; + struct tcp_pcb *pcb; + struct pbuf *p; + err_t err; + u16_t test_port = 1234; + u32_t seqno = 0; + LWIP_UNUSED_ARG(_i); + + /* create and initialize the pcb */ + tcp_ticks = SEQNO1 - ISS; + pcb = test_tcp_new_counters_pcb(&counters); + EXPECT_RET(pcb != NULL); + + /* Get seqno from SYN packet */ + test_txcounters.copy_tx_packets = 1; + err = tcp_connect(pcb, &test_remote_ip, test_port, NULL); + test_txcounters.copy_tx_packets = 0; + EXPECT(err == ERR_OK); + EXPECT(pcb->state == SYN_SENT); + EXPECT(test_txcounters.num_tx_calls == 1); + EXPECT_RET(test_txcounters.tx_packets != NULL); + if (test_txcounters.tx_packets != NULL) { + struct tcp_hdr tcphdr; + u16_t ret; + ret = pbuf_copy_partial(test_txcounters.tx_packets, &tcphdr, 20, 20); + EXPECT(ret == 20); + EXPECT(TCPH_FLAGS(&tcphdr) & TCP_SYN); + pbuf_free(test_txcounters.tx_packets); + test_txcounters.tx_packets = NULL; + seqno = lwip_htonl(tcphdr.seqno); + EXPECT(seqno == pcb->lastack); + } + + /* check correct syn packet */ + p = tcp_create_segment(&pcb->remote_ip, &pcb->local_ip, test_port, + pcb->local_port, NULL, 0, 12345, seqno + 1, TCP_SYN|TCP_ACK); + EXPECT_RET(p != NULL); + test_tcp_input(p, &test_netif); + EXPECT_RET(pcb->state == ESTABLISHED); + EXPECT_RET(test_txcounters.num_tx_calls == 2); + + /* make sure the pcb is freed */ + EXPECT(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 1); + tcp_abort(pcb); + EXPECT(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 0); +} +END_TEST + +START_TEST(test_tcp_active_close) +{ + struct tcp_pcb *pcb, *pcbl; + struct test_tcp_counters counters; + struct pbuf *p; + err_t err; + u32_t i; + LWIP_UNUSED_ARG(_i); + + /* create TCP in LISTEN state */ + memset(&counters, 0, sizeof(counters)); + pcb = test_tcp_new_counters_pcb(&counters); + EXPECT_RET(pcb != NULL); + EXPECT_RET(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 1); + err = tcp_bind(pcb, &test_netif.ip_addr, 1234); + EXPECT_RET(err == ERR_OK); + pcbl = tcp_listen(pcb); + EXPECT_RET(pcbl != NULL); + EXPECT_RET(pcbl->state == LISTEN); + EXPECT_RET(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 0); + EXPECT_RET(MEMP_STATS_GET(used, MEMP_TCP_PCB_LISTEN) == 1); + + memset(&test_txcounters, 0, sizeof(test_txcounters)); + err = tcp_close(pcbl); + EXPECT_RET(err == ERR_OK); + EXPECT_RET(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 0); + EXPECT_RET(MEMP_STATS_GET(used, MEMP_TCP_PCB_LISTEN) == 0); + EXPECT(test_txcounters.num_tx_calls == 0); + + /* close TCP in SYN_SENT state */ + memset(&counters, 0, sizeof(counters)); + pcb = test_tcp_new_counters_pcb(&counters); + EXPECT_RET(pcb != NULL); + err = tcp_connect(pcb, &test_netif.gw, 1234, NULL); + EXPECT_RET(err == ERR_OK); + EXPECT_RET(pcb->state == SYN_SENT); + EXPECT(test_txcounters.num_tx_calls == 1); + EXPECT_RET(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 1); + + memset(&test_txcounters, 0, sizeof(test_txcounters)); + err = tcp_close(pcb); + EXPECT_RET(err == ERR_OK); + EXPECT_RET(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 0); + EXPECT(test_txcounters.num_tx_calls == 0); + + /* close TCP in ESTABLISHED state */ + memset(&counters, 0, sizeof(counters)); + pcb = test_tcp_new_counters_pcb(&counters); + EXPECT_RET(pcb != NULL); + tcp_set_state(pcb, ESTABLISHED, &test_local_ip, &test_remote_ip, TEST_LOCAL_PORT, TEST_REMOTE_PORT); + EXPECT_RET(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 1); + + memset(&test_txcounters, 0, sizeof(test_txcounters)); + err = tcp_close(pcb); + EXPECT_RET(err == ERR_OK); + EXPECT_RET(pcb->state == FIN_WAIT_1); + EXPECT_RET(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 1); + /* test_tcp_tmr(); */ + EXPECT(test_txcounters.num_tx_calls == 1); + /* create a segment ACK and pass it to tcp_input */ + p = tcp_create_rx_segment(pcb, NULL, 0, 0, 1, TCP_ACK); + EXPECT_RET(p != NULL); + test_tcp_input(p, &test_netif); + EXPECT_RET(pcb->state == FIN_WAIT_2); + /* create a segment FIN and pass it to tcp_input */ + p = tcp_create_rx_segment(pcb, NULL, 0, 0, 0, TCP_FIN); + EXPECT_RET(p != NULL); + test_tcp_input(p, &test_netif); + EXPECT_RET(pcb->state == TIME_WAIT); + EXPECT_RET(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 1); + for (i = 0; i < 2 * TCP_MSL / TCP_TMR_INTERVAL + 1; i++) { + test_tcp_tmr(); + } + EXPECT_RET(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 0); +} +END_TEST + +START_TEST(test_tcp_imultaneous_close) +{ + struct test_tcp_counters counters; + struct tcp_pcb* pcb; + struct pbuf* p; + char data = 0x0f; + err_t err; + u32_t i; + LWIP_UNUSED_ARG(_i); + + /* initialize counter struct */ + memset(&counters, 0, sizeof(counters)); + counters.expected_data_len = 1; + counters.expected_data = &data; + + /* create and initialize the pcb */ + pcb = test_tcp_new_counters_pcb(&counters); + EXPECT_RET(pcb != NULL); + tcp_set_state(pcb, ESTABLISHED, &test_local_ip, &test_remote_ip, TEST_LOCAL_PORT, TEST_REMOTE_PORT); + err = tcp_close(pcb); + EXPECT_RET(err == ERR_OK); + EXPECT_RET(pcb->state == FIN_WAIT_1); + /* create a FIN segment */ + p = tcp_create_rx_segment(pcb, &data, 0, 0, 0, TCP_FIN); + EXPECT(p != NULL); + if (p != NULL) { + test_tcp_input(p, &test_netif); + } + EXPECT_RET(pcb->state == CLOSING); + /* create an ACK segment */ + p = tcp_create_rx_segment(pcb, &data, 0, 0, 1, TCP_ACK); + EXPECT(p != NULL); + if (p != NULL) { + test_tcp_input(p, &test_netif); + } + EXPECT_RET(pcb->state == TIME_WAIT); + for (i = 0; i < 2 * TCP_MSL / TCP_TMR_INTERVAL + 1; i++) { + test_tcp_tmr(); + } + EXPECT_RET(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 0); +} +END_TEST + + +/* RST was generated when receive any incoming segment in CLOSED state */ +START_TEST(test_tcp_gen_rst_in_CLOSED) +{ + struct pbuf *p; + ip_addr_t src_addr = test_remote_ip; + ip_addr_t dst_addr = test_local_ip; + LWIP_UNUSED_ARG(_i); + + /* Do not create any pcb */ + + /* create a segment */ + p = tcp_create_segment(&src_addr, &dst_addr, TEST_REMOTE_PORT, + TEST_LOCAL_PORT, NULL, 0, 12345, 54321, TCP_ACK); + EXPECT(p != NULL); + test_rst_generation_with_incoming_packet(p, &test_netif, &test_txcounters); + EXPECT(test_txcounters.num_tx_calls == 1); + +} +END_TEST + +/* RST was generated when receive ACK in LISTEN state */ +START_TEST(test_tcp_gen_rst_in_LISTEN) +{ + struct tcp_pcb_listen *lpcb; + struct pbuf *p; + ip_addr_t src_addr = test_remote_ip; + LWIP_UNUSED_ARG(_i); + + /* create a pcb in LISTEN state */ + lpcb = create_listening_pcb(TEST_LOCAL_PORT, NULL); + EXPECT_RET(lpcb != NULL); + + /* create a segment */ + p = tcp_create_segment(&src_addr,&lpcb->local_ip, TEST_REMOTE_PORT, + lpcb->local_port, NULL, 0, 12345, 54321, TCP_ACK); + EXPECT(p != NULL); + test_rst_generation_with_incoming_packet(p, &test_netif, &test_txcounters); + EXPECT(test_txcounters.num_tx_calls == 1); + + /* the PCB still in LISTEN state */ + EXPECT(MEMP_STATS_GET(used, MEMP_TCP_PCB_LISTEN) == 1); + if (MEMP_STATS_GET(used, MEMP_TCP_PCB_LISTEN) != 0) { + /* can not use tcp_abort() */ + tcp_close((struct tcp_pcb *)lpcb); + } + +} +END_TEST + + +/* RST was generated when receive an SYN in TIME_WAIT state */ +START_TEST(test_tcp_gen_rst_in_TIME_WAIT) +{ + struct tcp_pcb *pcb; + struct pbuf *p; + LWIP_UNUSED_ARG(_i); + + /* create a pcb in LISTEN state */ + pcb = tcp_new(); + EXPECT_RET(pcb != NULL); + tcp_set_state(pcb, TIME_WAIT, &test_local_ip, &test_remote_ip, TEST_LOCAL_PORT, TEST_REMOTE_PORT); + + /* create a segment */ + p = tcp_create_rx_segment(pcb, NULL, 0, 0, 0, TCP_SYN); + EXPECT(p != NULL); + test_rst_generation_with_incoming_packet(p, &test_netif, &test_txcounters); + EXPECT(test_txcounters.num_tx_calls == 1); + + EXPECT(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 1); + EXPECT(pcb->state == TIME_WAIT); +} +END_TEST + +/* receive TCP_RST with different seqno */ +START_TEST(test_tcp_process_rst_seqno) +{ + struct test_tcp_counters counters; + struct tcp_pcb *pcb; + struct pbuf *p; + err_t err; + LWIP_UNUSED_ARG(_i); + + /* create and initialize a pcb in SYN_SENT state */ + memset(&counters, 0, sizeof(counters)); + pcb = test_tcp_new_counters_pcb(&counters); + EXPECT_RET(pcb != NULL); + err = tcp_connect(pcb, &test_remote_ip, TEST_REMOTE_PORT, NULL); + EXPECT_RET(err == ERR_OK); + + /* a RST segment with incorrect seqno will not be accepted */ + p = tcp_create_segment(&pcb->remote_ip, &pcb->local_ip, TEST_REMOTE_PORT, + pcb->local_port, NULL, 0, 12345, pcb->snd_nxt-10, TCP_RST); + EXPECT(p != NULL); + test_tcp_input(p, &test_netif); + EXPECT(counters.err_calls == 0); + + /* a RST segment with correct seqno will be accepted */ + p = tcp_create_segment(&pcb->remote_ip, &pcb->local_ip, TEST_REMOTE_PORT, + pcb->local_port, NULL, 0, 12345, pcb->snd_nxt, TCP_RST); + EXPECT(p != NULL); + test_tcp_input(p, &test_netif); + EXPECT(counters.err_calls == 1); + counters.err_calls = 0; + EXPECT(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 0); + + /* create another pcb in ESTABLISHED state */ + memset(&counters, 0, sizeof(counters)); + pcb = test_tcp_new_counters_pcb(&counters); + EXPECT_RET(pcb != NULL); + tcp_set_state(pcb, ESTABLISHED, &test_local_ip, &test_remote_ip, TEST_LOCAL_PORT, TEST_REMOTE_PORT); + + /* a RST segment with incorrect seqno will not be accepted */ + p = tcp_create_segment(&pcb->remote_ip, &pcb->local_ip, pcb->remote_port, + pcb->local_port, NULL, 0, pcb->rcv_nxt-10, 54321, TCP_RST); + EXPECT(p != NULL); + test_tcp_input(p, &test_netif); + EXPECT(counters.err_calls == 0); + + /* a RST segment with correct seqno will be accepted */ + p = tcp_create_segment(&pcb->remote_ip, &pcb->local_ip, TEST_REMOTE_PORT, + pcb->local_port, NULL, 0, pcb->rcv_nxt, 54321, TCP_RST); + EXPECT(p != NULL); + test_tcp_input(p, &test_netif); + EXPECT(counters.err_calls == 1); + counters.err_calls = 0; + EXPECT(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 0); + +} +END_TEST + +/* RST was generated when receive an SYN+ACK with incorrect ACK number in SYN_SENT state */ +START_TEST(test_tcp_gen_rst_in_SYN_SENT_ackseq) +{ + struct tcp_pcb *pcb; + struct pbuf *p; + u16_t test_port = 1234; + err_t err; + LWIP_UNUSED_ARG(_i); + + /* create and initialize a pcb in listen state */ + pcb = tcp_new(); + EXPECT_RET(pcb != NULL); + err = tcp_connect(pcb, &test_remote_ip, test_port, NULL); + EXPECT_RET(err == ERR_OK); + + /* create a SYN+ACK segment with incorrect seqno */ + p = tcp_create_segment(&pcb->remote_ip, &pcb->local_ip, pcb->remote_port, + pcb->local_port, NULL, 0, 12345, pcb->lastack-10, TCP_SYN|TCP_ACK); + EXPECT(p != NULL); + test_rst_generation_with_incoming_packet(p, &test_netif, &test_txcounters); + + /* LWIP: send RST then re-send SYN immediately */ + EXPECT(test_txcounters.num_tx_calls == 2); + +} +END_TEST + +/* RST was generated when receive an ACK without SYN in SYN_SENT state */ +START_TEST(test_tcp_gen_rst_in_SYN_SENT_non_syn_ack) +{ + struct tcp_pcb *pcb; + struct pbuf *p; + u16_t test_port = 1234; + err_t err; + LWIP_UNUSED_ARG(_i); + + /* create and initialize a pcb in listen state */ + pcb = tcp_new(); + EXPECT_RET(pcb != NULL); + err = tcp_connect(pcb, &test_remote_ip, test_port, NULL); + EXPECT_RET(err == ERR_OK); + + /* create a SYN+ACK segment with incorrect seqno */ + p = tcp_create_segment(&pcb->remote_ip, &pcb->local_ip, pcb->remote_port, + pcb->local_port, NULL, 0, 12345, pcb->lastack, TCP_ACK); + EXPECT(p != NULL); + test_rst_generation_with_incoming_packet(p, &test_netif, &test_txcounters); + + /* LWIP: send RST then re-send SYN immediately */ + EXPECT(test_txcounters.num_tx_calls == 2); + +} +END_TEST + +/* RST was generated when receive an ACK with incorrect seqno in SYN_RCVD state */ +START_TEST(test_tcp_gen_rst_in_SYN_RCVD) +{ + struct tcp_pcb_listen *lpcb; + struct pbuf *p; + u32_t ack_seqno = 0; + ip_addr_t src_addr = test_remote_ip; + LWIP_UNUSED_ARG(_i); + + /* create and initialize a pcb in listen state */ + lpcb = create_listening_pcb(TEST_LOCAL_PORT, NULL); + EXPECT_RET(lpcb != NULL); + + /* LISTEN -> SYN_RCVD */ + p = tcp_create_segment(&src_addr, &lpcb->local_ip, TEST_REMOTE_PORT, + lpcb->local_port, NULL, 0, 1000, 54321, TCP_SYN); + EXPECT(p != NULL); + memset(&test_txcounters, 0, sizeof(struct test_tcp_txcounters)); + test_tcp_input(p, &test_netif); + EXPECT(test_txcounters.num_tx_calls == 1); + EXPECT(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 1); + if (MEMP_STATS_GET(used, MEMP_TCP_PCB) == 1) { + ack_seqno = tcp_active_pcbs[0].lastack; + } + + /* create a ACK segment with incorrect seqno */ + p = tcp_create_segment(&src_addr, &lpcb->local_ip, TEST_REMOTE_PORT, + lpcb->local_port, NULL, 0, 1001, ack_seqno+1111, TCP_ACK); + EXPECT(p != NULL); + test_rst_generation_with_incoming_packet(p, &test_netif, &test_txcounters); + EXPECT(test_txcounters.num_tx_calls == 1); + + /* the active pcb still exists */ + EXPECT(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 1); + EXPECT(MEMP_STATS_GET(used, MEMP_TCP_PCB_LISTEN) == 1); + if (MEMP_STATS_GET(used, MEMP_TCP_PCB_LISTEN) != 0) { + /* can not use tcp_abort() */ + tcp_close((struct tcp_pcb *)lpcb); + } +} +END_TEST + +/* a listen pcb returns to LISTEN from SYN_RCVD when RST received */ +START_TEST(test_tcp_receive_rst_SYN_RCVD_to_LISTEN) +{ + struct tcp_pcb_listen *lpcb; + struct pbuf *p; + u16_t tcp_flags; + ip_addr_t src_addr = test_remote_ip; + LWIP_UNUSED_ARG(_i); + + /* create and initialize a pcb in listen state */ + lpcb = create_listening_pcb(TEST_LOCAL_PORT, NULL); + EXPECT_RET(lpcb != NULL); + + /* create a SYN segment */ + p = tcp_create_segment(&src_addr, &lpcb->local_ip, TEST_REMOTE_PORT, + lpcb->local_port, NULL, 0, 1000, 54321, TCP_SYN); + EXPECT(p != NULL); + /* pass the segment to tcp_input */ + memset(&test_txcounters, 0, sizeof(struct test_tcp_txcounters)); + test_txcounters.copy_tx_packets = 1; + test_tcp_input(p, &test_netif); + test_txcounters.copy_tx_packets = 0; + /* check if packets are as expected */ + EXPECT(test_txcounters.num_tx_calls == 1); + tcp_flags = get_tcp_flags_from_packet(test_txcounters.tx_packets, 20); + pbuf_free(test_txcounters.tx_packets); + test_txcounters.tx_packets = NULL; + EXPECT((tcp_flags & TCP_SYN) && (tcp_flags & TCP_ACK)); + EXPECT(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 1); + + /* create a RST segment */ + p = tcp_create_segment(&src_addr, &lpcb->local_ip, TEST_REMOTE_PORT, + lpcb->local_port, NULL, 0, 1001, 54321, TCP_RST); + EXPECT(p != NULL); + test_tcp_input(p, &test_netif); + EXPECT(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 0); + EXPECT(MEMP_STATS_GET(used, MEMP_TCP_PCB_LISTEN) == 1); + + if (MEMP_STATS_GET(used, MEMP_TCP_PCB_LISTEN) != 0) { + /* can not use tcp_abort() */ + tcp_close((struct tcp_pcb *)lpcb); + } +} +END_TEST + +/** Create the suite including all tests for this module */ +Suite * +tcp_state_suite(void) +{ + testfunc tests[] = { + TESTFUNC(test_tcp_new_max_num), + TESTFUNC(test_tcp_new_max_num_remove_TIME_WAIT), + TESTFUNC(test_tcp_connect_active_open), + TESTFUNC(test_tcp_active_close), + TESTFUNC(test_tcp_imultaneous_close), + TESTFUNC(test_tcp_gen_rst_in_CLOSED), + TESTFUNC(test_tcp_gen_rst_in_LISTEN), + TESTFUNC(test_tcp_gen_rst_in_TIME_WAIT), + TESTFUNC(test_tcp_process_rst_seqno), + TESTFUNC(test_tcp_gen_rst_in_SYN_SENT_ackseq), + TESTFUNC(test_tcp_gen_rst_in_SYN_SENT_non_syn_ack), + TESTFUNC(test_tcp_gen_rst_in_SYN_RCVD), + TESTFUNC(test_tcp_receive_rst_SYN_RCVD_to_LISTEN), + }; + return create_suite("TCP_STATE", tests, sizeof(tests) / sizeof(testfunc), tcp_state_setup, tcp_state_teardown); +} diff --git a/test/unit/tcp/test_tcp_state.h b/test/unit/tcp/test_tcp_state.h new file mode 100644 index 00000000000..00d5c8a4f7c --- /dev/null +++ b/test/unit/tcp/test_tcp_state.h @@ -0,0 +1,8 @@ +#ifndef LWIP_HDR_TEST_TCP_STATE_H +#define LWIP_HDR_TEST_TCP_STATE_H + +#include "../lwip_check.h" + +Suite *tcp_state_suite(void); + +#endif |