net: tcp: Avoid SWS for the receiver
authorBarret Rhoden <brho@cs.berkeley.edu>
Mon, 12 Jun 2017 18:13:37 +0000 (14:13 -0400)
committerBarret Rhoden <brho@cs.berkeley.edu>
Fri, 21 Jul 2017 15:56:27 +0000 (11:56 -0400)
See RFC 813 and 1122.  Basically, don't advertise very small increases in
the window.

The driver for this is actually qemu's user networking behavior.  If you
*almost* fill Akaros's receive window (qemu guest), but it is still not
fully closed, qemu will stop sending data.  It seems to happen once the
window is less than the MSS.  Qemu waits 5 seconds, then fills the window
completely.  That sounds like a possibly buggy implementation of the
recommended sender algorithm in RFC 813 (grep 'additional.*enhancement').
It wasn't a SWS issue, but this algorithm for fixing SWS forces an ACK when
becomes rcv.wnd >= MSS.  That additional enhancement *alone* seems to imply
window updates from the receiver that are not driven by anything sent by
the sender - not sure if that's legal or not.

Using the MSS as the cutoff for a small packet was recommended by RFC 1122.
Actually, they said min(BUF / 2, MSS), and for practical situations, the
MSS is always less than BUF / 2.

The important thing for us is to treat the window as closed and to update
the window when it opens back up.  Regardless of the SWS business, if we
happened to have closed the window fully on an ACK, we would never tell the
sender, since we were turning off the acktimer.  (Note from the future: if
this statement seems wrong from something you're seeing, reverify it.  We
definitely need to keep the acktimer going for Qemu to advertise rcv.wnd >=
tcb->mss).

Note that the TCP stack doens't get a callback or anything when the window
opens up - if we wanted to, we could set up qio callbacks to update the
window when the user drains the read queue.  It's a little trickier than
that, since we care about an edge at > MSS, not > 0.  That's independent of
this change, but figured I'd mention it.  Also, the whole tcptimer setup is
a bit hokey.  The acktimers are put in for 1 'count' (50 / 50, grep
MSPTICK), and the expected value for the first tick is 25ms.  Just from
looking at tcpdumps, I'll see Akaros waiting for about 25-50ms between the
window-closing ACK and the window update.  Presumably we could have opened
the window before that.

Signed-off-by: Barret Rhoden <brho@cs.berkeley.edu>
kern/src/net/tcp.c

index 1254ec7..1655d78 100644 (file)
@@ -650,7 +650,8 @@ void tcpkick(void *x)
 }
 
 void tcprcvwin(struct conv *s)
-{      /* Call with tcb locked */
+{
+       /* Call with tcb locked */
        int w;
        Tcpctl *tcb;
 
@@ -658,8 +659,21 @@ void tcprcvwin(struct conv *s)
        w = tcb->window - qlen(s->rq);
        if (w < 0)
                w = 0;
-       tcb->rcv.wnd = w;
-       if (w == 0)
+
+       /* RFC 813: Avoid SWS.  We'll always reduce the window (because the qio
+        * increased - that's legit), and we'll always advertise the window
+        * increases (corresponding to qio drains) when those are greater than MSS.
+        * But we don't advertise increases less than MSS.
+        *
+        * Note we don't shrink the window at all - that'll result in tcptrim()
+        * dropping packets that were sent before the sender gets our update. */
+       if ((w < tcb->rcv.wnd) || (w >= tcb->mss))
+               tcb->rcv.wnd = w;
+       /* We've delayed sending an update to rcv.wnd, and we might never get
+        * another ACK to drive the TCP stack after the qio is drained.  We could
+        * replace this stuff with qio kicks or callbacks, but that might be
+        * trickier with the MSS limitation.  (and 'edge' isn't empty or not). */
+       if (w < tcb->mss)
                tcb->rcv.blocked = 1;
 }
 
@@ -3269,7 +3283,7 @@ void tcpoutput(struct conv *s)
                }
 
                /* force an ack when a window has opened up */
-               if (tcb->rcv.blocked && tcb->rcv.wnd > 0) {
+               if (tcb->rcv.blocked && tcb->rcv.wnd >= tcb->mss) {
                        tcb->rcv.blocked = 0;
                        tcb->flags |= FORCE;
                }
@@ -3297,8 +3311,11 @@ void tcpoutput(struct conv *s)
                tcb->flags &= ~FORCE;
                tcprcvwin(s);
 
-               /* By default we will generate an ack */
-               tcphalt(tpriv, &tcb->acktimer);
+               /* By default we will generate an ack, so we can normally turn off the
+                * timer.  If we're blocked, we'll want the timer so we can send a
+                * window update. */
+               if (!tcb->rcv.blocked)
+                       tcphalt(tpriv, &tcb->acktimer);
                tcb->rcv.una = 0;
                seg.source = s->lport;
                seg.dest = s->rport;