From 5c340188993440fd9027c812c91696045ac72479 Mon Sep 17 00:00:00 2001 From: "Sergio R. Caprile" Date: Fri, 7 Aug 2026 14:19:34 -0300 Subject: [PATCH 1/3] add optional TCP retransmission --- .github/workflows/nightly.yml | 30 ++++++++ mongoose.c | 129 +++++++++++++++++++++++++++----- mongoose.h | 1 + src/net_builtin.c | 129 +++++++++++++++++++++++++++----- src/net_builtin.h | 1 + test/mip_port_test.c | 27 +++++++ test/mip_tap_test.c | 27 +++++++ test/mip_test.c | 137 +++++++++++++++++++++++++++++++++- test/mip_x_test.c | 24 ++++-- 9 files changed, 464 insertions(+), 41 deletions(-) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index feb0e727..2f661d00 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -116,6 +116,21 @@ jobs: cat log test/health.awk < log > json scp -o "StrictHostKeyChecking=no" json "root@176.9.217.245:/data/downloads/health/${{ matrix.target }}_${{ matrix.cc }}_${{ matrix.ssl }}_$(date +"%Y%m%d").json" + + miprtx: + runs-on: ubuntu-latest + name: MIP RTX TAP + env: + IPV6: 0 + RTX: 1 + TFLAGS: -DMQTT_HOST -DIPV6_NOROUTING -DNO_ABORT + steps: + - uses: actions/checkout@v5 + with: { fetch-depth: 2 } + - run: | + source ./test/setup_ga_network.sh ; sed -i -e "s/127.0.0.1//" test/mosquitto.conf ; ./test/setup_mqtt_server.sh + make -C test mip_tap_test + mip89: runs-on: ubuntu-latest strategy: @@ -147,6 +162,21 @@ jobs: if [[ "${{ matrix.target }}" == *port* ]]; then source ./test/setup_ga_network.sh ; export ENV="-e Tmp=. -e WINEDEBUG=-all -e HOST_IP -e HOST_IPV6 -e HOST_IFC" ; sed -i -e "s/127.0.0.1//" test/mosquitto.conf; cat test/mosquitto.conf; ./test/setup_mqtt_server.sh ; make -C test port_tap_bridge ; test/port_tap_bridge & sleep 2 ; fi make -C test ${{ matrix.target }} IPV6=${{ matrix.ipv6 }} SSL=${{ matrix.ssl }} MULTIREC=NO + miprtxbe: + runs-on: ubuntu-latest + name: MIP RTX be s390 port + env: + IPV6: 0 + RTX: 1 + TFLAGS: -DMQTT_HOST -DIPV6_NOROUTING -DNO_ABORT + steps: + - uses: actions/checkout@v5 + with: { fetch-depth: 2 } + - run: | + sudo apt -y update ; sudo apt -y install binfmt-support qemu-user-static && docker run --rm --privileged multiarch/qemu-user-static --reset -p yes + source ./test/setup_ga_network.sh ; export ENV="-e Tmp=. -e WINEDEBUG=-all -e HOST_IP -e HOST_IPV6 -e HOST_IFC -e RTX" ; sed -i -e "s/127.0.0.1//" test/mosquitto.conf ; ./test/setup_mqtt_server.sh ; make -C test port_tap_bridge ; test/port_tap_bridge & sleep 2 + make -C test mip_port_s390 MULTIREC=NO + l2: runs-on: ubuntu-latest strategy: diff --git a/mongoose.c b/mongoose.c index 53a37123..30213817 100644 --- a/mongoose.c +++ b/mongoose.c @@ -8271,6 +8271,10 @@ void mg_multicast_restore(struct mg_connection *c, uint8_t *from) { #define MG_TCPIP_ARP_MS 100 // Timeout for ARP response #define MG_TCPIP_SYN_MS 15000 // Timeout for connection establishment #define MG_TCPIP_FIN_MS 1000 // Timeout for closing connection +#define MG_TCPIP_RTX_MS 1000 // Initial retransmission timeout (RFC-6298, 2.1) +#define MG_TCPIP_RTX_MAX_MS 60000 // Minimum permitted RTO cap (RFC-6298, 2.5) +#define MG_TCPIP_RTX_R1 3 // Delivery-problem threshold (RFC-9293, 3.8.3) +// No R2: MG_TCPIP_KEEPALIVE_MS eventually closes non-responsive peers #ifndef MG_TCPIP_WIN #define MG_TCPIP_WIN 6000 // TCP window size @@ -8279,9 +8283,12 @@ void mg_multicast_restore(struct mg_connection *c, uint8_t *from) { struct connstate { uint32_t seq, ack; // TCP seq/ack counters uint64_t timer; // TCP timer (see 'ttype' below) + uint64_t txq_timer; // TCP retransmission timer (RFC-6298, 5) uint32_t acked; // Last ACK-ed number size_t unacked; // Not acked bytes uint32_t maxseq; // Max send seq (ack + window) + uint32_t txq_seq; // Sequence of first txq record (RFC-9293, 3.8) + uint32_t txq_una; // Oldest unacknowledged sequence (RFC-9293, 3.4) uint16_t win; // destination current window size uint16_t dmss; // destination MSS (from TCP opts) uint8_t mac[sizeof(struct mg_l2addr)]; // Peer hw address @@ -8292,8 +8299,11 @@ struct connstate { #define MIP_TTYPE_SYN 3 // SYN sent, waiting for response #define MIP_TTYPE_FIN 4 // FIN sent, waiting until terminating the connection uint8_t tmiss; // Number of keep-alive misses + uint8_t txq_retries; // Retransmissions since ACK progress (RFC-9293, 3.8.3) bool fin_rcvd; // We have received FIN from the peer bool twclosure; // 3-way closure done + bool retransmit; // Retain sent data until acknowledged + struct mg_iobuf txq; // Length-prefixed sent TCP segments (RFC-9293, 3.8) }; #if defined(__DCC__) @@ -9570,6 +9580,8 @@ static struct mg_connection *accept_conn(struct mg_connection *lsn, return NULL; } s = (struct connstate *) (c + 1); + s->retransmit = lsn->mgr->ifp->enable_tcp_retransmit; + s->txq.align = MG_IO_SIZE; s->dmss = mss; // from options in client SYN s->seq = mg_ntohl(pkt->tcp->ack), s->ack = mg_ntohl(pkt->tcp->seq); s->win = mg_ntohs(pkt->tcp->win), s->maxseq = (uint32_t) (s->seq + s->win); @@ -9660,6 +9672,25 @@ static bool udp_send(struct mg_connection *c, const void *buf, size_t len) { return tx_udp(ifp, s->mac, &ips, &c->rem, c->dscp, buf, len); } +static size_t txq_next(struct connstate *s, uint8_t **buf) { + uint32_t len; + if (s->txq.len == 0) return 0; + assert(s->txq.len >= sizeof(len)); + memcpy(&len, s->txq.buf, sizeof(len)); + assert(len > 0 && len <= s->txq.len - sizeof(len)); + if (buf != NULL) *buf = s->txq.buf + sizeof(len); + return len; +} + +static bool txq_add(struct connstate *s, const void *buf, size_t len) { + uint32_t n = (uint32_t) len; + size_t off = s->txq.len, total = sizeof(n) + len; + if (mg_iobuf_add(&s->txq, off, NULL, total) != total) return false; + memcpy(s->txq.buf + off, &n, sizeof(n)); + memcpy(s->txq.buf + off + sizeof(n), buf, len); + return true; +} + long mg_io_send(struct mg_connection *c, const void *buf, size_t len) { struct connstate *s = (struct connstate *) (c + 1); len = trim_len(c, len); @@ -9669,19 +9700,34 @@ long mg_io_send(struct mg_connection *c, const void *buf, size_t len) { struct mg_tcpip_if *ifp = c->mgr->ifp; size_t sent; uint32_t room = s->maxseq - s->seq; + bool was_empty = s->txq.len == 0; + if (s->retransmit) { + uint32_t in_flight = s->txq.len > 0 ? s->seq - s->txq_una : 0; + uint32_t qroom = in_flight < MG_TCPIP_WIN ? MG_TCPIP_WIN - in_flight : 0; + room = s->win > in_flight ? s->win - in_flight : 0; + if (room > qroom) room = qroom; + } if (room == 0) return MG_IO_WAIT; - if (len > s->dmss) len = s->dmss; // RFC-6691: reduce if sending opts - if ((uint32_t) len > room) len = room; + if (len > s->dmss) len = s->dmss; // RFC-6691, reduce if sending opts + if (len > room) len = room; + if (s->retransmit) { + if (was_empty) s->txq_seq = s->txq_una = s->seq; + if (!txq_add(s, buf, len)) return MG_IO_WAIT; + } sent = tx_tcp(ifp, s->mac, &c->loc, &c->rem, c->dscp, TH_PUSH | TH_ACK, mg_htonl(s->seq), mg_htonl(s->ack), buf, len); - if (sent == 0) { - return MG_IO_WAIT; - } else if (sent == (size_t) -1) { - return MG_IO_ERR; - } else { - s->seq += (uint32_t) len; - if (s->ttype == MIP_TTYPE_ACK) settmout(c, MIP_TTYPE_KEEPALIVE); + if (sent == 0 || sent == (size_t) -1) { + if (s->retransmit) + mg_iobuf_del(&s->txq, s->txq.len - len - sizeof(uint32_t), + len + sizeof(uint32_t)); + return sent == 0 ? MG_IO_WAIT : MG_IO_ERR; } + s->seq += (uint32_t) len; + if (s->retransmit && was_empty) { + s->txq_timer = ifp->now + MG_TCPIP_RTX_MS; + s->txq_retries = 0; + } + if (s->ttype == MIP_TTYPE_ACK) settmout(c, MIP_TTYPE_KEEPALIVE); } return (long) len; } @@ -9709,12 +9755,52 @@ static void handle_tls_recv(struct mg_connection *c) { } } -static void handle_ack(struct connstate *s, uint32_t ackno, uint16_t win) { - if (ackno < (s->seq - s->win) || ackno > s->seq) return; +static void handle_ack(struct connstate *s, uint32_t ackno, uint16_t win, + uint64_t now) { + if (ackno > s->seq) return; + if (s->retransmit && s->txq.len > 0) { + if (ackno < s->txq_una) return; + if (ackno > s->txq_una) { + size_t len; + // Remove only entirely acknowledged segments (RFC-9293, 3.10.7.4) + while ((len = txq_next(s, NULL)) > 0 && ackno >= s->txq_seq + len) { + mg_iobuf_del(&s->txq, 0, len + sizeof(uint32_t)); + s->txq_seq += (uint32_t) len; + } + s->txq_una = ackno; + s->txq_retries = 0; + // Restart RTO when an ACK acknowledges new data (RFC-6298, 5.3) + s->txq_timer = now + MG_TCPIP_RTX_MS; + } // else: dup ack, ignore it but handle its window upddate + } else if (ackno < (s->seq - s->win)) { + return; + } s->maxseq = (uint32_t) (ackno + win); s->win = win; } +static void retransmit(struct mg_connection *c) { + struct connstate *s = (struct connstate *) (c + 1); + struct mg_tcpip_if *ifp = c->mgr->ifp; + uint8_t *buf, i; + uint64_t rto; + size_t len = txq_next(s, &buf); + // Send the front retransmission-queue segment only (RFC-9293, 3.10.8) + if (len == 0 || + tx_tcp(ifp, s->mac, &c->loc, &c->rem, c->dscp, TH_PUSH | TH_ACK, + mg_htonl(s->txq_seq), mg_htonl(s->ack), buf, len) == (size_t) -1) { + mg_error(c, "retransmit"); + return; + } + // Back off RTO after every timeout (RFC-6298, 5.5) + for (i = 0, rto = MG_TCPIP_RTX_MS; + i < s->txq_retries && rto < MG_TCPIP_RTX_MAX_MS; i++) { + rto *= 2; + if (rto > MG_TCPIP_RTX_MAX_MS) rto = MG_TCPIP_RTX_MAX_MS; + } + s->txq_timer = ifp->now + rto; +} + static void read_conn(struct mg_connection *c, struct pkt *pkt) { struct connstate *s = (struct connstate *) (c + 1); struct mg_iobuf *io = c->is_tls ? &c->rtls : &c->recv; @@ -9768,7 +9854,8 @@ static void read_conn(struct mg_connection *c, struct pkt *pkt) { } // Now process the segment for ACK and payload if (pkt->tcp->flags & TH_ACK) { - handle_ack(s, mg_ntohl(pkt->tcp->ack), mg_ntohs(pkt->tcp->win)); + handle_ack(s, mg_ntohl(pkt->tcp->ack), mg_ntohs(pkt->tcp->win), + c->mgr->ifp->now); if (pkt->pay.len == 0 && s->fin_rcvd && s->ttype == MIP_TTYPE_FIN) s->twclosure = true; } @@ -10322,6 +10409,13 @@ static void mg_tcpip_poll(struct mg_tcpip_if *ifp, uint64_t now) { struct connstate *s = (struct connstate *) (c + 1); if ((c->is_udp && !c->is_arplooking) || c->is_listening || c->is_resolving) continue; + if (s->retransmit && s->txq.len > 0 && ifp->now > s->txq_timer) { + if (s->txq_retries < 255) s->txq_retries++; + // R1 reports a delivery problem; it does not close (RFC-9293, 3.8.3). + if (s->txq_retries == MG_TCPIP_RTX_R1) + MG_ERROR(("%lu retransmit", c->id)); + retransmit(c); + } if (ifp->now > s->timer) { if (s->ttype == MIP_TTYPE_ARP) { mg_error(c, "ARP timeout"); @@ -10423,6 +10517,9 @@ static void l2addr_resolved(struct mg_connection *c) { void mg_connect_resolved(struct mg_connection *c) { struct mg_tcpip_if *ifp = c->mgr->ifp; uint8_t *l2addr; + struct connstate *s = (struct connstate *) (c + 1); + s->retransmit = ifp->enable_tcp_retransmit; + s->txq.align = MG_IO_SIZE; c->is_resolving = 0; if (ifp->eport < MG_EPHEMERAL_PORT_BASE) ifp->eport = MG_EPHEMERAL_PORT_BASE; c->loc.port = mg_htons(ifp->eport++); @@ -10444,7 +10541,6 @@ void mg_connect_resolved(struct mg_connection *c) { mg_call(c, MG_EV_RESOLVE, NULL); c->is_connecting = 1; if (c->is_udp && (l2addr = tcpip_mapip(ifp, &c->rem)) != NULL) { - struct connstate *s = (struct connstate *) (c + 1); memcpy(s->mac, l2addr, sizeof(s->mac)); l2addr_resolved(c); // broadcast or multicast #if MG_ENABLE_IPV6 @@ -10459,7 +10555,6 @@ void mg_connect_resolved(struct mg_connection *c) { settmout(c, MIP_TTYPE_ARP); c->is_arplooking = 1; } else if (ifp->gw6_ready) { - struct connstate *s = (struct connstate *) (c + 1); memcpy(s->mac, ifp->gw6mac, sizeof(s->mac)); l2addr_resolved(c); } else { @@ -10476,7 +10571,6 @@ void mg_connect_resolved(struct mg_connection *c) { settmout(c, MIP_TTYPE_ARP); c->is_arplooking = 1; } else if (ifp->gw_ready) { - struct connstate *s = (struct connstate *) (c + 1); memcpy(s->mac, ifp->gwmac, sizeof(s->mac)); l2addr_resolved(c); } else { @@ -10517,8 +10611,8 @@ static void init_closure(struct mg_connection *c) { static void close_conn(struct mg_connection *c) { struct connstate *s = (struct connstate *) (c + 1); + mg_iobuf_free(&s->txq); mg_close_conn(c); - (void) s; } static bool can_write(struct mg_connection *c) { @@ -10552,7 +10646,8 @@ void mg_mgr_poll(struct mg_mgr *mgr, int ms) { if (flush == MG_IO_ERR) mg_error(c, "tx err"); if (c->is_draining && c->send.len == 0) { if (c->is_udp) c->is_closing = 1; - if (!c->is_udp && flush == 0 && s->ttype != MIP_TTYPE_FIN) + if (!c->is_udp && flush == 0 && s->txq.len == 0 && + s->ttype != MIP_TTYPE_FIN) init_closure(c); } // For non-TLS, close immediately upon completing the 3-way closure diff --git a/mongoose.h b/mongoose.h index dc8156c7..995617ab 100644 --- a/mongoose.h +++ b/mongoose.h @@ -5100,6 +5100,7 @@ struct mg_tcpip_if { bool enable_req_sntp; // DHCP client requests an SNTP server address bool enable_fcs_check; // Verify and strip FCS from received frames bool enable_mac_check; // Drop frames not addressed to this MAC + bool enable_tcp_retransmit; // Enable optional TCP retransmission queue bool update_mac_hash_table; // Signal driver to refresh MAC multicast hash table bool is_ip_changed; // Set by stack when IP changes; triggers connection restart struct mg_tcpip_driver *driver; // Hardware driver; must be set before mg_tcpip_init() diff --git a/src/net_builtin.c b/src/net_builtin.c index e2c7ea8a..f37e4e09 100644 --- a/src/net_builtin.c +++ b/src/net_builtin.c @@ -13,6 +13,10 @@ #define MG_TCPIP_ARP_MS 100 // Timeout for ARP response #define MG_TCPIP_SYN_MS 15000 // Timeout for connection establishment #define MG_TCPIP_FIN_MS 1000 // Timeout for closing connection +#define MG_TCPIP_RTX_MS 1000 // Initial retransmission timeout (RFC-6298, 2.1) +#define MG_TCPIP_RTX_MAX_MS 60000 // Minimum permitted RTO cap (RFC-6298, 2.5) +#define MG_TCPIP_RTX_R1 3 // Delivery-problem threshold (RFC-9293, 3.8.3) +// No R2: MG_TCPIP_KEEPALIVE_MS eventually closes non-responsive peers #ifndef MG_TCPIP_WIN #define MG_TCPIP_WIN 6000 // TCP window size @@ -21,9 +25,12 @@ struct connstate { uint32_t seq, ack; // TCP seq/ack counters uint64_t timer; // TCP timer (see 'ttype' below) + uint64_t txq_timer; // TCP retransmission timer (RFC-6298, 5) uint32_t acked; // Last ACK-ed number size_t unacked; // Not acked bytes uint32_t maxseq; // Max send seq (ack + window) + uint32_t txq_seq; // Sequence of first txq record (RFC-9293, 3.8) + uint32_t txq_una; // Oldest unacknowledged sequence (RFC-9293, 3.4) uint16_t win; // destination current window size uint16_t dmss; // destination MSS (from TCP opts) uint8_t mac[sizeof(struct mg_l2addr)]; // Peer hw address @@ -34,8 +41,11 @@ struct connstate { #define MIP_TTYPE_SYN 3 // SYN sent, waiting for response #define MIP_TTYPE_FIN 4 // FIN sent, waiting until terminating the connection uint8_t tmiss; // Number of keep-alive misses + uint8_t txq_retries; // Retransmissions since ACK progress (RFC-9293, 3.8.3) bool fin_rcvd; // We have received FIN from the peer bool twclosure; // 3-way closure done + bool retransmit; // Retain sent data until acknowledged + struct mg_iobuf txq; // Length-prefixed sent TCP segments (RFC-9293, 3.8) }; #if defined(__DCC__) @@ -1312,6 +1322,8 @@ static struct mg_connection *accept_conn(struct mg_connection *lsn, return NULL; } s = (struct connstate *) (c + 1); + s->retransmit = lsn->mgr->ifp->enable_tcp_retransmit; + s->txq.align = MG_IO_SIZE; s->dmss = mss; // from options in client SYN s->seq = mg_ntohl(pkt->tcp->ack), s->ack = mg_ntohl(pkt->tcp->seq); s->win = mg_ntohs(pkt->tcp->win), s->maxseq = (uint32_t) (s->seq + s->win); @@ -1402,6 +1414,25 @@ static bool udp_send(struct mg_connection *c, const void *buf, size_t len) { return tx_udp(ifp, s->mac, &ips, &c->rem, c->dscp, buf, len); } +static size_t txq_next(struct connstate *s, uint8_t **buf) { + uint32_t len; + if (s->txq.len == 0) return 0; + assert(s->txq.len >= sizeof(len)); + memcpy(&len, s->txq.buf, sizeof(len)); + assert(len > 0 && len <= s->txq.len - sizeof(len)); + if (buf != NULL) *buf = s->txq.buf + sizeof(len); + return len; +} + +static bool txq_add(struct connstate *s, const void *buf, size_t len) { + uint32_t n = (uint32_t) len; + size_t off = s->txq.len, total = sizeof(n) + len; + if (mg_iobuf_add(&s->txq, off, NULL, total) != total) return false; + memcpy(s->txq.buf + off, &n, sizeof(n)); + memcpy(s->txq.buf + off + sizeof(n), buf, len); + return true; +} + long mg_io_send(struct mg_connection *c, const void *buf, size_t len) { struct connstate *s = (struct connstate *) (c + 1); len = trim_len(c, len); @@ -1411,19 +1442,34 @@ long mg_io_send(struct mg_connection *c, const void *buf, size_t len) { struct mg_tcpip_if *ifp = c->mgr->ifp; size_t sent; uint32_t room = s->maxseq - s->seq; + bool was_empty = s->txq.len == 0; + if (s->retransmit) { + uint32_t in_flight = s->txq.len > 0 ? s->seq - s->txq_una : 0; + uint32_t qroom = in_flight < MG_TCPIP_WIN ? MG_TCPIP_WIN - in_flight : 0; + room = s->win > in_flight ? s->win - in_flight : 0; + if (room > qroom) room = qroom; + } if (room == 0) return MG_IO_WAIT; - if (len > s->dmss) len = s->dmss; // RFC-6691: reduce if sending opts - if ((uint32_t) len > room) len = room; + if (len > s->dmss) len = s->dmss; // RFC-6691, reduce if sending opts + if (len > room) len = room; + if (s->retransmit) { + if (was_empty) s->txq_seq = s->txq_una = s->seq; + if (!txq_add(s, buf, len)) return MG_IO_WAIT; + } sent = tx_tcp(ifp, s->mac, &c->loc, &c->rem, c->dscp, TH_PUSH | TH_ACK, mg_htonl(s->seq), mg_htonl(s->ack), buf, len); - if (sent == 0) { - return MG_IO_WAIT; - } else if (sent == (size_t) -1) { - return MG_IO_ERR; - } else { - s->seq += (uint32_t) len; - if (s->ttype == MIP_TTYPE_ACK) settmout(c, MIP_TTYPE_KEEPALIVE); + if (sent == 0 || sent == (size_t) -1) { + if (s->retransmit) + mg_iobuf_del(&s->txq, s->txq.len - len - sizeof(uint32_t), + len + sizeof(uint32_t)); + return sent == 0 ? MG_IO_WAIT : MG_IO_ERR; } + s->seq += (uint32_t) len; + if (s->retransmit && was_empty) { + s->txq_timer = ifp->now + MG_TCPIP_RTX_MS; + s->txq_retries = 0; + } + if (s->ttype == MIP_TTYPE_ACK) settmout(c, MIP_TTYPE_KEEPALIVE); } return (long) len; } @@ -1451,12 +1497,52 @@ static void handle_tls_recv(struct mg_connection *c) { } } -static void handle_ack(struct connstate *s, uint32_t ackno, uint16_t win) { - if (ackno < (s->seq - s->win) || ackno > s->seq) return; +static void handle_ack(struct connstate *s, uint32_t ackno, uint16_t win, + uint64_t now) { + if (ackno > s->seq) return; + if (s->retransmit && s->txq.len > 0) { + if (ackno < s->txq_una) return; + if (ackno > s->txq_una) { + size_t len; + // Remove only entirely acknowledged segments (RFC-9293, 3.10.7.4) + while ((len = txq_next(s, NULL)) > 0 && ackno >= s->txq_seq + len) { + mg_iobuf_del(&s->txq, 0, len + sizeof(uint32_t)); + s->txq_seq += (uint32_t) len; + } + s->txq_una = ackno; + s->txq_retries = 0; + // Restart RTO when an ACK acknowledges new data (RFC-6298, 5.3) + s->txq_timer = now + MG_TCPIP_RTX_MS; + } // else: dup ack, ignore it but handle its window upddate + } else if (ackno < (s->seq - s->win)) { + return; + } s->maxseq = (uint32_t) (ackno + win); s->win = win; } +static void retransmit(struct mg_connection *c) { + struct connstate *s = (struct connstate *) (c + 1); + struct mg_tcpip_if *ifp = c->mgr->ifp; + uint8_t *buf, i; + uint64_t rto; + size_t len = txq_next(s, &buf); + // Send the front retransmission-queue segment only (RFC-9293, 3.10.8) + if (len == 0 || + tx_tcp(ifp, s->mac, &c->loc, &c->rem, c->dscp, TH_PUSH | TH_ACK, + mg_htonl(s->txq_seq), mg_htonl(s->ack), buf, len) == (size_t) -1) { + mg_error(c, "retransmit"); + return; + } + // Back off RTO after every timeout (RFC-6298, 5.5) + for (i = 0, rto = MG_TCPIP_RTX_MS; + i < s->txq_retries && rto < MG_TCPIP_RTX_MAX_MS; i++) { + rto *= 2; + if (rto > MG_TCPIP_RTX_MAX_MS) rto = MG_TCPIP_RTX_MAX_MS; + } + s->txq_timer = ifp->now + rto; +} + static void read_conn(struct mg_connection *c, struct pkt *pkt) { struct connstate *s = (struct connstate *) (c + 1); struct mg_iobuf *io = c->is_tls ? &c->rtls : &c->recv; @@ -1510,7 +1596,8 @@ static void read_conn(struct mg_connection *c, struct pkt *pkt) { } // Now process the segment for ACK and payload if (pkt->tcp->flags & TH_ACK) { - handle_ack(s, mg_ntohl(pkt->tcp->ack), mg_ntohs(pkt->tcp->win)); + handle_ack(s, mg_ntohl(pkt->tcp->ack), mg_ntohs(pkt->tcp->win), + c->mgr->ifp->now); if (pkt->pay.len == 0 && s->fin_rcvd && s->ttype == MIP_TTYPE_FIN) s->twclosure = true; } @@ -2064,6 +2151,13 @@ static void mg_tcpip_poll(struct mg_tcpip_if *ifp, uint64_t now) { struct connstate *s = (struct connstate *) (c + 1); if ((c->is_udp && !c->is_arplooking) || c->is_listening || c->is_resolving) continue; + if (s->retransmit && s->txq.len > 0 && ifp->now > s->txq_timer) { + if (s->txq_retries < 255) s->txq_retries++; + // R1 reports a delivery problem; it does not close (RFC-9293, 3.8.3). + if (s->txq_retries == MG_TCPIP_RTX_R1) + MG_ERROR(("%lu retransmit", c->id)); + retransmit(c); + } if (ifp->now > s->timer) { if (s->ttype == MIP_TTYPE_ARP) { mg_error(c, "ARP timeout"); @@ -2165,6 +2259,9 @@ static void l2addr_resolved(struct mg_connection *c) { void mg_connect_resolved(struct mg_connection *c) { struct mg_tcpip_if *ifp = c->mgr->ifp; uint8_t *l2addr; + struct connstate *s = (struct connstate *) (c + 1); + s->retransmit = ifp->enable_tcp_retransmit; + s->txq.align = MG_IO_SIZE; c->is_resolving = 0; if (ifp->eport < MG_EPHEMERAL_PORT_BASE) ifp->eport = MG_EPHEMERAL_PORT_BASE; c->loc.port = mg_htons(ifp->eport++); @@ -2186,7 +2283,6 @@ void mg_connect_resolved(struct mg_connection *c) { mg_call(c, MG_EV_RESOLVE, NULL); c->is_connecting = 1; if (c->is_udp && (l2addr = tcpip_mapip(ifp, &c->rem)) != NULL) { - struct connstate *s = (struct connstate *) (c + 1); memcpy(s->mac, l2addr, sizeof(s->mac)); l2addr_resolved(c); // broadcast or multicast #if MG_ENABLE_IPV6 @@ -2201,7 +2297,6 @@ void mg_connect_resolved(struct mg_connection *c) { settmout(c, MIP_TTYPE_ARP); c->is_arplooking = 1; } else if (ifp->gw6_ready) { - struct connstate *s = (struct connstate *) (c + 1); memcpy(s->mac, ifp->gw6mac, sizeof(s->mac)); l2addr_resolved(c); } else { @@ -2218,7 +2313,6 @@ void mg_connect_resolved(struct mg_connection *c) { settmout(c, MIP_TTYPE_ARP); c->is_arplooking = 1; } else if (ifp->gw_ready) { - struct connstate *s = (struct connstate *) (c + 1); memcpy(s->mac, ifp->gwmac, sizeof(s->mac)); l2addr_resolved(c); } else { @@ -2259,8 +2353,8 @@ static void init_closure(struct mg_connection *c) { static void close_conn(struct mg_connection *c) { struct connstate *s = (struct connstate *) (c + 1); + mg_iobuf_free(&s->txq); mg_close_conn(c); - (void) s; } static bool can_write(struct mg_connection *c) { @@ -2294,7 +2388,8 @@ void mg_mgr_poll(struct mg_mgr *mgr, int ms) { if (flush == MG_IO_ERR) mg_error(c, "tx err"); if (c->is_draining && c->send.len == 0) { if (c->is_udp) c->is_closing = 1; - if (!c->is_udp && flush == 0 && s->ttype != MIP_TTYPE_FIN) + if (!c->is_udp && flush == 0 && s->txq.len == 0 && + s->ttype != MIP_TTYPE_FIN) init_closure(c); } // For non-TLS, close immediately upon completing the 3-way closure diff --git a/src/net_builtin.h b/src/net_builtin.h index 37fc3025..7df3b9b4 100644 --- a/src/net_builtin.h +++ b/src/net_builtin.h @@ -59,6 +59,7 @@ struct mg_tcpip_if { bool enable_req_sntp; // DHCP client requests an SNTP server address bool enable_fcs_check; // Verify and strip FCS from received frames bool enable_mac_check; // Drop frames not addressed to this MAC + bool enable_tcp_retransmit; // Enable optional TCP retransmission queue bool update_mac_hash_table; // Signal driver to refresh MAC multicast hash table bool is_ip_changed; // Set by stack when IP changes; triggers connection restart struct mg_tcpip_driver *driver; // Hardware driver; must be set before mg_tcpip_init() diff --git a/test/mip_port_test.c b/test/mip_port_test.c index e384e536..989b1985 100644 --- a/test/mip_port_test.c +++ b/test/mip_port_test.c @@ -19,7 +19,33 @@ static size_t sock_rx(void *buf, size_t len, struct mg_tcpip_if *ifp) { return (size_t) received; } +// ignore IPv6, testing segment loss under IPv4 is enough +static bool is_tcp_segment(const void *buf, size_t len) { + const struct eth *eth = (const struct eth *) buf; + const struct ip *ip; + const struct tcp *tcp; + size_t hlen; + if (len < sizeof(*eth)) return false; + if (eth->type != mg_htons(0x800)) return false; + ip = (const struct ip *) (eth + 1); + if (len < sizeof(*eth) + sizeof(*ip)) return false; + hlen = (size_t) (ip->ver & 15) * 4; + if ((ip->ver >> 4) != 4 || ip->proto != 6 || hlen < sizeof(*ip) || + len < sizeof(*eth) + hlen + sizeof(*tcp)) + return false; + tcp = (const struct tcp *) ((const char *) ip + hlen); + return (tcp->flags & (TH_SYN | TH_FIN)) == 0; +} + static size_t sock_tx(const void *buf, size_t len, struct mg_tcpip_if *ifp) { + uint32_t random; + if (ifp->enable_tcp_retransmit && is_tcp_segment(buf, len)) { // introduce a 30% segment loss + mg_random(&random, sizeof(random)); + if (random % 10 < 3) { + MG_DEBUG(("dropping TCP segment")); + return len; + } + } ssize_t res = send(((struct port_driver_data *)ifp->driver_data)->sockfd, buf, len, 0); if (res < 0) { MG_ERROR(("sock_tx failed: %d", errno)); @@ -113,6 +139,7 @@ int main(void) { } // RUN TESTS + mif.enable_tcp_retransmit = getenv("RTX") != NULL; result = mip_x_test(&mgr); close(pdd.sockfd); if (!result) return EXIT_FAILURE; diff --git a/test/mip_tap_test.c b/test/mip_tap_test.c index 917939b6..0b170d80 100644 --- a/test/mip_tap_test.c +++ b/test/mip_tap_test.c @@ -22,7 +22,33 @@ static size_t tap_rx(void *buf, size_t len, struct mg_tcpip_if *ifp) { return (size_t) received; } +// ignore IPv6, testing segment loss under IPv4 is enough +static bool is_tcp_segment(const void *buf, size_t len) { + const struct eth *eth = (const struct eth *) buf; + const struct ip *ip; + const struct tcp *tcp; + size_t hlen; + if (len < sizeof(*eth)) return false; + if (eth->type != mg_htons(0x800)) return false; + ip = (const struct ip *) (eth + 1); + if (len < sizeof(*eth) + sizeof(*ip)) return false; + hlen = (size_t) (ip->ver & 15) * 4; + if ((ip->ver >> 4) != 4 || ip->proto != 6 || hlen < sizeof(*ip) || + len < sizeof(*eth) + hlen + sizeof(*tcp)) + return false; + tcp = (const struct tcp *) ((const char *) ip + hlen); + return (tcp->flags & (TH_SYN | TH_FIN)) == 0; +} + static size_t tap_tx(const void *buf, size_t len, struct mg_tcpip_if *ifp) { + uint32_t random; + if (ifp->enable_tcp_retransmit && is_tcp_segment(buf, len)) { // introduce a 30% segment loss + mg_random(&random, sizeof(random)); + if (random % 10 < 3) { + MG_DEBUG(("dropping TCP segment")); + return len; + } + } ssize_t res = write(*(int *) ifp->driver_data, buf, len); if (res < 0) { MG_ERROR(("tap_tx failed: %d", errno)); @@ -125,6 +151,7 @@ int main(void) { } // RUN TESTS + mif.enable_tcp_retransmit = getenv("RTX") != NULL; result = mip_x_test(&mgr); close(fd); if (!result) return EXIT_FAILURE; diff --git a/test/mip_test.c b/test/mip_test.c index d911c568..29b2660a 100644 --- a/test/mip_test.c +++ b/test/mip_test.c @@ -157,6 +157,15 @@ static void txwindow_fn(struct mg_connection *c, int ev, void *ev_data) { (void) c, (void) ev_data; } +static char s_rtx_data[MG_TCPIP_WIN]; +static size_t s_rtx_len; +static uint16_t s_tcp_window; + +static void rtx_fn(struct mg_connection *c, int ev, void *ev_data) { + if (ev == MG_EV_ACCEPT) mg_send(c, s_rtx_data, s_rtx_len); + (void) ev_data; +} + static void client_fn(struct mg_connection *c, int ev, void *ev_data) { if (ev == MG_EV_ERROR || ev == MG_EV_CONNECT) (*(int *) c->fn_data) = ev; (void) c, (void) ev_data; @@ -210,6 +219,7 @@ static void test_poll(void) { struct driver_data { char buf[DRIVER_BUF_SIZE]; size_t len; + size_t tx_count; bool tx_ready; // data can be read from tx }; @@ -219,6 +229,7 @@ static size_t if_tx(const void *buf, size_t len, struct mg_tcpip_if *ifp) { struct driver_data *driver_data = (struct driver_data *) ifp->driver_data; if (len > DRIVER_BUF_SIZE) len = DRIVER_BUF_SIZE; driver_data->len = len; + driver_data->tx_count++; memcpy(driver_data->buf, buf, len); driver_data->tx_ready = true; return len; @@ -255,7 +266,7 @@ static void create_tcp_seg(struct eth *e, struct ipp *ipp, uint32_t seq, t.ack = mg_htonl(ack); t.sport = mg_htons(sport); t.dport = mg_htons(dport); - t.win = mg_htons(TCP_TEST_WIN); + t.win = mg_htons(s_tcp_window); t.off = (uint8_t) ((sizeof(t) / 4) << 4) + (uint8_t) ((opts_len / 4) << 4); memcpy(s_driver_data.buf, e, sizeof(*e)); #if MG_ENABLE_IPV6 @@ -307,6 +318,7 @@ static void init_tests(struct mg_mgr *mgr, struct eth *e, struct ipp *ipp, mg_mgr_init(mgr); memset(mif, 0, sizeof(*mif)); memset(&s_driver_data, 0, sizeof(struct driver_data)); + s_tcp_window = TCP_TEST_WIN; driver->init = NULL, driver->tx = if_tx, driver->poll = if_poll, driver->rx = if_rx; mif->driver = driver; @@ -962,6 +974,127 @@ static void test_tcp_retransmit(void) { mg_mgr_free(&mgr); } +static void test_tcp_retransmit_queue(void) { + struct mg_mgr mgr; + struct eth e; + struct ip ip; + struct ipp ipp; + struct tcp *t = (struct tcp *) (s_driver_data.buf + sizeof(e) + sizeof(ip)); + struct connstate *s; + size_t i, first, off, len = 0, retired; + uint8_t *data; + struct mg_tcpip_driver driver; + struct mg_tcpip_if mif; + + ipp.ip4 = &ip; + ipp.ip6 = NULL; + for (i = 0; i < sizeof(s_rtx_data); i++) s_rtx_data[i] = (char) i; + s_rtx_len = sizeof(s_rtx_data); + init_tcp_tests(&mgr, &e, &ipp, &driver, &mif, rtx_fn); + mif.enable_tcp_retransmit = true; + s_tcp_window = MG_TCPIP_WIN; + init_tcp_handshake(&e, &ipp, &mgr); + s = (struct connstate *) (mgr.conns + 1); + + // Fill the retransmission queue and verify its hard cap + while (s->seq - s->txq_una < s_rtx_len) { + while (!received_response(&s_driver_data)) mg_mgr_poll(&mgr, 0); + } + ASSERT(s->txq_seq == 2); + ASSERT(s->txq_una == 2); + ASSERT(s->seq - s->txq_una == s_rtx_len); + first = txq_next(s, &data); + ASSERT(first == s->dmss); + ASSERT(memcmp(data, s_rtx_data, first) == 0); + ASSERT(mg_io_send(mgr.conns, s_rtx_data, 1) == MG_IO_WAIT); + + // Timeout resends the first retained segment only + s_driver_data.len = 0; + s_driver_data.tx_ready = false; + s_driver_data.tx_count = 0; + s->txq_timer = 0; + mg_mgr_poll(&mgr, 0); + ASSERT(s_driver_data.tx_count == 1); + ASSERT(t->flags == (TH_PUSH | TH_ACK)); + ASSERT(t->seq == mg_htonl(2)); + ASSERT(s->txq_retries == 1); + ASSERT(txq_next(s, &data) == first); + ASSERT(memcmp(data, s_rtx_data, first) == 0); + + // A piggybacked partial ACK advances SND.UNA but retains the segment + retired = first / 2; + create_tcp_simpleseg(&e, &ipp, 1001, 2 + (uint32_t) retired, + TH_PUSH | TH_ACK, 2); + mg_mgr_poll(&mgr, 0); + ASSERT(s->txq_seq == 2); + ASSERT(s->txq_una == 2 + retired); + ASSERT(s->seq - s->txq_una == s_rtx_len - retired); + ASSERT(s->txq_retries == 0); + ASSERT(txq_next(s, &data) == first); + ASSERT(memcmp(data, s_rtx_data, first) == 0); + ASSERT(mgr.conns->recv.len == 2); + + // Timeout after partial ACK retains the segment's original SEQ and data + s_driver_data.tx_count = 0; + s->txq_timer = 0; + mg_mgr_poll(&mgr, 0); + ASSERT(s_driver_data.tx_count == 1); + ASSERT(t->seq == mg_htonl(2)); + ASSERT(s->txq_retries == 1); + ASSERT(txq_next(s, &data) == first); + ASSERT(memcmp(data, s_rtx_data, first) == 0); + + // ACKing record one and part of two retires only record one + create_tcp_simpleseg(&e, &ipp, 1003, 2 + (uint32_t) (first + retired), + TH_ACK, 0); + mg_mgr_poll(&mgr, 0); + ASSERT(s->txq_seq == 2 + first); + ASSERT(s->txq_una == 2 + first + retired); + ASSERT(s->txq_retries == 0); + ASSERT(txq_next(s, &data) == first); + ASSERT(memcmp(data, s_rtx_data + first, first) == 0); + + // Timeout retransmits record two with its original SEQ and data + s_driver_data.tx_count = 0; + s->txq_timer = 0; + mg_mgr_poll(&mgr, 0); + ASSERT(s_driver_data.tx_count == 1); + ASSERT(t->seq == mg_htonl(2 + (uint32_t) first)); + ASSERT(s->txq_retries == 1); + ASSERT(txq_next(s, &data) == first); + ASSERT(memcmp(data, s_rtx_data + first, first) == 0); + + // A complete ACK retires record two; appending restores the full flight + create_tcp_simpleseg(&e, &ipp, 1003, 2 + (uint32_t) (2 * first), TH_ACK, 0); + mg_mgr_poll(&mgr, 0); + ASSERT(s->txq_seq == 2 + 2 * first); + ASSERT(s->txq_una == 2 + 2 * first); + ASSERT(txq_next(s, &data) == first); + ASSERT(memcmp(data, s_rtx_data + 2 * first, first) == 0); + mg_send(mgr.conns, s_rtx_data, 2 * first); + while (s->seq - s->txq_una < s_rtx_len) { + while (!received_response(&s_driver_data)) mg_mgr_poll(&mgr, 0); + } + for (off = 0; off < s->txq.len; off += sizeof(uint32_t) + len) { + memcpy(&len, s->txq.buf + off, sizeof(uint32_t)); + } + ASSERT(off == s->txq.len); + ASSERT(len == first); + ASSERT(memcmp(s->txq.buf + off - len, s_rtx_data + first, len) == 0); + + // Draining waits for the rebuilt flight to be acknowledged before FIN + mgr.conns->is_draining = 1; + s_driver_data.len = 0; + mg_mgr_poll(&mgr, 0); + ASSERT(!received_response(&s_driver_data)); + create_tcp_simpleseg(&e, &ipp, 1003, s->seq, TH_ACK, 0); + while (!received_response(&s_driver_data)) mg_mgr_poll(&mgr, 0); + ASSERT(t->flags == (TH_FIN | TH_ACK)); + ASSERT(s->txq.len == 0); + s_driver_data.len = 0; + mg_mgr_free(&mgr); +} + static void test_tcp_txwindow(void) { struct mg_mgr mgr; @@ -987,6 +1120,7 @@ static void test_tcp_txwindow(void) { seq = (uint32_t)(mg_htonl(t->seq) + s_driver_data.len - (size_t)((char *)((uint32_t *)t + (t->off >> 4)) - s_driver_data.buf)); } while (seq < (TCP_TEST_WIN + 2)); stallcount = count; + ASSERT(((struct connstate *) (mgr.conns + 1))->txq.buf == NULL); mg_mgr_poll(&mgr, 0), s_driver_data.len = 0; mg_mgr_poll(&mgr, 0), s_driver_data.len = 0; ASSERT((stallcount == count)); @@ -1248,6 +1382,7 @@ static void test_tcp(bool ipv6) { if (!ipv6) { test_tcp_backlog(); test_tcp_retransmit(); + test_tcp_retransmit_queue(); test_tcp_txwindow(); test_tcp_ackseq(); test_tcp_drain(false); diff --git a/test/mip_x_test.c b/test/mip_x_test.c index 7f4849fd..55eb2002 100644 --- a/test/mip_x_test.c +++ b/test/mip_x_test.c @@ -75,6 +75,8 @@ static char *host_ip, *host_ip6; static int s_num_tests = 0; static bool s_error = false; +#define POLL_LIMIT(n) (mgr->ifp->enable_tcp_retransmit ? (n) * 10 : (n)) + #ifdef NO_ABORT static int s_abort = 0; #define ABORT() ++s_abort, s_error = true @@ -205,7 +207,7 @@ static int fetch(struct mg_mgr *mgr, char *buf, const char *url, buf[0] = '\0'; // - TLS: multiple (small) records: allow enough loops so mg_mgr_poll can // process buffered records when no more frames are coming in - for (i = 0; i < 500 && buf[0] == '\0' && !fd.closed; i++) { + for (i = 0; i < POLL_LIMIT(500) && buf[0] == '\0' && !fd.closed; i++) { mg_mgr_poll(mgr, 0); usleep(5000); // 5 ms. Slow down poll loop to ensure packet transit, but // allow enough loops to get the ARP response, otherwise, @@ -360,7 +362,9 @@ static void test_mqtt_connsubpub(struct mg_mgr *mgr) { mg_mgr_poll(mgr, 0); s_conn = mg_mqtt_connect(mgr, data.url, &opts, mqtt_fn, &data); ASSERT(s_conn != NULL); - for (int i = 0; i < 1000 && s_conn != NULL && !s_conn->is_closing; i++) { + for (int i = 0; + i < POLL_LIMIT(1000) && s_conn != NULL && !s_conn->is_closing; + i++) { mg_mgr_poll(mgr, 0); usleep(5000); // 5 ms (*) See fetch() above for reasons } @@ -406,7 +410,9 @@ static void test_mqtt_connsubpub(struct mg_mgr *mgr) { mg_mgr_poll(mgr, 0); s_conn = mg_mqtt_connect(mgr, data.url, &opts, mqtt_fn, &data); ASSERT(s_conn != NULL); - for (int i = 0; i < 1000 && s_conn != NULL && !s_conn->is_closing; i++) { + for (int i = 0; + i < POLL_LIMIT(1000) && s_conn != NULL && !s_conn->is_closing; + i++) { mg_mgr_poll(mgr, 0); usleep(5000); // 5 ms (*) See fetch() above for reasons } @@ -418,10 +424,12 @@ static void test_mqtt_connsubpub(struct mg_mgr *mgr) { #ifndef NO_HTTPSERVER_TEST #include +static volatile bool s_poll_done; + static void *poll_thread(void *p) { struct mg_mgr *mgr = (struct mg_mgr *) p; int i; - for (i = 0; i < 300; i++) { + for (i = 0; i < POLL_LIMIT(300) && !s_poll_done; i++) { mg_mgr_poll(mgr, 0); usleep(10000); // 10 ms. Slow down poll loop to ensure packet transit } @@ -436,6 +444,7 @@ static void test_http_server(struct mg_mgr *mgr) { struct mg_connection *c; char *cmd; pthread_t thread_id = (pthread_t) 0; + int rc; #if MG_TLS struct mg_tls_opts opts; memset(&opts, 0, sizeof(opts)); @@ -455,12 +464,15 @@ static void test_http_server(struct mg_mgr *mgr) { #endif ASSERT(c != NULL); ASSERT (mg_send(c, "NADA", 0)); // check mg_send allows len=0 + s_poll_done = false; pthread_create(&thread_id, NULL, poll_thread, mgr); // simpler this way, no concurrency anyway MG_DEBUG(("CURL")); - ASSERT(system(cmd) == 0); // wait for curl + rc = system(cmd); // wait for curl MG_DEBUG(("MONGOOSE")); + s_poll_done = true; pthread_join(thread_id, NULL); // wait for Mongoose + ASSERT(rc == 0); MG_DEBUG(("DONE")); free(cmd); #endif @@ -529,7 +541,7 @@ static bool sntpms(struct mg_mgr *mgr, const char *url) { int64_t ms = 0; int i; mg_sntp_connect(mgr, url, sntpcb, &ms); - for (i = 0; i < 50 && ms == 0; i++) { + for (i = 0; i < POLL_LIMIT(50) && ms == 0; i++) { mg_mgr_poll(mgr, 0); usleep(10000); // 10 ms. Slow down poll loop to ensure packet transit } From ffcad7ee47d49ac61e450008ab6e6b61163ec0d3 Mon Sep 17 00:00:00 2001 From: "Sergio R. Caprile" Date: Tue, 8 Sep 2026 12:04:25 -0300 Subject: [PATCH 2/3] fix too small MSSs, enable auto config --- mongoose.c | 21 +++++++++++++++++---- mongoose.h | 12 ++++++++---- src/config.h | 12 ++++++++---- src/net_builtin.c | 21 +++++++++++++++++---- 4 files changed, 50 insertions(+), 16 deletions(-) diff --git a/mongoose.c b/mongoose.c index 30213817..db675f79 100644 --- a/mongoose.c +++ b/mongoose.c @@ -8280,6 +8280,14 @@ void mg_multicast_restore(struct mg_connection *c, uint8_t *from) { #define MG_TCPIP_WIN 6000 // TCP window size #endif +#ifndef MG_TCPIP_MIN_MSS +#define MG_TCPIP_MIN_MSS 64 // Minimum peer MSS +#endif + +#ifndef MG_TCPIP_TXQ_ALIGN +#define MG_TCPIP_TXQ_ALIGN 1024 // Retransmit queue allocation quantum +#endif + struct connstate { uint32_t seq, ack; // TCP seq/ack counters uint64_t timer; // TCP timer (see 'ttype' below) @@ -9581,7 +9589,7 @@ static struct mg_connection *accept_conn(struct mg_connection *lsn, } s = (struct connstate *) (c + 1); s->retransmit = lsn->mgr->ifp->enable_tcp_retransmit; - s->txq.align = MG_IO_SIZE; + s->txq.align = MG_TCPIP_TXQ_ALIGN; s->dmss = mss; // from options in client SYN s->seq = mg_ntohl(pkt->tcp->ack), s->ack = mg_ntohl(pkt->tcp->seq); s->win = mg_ntohs(pkt->tcp->win), s->maxseq = (uint32_t) (s->seq + s->win); @@ -9683,6 +9691,7 @@ static size_t txq_next(struct connstate *s, uint8_t **buf) { } static bool txq_add(struct connstate *s, const void *buf, size_t len) { + assert(len > 0); uint32_t n = (uint32_t) len; size_t off = s->txq.len, total = sizeof(n) + len; if (mg_iobuf_add(&s->txq, off, NULL, total) != total) return false; @@ -9960,8 +9969,11 @@ static bool handle_opt(struct connstate *s, struct tcp *tcp, bool ip6) { if (kind == 0) break; // End of Option List if (len < 2 || opts[1] == 0 || opts[1] > len) return false; // Malformed optlen = opts[1]; - if (kind == 2 && optlen == 4) // set received MSS - s->dmss = (uint16_t) (((uint16_t) opts[2] << 8) + opts[3]); + if (kind == 2 && optlen == 4) { // set received MSS + uint16_t mss = (uint16_t) (((uint16_t) opts[2] << 8) + opts[3]); + if (mss == 0 || mss < MG_TCPIP_MIN_MSS) return false; + s->dmss = mss; + } } MG_VERBOSE(("kind: %u, optlen: %u, len: %d\n", kind, optlen, len)); opts += optlen; @@ -10464,6 +10476,7 @@ void mg_tcpip_init(struct mg_mgr *mgr, struct mg_tcpip_if *ifp) { // If L2 address is not set, make a random one; fill MTU mg_l2_init(ifp); ifp->mtu = ifp->l2mtu; + if (MG_ENABLE_TCPIP_TCPRTX) ifp->enable_tcp_retransmit = true; if (ifp->dhcp_name[0] == '\0') // If DHCP name is not set, use "mip" memcpy(ifp->dhcp_name, "mip", 4); @@ -10519,7 +10532,7 @@ void mg_connect_resolved(struct mg_connection *c) { uint8_t *l2addr; struct connstate *s = (struct connstate *) (c + 1); s->retransmit = ifp->enable_tcp_retransmit; - s->txq.align = MG_IO_SIZE; + s->txq.align = MG_TCPIP_TXQ_ALIGN; c->is_resolving = 0; if (ifp->eport < MG_EPHEMERAL_PORT_BASE) ifp->eport = MG_EPHEMERAL_PORT_BASE; c->loc.port = mg_htons(ifp->eport++); diff --git a/mongoose.h b/mongoose.h index 995617ab..8b844644 100644 --- a/mongoose.h +++ b/mongoose.h @@ -1456,6 +1456,14 @@ struct timeval { #define MG_ENABLE_TCPIP_DRIVER_INIT 1 // enabled built-in driver for #endif // Mongoose built-in network stack +#ifndef MG_ENABLE_TCPIP_TCPRTX +#define MG_ENABLE_TCPIP_TCPRTX 0 // TCP retransmission queue +#endif + +#ifndef MG_TCPIP_DHCPNAME_SIZE +#define MG_TCPIP_DHCPNAME_SIZE 18 // struct mg_tcpip_if :: dhcp_name size +#endif + #ifndef MG_TCPIP_IP // e.g. MG_IPV4(192, 168, 0, 223) #define MG_TCPIP_IP MG_IPV4(0, 0, 0, 0) // Default is 0.0.0.0 (DHCP) #endif @@ -1492,10 +1500,6 @@ struct timeval { #define MG_SET_MAC_ADDRESS(mac) #endif -#ifndef MG_TCPIP_DHCPNAME_SIZE -#define MG_TCPIP_DHCPNAME_SIZE 18 // struct mg_tcpip_if :: dhcp_name size -#endif - #ifndef MG_SET_WIFI_CONFIG #define MG_SET_WIFI_CONFIG(ifp, driver_data) #endif diff --git a/src/config.h b/src/config.h index 49250e9c..3681f524 100644 --- a/src/config.h +++ b/src/config.h @@ -175,6 +175,14 @@ #define MG_ENABLE_TCPIP_DRIVER_INIT 1 // enabled built-in driver for #endif // Mongoose built-in network stack +#ifndef MG_ENABLE_TCPIP_TCPRTX +#define MG_ENABLE_TCPIP_TCPRTX 0 // TCP retransmission queue +#endif + +#ifndef MG_TCPIP_DHCPNAME_SIZE +#define MG_TCPIP_DHCPNAME_SIZE 18 // struct mg_tcpip_if :: dhcp_name size +#endif + #ifndef MG_TCPIP_IP // e.g. MG_IPV4(192, 168, 0, 223) #define MG_TCPIP_IP MG_IPV4(0, 0, 0, 0) // Default is 0.0.0.0 (DHCP) #endif @@ -211,10 +219,6 @@ #define MG_SET_MAC_ADDRESS(mac) #endif -#ifndef MG_TCPIP_DHCPNAME_SIZE -#define MG_TCPIP_DHCPNAME_SIZE 18 // struct mg_tcpip_if :: dhcp_name size -#endif - #ifndef MG_SET_WIFI_CONFIG #define MG_SET_WIFI_CONFIG(ifp, driver_data) #endif diff --git a/src/net_builtin.c b/src/net_builtin.c index f37e4e09..beb29a38 100644 --- a/src/net_builtin.c +++ b/src/net_builtin.c @@ -22,6 +22,14 @@ #define MG_TCPIP_WIN 6000 // TCP window size #endif +#ifndef MG_TCPIP_MIN_MSS +#define MG_TCPIP_MIN_MSS 64 // Minimum peer MSS +#endif + +#ifndef MG_TCPIP_TXQ_ALIGN +#define MG_TCPIP_TXQ_ALIGN 1024 // Retransmit queue allocation quantum +#endif + struct connstate { uint32_t seq, ack; // TCP seq/ack counters uint64_t timer; // TCP timer (see 'ttype' below) @@ -1323,7 +1331,7 @@ static struct mg_connection *accept_conn(struct mg_connection *lsn, } s = (struct connstate *) (c + 1); s->retransmit = lsn->mgr->ifp->enable_tcp_retransmit; - s->txq.align = MG_IO_SIZE; + s->txq.align = MG_TCPIP_TXQ_ALIGN; s->dmss = mss; // from options in client SYN s->seq = mg_ntohl(pkt->tcp->ack), s->ack = mg_ntohl(pkt->tcp->seq); s->win = mg_ntohs(pkt->tcp->win), s->maxseq = (uint32_t) (s->seq + s->win); @@ -1425,6 +1433,7 @@ static size_t txq_next(struct connstate *s, uint8_t **buf) { } static bool txq_add(struct connstate *s, const void *buf, size_t len) { + assert(len > 0); uint32_t n = (uint32_t) len; size_t off = s->txq.len, total = sizeof(n) + len; if (mg_iobuf_add(&s->txq, off, NULL, total) != total) return false; @@ -1702,8 +1711,11 @@ static bool handle_opt(struct connstate *s, struct tcp *tcp, bool ip6) { if (kind == 0) break; // End of Option List if (len < 2 || opts[1] == 0 || opts[1] > len) return false; // Malformed optlen = opts[1]; - if (kind == 2 && optlen == 4) // set received MSS - s->dmss = (uint16_t) (((uint16_t) opts[2] << 8) + opts[3]); + if (kind == 2 && optlen == 4) { // set received MSS + uint16_t mss = (uint16_t) (((uint16_t) opts[2] << 8) + opts[3]); + if (mss == 0 || mss < MG_TCPIP_MIN_MSS) return false; + s->dmss = mss; + } } MG_VERBOSE(("kind: %u, optlen: %u, len: %d\n", kind, optlen, len)); opts += optlen; @@ -2206,6 +2218,7 @@ void mg_tcpip_init(struct mg_mgr *mgr, struct mg_tcpip_if *ifp) { // If L2 address is not set, make a random one; fill MTU mg_l2_init(ifp); ifp->mtu = ifp->l2mtu; + if (MG_ENABLE_TCPIP_TCPRTX) ifp->enable_tcp_retransmit = true; if (ifp->dhcp_name[0] == '\0') // If DHCP name is not set, use "mip" memcpy(ifp->dhcp_name, "mip", 4); @@ -2261,7 +2274,7 @@ void mg_connect_resolved(struct mg_connection *c) { uint8_t *l2addr; struct connstate *s = (struct connstate *) (c + 1); s->retransmit = ifp->enable_tcp_retransmit; - s->txq.align = MG_IO_SIZE; + s->txq.align = MG_TCPIP_TXQ_ALIGN; c->is_resolving = 0; if (ifp->eport < MG_EPHEMERAL_PORT_BASE) ifp->eport = MG_EPHEMERAL_PORT_BASE; c->loc.port = mg_htons(ifp->eport++); From 4660f2ca0f20193dc36184241858083585b4c743 Mon Sep 17 00:00:00 2001 From: "Sergio R. Caprile" Date: Tue, 8 Sep 2026 12:22:31 -0300 Subject: [PATCH 3/3] add nightly TLS test --- .github/workflows/nightly.yml | 7 ++++++- test/mip_x_test.c | 1 + 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 2f661d00..e038fec9 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -119,10 +119,15 @@ jobs: miprtx: runs-on: ubuntu-latest - name: MIP RTX TAP + strategy: + fail-fast: false + matrix: + ssl: ["", BUILTIN] + name: MIP RTX TAP SSL=${{ matrix.ssl }} env: IPV6: 0 RTX: 1 + SSL: ${{ matrix.ssl }} TFLAGS: -DMQTT_HOST -DIPV6_NOROUTING -DNO_ABORT steps: - uses: actions/checkout@v5 diff --git a/test/mip_x_test.c b/test/mip_x_test.c index 55eb2002..f8268209 100644 --- a/test/mip_x_test.c +++ b/test/mip_x_test.c @@ -306,6 +306,7 @@ static void mqtt_fn(struct mg_connection *c, int ev, void *ev_data) { // close on farewell MG_INFO(("%lu CLOSING", c->id)); mg_mqtt_disconnect(c, NULL); + c->is_draining = true; data->passed = true; } else if (mm->data.len == 21098) { struct mg_mqtt_opts pub_opts;