tcp_input: snd_wnd processing

* Do not accept the window in old segments.
  Implement SND.WL1/WL2 things in the RFC.

* Do not accept the window in the segment w/o ACK bit set.
  The window is an offset from the ack seq.
  (maybe it's simpler to just drop segments w/o ACK though)

* Subtract snd_wnd by the amount of the ack advancement.
This commit is contained in:
YAMAMOTO Takashi 2021-07-16 19:47:08 +09:00 committed by archer
parent af57d04433
commit 1b82f1c749
2 changed files with 86 additions and 9 deletions

View File

@ -202,6 +202,8 @@ struct tcp_conn_s
uint16_t snd_wnd; /* Sequence and acknowledgement numbers of last
* window update */
#endif
uint32_t snd_wl1;
uint32_t snd_wl2;
#if CONFIG_NET_RECV_BUFSIZE > 0
int32_t rcv_bufs; /* Maximum amount of bytes queued in recv */
#endif

View File

@ -190,6 +190,77 @@ static bool tcp_trim_head(FAR struct net_driver_s *dev,
return false;
}
static void tcp_snd_wnd_init(FAR struct tcp_conn_s *conn,
FAR struct tcp_hdr_s *tcp)
{
/* Just ensure that the next tcp_update_snd_wnd will be accepted. */
DEBUGASSERT((tcp->flags & TCP_ACK) != 0);
conn->snd_wl1 = TCP_SEQ_SUB(tcp_getsequence(tcp->seqno), 1);
conn->snd_wl2 = tcp_getsequence(tcp->ackno);
conn->snd_wnd = 0;
ninfo("snd_wnd init: wl1 %" PRIu32 "\n", conn->snd_wl1);
}
static void tcp_snd_wnd_update(FAR struct tcp_conn_s *conn,
FAR struct tcp_hdr_s *tcp)
{
uint32_t ackseq = tcp_getsequence(tcp->ackno);
uint32_t seq = tcp_getsequence(tcp->seqno);
uint16_t unscaled_wnd = ((uint16_t)tcp->wnd[0] << 8) + tcp->wnd[1];
#ifdef CONFIG_NET_TCP_WINDOW_SCALE
uint32_t wnd = (uint32_t)unscaled_wnd << conn->snd_scale;
#else
uint16_t wnd = unscaled_wnd;
#endif
uint32_t wl2 = conn->snd_wl2;
DEBUGASSERT((tcp->flags & TCP_ACK) != 0);
if (TCP_SEQ_LT(wl2, ackseq))
{
uint32_t nacked = TCP_SEQ_SUB(ackseq, wl2);
ninfo("snd_wnd acked: "
"wl2 %" PRIu32 " -> %" PRIu32 " subtracting wnd %" PRIu32
" by %" PRIu32 "\n",
wl2,
ackseq,
(uint32_t)conn->snd_wnd,
nacked);
if (nacked > conn->snd_wnd)
{
conn->snd_wnd = 0;
}
else
{
conn->snd_wnd -= nacked;
}
conn->snd_wl2 = ackseq;
}
if (TCP_SEQ_LT(conn->snd_wl1, seq) ||
(conn->snd_wl1 == seq && TCP_SEQ_LT(wl2, ackseq)) ||
(wl2 == ackseq && conn->snd_wnd < wnd))
{
ninfo("snd_wnd update: "
"wl1 %" PRIu32 " wl2 %" PRIu32 " wnd %" PRIu32 " -> "
"wl1 %" PRIu32 " wl2 %" PRIu32 " wnd %" PRIu32 "\n",
conn->snd_wl1,
wl2,
(uint32_t)conn->snd_wnd,
seq,
ackseq,
(uint32_t)wnd);
conn->snd_wl1 = seq;
conn->snd_wl2 = ackseq;
conn->snd_wnd = wnd;
}
}
/****************************************************************************
* Name: tcp_input
*
@ -447,15 +518,6 @@ reset:
found:
/* Update the connection's window size */
#ifdef CONFIG_NET_TCP_WINDOW_SCALE
conn->snd_wnd = (((uint32_t)tcp->wnd[0] << 8) + (uint32_t)tcp->wnd[1]) <<
conn->snd_scale;
#else
conn->snd_wnd = (((uint16_t)tcp->wnd[0] << 8) + (uint16_t)tcp->wnd[1]);
#endif
flags = 0;
/* We do a very naive form of TCP reset processing; we just accept
@ -680,6 +742,14 @@ found:
conn->timer = conn->rto;
}
/* Update the connection's window size */
if ((tcp->flags & TCP_ACK) != 0 &&
(conn->tcpstateflags & TCP_STATE_MASK) != TCP_SYN_RCVD)
{
tcp_snd_wnd_update(conn, tcp);
}
/* Do different things depending on in what state the connection is. */
switch (conn->tcpstateflags & TCP_STATE_MASK)
@ -736,6 +806,9 @@ found:
conn->sndseq_max = 0;
#endif
conn->tx_unacked = 0;
tcp_snd_wnd_init(conn, tcp);
tcp_snd_wnd_update(conn, tcp);
flags = TCP_CONNECTED;
ninfo("TCP state: TCP_ESTABLISHED\n");
@ -834,6 +907,8 @@ found:
conn->tcpstateflags = TCP_ESTABLISHED;
memcpy(conn->rcvseq, tcp->seqno, 4);
conn->rcv_adv = tcp_getsequence(conn->rcvseq);
tcp_snd_wnd_init(conn, tcp);
tcp_snd_wnd_update(conn, tcp);
net_incr32(conn->rcvseq, 1); /* ack SYN */
conn->tx_unacked = 0;