From 7ee1f14a6d6d8d0a9e1eeb178da252db39a09d0e Mon Sep 17 00:00:00 2001 From: "Sergio R. Caprile" Date: Tue, 19 Aug 2025 17:51:01 -0300 Subject: [PATCH] add TCP backlog to handle client MSS --- mongoose.c | 89 ++++++++++++-- mongoose.h | 70 +++++------ src/net_builtin.c | 89 ++++++++++++-- test/mip_test.c | 308 +++++++++++++++++++++++++++++++++------------- 4 files changed, 413 insertions(+), 143 deletions(-) diff --git a/mongoose.c b/mongoose.c index de9839c7..7b616508 100644 --- a/mongoose.c +++ b/mongoose.c @@ -4739,7 +4739,7 @@ static size_t tx_tcp_pkt(struct mg_tcpip_if *ifp, struct pkt *pkt, } static struct mg_connection *accept_conn(struct mg_connection *lsn, - struct pkt *pkt) { + struct pkt *pkt, uint16_t mss) { struct mg_connection *c = mg_alloc_conn(lsn->mgr); struct connstate *s; if (c == NULL) { @@ -4747,7 +4747,7 @@ static struct mg_connection *accept_conn(struct mg_connection *lsn, return NULL; } s = (struct connstate *) (c + 1); - s->dmss = 536; // assume default, RFC-9293 3.7.1 + s->dmss = mss; // from options in client SYN s->seq = mg_ntohl(pkt->tcp->ack), s->ack = mg_ntohl(pkt->tcp->seq); memcpy(s->mac, pkt->eth->src, sizeof(s->mac)); settmout(c, MIP_TTYPE_KEEPALIVE); @@ -4851,7 +4851,7 @@ static void read_conn(struct mg_connection *c, struct pkt *pkt) { if (pkt->tcp->flags & TH_FIN) { uint8_t flags = TH_ACK; if (mg_ntohl(pkt->tcp->seq) != s->ack) { - MG_VERBOSE(("ignoring FIN, SEQ != ACK: %x %x", mg_ntohl(pkt->tcp->seq), s->ack)); + MG_VERBOSE(("ignoring FIN, %x != %x", mg_ntohl(pkt->tcp->seq), s->ack)); tx_tcp(c->mgr->ifp, s->mac, rem_ip, TH_ACK, c->loc.port, c->rem.port, mg_htonl(s->seq), mg_htonl(s->ack), "", 0); return; @@ -4934,6 +4934,57 @@ static void read_conn(struct mg_connection *c, struct pkt *pkt) { } } + +// TCP backlog +struct mg_backlog { + uint16_t port, mss; // use port=0 for available entries + uint8_t age; +}; + +static int backlog_insert(struct mg_connection *c, uint16_t port, + uint16_t mss) { + struct mg_backlog *p = (struct mg_backlog *) c->data; + size_t i; + for (i = 0; i < sizeof(c->data) / sizeof(*p); i++) { + if (p[i].port != 0) continue; + p[i].age = 2; // remove after two calls, average 1.5 call rate + p[i].port = port, p[i].mss = mss; + return (int) i; + } + return -1; +} + +static struct mg_backlog *backlog_retrieve(struct mg_connection *c, + uint16_t key, uint16_t port) { + struct mg_backlog *p = (struct mg_backlog *) c->data; + if (key >= sizeof(c->data) / sizeof(*p)) return NULL; + if (p[key].port != port) return NULL; + p += key; + return p; +} + +static void backlog_remove(struct mg_connection *c, uint16_t key) { + struct mg_backlog *p = (struct mg_backlog *) c->data; + p[key].port = 0; +} + +static void backlog_maintain(struct mg_connection *c) { + struct mg_backlog *p = (struct mg_backlog *) c->data; + size_t i; // dec age and remove those where it reaches 0 + for (i = 0; i < sizeof(c->data) / sizeof(*p); i++) { + if (p[i].port == 0) continue; + if (p[i].age != 0) --p[i].age; + if (p[i].age == 0) p[i].port = 0; + } +} + +static void backlog_poll(struct mg_mgr *mgr) { + struct mg_connection *c = NULL; + for (c = mgr->conns; c != NULL; c = c->next) { + if (!c->is_udp && c->is_listening) backlog_maintain(c); + } +} + // process options (MSS) static void handle_opt(struct connstate *s, struct tcp *tcp) { uint8_t *opts = (uint8_t *) (tcp + 1); @@ -4991,15 +5042,30 @@ static void rx_tcp(struct mg_tcpip_if *ifp, struct pkt *pkt) { if (c->is_accepted) mg_error(c, "peer RST"); // RFC-1122 4.2.2.13 // ignore RST if not connected } else if (pkt->tcp->flags & TH_SYN) { - // TODO(): handle_opt(s, pkt->tcp); // process options (MSS) - // At this point, s = NULL, there is no connection. - // Use peer's source port as ISN, in order to recognise the handshake - uint32_t isn = mg_htonl((uint32_t) mg_ntohs(pkt->tcp->sport)); - tx_tcp_pkt(ifp, pkt, TH_SYN | TH_ACK, isn, NULL, 0); + struct connstate cs; // At this point, s = NULL, there is no connection + int key; + uint32_t isn; + if (pkt->tcp->sport != 0) { + handle_opt(&cs, pkt->tcp); // process options (MSS) + key = backlog_insert(c, pkt->tcp->sport, cs.dmss); // backlog options (MSS) + if (key < 0) return; // no room in backlog, discard SYN, client retries + // Use peer's src port and bl key as ISN, to later identify the handshake + isn = (mg_htonl(((uint32_t)key << 16) | mg_ntohs(pkt->tcp->sport))); + tx_tcp_pkt(ifp, pkt, TH_SYN | TH_ACK, isn, NULL, 0); + } // what should we do when port=0 ? Linux takes port 0 as any other port } else if (pkt->tcp->flags & TH_FIN) { tx_tcp_pkt(ifp, pkt, TH_FIN | TH_ACK, pkt->tcp->ack, NULL, 0); - } else if (mg_htonl(pkt->tcp->ack) == mg_htons(pkt->tcp->sport) + 1U) { - accept_conn(c, pkt); + } else if ((uint16_t) (mg_htonl(pkt->tcp->ack) - 1) == + mg_htons(pkt->tcp->sport)) { + uint16_t key = (uint16_t) ((mg_htonl(pkt->tcp->ack) - 1) >> 16); + struct mg_backlog *b = backlog_retrieve(c, key, pkt->tcp->sport); + if (b != NULL) { + accept_conn(c, pkt, b->mss); // pass options + backlog_remove(c, key); + } else if (!c->is_accepted) { // not an actual match, reset + tx_tcp_pkt(ifp, pkt, TH_RST | TH_ACK, pkt->tcp->ack, NULL, 0); + // TODO(scaprile): revisit this and below, weird scenarios + } } else if (!c->is_accepted) { // no peer tx_tcp_pkt(ifp, pkt, TH_RST | TH_ACK, pkt->tcp->ack, NULL, 0); } else { @@ -5127,6 +5193,7 @@ static void mg_tcpip_poll(struct mg_tcpip_if *ifp, uint64_t now) { names[ifp->state], mg_print_ip4, &ifp->ip, ifp->nrecv, ifp->nsent, ifp->ndrop, ifp->nerr)); #endif + backlog_poll(ifp->mgr); } // Handle gw ARP request timeout, order is important if (expired_1000ms && ifp->state == MG_TCPIP_STATE_IP) { @@ -5460,7 +5527,7 @@ static void setdns4(struct mg_tcpip_if *ifp, uint32_t *ip) { MG_DEBUG(("Set DNS URL to %s", dnsc->url)); if (ifp->mgr->use_dns6) return; if (dnsc->c != NULL) mg_close_conn(dnsc->c); - if (!mg_dnsc_init(ifp->mgr, dnsc)) // create DNS connection + if (!mg_dnsc_init(ifp->mgr, dnsc)) // create DNS connection MG_ERROR(("DNS connection creation failed")); } diff --git a/mongoose.h b/mongoose.h index b9849edd..6148b9e4 100644 --- a/mongoose.h +++ b/mongoose.h @@ -3693,6 +3693,41 @@ struct mg_tcpip_driver_tms570_data { +#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_XMC7) && MG_ENABLE_DRIVER_XMC7 + +struct mg_tcpip_driver_xmc7_data { + int mdc_cr; // Valid values: -1, 0, 1, 2, 3, 4, 5 + uint8_t phy_addr; +}; + +#ifndef MG_TCPIP_PHY_ADDR +#define MG_TCPIP_PHY_ADDR 0 +#endif + +#ifndef MG_DRIVER_MDC_CR +#define MG_DRIVER_MDC_CR 3 +#endif + +#define MG_TCPIP_DRIVER_INIT(mgr) \ + do { \ + static struct mg_tcpip_driver_xmc7_data driver_data_; \ + static struct mg_tcpip_if mif_; \ + driver_data_.mdc_cr = MG_DRIVER_MDC_CR; \ + driver_data_.phy_addr = MG_TCPIP_PHY_ADDR; \ + mif_.ip = MG_TCPIP_IP; \ + mif_.mask = MG_TCPIP_MASK; \ + mif_.gw = MG_TCPIP_GW; \ + mif_.driver = &mg_tcpip_driver_xmc7; \ + mif_.driver_data = &driver_data_; \ + MG_SET_MAC_ADDRESS(mif_.mac); \ + mg_tcpip_init(mgr, &mif_); \ + MG_INFO(("Driver: xmc7, MAC: %M", mg_print_mac, mif_.mac)); \ + } while (0) + +#endif + + + #if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_XMC) && MG_ENABLE_DRIVER_XMC struct mg_tcpip_driver_xmc_data { @@ -3739,41 +3774,6 @@ struct mg_tcpip_driver_xmc_data { #endif - -#if MG_ENABLE_TCPIP && defined(MG_ENABLE_DRIVER_XMC7) && MG_ENABLE_DRIVER_XMC7 - -struct mg_tcpip_driver_xmc7_data { - int mdc_cr; // Valid values: -1, 0, 1, 2, 3, 4, 5 - uint8_t phy_addr; -}; - -#ifndef MG_TCPIP_PHY_ADDR -#define MG_TCPIP_PHY_ADDR 0 -#endif - -#ifndef MG_DRIVER_MDC_CR -#define MG_DRIVER_MDC_CR 3 -#endif - -#define MG_TCPIP_DRIVER_INIT(mgr) \ - do { \ - static struct mg_tcpip_driver_xmc7_data driver_data_; \ - static struct mg_tcpip_if mif_; \ - driver_data_.mdc_cr = MG_DRIVER_MDC_CR; \ - driver_data_.phy_addr = MG_TCPIP_PHY_ADDR; \ - mif_.ip = MG_TCPIP_IP; \ - mif_.mask = MG_TCPIP_MASK; \ - mif_.gw = MG_TCPIP_GW; \ - mif_.driver = &mg_tcpip_driver_xmc7; \ - mif_.driver_data = &driver_data_; \ - MG_SET_MAC_ADDRESS(mif_.mac); \ - mg_tcpip_init(mgr, &mif_); \ - MG_INFO(("Driver: xmc7, MAC: %M", mg_print_mac, mif_.mac)); \ - } while (0) - -#endif - - #ifdef __cplusplus } #endif diff --git a/src/net_builtin.c b/src/net_builtin.c index a8882695..e2d6a9fd 100644 --- a/src/net_builtin.c +++ b/src/net_builtin.c @@ -582,7 +582,7 @@ static size_t tx_tcp_pkt(struct mg_tcpip_if *ifp, struct pkt *pkt, } static struct mg_connection *accept_conn(struct mg_connection *lsn, - struct pkt *pkt) { + struct pkt *pkt, uint16_t mss) { struct mg_connection *c = mg_alloc_conn(lsn->mgr); struct connstate *s; if (c == NULL) { @@ -590,7 +590,7 @@ static struct mg_connection *accept_conn(struct mg_connection *lsn, return NULL; } s = (struct connstate *) (c + 1); - s->dmss = 536; // assume default, RFC-9293 3.7.1 + s->dmss = mss; // from options in client SYN s->seq = mg_ntohl(pkt->tcp->ack), s->ack = mg_ntohl(pkt->tcp->seq); memcpy(s->mac, pkt->eth->src, sizeof(s->mac)); settmout(c, MIP_TTYPE_KEEPALIVE); @@ -694,7 +694,7 @@ static void read_conn(struct mg_connection *c, struct pkt *pkt) { if (pkt->tcp->flags & TH_FIN) { uint8_t flags = TH_ACK; if (mg_ntohl(pkt->tcp->seq) != s->ack) { - MG_VERBOSE(("ignoring FIN, SEQ != ACK: %x %x", mg_ntohl(pkt->tcp->seq), s->ack)); + MG_VERBOSE(("ignoring FIN, %x != %x", mg_ntohl(pkt->tcp->seq), s->ack)); tx_tcp(c->mgr->ifp, s->mac, rem_ip, TH_ACK, c->loc.port, c->rem.port, mg_htonl(s->seq), mg_htonl(s->ack), "", 0); return; @@ -777,6 +777,57 @@ static void read_conn(struct mg_connection *c, struct pkt *pkt) { } } + +// TCP backlog +struct mg_backlog { + uint16_t port, mss; // use port=0 for available entries + uint8_t age; +}; + +static int backlog_insert(struct mg_connection *c, uint16_t port, + uint16_t mss) { + struct mg_backlog *p = (struct mg_backlog *) c->data; + size_t i; + for (i = 0; i < sizeof(c->data) / sizeof(*p); i++) { + if (p[i].port != 0) continue; + p[i].age = 2; // remove after two calls, average 1.5 call rate + p[i].port = port, p[i].mss = mss; + return (int) i; + } + return -1; +} + +static struct mg_backlog *backlog_retrieve(struct mg_connection *c, + uint16_t key, uint16_t port) { + struct mg_backlog *p = (struct mg_backlog *) c->data; + if (key >= sizeof(c->data) / sizeof(*p)) return NULL; + if (p[key].port != port) return NULL; + p += key; + return p; +} + +static void backlog_remove(struct mg_connection *c, uint16_t key) { + struct mg_backlog *p = (struct mg_backlog *) c->data; + p[key].port = 0; +} + +static void backlog_maintain(struct mg_connection *c) { + struct mg_backlog *p = (struct mg_backlog *) c->data; + size_t i; // dec age and remove those where it reaches 0 + for (i = 0; i < sizeof(c->data) / sizeof(*p); i++) { + if (p[i].port == 0) continue; + if (p[i].age != 0) --p[i].age; + if (p[i].age == 0) p[i].port = 0; + } +} + +static void backlog_poll(struct mg_mgr *mgr) { + struct mg_connection *c = NULL; + for (c = mgr->conns; c != NULL; c = c->next) { + if (!c->is_udp && c->is_listening) backlog_maintain(c); + } +} + // process options (MSS) static void handle_opt(struct connstate *s, struct tcp *tcp) { uint8_t *opts = (uint8_t *) (tcp + 1); @@ -834,15 +885,30 @@ static void rx_tcp(struct mg_tcpip_if *ifp, struct pkt *pkt) { if (c->is_accepted) mg_error(c, "peer RST"); // RFC-1122 4.2.2.13 // ignore RST if not connected } else if (pkt->tcp->flags & TH_SYN) { - // TODO(): handle_opt(s, pkt->tcp); // process options (MSS) - // At this point, s = NULL, there is no connection. - // Use peer's source port as ISN, in order to recognise the handshake - uint32_t isn = mg_htonl((uint32_t) mg_ntohs(pkt->tcp->sport)); - tx_tcp_pkt(ifp, pkt, TH_SYN | TH_ACK, isn, NULL, 0); + struct connstate cs; // At this point, s = NULL, there is no connection + int key; + uint32_t isn; + if (pkt->tcp->sport != 0) { + handle_opt(&cs, pkt->tcp); // process options (MSS) + key = backlog_insert(c, pkt->tcp->sport, cs.dmss); // backlog options (MSS) + if (key < 0) return; // no room in backlog, discard SYN, client retries + // Use peer's src port and bl key as ISN, to later identify the handshake + isn = (mg_htonl(((uint32_t)key << 16) | mg_ntohs(pkt->tcp->sport))); + tx_tcp_pkt(ifp, pkt, TH_SYN | TH_ACK, isn, NULL, 0); + } // what should we do when port=0 ? Linux takes port 0 as any other port } else if (pkt->tcp->flags & TH_FIN) { tx_tcp_pkt(ifp, pkt, TH_FIN | TH_ACK, pkt->tcp->ack, NULL, 0); - } else if (mg_htonl(pkt->tcp->ack) == mg_htons(pkt->tcp->sport) + 1U) { - accept_conn(c, pkt); + } else if ((uint16_t) (mg_htonl(pkt->tcp->ack) - 1) == + mg_htons(pkt->tcp->sport)) { + uint16_t key = (uint16_t) ((mg_htonl(pkt->tcp->ack) - 1) >> 16); + struct mg_backlog *b = backlog_retrieve(c, key, pkt->tcp->sport); + if (b != NULL) { + accept_conn(c, pkt, b->mss); // pass options + backlog_remove(c, key); + } else if (!c->is_accepted) { // not an actual match, reset + tx_tcp_pkt(ifp, pkt, TH_RST | TH_ACK, pkt->tcp->ack, NULL, 0); + // TODO(scaprile): revisit this and below, weird scenarios + } } else if (!c->is_accepted) { // no peer tx_tcp_pkt(ifp, pkt, TH_RST | TH_ACK, pkt->tcp->ack, NULL, 0); } else { @@ -970,6 +1036,7 @@ static void mg_tcpip_poll(struct mg_tcpip_if *ifp, uint64_t now) { names[ifp->state], mg_print_ip4, &ifp->ip, ifp->nrecv, ifp->nsent, ifp->ndrop, ifp->nerr)); #endif + backlog_poll(ifp->mgr); } // Handle gw ARP request timeout, order is important if (expired_1000ms && ifp->state == MG_TCPIP_STATE_IP) { @@ -1303,7 +1370,7 @@ static void setdns4(struct mg_tcpip_if *ifp, uint32_t *ip) { MG_DEBUG(("Set DNS URL to %s", dnsc->url)); if (ifp->mgr->use_dns6) return; if (dnsc->c != NULL) mg_close_conn(dnsc->c); - if (!mg_dnsc_init(ifp->mgr, dnsc)) // create DNS connection + if (!mg_dnsc_init(ifp->mgr, dnsc)) // create DNS connection MG_ERROR(("DNS connection creation failed")); } diff --git a/test/mip_test.c b/test/mip_test.c index 5acae37b..51b2242f 100644 --- a/test/mip_test.c +++ b/test/mip_test.c @@ -2,6 +2,7 @@ #define MG_ENABLE_TCPIP_DRIVER_INIT 0 #include "mongoose.c" // order is important, this one first + #include "driver_mock.c" static int s_num_tests = 0; @@ -134,86 +135,157 @@ static bool received_response(struct driver_data *driver) { return was_ready; } -static void create_tcp_pkt(struct eth *e, struct ip *ip, uint32_t seq, - uint32_t ack, uint8_t flags, size_t payload_len) { +static void create_tcp_seg(struct eth *e, struct ip *ip, uint32_t seq, + uint32_t ack, uint8_t flags, uint16_t sport, + uint16_t dport, size_t payload_len, void *opts, unsigned int opts_len) { struct tcp t; memset(&t, 0, sizeof(struct tcp)); t.flags = flags; t.seq = mg_htonl(seq); t.ack = mg_htonl(ack); - t.off = 5 << 4; + t.sport = mg_htons(sport); + t.dport = mg_htons(dport); + t.off = (uint8_t) (sizeof(t) / 4 << 4) + (uint8_t) (opts_len / 4 << 4); memcpy(s_driver_data.buf, e, sizeof(*e)); - ip->len = mg_htons((uint16_t)(sizeof(*ip) + sizeof(struct tcp) + payload_len)); + ip->len = + mg_htons((uint16_t) (sizeof(*ip) + sizeof(struct tcp) + payload_len)); memcpy(s_driver_data.buf + sizeof(*e), ip, sizeof(*ip)); memcpy(s_driver_data.buf + sizeof(*e) + sizeof(*ip), &t, sizeof(t)); - s_driver_data.len = sizeof(*e) + sizeof(*ip) + sizeof(t) + payload_len; + if (opts != NULL && opts_len) + memcpy(s_driver_data.buf + sizeof(*e) + sizeof(*ip) + sizeof(t), opts, opts_len); + s_driver_data.len = sizeof(*e) + sizeof(*ip) + sizeof(t) + payload_len + opts_len; } -static void init_tcp_handshake(struct eth *e, struct ip *ip, struct tcp *tcp, - struct mg_mgr *mgr) { - // SYN - create_tcp_pkt(e, ip, 1000, 0, TH_SYN | TH_ACK, 0); +static void create_tcp_simpleseg(struct eth *e, struct ip *ip, uint32_t seq, + uint32_t ack, uint8_t flags, + size_t payload_len) { + // use sport=1 to ease seqno stuff, dport=80 due to init_tcp_tests() below + create_tcp_seg(e, ip, seq, ack, flags, 1, 80, payload_len, NULL, 0); +} + +static void init_tcp_tests(struct mg_mgr *mgr, struct eth *e, struct ip *ip, + struct mg_tcpip_driver *driver, + struct mg_tcpip_if *mif, mg_event_handler_t f) { + mg_mgr_init(mgr); + memset(mif, 0, sizeof(*mif)); + memset(&s_driver_data, 0, sizeof(struct driver_data)); + driver->init = NULL, driver->tx = if_tx, driver->poll = if_poll, + driver->rx = if_rx; + mif->driver = driver; + mif->driver_data = &s_driver_data; + mg_tcpip_init(mgr, mif); + mg_http_listen(mgr, "http://0.0.0.0:80", f, NULL); + mgr->conns->pfn = NULL; // HTTP handler not needed mg_mgr_poll(mgr, 0); + // setting the Ethernet header + memset(e, 0, sizeof(*e)); + memcpy(e->dst, mif->mac, 6 * sizeof(uint8_t)); + e->type = mg_htons(0x800); + + // setting the IP header + memset(ip, 0, sizeof(*ip)); + ip->ver = 4 << 4 | 5; + ip->proto = 6; +} + +static void init_tcp_handshake(struct eth *e, struct ip *ip, + struct mg_mgr *mgr) { + struct tcp *t = (struct tcp *)(s_driver_data.buf + sizeof(*e) + sizeof(*ip)); + + // SYN + create_tcp_simpleseg(e, ip, 1000, 0, TH_SYN | TH_ACK, 0); + mg_mgr_poll(mgr, 0); // make sure we clean former stuff in buffer + // SYN-ACK while (!received_response(&s_driver_data)) mg_mgr_poll(mgr, 0); - tcp = (struct tcp *) (s_driver_data.buf + sizeof(struct eth) + - sizeof(struct ip)); - ASSERT((tcp->flags == (TH_SYN | TH_ACK))); - ASSERT((tcp->ack == mg_htonl(1001))); + ASSERT((t->flags == (TH_SYN | TH_ACK))); + ASSERT((t->ack == mg_htonl(1001))); // ACK - create_tcp_pkt(e, ip, 1001, 1, TH_ACK, 0); + create_tcp_simpleseg(e, ip, 1001, 2, TH_ACK, 0); mg_mgr_poll(mgr, 0); } + +static void test_tcp_basics(void) { + struct mg_mgr mgr; + struct eth e; + struct ip ip; + struct tcp *t = (struct tcp *) (s_driver_data.buf + sizeof(e) + sizeof(ip)); + // uint64_t start, now; + // bool response_recv = true; + struct mg_tcpip_driver driver; + struct mg_tcpip_if mif; + + init_tcp_tests(&mgr, &e, &ip, &driver, &mif, fn); + + // send SYN to a non-used port, expect RST + ACK + create_tcp_seg(&e, &ip, 1234, 1, TH_SYN, 0, 69, 0, NULL, 0); + mg_mgr_poll(&mgr, 0); // make sure we clean former stuff in buffer + while (!received_response(&s_driver_data)) mg_mgr_poll(&mgr, 0); + ASSERT((t->flags == (TH_RST | TH_ACK))); + ASSERT((t->ack == mg_htonl(1235))); + + // send data to a non-used port, expect a RST + ACK + create_tcp_seg(&e, &ip, 1234, 1, TH_PUSH, 0, 69, 0, NULL, 0); + mg_mgr_poll(&mgr, 0); + while (!received_response(&s_driver_data)) mg_mgr_poll(&mgr, 0); + ASSERT((t->flags == (TH_RST | TH_ACK))); + ASSERT((t->ack == mg_htonl(1234))); + + + + init_tcp_handshake(&e, &ip, &mgr); // starts with seq_no=1000, ackno=2 + + // no MSS sent, so it must default to 536 (RFC-9293 3.7.1) + ASSERT((((struct connstate *)(mgr.conns + 1))->dmss == 536)); + + // segment with seq_no within window + create_tcp_simpleseg(&e, &ip, 1010, 2, TH_PUSH, 2); + mg_mgr_poll(&mgr, 0); + while (!received_response(&s_driver_data)) mg_mgr_poll(&mgr, 0); + ASSERT((t->flags == TH_ACK)); + ASSERT((t->ack == mg_htonl(1001))); // expecting 1001, dude + + // segment with seq_no way out of window + create_tcp_simpleseg(&e, &ip, 1000000, 2, TH_PUSH, 2); + mg_mgr_poll(&mgr, 0); + while (!received_response(&s_driver_data)) mg_mgr_poll(&mgr, 0); + ASSERT((t->flags == TH_ACK)); + ASSERT((t->ack == mg_htonl(1001))); // expecting 1001, dude + + // we currently don't validate checksum, no silently discarded segment test + + s_driver_data.len = 0; + mg_mgr_free(&mgr); +} + // NOTE: a 1-byte payload could be an erroneous Keep-Alive, keep length > 1 in // this operation, we're testing retransmissions and having len=1 won't work -static void test_retransmit(void) { +static void test_tcp_retransmit(void) { struct mg_mgr mgr; struct eth e; struct ip ip; - struct tcp *t = NULL; + struct tcp *t = (struct tcp *) (s_driver_data.buf + sizeof(e) + sizeof(ip)); uint64_t start, now; bool response_recv = true; struct mg_tcpip_driver driver; struct mg_tcpip_if mif; - mg_mgr_init(&mgr); - memset(&mif, 0, sizeof(mif)); - memset(&s_driver_data, 0, sizeof(struct driver_data)); - driver.init = NULL, driver.tx = if_tx, driver.poll = if_poll, - driver.rx = if_rx; - mif.driver = &driver; - mif.driver_data = &s_driver_data; - mg_tcpip_init(&mgr, &mif); - mg_http_listen(&mgr, "http://0.0.0.0:0", fn, NULL); - mgr.conns->pfn = NULL; // HTTP handler not needed - mg_mgr_poll(&mgr, 0); + init_tcp_tests(&mgr, &e, &ip, &driver, &mif, fn); - // setting the Ethernet header - memset(&e, 0, sizeof(e)); - memcpy(e.dst, mif.mac, 6 * sizeof(uint8_t)); - e.type = mg_htons(0x800); - - // setting the IP header - memset(&ip, 0, sizeof(ip)); - ip.ver = 4 << 4, ip.proto = 6; - - init_tcp_handshake(&e, &ip, t, &mgr); + init_tcp_handshake(&e, &ip, &mgr); // starts with seq_no=1000, ackno=2 // packet with seq_no = 1001 - create_tcp_pkt(&e, &ip, 1001, 1, TH_PUSH | TH_ACK, 2); - mg_mgr_poll(&mgr, 0); + create_tcp_simpleseg(&e, &ip, 1001, 2, TH_PUSH | TH_ACK, 2); while (!received_response(&s_driver_data)) mg_mgr_poll(&mgr, 0); - t = (struct tcp *) (s_driver_data.buf + sizeof(struct eth) + - sizeof(struct ip)); ASSERT((t->flags == TH_ACK)); ASSERT((t->ack == mg_htonl(1003))); // OK // resend packet with seq_no = 1001 (e.g.: MIP ACK lost) - create_tcp_pkt(&e, &ip, 1001, 1, TH_PUSH | TH_ACK, 2); + create_tcp_simpleseg(&e, &ip, 1001, 2, TH_PUSH | TH_ACK, 2); mg_mgr_poll(&mgr, 0); start = mg_millis(); while (!received_response(&s_driver_data)) { @@ -228,7 +300,7 @@ static void test_retransmit(void) { ASSERT((!response_recv)); // replies should not be sent for duplicate packets // packet with seq_no = 1003 got lost/delayed, send seq_no = 1005 - create_tcp_pkt(&e, &ip, 1005, 1, TH_PUSH | TH_ACK, 2); + create_tcp_simpleseg(&e, &ip, 1005, 2, TH_PUSH | TH_ACK, 2); mg_mgr_poll(&mgr, 0); start = mg_millis(); while (!received_response(&s_driver_data)) { @@ -237,22 +309,17 @@ static void test_retransmit(void) { if (now > 2 * MIP_TCP_ACK_MS) ASSERT(0); // response should have been received by now } - t = (struct tcp *) (s_driver_data.buf + sizeof(struct eth) + - sizeof(struct ip)); ASSERT((t->flags == TH_ACK)); ASSERT((t->ack == mg_htonl(1003))); // dup ACK // retransmitting packet with seq_no = 1003 - create_tcp_pkt(&e, &ip, 1003, 1, TH_PUSH | TH_ACK, 2); - mg_mgr_poll(&mgr, 0); + create_tcp_simpleseg(&e, &ip, 1003, 2, TH_PUSH | TH_ACK, 2); while (!received_response(&s_driver_data)) mg_mgr_poll(&mgr, 0); - t = (struct tcp *) (s_driver_data.buf + sizeof(struct eth) + - sizeof(struct ip)); ASSERT((t->flags == TH_ACK)); ASSERT((t->ack == mg_htonl(1005))); // OK // packet with seq_no = 1005 got delayed, send FIN with seq_no = 1007 - create_tcp_pkt(&e, &ip, 1007, 1, TH_FIN, 0); + create_tcp_simpleseg(&e, &ip, 1007, 2, TH_FIN, 0); mg_mgr_poll(&mgr, 0); start = mg_millis(); while (!received_response(&s_driver_data)) { @@ -261,28 +328,20 @@ static void test_retransmit(void) { if (now > 2 * MIP_TCP_ACK_MS) ASSERT(0); // response should have been received by now } - t = (struct tcp *) (s_driver_data.buf + sizeof(struct eth) + - sizeof(struct ip)); ASSERT((t->flags == TH_ACK)); ASSERT((t->ack == mg_htonl(1005))); // dup ACK // retransmitting packet with seq_no = 1005 - create_tcp_pkt(&e, &ip, 1005, 1, TH_PUSH | TH_ACK, 2); - mg_mgr_poll(&mgr, 0); + create_tcp_simpleseg(&e, &ip, 1005, 2, TH_PUSH | TH_ACK, 2); while (!received_response(&s_driver_data)) mg_mgr_poll(&mgr, 0); - t = (struct tcp *) (s_driver_data.buf + sizeof(struct eth) + - sizeof(struct ip)); ASSERT((t->flags == TH_ACK)); ASSERT((t->ack == mg_htonl(1007))); // OK // retransmitting FIN packet with seq_no = 1007 - create_tcp_pkt(&e, &ip, 1007, 1, TH_FIN | TH_ACK, 0); - mg_mgr_poll(&mgr, 0); + create_tcp_simpleseg(&e, &ip, 1007, 2, TH_FIN | TH_ACK, 0); while (!received_response(&s_driver_data)) mg_mgr_poll(&mgr, 0); - t = (struct tcp *) (s_driver_data.buf + sizeof(struct eth) + - sizeof(struct ip)); ASSERT((t->flags == (TH_FIN | TH_ACK))); // check we respond with FIN ACK - ASSERT((t->ack == mg_htonl(1008))); // OK + ASSERT((t->ack == mg_htonl(1008))); // OK s_driver_data.len = 0; mg_mgr_free(&mgr); @@ -292,36 +351,16 @@ static void test_frag_recv_path(void) { struct mg_mgr mgr; struct eth e; struct ip ip; - struct tcp *t = NULL; struct mg_tcpip_driver driver; struct mg_tcpip_if mif; - mg_mgr_init(&mgr); - memset(&mif, 0, sizeof(mif)); - memset(&s_driver_data, 0, sizeof(struct driver_data)); - driver.init = NULL, driver.tx = if_tx, driver.poll = if_poll, - driver.rx = if_rx; - mif.driver = &driver; - mif.driver_data = &s_driver_data; - mg_tcpip_init(&mgr, &mif); - mg_http_listen(&mgr, "http://0.0.0.0:0", frag_recv_fn, NULL); - mgr.conns->pfn = NULL; - mg_mgr_poll(&mgr, 0); + init_tcp_tests(&mgr, &e, &ip, &driver, &mif, frag_recv_fn); - // setting the Ethernet header - memset(&e, 0, sizeof(e)); - memcpy(e.dst, mif.mac, 6 * sizeof(uint8_t)); - e.type = mg_htons(0x800); - - // setting the IP header - memset(&ip, 0, sizeof(ip)); - ip.ver = 0x45, ip.proto = 6; - - init_tcp_handshake(&e, &ip, t, &mgr); + init_tcp_handshake(&e, &ip, &mgr); // starts with seq_no=1000, ackno=2 // send fragmented TCP packet ip.frag |= IP_MORE_FRAGS_MSK; // setting More Fragments bit to 1 - create_tcp_pkt(&e, &ip, 1001, 1, TH_PUSH | TH_ACK, 1000); + create_tcp_simpleseg(&e, &ip, 1001, 2, TH_PUSH | TH_ACK, 1000); s_sent_fragment = 1; // "enable" fn mg_mgr_poll(&mgr, 0); // call it (process fake frag IP) ASSERT(s_sent_fragment == 2); // check it followed the right path @@ -345,7 +384,7 @@ static void test_frag_send_path(void) { mif.driver_data = &s_driver_data; mg_tcpip_init(&mgr, &mif); mif.mtu = 500; // force ad hoc small MTU to fragment IP - mg_http_listen(&mgr, "http://0.0.0.0:0", frag_send_fn, NULL); + mg_http_listen(&mgr, "http://0.0.0.0:80", frag_send_fn, NULL); mgr.conns->pfn = NULL; for (i = 0; i < 10; i++) mg_mgr_poll(&mgr, 0); ASSERT(s_seg_sent == 3); @@ -358,11 +397,108 @@ static void test_fragmentation(void) { test_frag_send_path(); } + +static void test_tcp_backlog(void) { + struct mg_mgr mgr; + struct eth e; + struct ip ip; + struct tcp *t = (struct tcp *) (s_driver_data.buf + sizeof(e) + sizeof(ip)); + struct ip *i = (struct ip *) (s_driver_data.buf + sizeof(e)); + uint64_t start, now; + //bool response_recv = true; + struct mg_tcpip_driver driver; + struct mg_tcpip_if mif; + uint16_t opts[4 / 2]; // Send MSS, RFC-9293 3.7.1 + struct mg_connection *c; + unsigned int j; +#define LOGSZ (sizeof(c->data) / sizeof(struct mg_backlog)) + uint32_t seqnos[LOGSZ]; + + init_tcp_tests(&mgr, &e, &ip, &driver, &mif, fn); + + // test expired connection attempts cleanup + create_tcp_seg(&e, &ip, 1234, 0, TH_SYN, 1, 80, 0, NULL, 0); + mg_mgr_poll(&mgr, 0); // make sure we clean former stuff in buffer + while (!received_response(&s_driver_data)) mg_mgr_poll(&mgr, 0); + ASSERT(t->flags == (TH_SYN | TH_ACK)); + // delay ACK so it is removed from the backlog + start = mg_millis(); + do { + mg_mgr_poll(&mgr, 0); + now = mg_millis() - start; + } while (now < 2100); + // Mongoose may have retransmitted SYN + ACK, so + create_tcp_simpleseg(&e, &ip, 1235, 2, TH_ACK, 0); + mg_mgr_poll(&mgr, 0); // make sure we clean former stuff in buffer + while (!received_response(&s_driver_data) || i->proto != 6) mg_mgr_poll(&mgr, 0); + ASSERT(t->flags == (TH_RST | TH_ACK)); + + + opts[0] = mg_htons(0x0204); // RFC-9293 3.2 + // fill the backlog + for (j = 0; j < LOGSZ; j++) { + // assign one MSS for each connection + opts[1] = mg_htons((uint16_t)(1010 + j)); + create_tcp_seg(&e, &ip, 100 + j, 0, TH_SYN, (uint16_t)(j + 1), 80, 0, opts, sizeof(opts)); + while (!received_response(&s_driver_data) || i->proto != 6) + mg_mgr_poll(&mgr, 0); + ASSERT(t->flags == (TH_SYN | TH_ACK)); + seqnos[j] = ntohl(t->seq); + MG_VERBOSE(("SEQ: %p", seqnos[j])); + } + // check backlog is full and MSS are there + c = mgr.conns; + for (j = 0; j < LOGSZ; j++) { + struct mg_backlog *b = (struct mg_backlog *)(c->data) + j; + ASSERT(b->port != 0); + MG_DEBUG(("SEQ: %p, MSS: %u", seqnos[j], (unsigned int)b->mss)); + ASSERT(b->mss == (1010 + j)); + } + // one more attempt, it must fail + opts[1] = mg_htons((uint16_t)(1010 + j)); + create_tcp_seg(&e, &ip, 100 + j, 0, TH_SYN, (uint16_t)(j + 1), 80, 0, opts, sizeof(opts)); + mg_mgr_poll(&mgr, 0); + ASSERT(!received_response(&s_driver_data) || i->proto != 6); + // a late response for this attempt would break what follows + // establish all connections + for (j = 0; j < LOGSZ; j++) { + create_tcp_seg(&e, &ip, 100 + j + 1, seqnos[j] + 1, TH_ACK, (uint16_t)(j + 1), 80, 0, NULL, 0); + mg_mgr_poll(&mgr, 0); + ASSERT(!received_response(&s_driver_data) || i->proto != 6); + } + // check backlog is now empty + c = mgr.conns; // last one is the listener + for (; c->next != NULL; c = c->next); + for (j = 0; j < LOGSZ; j++) { + struct mg_backlog *b = (struct mg_backlog *)(c->data) + j; + ASSERT(b->port == 0); + } + c = mgr.conns; // first one is more recent + // check MSS is what we sent, everything's fine + for (j = LOGSZ; j > 0 ; j--, c = c->next) { + struct connstate *s = (struct connstate *)(c + 1); + ASSERT(c != NULL); + MG_DEBUG(("MSS: %u", (unsigned int) s->dmss)); + ASSERT(s->dmss == (1010 + j - 1)); + } + ASSERT(c != NULL); // last one is the listener + ASSERT(c->next == NULL); + + s_driver_data.len = 0; + mg_mgr_free(&mgr); +} + +static void test_tcp(void) { + test_tcp_basics(); + test_tcp_backlog(); + test_tcp_retransmit(); +} + int main(void) { test_csum(); test_statechange(); test_poll(); - test_retransmit(); + test_tcp(); test_fragmentation(); printf("SUCCESS. Total tests: %d\n", s_num_tests); return 0;