add TCP backlog to handle client MSS

This commit is contained in:
Sergio R. Caprile committed 2025-08-22 11:42:41 -03:00
1 parent 3aaf7d0da7
commit 7ee1f14a6d
4 files changed
+413 -143

No files matched your search

+222 -86
View File
@@ -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;