]> git.meshlink.io Git - utcp/blobdiff - utcp.c
Implement fast recovery according to RFC 5681.
[utcp] / utcp.c
diff --git a/utcp.c b/utcp.c
index f35f96560af29eaafcf67a95030884414d8e3cfd..3a6c506062d70a26171f67c653ecea62a44c168c 100644 (file)
--- a/utcp.c
+++ b/utcp.c
        } while (0)
 #endif
 
+static inline size_t min(size_t a, size_t b) {
+       return a < b ? a : b;
+}
+
 static inline size_t max(size_t a, size_t b) {
        return a > b ? a : b;
 }
@@ -113,9 +117,14 @@ static void print_packet(struct utcp *utcp, const char *dir, const void *pkt, si
 
        debug("\n");
 }
+
+static void debug_cwnd(struct utcp_connection *c) {
+       debug("snd.cwnd = %u\n", c->snd.cwnd);
+}
 #else
 #define debug(...) do {} while(0)
 #define print_packet(...) do {} while(0)
+#define debug_cwnd(...) do {} while(0)
 #endif
 
 static void set_state(struct utcp_connection *c, enum state state) {
@@ -382,9 +391,10 @@ static struct utcp_connection *allocate_connection(struct utcp *utcp, uint16_t s
 #endif
        c->snd.una = c->snd.iss;
        c->snd.nxt = c->snd.iss + 1;
-       c->rcv.wnd = utcp->mtu;
        c->snd.last = c->snd.nxt;
-       c->snd.cwnd = utcp->mtu;
+       c->snd.cwnd = (utcp->mtu > 2190 ? 2 : utcp->mtu > 1095 ? 3 : 4) * utcp->mtu;
+       c->snd.ssthresh = ~0;
+       debug_cwnd(c);
        c->utcp = utcp;
 
        // Add it to the sorted list of connections
@@ -415,13 +425,13 @@ static void update_rtt(struct utcp_connection *c, uint32_t rtt) {
        if(!utcp->srtt) {
                utcp->srtt = rtt;
                utcp->rttvar = rtt / 2;
-               utcp->rto = rtt + max(2 * rtt, CLOCK_GRANULARITY);
        } else {
                utcp->rttvar = (utcp->rttvar * 3 + absdiff(utcp->srtt, rtt)) / 4;
                utcp->srtt = (utcp->srtt * 7 + rtt) / 8;
-               utcp->rto = utcp->srtt + max(utcp->rttvar, CLOCK_GRANULARITY);
        }
 
+       utcp->rto = utcp->srtt + max(4 * utcp->rttvar, CLOCK_GRANULARITY);
+
        if(utcp->rto > MAX_RTO) {
                utcp->rto = MAX_RTO;
        }
@@ -468,7 +478,7 @@ struct utcp_connection *utcp_connect_ex(struct utcp *utcp, uint16_t dst, utcp_re
        pkt.hdr.dst = c->dst;
        pkt.hdr.seq = c->snd.iss;
        pkt.hdr.ack = 0;
-       pkt.hdr.wnd = c->rcv.wnd;
+       pkt.hdr.wnd = c->rcvbuf.maxsize;
        pkt.hdr.ctl = SYN;
        pkt.hdr.aux = 0x0101;
        pkt.init[0] = 1;
@@ -507,19 +517,22 @@ void utcp_accept(struct utcp_connection *c, utcp_recv_t recv, void *priv) {
 
 static void ack(struct utcp_connection *c, bool sendatleastone) {
        int32_t left = seqdiff(c->snd.last, c->snd.nxt);
-       int32_t cwndleft = c->snd.cwnd - seqdiff(c->snd.nxt, c->snd.una);
-       debug("cwndleft = %d\n", cwndleft);
+       int32_t cwndleft = min(c->snd.cwnd, c->snd.wnd) - seqdiff(c->snd.nxt, c->snd.una);
 
        assert(left >= 0);
 
        if(cwndleft <= 0) {
-               cwndleft = 0;
-       }
-
-       if(cwndleft < left) {
+               left = 0;
+       } else if(cwndleft < left) {
                left = cwndleft;
+
+               if(!sendatleastone || cwndleft > c->utcp->mtu) {
+                       left -= left % c->utcp->mtu;
+               }
        }
 
+       debug("cwndleft = %d, left = %d\n", cwndleft, left);
+
        if(!left && !sendatleastone) {
                return;
        }
@@ -538,7 +551,7 @@ static void ack(struct utcp_connection *c, bool sendatleastone) {
        pkt->hdr.src = c->src;
        pkt->hdr.dst = c->dst;
        pkt->hdr.ack = c->rcv.nxt;
-       pkt->hdr.wnd = c->snd.wnd;
+       pkt->hdr.wnd = c->rcvbuf.maxsize;
        pkt->hdr.ctl = ACK;
        pkt->hdr.aux = 0;
 
@@ -629,6 +642,8 @@ ssize_t utcp_send(struct utcp_connection *c, const void *data, size_t len) {
 
        if(is_reliable(c) || (c->state != SYN_SENT && c->state != SYN_RECEIVED)) {
                len = buffer_put(&c->sndbuf, data, len);
+       } else {
+               return 0;
        }
 
        if(len <= 0) {
@@ -673,6 +688,59 @@ static void swap_ports(struct hdr *hdr) {
        hdr->dst = tmp;
 }
 
+static void fast_retransmit(struct utcp_connection *c) {
+       if(c->state == CLOSED || c->snd.last == c->snd.una) {
+               debug("fast_retransmit() called but nothing to retransmit!\n");
+               return;
+       }
+
+       struct utcp *utcp = c->utcp;
+
+       struct {
+               struct hdr hdr;
+               uint8_t data[];
+       } *pkt;
+
+       pkt = malloc(sizeof(pkt->hdr) + c->utcp->mtu);
+
+       if(!pkt) {
+               return;
+       }
+
+       pkt->hdr.src = c->src;
+       pkt->hdr.dst = c->dst;
+       pkt->hdr.wnd = c->rcvbuf.maxsize;
+       pkt->hdr.aux = 0;
+
+       switch(c->state) {
+       case ESTABLISHED:
+       case FIN_WAIT_1:
+       case CLOSE_WAIT:
+       case CLOSING:
+       case LAST_ACK:
+               // Send unacked data again.
+               pkt->hdr.seq = c->snd.una;
+               pkt->hdr.ack = c->rcv.nxt;
+               pkt->hdr.ctl = ACK;
+               uint32_t len = min(seqdiff(c->snd.last, c->snd.una), utcp->mtu);
+
+               if(fin_wanted(c, c->snd.una + len)) {
+                       len--;
+                       pkt->hdr.ctl |= FIN;
+               }
+
+               buffer_copy(&c->sndbuf, pkt->data, 0, len);
+               print_packet(c->utcp, "rtrx", pkt, sizeof(pkt->hdr) + len);
+               utcp->send(utcp, pkt, sizeof(pkt->hdr) + len);
+               break;
+
+       default:
+               break;
+       }
+
+       free(pkt);
+}
+
 static void retransmit(struct utcp_connection *c) {
        if(c->state == CLOSED || c->snd.last == c->snd.una) {
                debug("Retransmit() called but nothing to retransmit!\n");
@@ -695,7 +763,7 @@ static void retransmit(struct utcp_connection *c) {
 
        pkt->hdr.src = c->src;
        pkt->hdr.dst = c->dst;
-       pkt->hdr.wnd = c->rcv.wnd;
+       pkt->hdr.wnd = c->rcvbuf.maxsize;
        pkt->hdr.aux = 0;
 
        switch(c->state) {
@@ -743,7 +811,12 @@ static void retransmit(struct utcp_connection *c) {
                }
 
                c->snd.nxt = c->snd.una + len;
-               c->snd.cwnd = utcp->mtu; // reduce cwnd on retransmit
+
+               // RFC 5681 slow start after timeout
+               c->snd.ssthresh = max(c->snd.cwnd / 2, utcp->mtu * 2); // eq. 4
+               c->snd.cwnd = utcp->mtu;
+               debug_cwnd(c);
+
                buffer_copy(&c->sndbuf, pkt->data, 0, len);
                print_packet(c->utcp, "rtrx", pkt, sizeof(pkt->hdr) + len);
                utcp->send(utcp, pkt, sizeof(pkt->hdr) + len);
@@ -1071,7 +1144,7 @@ synack:
                        pkt.hdr.dst = c->dst;
                        pkt.hdr.ack = c->rcv.irs + 1;
                        pkt.hdr.seq = c->snd.iss;
-                       pkt.hdr.wnd = c->rcv.wnd;
+                       pkt.hdr.wnd = c->rcvbuf.maxsize;
                        pkt.hdr.ctl = SYN | ACK;
 
                        if(init) {
@@ -1314,8 +1387,10 @@ synack:
 
                assert(data_acked >= 0);
 
+#ifndef NDEBUG
                int32_t bufused = seqdiff(c->snd.last, c->snd.una);
                assert(data_acked <= bufused);
+#endif
 
                if(data_acked) {
                        buffer_get(&c->sndbuf, NULL, data_acked);
@@ -1328,13 +1403,27 @@ synack:
 
                c->snd.una = hdr.ack;
 
-               c->dupack = 0;
-               c->snd.cwnd += utcp->mtu;
+               if(c->dupack) {
+                       if(c->dupack >= 3) {
+                               c->snd.cwnd = c->snd.ssthresh;
+                       }
+
+                       c->dupack = 0;
+               }
+
+               // Increase the congestion window according to RFC 5681
+               if(c->snd.cwnd < c->snd.ssthresh) {
+                       c->snd.cwnd += min(advanced, utcp->mtu); // eq. 2
+               } else {
+                       c->snd.cwnd += max(1, (utcp->mtu * utcp->mtu) / c->snd.cwnd); // eq. 3
+               }
 
                if(c->snd.cwnd > c->sndbuf.maxsize) {
                        c->snd.cwnd = c->sndbuf.maxsize;
                }
 
+               debug_cwnd(c);
+
                // Check if we have sent a FIN that is now ACKed.
                switch(c->state) {
                case FIN_WAIT_1:
@@ -1362,12 +1451,26 @@ synack:
 
                        if(c->dupack == 3) {
                                debug("Triplicate ACK\n");
-                               //TODO: Resend one packet and go to fast recovery mode. See RFC 6582.
-                               //We do a very simple variant here; reset the nxt pointer to the last acknowledged packet from the peer.
-                               //Reset the congestion window so we wait for ACKs.
-                               c->snd.nxt = c->snd.una;
-                               c->snd.cwnd = utcp->mtu;
-                               start_retransmit_timer(c);
+
+                               // RFC 5681 fast recovery
+                               c->snd.ssthresh = max(c->snd.cwnd / 2, utcp->mtu * 2); // eq. 4
+                               c->snd.cwnd = max(c->snd.ssthresh + 3 * utcp->mtu, c->sndbuf.maxsize);
+
+                               if(c->snd.cwnd > c->sndbuf.maxsize) {
+                                       c->snd.cwnd = c->sndbuf.maxsize;
+                               }
+
+                               debug_cwnd(c);
+
+                               fast_retransmit(c);
+                       } else if(c->dupack > 3) {
+                               c->snd.cwnd += utcp->mtu;
+
+                               if(c->snd.cwnd > c->sndbuf.maxsize) {
+                                       c->snd.cwnd = c->sndbuf.maxsize;
+                               }
+
+                               debug_cwnd(c);
                        }
                }
        }