mirror of
https://github.com/cesanta/mongoose.git
synced 2026-09-30 21:42:42 +07:00
Merge pull request #3682 from cesanta/tcprtx
add optional TCP retransmission
This commit is contained in:
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user