incubator-nuttx/net/tcp/tcp_cc.c

302 lines
9.0 KiB
C

/****************************************************************************
* net/tcp/tcp_cc.c
* Handling TCP congestion control
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. The
* ASF licenses this file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
****************************************************************************/
/****************************************************************************
* Included Files
****************************************************************************/
#include <debug.h>
#include "tcp/tcp.h"
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
#define TCP_IPV4_DEFAULT_MSS 536
/* Initial Window threshold constants */
#define IW_MAX 4380 /* Initial Window maximum */
#define IW_MAX_HALF 2190
#define IW_MAX_QUATER 1095
/* Calculate the Initial Window, also used as Restart Window
* RFC5681 Section 3.1 specifies the default conservative values.
*/
#define CC_INIT_CWND(cwnd, mss) \
do { \
if ((mss) > IW_MAX_HALF) \
{ \
(cwnd) = 2 * (mss); \
} \
else if ((mss) > IW_MAX_QUATER) \
{ \
(cwnd) = 3 * (mss); \
} \
else \
{ \
(cwnd) = 4 * (mss); \
} \
} while(0)
/* Increments a size inc and holds at max value rather than rollover. */
#define CC_CWND_INC(wnd, inc) \
do { \
if ((uint32_t)((wnd) + (inc)) >= (wnd)) \
{ \
(wnd) = (uint32_t)((wnd) + (inc)); \
} \
else \
{ \
(wnd) = (uint32_t)-1; \
} \
} while(0)
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: tcp_cc_init
*
* Description:
* Initialize the congestion control variables, cwnd, ssthresh and dupacks.
* The function is called on starting a new connection.
*
* Input Parameters:
* conn - The TCP connection of interest
*
* Returned Value:
* None
*
* Assumptions:
* The normal user level code is calling the connect/accept to start a new
* connection.
*
****************************************************************************/
void tcp_cc_init(FAR struct tcp_conn_s *conn)
{
CC_INIT_CWND(conn->cwnd, conn->mss);
/* RFC 5681 recommends setting ssthresh arbitrarily high and
* gives an example of using the largest advertised receive window.
* We've seen complications with receiving TCPs that use window
* scaling and/or window auto-tuning where the initial advertised
* window is very small and then grows rapidly once the connection
* is established. To avoid these complications, we set ssthresh to
* the largest effective cwnd (amount of in-flight data) that the
* sender can have.
*/
conn->ssthresh = 2 * TCP_IPV4_DEFAULT_MSS;
conn->dupacks = 0;
}
/****************************************************************************
* Name: tcp_cc_update
*
* Description:
* Update the congestion control variables when recieve the SYNACK/ACK
* packet from the peer in the connection phase.
*
* Input Parameters:
* conn - The TCP connection of interest
* tcp - The TCP header.
*
* Returned Value:
* None
*
* Assumptions:
* The network is locked.
*
****************************************************************************/
void tcp_cc_update(FAR struct tcp_conn_s *conn, FAR struct tcp_hdr_s *tcp)
{
/* After Fast retransmitted, set ssthresh to the maximum of
* the unacked and the 2*SMSS, and enter to Fast Recovery.
* ssthresh = max (FlightSize / 2, 2*SMSS) referring to rfc5681
* cwnd=ssthresh + 3*SMSS referring to rfc5681
*/
if (conn->flags & TCP_INFT)
{
conn->ssthresh = MAX(conn->tx_unacked / 2, 2 * conn->mss);
conn->cwnd = conn->ssthresh + 3 * conn->mss;
conn->flags &= ~TCP_INFT;
conn->flags |= TCP_INFR;
}
/* Update the cc parameters in the TCP_SYN_RCVD and TCP_SYN_SENT states
* when the tcp connection is established.
*/
else
{
conn->last_ackno = tcp_getsequence(tcp->ackno);
CC_INIT_CWND(conn->cwnd, conn->mss);
conn->max_cwnd = conn->snd_wnd;
conn->ssthresh = MAX(conn->snd_wnd, conn->ssthresh);
}
}
/****************************************************************************
* Name: tcp_cc_recv_ack
*
* Description:
* Update congestion control variables
*
* Input Parameters:
* conn - The TCP connection of interest
* tcp - The TCP header.
*
* Returned Value:
* None
*
* Assumptions:
* The network is locked.
*
****************************************************************************/
void tcp_cc_recv_ack(FAR struct tcp_conn_s *conn, FAR struct tcp_hdr_s *tcp)
{
uint32_t ackno = tcp_getsequence(tcp->ackno);
/* Its only a duplicate ack if:
* 1) It doesn't ACK new data
* 2) There is outstanding unacknowledged data (retransmission
* timer running)
* 3) The ACK is == biggest ACK sequence number so far (last_ackno)
*
* If it passes all conditions, should process as a dupack:
* a) dupacks < 3: do nothing
* b) dupacks == 3: fast retransmit
* c) dupacks > 3: increase cwnd
*
* If ackno is between last_ackno and snd_seq, should reset dupack counter.
*/
/* Clause 1 */
if (TCP_SEQ_LTE(ackno, conn->last_ackno))
{
/* Clause 2 and Clause 3 */
if (conn->timer >= 0 &&
conn->last_ackno == ackno)
{
if (++conn->dupacks > TCP_FAST_RETRANSMISSION_THRESH)
{
/* Inflate the congestion window */
CC_CWND_INC(conn->cwnd, conn->mss);
}
if (conn->dupacks >= TCP_FAST_RETRANSMISSION_THRESH)
{
/* Do fast retransmit, but it is delayed in
* psock_send_eventhandler. Set the TCP_INFT flag.
*/
conn->flags |= TCP_INFT;
conn->fr_recover = tcp_getsequence(conn->sndseq);
}
}
}
else if (TCP_SEQ_GT(ackno, conn->last_ackno) &&
TCP_SEQ_LTE(ackno, tcp_getsequence(conn->sndseq)))
{
/* We come here when the ACK acknowledges new data. */
uint32_t acked = TCP_SEQ_SUB(ackno, conn->last_ackno);
/* Reset dupacks and update last_ackno. */
conn->dupacks = 0;
conn->last_ackno = ackno;
/* When the ackno covers more than the fr_recover, exit the
* fast recovery. Then, reset the "IN Fast Recovery" flags.
* Also reset the congestion window to the slow start threshold.
* If not, cwnd should be increased by mss. RFC6582.
*/
if (conn->flags & TCP_INFR)
{
if (ackno - 1 > conn->fr_recover)
{
/* Reset the fast retransmit variables. */
conn->flags &= ~TCP_INFR;
conn->cwnd = conn->ssthresh;
}
else
{
CC_CWND_INC(conn->cwnd, conn->mss);
return;
}
}
/* Update the congestion control variables (cwnd and ssthresh). */
if (conn->tcpstateflags >= TCP_ESTABLISHED)
{
uint32_t increase;
if (conn->cwnd < conn->ssthresh)
{
/* slow start (RFC 5681):
* Grow cwnd exponentially by maxseg(smss) per ACK.
*/
increase = acked > 0 ? MIN(acked, conn->mss) : conn->mss;
CC_CWND_INC(conn->cwnd, increase);
ninfo("update slow start cwnd to %u\n", conn->cwnd);
}
else
{
/* cong avoid (RFC 5681):
* Grow cwnd linearly by approximately maxseg per RTT using
* maxseg^2 / cwnd per ACK as the increment.
* If cwnd > maxseg^2, fix the cwnd increment at 1 byte to
* avoid capping cwnd.
*/
increase = MAX((conn->mss * conn->mss / conn->cwnd), 1);
CC_CWND_INC(conn->cwnd, increase);
conn->cwnd = MIN(conn->cwnd, conn->max_cwnd);
ninfo("update congestion avoidance cwnd to %u\n", conn->cwnd);
}
}
}
}