Merge pull request #3682 from cesanta/tcprtx

add optional TCP retransmission
This commit is contained in:
Sergio R. Caprile
2026-09-08 12:33:39 -03:00
committed by GitHub
10 changed files with 516 additions and 53 deletions
+35
View File
@@ -116,6 +116,26 @@ 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
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
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 +167,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:
+127 -19
View File
@@ -8271,17 +8271,32 @@ 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
#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)
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 +8307,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 +9588,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_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);
@@ -9660,6 +9680,26 @@ 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) {
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;
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 +9709,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 +9764,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 +9863,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;
}
@@ -9873,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;
@@ -10322,6 +10421,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");
@@ -10370,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);
@@ -10423,6 +10530,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_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++);
@@ -10444,7 +10554,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 +10568,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 +10584,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 +10624,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 +10659,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
+9 -4
View File
@@ -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
@@ -5100,6 +5104,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()
+8 -4
View File
@@ -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
+127 -19
View File
@@ -13,17 +13,32 @@
#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
#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)
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 +49,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 +1330,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_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);
@@ -1402,6 +1422,26 @@ 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) {
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;
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 +1451,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 +1506,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 +1605,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;
}
@@ -1615,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;
@@ -2064,6 +2163,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");
@@ -2112,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);
@@ -2165,6 +2272,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_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++);
@@ -2186,7 +2296,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 +2310,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 +2326,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 +2366,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 +2401,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
+1
View File
@@ -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()
+27
View File
@@ -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;
+27
View File
@@ -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;
+136 -1
View File
@@ -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);
+19 -6
View File
@@ -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,
@@ -304,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;
@@ -360,7 +363,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 +411,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 +425,12 @@ static void test_mqtt_connsubpub(struct mg_mgr *mgr) {
#ifndef NO_HTTPSERVER_TEST
#include <pthread.h>
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 +445,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 +465,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 +542,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
}