diff --git a/0072-add-tcp-cubic-congestion-control-algorithm.patch b/0072-add-tcp-cubic-congestion-control-algorithm.patch new file mode 100644 index 0000000000000000000000000000000000000000..b2118c1c6d828da883c648914836f0a9d1fffa49 --- /dev/null +++ b/0072-add-tcp-cubic-congestion-control-algorithm.patch @@ -0,0 +1,2173 @@ +From a7ba19688eeb604d1758d001b2fd7c591d703088 Mon Sep 17 00:00:00 2001 +From: whr +Date: Tue, 3 Oct 2023 10:55:37 +0800 +Subject: [PATCH] add tcp cubic congestion control algorithm + +--- + src/Filelists.cmake | 3 + + src/Filelists.mk | 3 + + src/core/dir.mk | 2 +- + src/core/tcp.c | 15 +- + src/core/tcp_ca_cubic.c | 460 +++++++++++++++++ + src/core/tcp_ca_reno.c | 73 +++ + src/core/tcp_cong.c | 51 ++ + src/core/tcp_in.c | 70 ++- + src/core/tcp_out.c | 95 +++- + src/include/lwip/priv/tcp_priv.h | 16 + + src/include/lwip/tcp.h | 34 +- + test/unit/lwipopts.h | 34 +- + test/unit/tcp/test_tcp.c | 842 ++++++++++++++++++++++++++++++- + 13 files changed, 1624 insertions(+), 74 deletions(-) + create mode 100644 src/core/tcp_ca_cubic.c + create mode 100644 src/core/tcp_ca_reno.c + create mode 100644 src/core/tcp_cong.c + +diff --git a/src/Filelists.cmake b/src/Filelists.cmake +index 21d7b49..96c0836 100644 +--- a/src/Filelists.cmake ++++ b/src/Filelists.cmake +@@ -48,6 +48,9 @@ set(lwipcore_SRCS + ${LWIP_DIR}/src/core/altcp_alloc.c + ${LWIP_DIR}/src/core/altcp_tcp.c + ${LWIP_DIR}/src/core/tcp.c ++ ${LWIP_DIR}/src/core/tcp_cong.c ++ ${LWIP_DIR}/src/core/tcp_ca_reno.c ++ ${LWIP_DIR}/src/core/tcp_ca_cubic.c + ${LWIP_DIR}/src/core/tcp_in.c + ${LWIP_DIR}/src/core/tcp_out.c + ${LWIP_DIR}/src/core/timeouts.c +diff --git a/src/Filelists.mk b/src/Filelists.mk +index 828b9f2..c1cd28b 100644 +--- a/src/Filelists.mk ++++ b/src/Filelists.mk +@@ -46,6 +46,9 @@ COREFILES=$(LWIPDIR)/core/init.c \ + $(LWIPDIR)/core/altcp_alloc.c \ + $(LWIPDIR)/core/altcp_tcp.c \ + $(LWIPDIR)/core/tcp.c \ ++ $(LWIPDIR)/core/tcp_cong.c \ ++ $(LWIPDIR)/core/tcp_ca_reno.c \ ++ $(LWIPDIR)/core/tcp_ca_cubic.c \ + $(LWIPDIR)/core/tcp_in.c \ + $(LWIPDIR)/core/tcp_out.c \ + $(LWIPDIR)/core/timeouts.c \ +diff --git a/src/core/dir.mk b/src/core/dir.mk +index 57a9670..b9f24d5 100644 +--- a/src/core/dir.mk ++++ b/src/core/dir.mk +@@ -1,6 +1,6 @@ + SRC = def.c inet_chksum.c init.c ip.c mem.c memp.c netif.c pbuf.c \ + raw.c tcp.c tcp_in.c tcp_out.c timeouts.c udp.c stats.c\ + ipv4/icmp.c ipv4/ip4_addr.c ipv4/ip4_frag.c ipv4/etharp.c \ +- ipv4/ip4.c ipv4/igmp.c ++ ipv4/ip4.c ipv4/igmp.c tcp_cong.c tcp_ca_reno.c tcp_ca_cubic.c + + $(eval $(call register_dir, core, $(SRC))) +diff --git a/src/core/tcp.c b/src/core/tcp.c +index 538a664..413bbd4 100644 +--- a/src/core/tcp.c ++++ b/src/core/tcp.c +@@ -1319,6 +1319,10 @@ tcp_connect(struct tcp_pcb *pcb, const ip_addr_t *ipaddr, u16_t port, + LWIP_UNUSED_ARG(connected); + #endif /* LWIP_CALLBACK_API */ + ++ /* Init the congestion algorithm*/ ++ if(pcb->cong_ops->init != NULL) ++ pcb->cong_ops->init(pcb); ++ + /* Send a SYN together with the MSS option. */ + ret = tcp_enqueue_flags(pcb, TCP_SYN); + if (ret == ERR_OK) { +@@ -1353,7 +1357,6 @@ void + tcp_slowtmr(void) + { + struct tcp_pcb *pcb, *prev; +- tcpwnd_size_t eff_wnd; + u8_t pcb_remove; /* flag if a PCB should be removed */ + u8_t pcb_reset; /* flag if a RST should be sent when removing */ + err_t err; +@@ -1453,17 +1456,14 @@ tcp_slowtmr_start: + /* Reset the retransmission timer. */ + pcb->rtime = 0; + +- /* Reduce congestion window and ssthresh. */ +- eff_wnd = LWIP_MIN(pcb->cwnd, pcb->snd_wnd); +- pcb->ssthresh = eff_wnd >> 1; +- if (pcb->ssthresh < (tcpwnd_size_t)(pcb->mss << 1)) { +- pcb->ssthresh = (tcpwnd_size_t)(pcb->mss << 1); +- } ++ /* Reduce congestion window and call the congestion algorithm to calculate ssthresh. */ ++ pcb->ssthresh = pcb->cong_ops->ssthresh(pcb); + pcb->cwnd = pcb->mss; + LWIP_DEBUGF(TCP_CWND_DEBUG, ("tcp_slowtmr: cwnd %"TCPWNDSIZE_F + " ssthresh %"TCPWNDSIZE_F"\n", + pcb->cwnd, pcb->ssthresh)); + pcb->bytes_acked = 0; ++ pcb->rttmin = 0; + + /* The following needs to be called AFTER cwnd is set to one + mss - STJ */ +@@ -2111,6 +2111,7 @@ tcp_alloc(u8_t prio) + 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. */ + pcb->ssthresh = TCP_SND_BUF; ++ pcb->cong_ops = &tcp_ca_cubic; + + #if LWIP_CALLBACK_API + pcb->recv = tcp_recv_null; +diff --git a/src/core/tcp_ca_cubic.c b/src/core/tcp_ca_cubic.c +new file mode 100644 +index 0000000..ab09abc +--- /dev/null ++++ b/src/core/tcp_ca_cubic.c +@@ -0,0 +1,460 @@ ++/* ++ * Copyright (c) 2001-2004 Swedish Institute of Computer Science. ++ * All rights reserved. ++ * ++ * Redistribution and use in source and binary forms, with or without modification, ++ * are permitted provided that the following conditions are met: ++ * ++ * 1. Redistributions of source code must retain the above copyright notice, ++ * this list of conditions and the following disclaimer. ++ * 2. Redistributions in binary form must reproduce the above copyright notice, ++ * this list of conditions and the following disclaimer in the documentation ++ * and/or other materials provided with the distribution. ++ * 3. The name of the author may not be used to endorse or promote products ++ * derived from this software without specific prior written permission. ++ * ++ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED ++ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF ++ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT ++ * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, ++ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT ++ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS ++ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN ++ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING ++ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY ++ * OF SUCH DAMAGE. ++ * ++ * This file is part of the lwIP TCP/IP stack. ++ * ++ * Cubic congestion algorithm is migrated from Linux kernel 6.1 . ++ * ++ * Author: Haoran Wang ++ * ++ */ ++ ++#include "lwip/tcp.h" ++#include "lwip/priv/tcp_priv.h" ++#include ++ ++#define CUBICTCP_BETA_SCALE 1024 /* Scale factor beta calculation ++ * max_cwnd = snd_cwnd * beta ++ */ ++#define HZ 1000 /* sys_now() time unit, ms */ ++#define CUBICTCP_HZ 10 /* BIC HZ 2^10 = 1024 */ ++ ++/* Two methods of hybrid slow start */ ++#define HYSTART_ACK_TRAIN 0x1 ++#define HYSTART_DELAY 0x2 ++ ++/* Number of delay samples for detecting the increase of delay */ ++#define HYSTART_MIN_SAMPLES 8 ++#define HYSTART_DELAY_MIN (4U) /* 4 ms */ ++#define HYSTART_DELAY_MAX (16U) /* 16 ms */ ++#define HYSTART_DELAY_THRESH(x) LWIP_MIN(LWIP_MAX(x, HYSTART_DELAY_MIN), HYSTART_DELAY_MAX) ++ ++static u32_t fast_convergence = 1; ++static u32_t beta = 717; /* = 717/1024 (BICTCP_BETA_SCALE) */ ++static u32_t cubic_scale = 41; ++static u32_t tcp_friendliness = 1; ++ ++static u32_t hystart = 1; ++static u32_t hystart_detect = HYSTART_ACK_TRAIN | HYSTART_DELAY; ++static u32_t hystart_low_window = 16; ++static u32_t hystart_ack_delta_ms = 2; ++ ++static u32_t cube_rtt_scale; ++static u32_t beta_scale; ++static u64_t cube_factor; ++ ++ ++/* CUBIC TCP Parameters */ ++struct cubictcp { ++ u32_t cnt; /* increase cwnd by 1 after ACKs (packet)*/ ++ u32_t last_max_cwnd; /* last maximum snd_cwnd (packet)*/ ++ u32_t last_cwnd; /* the last snd_cwnd (packet)*/ ++ u32_t last_time; /* time when updated last_cwnd */ ++ u32_t cubic_origin_point;/* origin point of bic function */ ++ u32_t cubic_K; /* time to origin point ++ from the beginning of the current epoch */ ++ u32_t delay_min; /* min delay (msec) */ ++ u32_t epoch_start; /* beginning of an epoch */ ++ u32_t ack_cnt; /* number of acks (byte) */ ++ u32_t tcp_cwnd; /* estimated tcp cwnd (packet)*/ ++ u16_t mss; ++ ++ /* for hystart*/ ++ u8_t sample_cnt; /* number of samples to decide curr_rtt */ ++ u8_t found; /* the exit point is found? */ ++ u32_t round_start; /* beginning of each round */ ++ u32_t end_seq; /* end_seq of the round */ ++ u32_t last_ack; /* last time when the ACK spacing is close */ ++ u32_t curr_rtt; /* the minimum rtt of current round */ ++}; ++ ++static inline void cubictcp_reset(struct cubictcp *ca) ++{ ++ memset(ca, 0, offsetof(struct cubictcp, mss)); ++ ca->found = 0; ++} ++ ++static inline void cubictcp_hystart_reset(struct tcp_pcb *pcb) ++{ ++ struct cubictcp *ca = (struct cubictcp *)pcb->tcp_congestion_priv; ++ ++ ca->round_start = ca->last_ack = pcb->lacktime; ++ ca->end_seq = pcb->snd_nxt; ++ ca->curr_rtt = ~0U; ++ ca->sample_cnt = 0; ++} ++ ++static void tcp_cubic_init(struct tcp_pcb *pcb) ++{ ++ struct cubictcp *ca = (struct cubictcp *)pcb->tcp_congestion_priv; ++ ++ cubictcp_reset(ca); ++ ++ cube_rtt_scale = cubic_scale * 10; ++ beta_scale = 8*(CUBICTCP_BETA_SCALE+beta) / 3 / (CUBICTCP_BETA_SCALE - beta); ++ cube_factor = (((u64_t)1) << (10+3*CUBICTCP_HZ)) / cube_rtt_scale; ++ ++ if (hystart) ++ cubictcp_hystart_reset(pcb); ++ ++} ++ ++static void tcp_cubic_cwnd_event(struct tcp_pcb *pcb, u8_t event) ++{ ++ struct cubictcp *ca = (struct cubictcp *)pcb->tcp_congestion_priv; ++ u32_t now; ++ s32_t delta; ++ ++ switch (event) { ++ case CA_EVENT_TX_START: ++ now = sys_now(); ++ delta = now - pcb->lsndtime; ++ ++ /* We were application limited (idle) for a while. ++ * Shift epoch_start to keep cwnd growth to cubic curve. ++ */ ++ if (ca->epoch_start && delta > 0) { ++ ca->epoch_start += delta; ++ if (TCP_SEQ_GT(ca->epoch_start, now)) ++ ca->epoch_start = now; ++ } ++ return; ++ case CA_EVENT_LOSS: ++ cubictcp_reset((struct cubictcp *)pcb->tcp_congestion_priv); ++ cubictcp_hystart_reset(pcb); ++ return; ++ default: ++ return; ++ } ++} ++ ++static u32_t fls64(uint64_t v) ++{ ++ static const u64_t debruijn_multiplicator = 0x6c04f118e9966f6bUL; ++ static const u8_t debruijn_bit_position[128] = { ++ 0, /* change to 1 if you want bitSize(0) = 1 */ ++ 48, -1, -1, 31, -1, 15, 51, -1, 63, 5, -1, -1, -1, 19, -1, ++ 23, 28, -1, -1, -1, 40, 36, 46, -1, 13, -1, -1, -1, 34, -1, 58, ++ -1, 60, 2, 43, 55, -1, -1, -1, 50, 62, 4, -1, 18, 27, -1, 39, ++ 45, -1, -1, 33, 57, -1, 1, 54, -1, 49, -1, 17, -1, -1, 32, -1, ++ 53, -1, 16, -1, -1, 52, -1, -1, -1, 64, 6, 7, 8, -1, 9, -1, ++ -1, -1, 20, 10, -1, -1, 24, -1, 29, -1, -1, 21, -1, 11, -1, -1, ++ 41, -1, 25, 37, -1, 47, -1, 30, 14, -1, -1, -1, -1, 22, -1, -1, ++ 35, 12, -1, -1, -1, 59, 42, -1, -1, 61, 3, 26, 38, 44, -1, 56 ++ }; ++ ++ v |= v >> 1; ++ v |= v >> 2; ++ v |= v >> 4; ++ v |= v >> 8; ++ v |= v >> 16; ++ v |= v >> 32; ++ return debruijn_bit_position[(u64_t)(v * debruijn_multiplicator) >> 57]; ++} ++ ++/* calculate the cubic root of x using a table lookup followed by one ++ * Newton-Raphson iteration. ++ * Avg err ~= 0.195% ++ */ ++static u32_t cubic_root(u64_t a) ++{ ++ u32_t x, b, shift; ++ /* ++ * cbrt(x) MSB values for x MSB values in [0..63]. ++ * Precomputed then refined by hand - Willy Tarreau ++ * ++ * For x in [0..63], ++ * v = cbrt(x << 18) - 1 ++ * cbrt(x) = (v[x] + 10) >> 6 ++ */ ++ static const u8_t v[] = { ++ /* 0x00 */ 0, 54, 54, 54, 118, 118, 118, 118, ++ /* 0x08 */ 123, 129, 134, 138, 143, 147, 151, 156, ++ /* 0x10 */ 157, 161, 164, 168, 170, 173, 176, 179, ++ /* 0x18 */ 181, 185, 187, 190, 192, 194, 197, 199, ++ /* 0x20 */ 200, 202, 204, 206, 209, 211, 213, 215, ++ /* 0x28 */ 217, 219, 221, 222, 224, 225, 227, 229, ++ /* 0x30 */ 231, 232, 234, 236, 237, 239, 240, 242, ++ /* 0x38 */ 244, 245, 246, 248, 250, 251, 252, 254, ++ }; ++ ++ b = fls64(a); ++ if (b < 7) { ++ /* a in [0..63] */ ++ return ((u32_t)v[(u32_t)a] + 35) >> 6; ++ } ++ ++ b = ((b * 84) >> 8) - 1; ++ shift = (a >> (b * 3)); ++ ++ x = ((u32_t)(((u32_t)v[shift] + 10) << b)) >> 6; ++ ++ /* ++ * Newton-Raphson iteration ++ * 2 ++ * x = ( 2 * x + a / x ) / 3 ++ * k+1 k k ++ */ ++ x = (2 * x + (u32_t)(a / ((u64_t)x * (u64_t)(x - 1)))); ++ x = ((x * 341) >> 10); ++ return x; ++} ++ ++static inline void tcp_cubic_update(struct cubictcp *ca, u32_t cwnd, u32_t acked) ++{ ++ u32_t delta, cubic_target, max_cnt, now; ++ u64_t offs, t; ++ ++ ca->ack_cnt += acked; /* count of ACKed bytes */ ++ cwnd /= ca->mss; /* convert cwnd in byte to cwnd in segment*/ ++ now = sys_now(); ++ ++ if (ca->last_cwnd == cwnd && ++ (s32_t)(now - ca->last_time) <= HZ / 32) ++ return; ++ ++ /* The CUBIC function can update ca->cnt at most once per jiffy. ++ * On all cwnd reduction events, ca->epoch_start is set to 0, ++ * which will force a recalculation of ca->cnt. ++ */ ++ if (ca->epoch_start && now == ca->last_time) ++ goto tcp_friendliness; ++ ++ ca->last_cwnd = cwnd; ++ ca->last_time = now; ++ ++ if (ca->epoch_start == 0) { ++ ca->epoch_start = now; /* record beginning */ ++ ca->ack_cnt = acked; /* start counting */ ++ ca->tcp_cwnd = cwnd; /* syn with cubic */ ++ ++ if (ca->last_max_cwnd <= cwnd) { ++ ca->cubic_K = 0; ++ ca->cubic_origin_point = cwnd; ++ } else { ++ /* Compute new K based on ++ * (wmax-cwnd) * (srtt>>3 / HZ) / c * 2^(3*bictcp_HZ) ++ */ ++ ca->cubic_K = cubic_root(cube_factor ++ * (ca->last_max_cwnd - cwnd)); ++ ca->cubic_origin_point = ca->last_max_cwnd; ++ } ++ } ++ ++ /* cubic function - calc*/ ++ /* calculate c * time^3 / rtt, ++ * while considering overflow in calculation of time^3 ++ * (so time^3 is done by using 64 bit) ++ * and without the support of division of 64bit numbers ++ * (so all divisions are done by using 32 bit) ++ * also NOTE the unit of those veriables ++ * time = (t - K) / 2^bictcp_HZ ++ * c = bic_scale >> 10 ++ * rtt = (srtt >> 3) / HZ ++ * !!! The following code does not have overflow problems, ++ * if the cwnd < 1 million packets !!! ++ */ ++ ++ t = (s32_t)(now - ca->epoch_start); ++ t += ca->delay_min; ++ /* change the unit from HZ to bictcp_HZ */ ++ t <<= CUBICTCP_HZ; ++ t /= HZ; ++ ++ if (t < ca->cubic_K) /* t - K */ ++ offs = ca->cubic_K - t; ++ else ++ offs = t - ca->cubic_K; ++ ++ /* c/rtt * (t-K)^3 */ ++ delta = (cube_rtt_scale * offs * offs * offs) >> (10+3*CUBICTCP_HZ); ++ if (t < ca->cubic_K) /* below origin*/ ++ cubic_target = ca->cubic_origin_point - delta; ++ else /* above origin*/ ++ cubic_target = ca->cubic_origin_point + delta; ++ ++ /* cubic function - calc bictcp_cnt*/ ++ if (cubic_target > cwnd) { ++ ca->cnt = cwnd / (cubic_target - cwnd); ++ } else { ++ ca->cnt = 100 * cwnd; /* very small increment*/ ++ } ++ ++ /* ++ * The initial growth of cubic function may be too conservative ++ * when the available bandwidth is still unknown. ++ */ ++ if (ca->last_max_cwnd == 0 && ca->cnt > 20) ++ ca->cnt = 20; /* increase cwnd 5% per RTT */ ++ ++tcp_friendliness: ++ /* TCP Friendly */ ++ if (tcp_friendliness) { ++ u32_t scale = beta_scale; ++ ++ delta = ((cwnd * scale) >> 3) * ca->mss; ++ while (ca->ack_cnt > delta) { /* update tcp cwnd */ ++ ca->ack_cnt -= delta; ++ ca->tcp_cwnd++; ++ } ++ ++ if (ca->tcp_cwnd > cwnd) { /* if bic is slower than tcp */ ++ delta = ca->tcp_cwnd - cwnd; ++ max_cnt = cwnd / delta; ++ if (ca->cnt > max_cnt) ++ ca->cnt = max_cnt; ++ } ++ } ++ ++ /* The maximum rate of cwnd increase CUBIC allows is 1 packet per ++ * 2 packets ACKed, meaning cwnd grows at 1.5x per RTT. ++ */ ++ ca->cnt = LWIP_MAX(ca->cnt, 2U); ++} ++ ++static u32_t tcp_cubic_slow_start(struct tcp_pcb *pcb, u32_t acked) ++{ ++ u32_t delta = LWIP_MIN(acked, (u32_t)pcb->ssthresh - pcb->cwnd); ++ ++ acked -= delta; ++ ++ TCP_WND_INC(pcb->cwnd, delta); ++ LWIP_DEBUGF(TCP_CWND_DEBUG, ("tcp_cubic_slow_start: %"U16_F" -> %"U16_F": slow start cwnd %"TCPWNDSIZE_F"\n", pcb->local_port, pcb->remote_port, pcb->cwnd)); ++ ++ return acked; ++} ++ ++static void tcp_cubic_cong_avoid(struct tcp_pcb *pcb, u32_t acked) ++{ ++ struct cubictcp *ca = (struct cubictcp *)pcb->tcp_congestion_priv; ++ ca->mss = pcb->mss; ++ LWIP_DEBUGF(TCP_CWND_DEBUG, ("tcp_cubic_cong_avoid: %"U16_F" -> %"U16_F": mss:%"U16_F", ssthresh %"TCPWNDSIZE_F", cwnd %"TCPWNDSIZE_F"\n", pcb->local_port, pcb->remote_port, pcb->mss, pcb->ssthresh, pcb->cwnd)); ++ /*if (!pcb->is_cwnd_limited) ++ { ++ LWIP_DEBUGF(TCP_CWND_DEBUG, ("tcp_cubic_cong_avoid: %"U16_F" -> %"U16_F": cwnd limited, cwnd %"TCPWNDSIZE_F"\n", pcb->local_port, pcb->remote_port, pcb->cwnd)); ++ return; ++ } ++ */ ++ ++ if (tcp_in_slow_start(pcb)) { ++ acked = tcp_cubic_slow_start(pcb, acked); ++ if (!acked) ++ return; ++ } ++ tcp_cubic_update(ca, pcb->cwnd, acked); ++ tcp_cong_avoid_ai(pcb, ca->cnt * pcb->mss, acked); ++} ++ ++/* Slow start threshold is half the congestion window (min 2) */ ++static tcpwnd_size_t tcp_cubic_ssthresh(struct tcp_pcb *pcb) ++{ ++ struct cubictcp *ca = (struct cubictcp *)pcb->tcp_congestion_priv; ++ u32_t cwnd = pcb->cwnd / pcb->mss; /* convert cwnd in byte to cwnd in segment*/ ++ ++ ca->epoch_start = 0; /* end of epoch */ ++ ++ if (cwnd < ca->last_max_cwnd && fast_convergence) ++ ca->last_max_cwnd = (cwnd * (CUBICTCP_BETA_SCALE + beta)) ++ / (2 * CUBICTCP_BETA_SCALE); ++ else ++ ca->last_max_cwnd = cwnd; ++ ++ return LWIP_MAX((cwnd * beta) / CUBICTCP_BETA_SCALE, 2) * pcb->mss; ++} ++ ++static void hystart_update(struct tcp_pcb *pcb, u32_t delay) ++{ ++ struct cubictcp *ca = (struct cubictcp *)pcb->tcp_congestion_priv; ++ u32_t threshold; ++ ++ if (TCP_SEQ_GT(pcb->lastack + 1, ca->end_seq)) ++ cubictcp_hystart_reset(pcb); ++ ++ if (hystart_detect & HYSTART_ACK_TRAIN) { ++ u32_t now = pcb->lacktime; ++ ++ /* first detection parameter - ack-train detection */ ++ if ((s32_t)(now - ca->last_ack) <= (s32_t)hystart_ack_delta_ms) { ++ ca->last_ack = now; ++ ++ threshold = ca->delay_min; ++ ++ if ((s32_t)(now - ca->round_start) > (s32_t)threshold) { ++ ca->found = 1; ++ pcb->ssthresh = pcb->cwnd; ++ } ++ } ++ } ++ ++ if (hystart_detect & HYSTART_DELAY) { ++ /* obtain the minimum delay of more than sampling packets */ ++ if (ca->curr_rtt > delay) ++ ca->curr_rtt = delay; ++ if (ca->sample_cnt < HYSTART_MIN_SAMPLES) { ++ ca->sample_cnt++; ++ } else { ++ if (ca->curr_rtt > ca->delay_min + ++ HYSTART_DELAY_THRESH(ca->delay_min >> 3)) { ++ ca->found = 1; ++ pcb->ssthresh = pcb->cwnd; ++ } ++ } ++ } ++} ++ ++static void tcp_cubic_acked(struct tcp_pcb *pcb, u32_t rtt_ms) ++{ ++ struct cubictcp *ca = (struct cubictcp *)pcb->tcp_congestion_priv; ++ u32_t delay; ++ ++ LWIP_DEBUGF(TCP_CWND_DEBUG, ("tcp_cubic_acked: %"U16_F" -> %"U16_F": rtt %"U32_F"\n", pcb->local_port, pcb->remote_port, rtt_ms)); ++ ++ /* Discard delay samples right after fast recovery */ ++ if (ca->epoch_start && (pcb->flags & TF_RTO)) ++ return; ++ ++ delay = rtt_ms; ++ if (delay == 0) ++ delay = 1; ++ ++ /* first time call or link delay decreases */ ++ if (ca->delay_min == 0 || ca->delay_min > delay) ++ ca->delay_min = delay; ++ ++ /* hystart triggers when cwnd is larger than some threshold */ ++ if (!ca->found && tcp_in_slow_start(pcb) && hystart && ++ pcb->cwnd >= hystart_low_window * pcb->mss) ++ hystart_update(pcb, delay); ++} ++ ++struct tcp_congestion_ops tcp_ca_cubic = { ++ tcp_cubic_ssthresh, ++ tcp_cubic_cong_avoid, ++ tcp_cubic_cwnd_event, ++ tcp_cubic_acked, ++ "cubic", ++ tcp_cubic_init ++}; +diff --git a/src/core/tcp_ca_reno.c b/src/core/tcp_ca_reno.c +new file mode 100644 +index 0000000..825eaf2 +--- /dev/null ++++ b/src/core/tcp_ca_reno.c +@@ -0,0 +1,73 @@ ++/* ++ * Copyright (c) 2001-2004 Swedish Institute of Computer Science. ++ * All rights reserved. ++ * ++ * Redistribution and use in source and binary forms, with or without modification, ++ * are permitted provided that the following conditions are met: ++ * ++ * 1. Redistributions of source code must retain the above copyright notice, ++ * this list of conditions and the following disclaimer. ++ * 2. Redistributions in binary form must reproduce the above copyright notice, ++ * this list of conditions and the following disclaimer in the documentation ++ * and/or other materials provided with the distribution. ++ * 3. The name of the author may not be used to endorse or promote products ++ * derived from this software without specific prior written permission. ++ * ++ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED ++ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF ++ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT ++ * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, ++ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT ++ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS ++ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN ++ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING ++ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY ++ * OF SUCH DAMAGE. ++ * ++ * This file is part of the lwIP TCP/IP stack. ++ * ++ * Author: Haoran Wang ++ * ++ */ ++ ++#include "lwip/tcp.h" ++#include "lwip/priv/tcp_priv.h" ++ ++/* This is Jacobson's slow start and congestion avoidance. ++ * SIGCOMM '88, p. 328. ++ */ ++static u32_t tcp_reno_slow_start(struct tcp_pcb *pcb, u32_t acked) ++{ ++ u32_t num_seg = (pcb->flags & TF_RTO) ? 1 : 2; ++ acked = LWIP_MIN(acked, num_seg * pcb->mss); ++ TCP_WND_INC(pcb->cwnd, acked); ++ LWIP_DEBUGF(TCP_CWND_DEBUG, ("tcp_reno_slow_start: %"U16_F" -> %"U16_F": slow start cwnd %"TCPWNDSIZE_F"\n", pcb->local_port, pcb->remote_port, pcb->cwnd)); ++ return 0; ++} ++ ++static void tcp_reno_cong_avoid(struct tcp_pcb *pcb, u32_t acked) ++{ ++ LWIP_DEBUGF(TCP_CWND_DEBUG, ("tcp_reno_cong_avoid: %"U16_F" -> %"U16_F": ssthresh %"TCPWNDSIZE_F", cwnd %"TCPWNDSIZE_F"\n", pcb->local_port, pcb->remote_port, pcb->ssthresh, pcb->cwnd)); ++ /* In "safe" area, increase. */ ++ if (tcp_in_slow_start(pcb)) { ++ tcp_reno_slow_start(pcb, acked); ++ return; ++ } ++ /* In dangerous area, increase slowly. */ ++ tcp_cong_avoid_ai(pcb, pcb->cwnd, acked); ++} ++ ++/* Slow start threshold is half the congestion window (min 2) */ ++static tcpwnd_size_t tcp_reno_ssthresh(struct tcp_pcb *pcb) ++{ ++ return LWIP_MAX(pcb->cwnd >> 1U, 2 * pcb->mss); ++} ++ ++struct tcp_congestion_ops tcp_ca_reno = { ++ tcp_reno_ssthresh, ++ tcp_reno_cong_avoid, ++ NULL, ++ NULL, ++ "reno", ++ NULL ++}; +diff --git a/src/core/tcp_cong.c b/src/core/tcp_cong.c +new file mode 100644 +index 0000000..5674ef3 +--- /dev/null ++++ b/src/core/tcp_cong.c +@@ -0,0 +1,51 @@ ++/* ++ * Copyright (c) 2001-2004 Swedish Institute of Computer Science. ++ * All rights reserved. ++ * ++ * Redistribution and use in source and binary forms, with or without modification, ++ * are permitted provided that the following conditions are met: ++ * ++ * 1. Redistributions of source code must retain the above copyright notice, ++ * this list of conditions and the following disclaimer. ++ * 2. Redistributions in binary form must reproduce the above copyright notice, ++ * this list of conditions and the following disclaimer in the documentation ++ * and/or other materials provided with the distribution. ++ * 3. The name of the author may not be used to endorse or promote products ++ * derived from this software without specific prior written permission. ++ * ++ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED ++ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF ++ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT ++ * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, ++ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT ++ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS ++ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN ++ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING ++ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY ++ * OF SUCH DAMAGE. ++ * ++ * This file is part of the lwIP TCP/IP stack. ++ * ++ * Author: Haoran Wang ++ * ++ */ ++ ++#include "lwip/tcp.h" ++#include "lwip/priv/tcp_priv.h" ++ ++void tcp_cong_avoid_ai(struct tcp_pcb *pcb, u32_t w, u32_t acked) ++{ ++ /* If credits accumulated at a higher w, apply them gently now. */ ++ if (pcb->bytes_acked >= w) { ++ pcb->bytes_acked = 0; ++ TCP_WND_INC(pcb->cwnd, pcb->mss); ++ } ++ ++ TCP_WND_INC(pcb->bytes_acked, acked); ++ if (pcb->bytes_acked >= w) { ++ u32_t delta = pcb->bytes_acked / w; ++ pcb->bytes_acked -= delta * w; ++ TCP_WND_INC(pcb->cwnd, delta * pcb->mss); ++ } ++ LWIP_DEBUGF(TCP_CWND_DEBUG, ("tcp_cong_avoid_ai: %"U16_F" -> %"U16_F": congestion avoidance ssthresh %"TCPWNDSIZE_F"; cwnd %"TCPWNDSIZE_F"\n", pcb->local_port, pcb->remote_port, pcb->ssthresh, pcb->cwnd)); ++} +diff --git a/src/core/tcp_in.c b/src/core/tcp_in.c +index 5014a21..dd01540 100644 +--- a/src/core/tcp_in.c ++++ b/src/core/tcp_in.c +@@ -90,6 +90,7 @@ static PER_THREAD u32_t ackno; + static PER_THREAD tcpwnd_size_t recv_acked; + static PER_THREAD u16_t tcplen; + static PER_THREAD u8_t flags; ++static PER_THREAD u32_t rtt_ms; + + static PER_THREAD u8_t recv_flags; + static PER_THREAD struct pbuf *recv_data; +@@ -824,7 +825,7 @@ tcp_listen_input(struct tcp_pcb_listen *pcb) + return; + } + } +-#endif ++#endif + + /* Parse any options in the SYN. */ + tcp_parseopt(npcb); +@@ -835,6 +836,9 @@ tcp_listen_input(struct tcp_pcb_listen *pcb) + npcb->mss = tcp_eff_send_mss(npcb->mss, &npcb->local_ip, &npcb->remote_ip); + #endif /* TCP_CALCULATE_EFF_SEND_MSS */ + ++ if(npcb->cong_ops->init != NULL) ++ npcb->cong_ops->init(npcb); ++ + MIB2_STATS_INC(mib2.tcppassiveopens); + + #if LWIP_TCP_PCB_NUM_EXT_ARGS +@@ -1279,6 +1283,9 @@ tcp_free_acked_segments(struct tcp_pcb *pcb, struct tcp_seg *seg_list, const cha + + pcb->snd_queuelen = (u16_t)(pcb->snd_queuelen - clen); + recv_acked = (tcpwnd_size_t)(recv_acked + next->len); ++ if (next->snd_time == 1) { ++ rtt_ms = LWIP_MIN(pcb->lacktime - next->snd_timestamp, rtt_ms); ++ } + tcp_seg_free(next); + + LWIP_DEBUGF(TCP_QLEN_DEBUG, ("%"TCPWNDSIZE_F" (after freeing %s)\n", +@@ -1444,23 +1451,7 @@ tcp_receive(struct tcp_pcb *pcb) + /* Update the congestion control variables (cwnd and + ssthresh). */ + if (pcb->state >= ESTABLISHED) { +- if (pcb->cwnd < pcb->ssthresh) { +- tcpwnd_size_t increase; +- /* limit to 1 SMSS segment during period following RTO */ +- u8_t num_seg = (pcb->flags & TF_RTO) ? 1 : 2; +- /* RFC 3465, section 2.2 Slow Start */ +- increase = LWIP_MIN(acked, (tcpwnd_size_t)(num_seg * pcb->mss)); +- TCP_WND_INC(pcb->cwnd, increase); +- LWIP_DEBUGF(TCP_CWND_DEBUG, ("tcp_receive: slow start cwnd %"TCPWNDSIZE_F"\n", pcb->cwnd)); +- } else { +- /* RFC 3465, section 2.1 Congestion Avoidance */ +- TCP_WND_INC(pcb->bytes_acked, acked); +- if (pcb->bytes_acked >= pcb->cwnd) { +- pcb->bytes_acked = (tcpwnd_size_t)(pcb->bytes_acked - pcb->cwnd); +- TCP_WND_INC(pcb->cwnd, pcb->mss); +- } +- LWIP_DEBUGF(TCP_CWND_DEBUG, ("tcp_receive: congestion avoidance cwnd %"TCPWNDSIZE_F"\n", pcb->cwnd)); +- } ++ pcb->cong_ops->cong_avoid(pcb, acked); + } + LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_receive: ACK for %"U32_F", unacked->seqno %"U32_F":%"U32_F"\n", + ackno, +@@ -1469,6 +1460,8 @@ tcp_receive(struct tcp_pcb *pcb) + pcb->unacked != NULL ? + lwip_ntohl(pcb->unacked->tcphdr->seqno) + TCP_TCPLEN(pcb->unacked) : 0)); + ++ rtt_ms = (u32_t)-1; ++ pcb->lacktime = sys_now(); + /* Remove segment from the unacknowledged list if the incoming + ACK acknowledges them. */ + pcb->unacked = tcp_free_acked_segments(pcb, pcb->unacked, "unacked", pcb->unsent); +@@ -1484,6 +1477,16 @@ tcp_receive(struct tcp_pcb *pcb) + if (pcb->unsent == NULL) + pcb->last_unsent = NULL; + ++ if (pcb->rttmin == 0) { ++ pcb->rttmin = rtt_ms; ++ } else { ++ pcb->rttmin = LWIP_MIN(pcb->rttmin, rtt_ms); ++ } ++ /* Call congestion algorithm with rtt*/ ++ if (pcb->cong_ops->pkts_acked != NULL) { ++ pcb->cong_ops->pkts_acked(pcb, rtt_ms); ++ } ++ + /* If there's nothing left to acknowledge, stop the retransmit + timer, otherwise reset it to start again */ + if (pcb->unacked == NULL) { +@@ -2110,6 +2113,10 @@ tcp_parseopt(struct tcp_pcb *pcb) + #if LWIP_TCP_TIMESTAMPS + u32_t tsval; + #endif ++#if LWIP_TCP_SACK_OUT ++ u8_t len; ++ u8_t i; ++#endif + + LWIP_ASSERT("tcp_parseopt: invalid pcb", pcb != NULL); + +@@ -2204,6 +2211,33 @@ tcp_parseopt(struct tcp_pcb *pcb) + tcp_set_flags(pcb, TF_SACK); + } + break; ++ case LWIP_TCP_OPT_SACK: ++ LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_parseopt: SACK\n")); ++ len = tcp_get_next_optbyte(); ++ if (len <= LWIP_TCP_OPT_LEN_SACK_PERM || (len - 2) % 8 != 0 ||(tcp_optidx - 2 + LWIP_TCP_OPT_LEN_SACK_PERM) > tcphdr_optlen) { ++ /* Bad length */ ++ LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_parseopt: bad length\n")); ++ return; ++ } ++ if ((pcb->flags & TF_SACK) == 0) { ++ LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_parseopt: bad option\n")); ++ return; ++ } ++ pcb->snd_sacks_valid_count = (len - 2) / 8; ++ for(i = 0; i < pcb->snd_sacks_valid_count; i++) { ++ pcb->snd_sacks[i].left = tcp_get_next_optbyte(); ++ pcb->snd_sacks[i].left |= (tcp_get_next_optbyte() << 8); ++ pcb->snd_sacks[i].left |= (tcp_get_next_optbyte() << 16); ++ pcb->snd_sacks[i].left |= (tcp_get_next_optbyte() << 24); ++ pcb->snd_sacks[i].left = lwip_ntohl(pcb->snd_sacks[i].left); ++ pcb->snd_sacks[i].right = tcp_get_next_optbyte(); ++ pcb->snd_sacks[i].right |= (tcp_get_next_optbyte() << 8); ++ pcb->snd_sacks[i].right |= (tcp_get_next_optbyte() << 16); ++ pcb->snd_sacks[i].right |= (tcp_get_next_optbyte() << 24); ++ pcb->snd_sacks[i].right = lwip_ntohl(pcb->snd_sacks[i].right); ++ LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_parseopt: left %u, right %u\n", pcb->snd_sacks[i].left, pcb->snd_sacks[i].right)); ++ } ++ break; + #endif /* LWIP_TCP_SACK_OUT */ + default: + LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_parseopt: other\n")); +diff --git a/src/core/tcp_out.c b/src/core/tcp_out.c +index 6250e6b..17b8bef 100644 +--- a/src/core/tcp_out.c ++++ b/src/core/tcp_out.c +@@ -172,6 +172,8 @@ void tcp_init_segment(struct tcp_seg *seg, const struct tcp_pcb *pcb, struct pbu + seg->next = NULL; + seg->p = p; + seg->len = p->tot_len - optlen; ++ seg->snd_time = 0; ++ seg->snd_timestamp = 0; + + /* build TCP header */ + pbuf_add_header(p, TCP_HLEN); +@@ -191,7 +193,7 @@ tcp_create_segment(const struct tcp_pcb *pcb, struct pbuf *p, u8_t hdrflags, u32 + + seg = (struct tcp_seg *)((uint8_t *)p + sizeof(struct pbuf_custom)); + +- tcp_init_segment(seg, pcb, p, hdrflags, seqno, optflags); ++ tcp_init_segment(seg, pcb, p, hdrflags, seqno, optflags); + + return seg; + } +@@ -217,6 +219,8 @@ tcp_create_segment(const struct tcp_pcb *pcb, struct pbuf *p, u8_t hdrflags, u32 + seg->p = p; + LWIP_ASSERT("p->tot_len >= optlen", p->tot_len >= optlen); + seg->len = p->tot_len - optlen; ++ seg->snd_time = 0; ++ seg->snd_timestamp = 0; + #if TCP_OVERSIZE_DBGCHECK + seg->oversize_left = 0; + #endif /* TCP_OVERSIZE_DBGCHECK */ +@@ -398,7 +402,7 @@ tcp_write_checks(struct tcp_pcb *pcb, u16_t len) + * it can send them more efficiently by combining them together). + * To prompt the system to send data now, call tcp_output() after + * calling tcp_write(). +- * ++ * + * This function enqueues the data pointed to by the argument dataptr. The length of + * the data is passed as the len parameter. The apiflags can be one or more of: + * - TCP_WRITE_FLAG_COPY: indicates whether the new memory should be allocated +@@ -492,6 +496,10 @@ tcp_write(struct tcp_pcb *pcb, const void *arg, u16_t len, u8_t apiflags) + optlen = LWIP_TCP_OPT_LENGTH_SEGMENT(0, pcb); + } + ++ /* new data comes after send idle*/ ++ if (pcb->cong_ops->cwnd_event != NULL && pcb->unsent == NULL && pcb->unacked == NULL) { ++ pcb->cong_ops->cwnd_event(pcb, CA_EVENT_TX_START); ++ } + + /* + * TCP segmentation is done in three phases with increasing complexity: +@@ -1381,10 +1389,11 @@ tcp_output(struct tcp_pcb *pcb) + if (seg == NULL) { + LWIP_DEBUGF(TCP_OUTPUT_DEBUG, ("tcp_output: nothing to send (%p)\n", + (void *)pcb->unsent)); +- LWIP_DEBUGF(TCP_CWND_DEBUG, ("tcp_output: snd_wnd %"TCPWNDSIZE_F ++ /*LWIP_DEBUGF(TCP_CWND_DEBUG, ("tcp_output: snd_wnd %"TCPWNDSIZE_F + ", cwnd %"TCPWNDSIZE_F", wnd %"U32_F + ", seg == NULL, ack %"U32_F"\n", + pcb->snd_wnd, pcb->cwnd, wnd, pcb->lastack)); ++ */ + + /* If the TF_ACK_NOW flag is set and the ->unsent queue is empty, construct + * an empty ACK segment and send it. */ +@@ -1394,12 +1403,13 @@ tcp_output(struct tcp_pcb *pcb) + /* nothing to send: shortcut out of here */ + goto output_done; + } else { +- LWIP_DEBUGF(TCP_CWND_DEBUG, ++ /*LWIP_DEBUGF(TCP_CWND_DEBUG, + ("tcp_output: snd_wnd %"TCPWNDSIZE_F", cwnd %"TCPWNDSIZE_F", wnd %"U32_F + ", effwnd %"U32_F", seq %"U32_F", ack %"U32_F"\n", + pcb->snd_wnd, pcb->cwnd, wnd, + lwip_ntohl(seg->tcphdr->seqno) - pcb->lastack + seg->len, + lwip_ntohl(seg->tcphdr->seqno), pcb->lastack)); ++ */ + } + + if (pcb->pcb_if == NULL) { +@@ -1535,6 +1545,11 @@ tcp_output(struct tcp_pcb *pcb) + tmp_seg->p->next = NULL; + if (TCP_TCPLEN(tmp_seg) > 0) { + tmp_seg->next = NULL; ++ ++ pcb->lsndtime = sys_now(); ++ tmp_seg->snd_timestamp = pcb->lsndtime; ++ tmp_seg->snd_time++; ++ + if (pcb->unacked == NULL) { + pcb->last_unacked = tmp_seg; + pcb->unacked = tmp_seg; +@@ -1622,6 +1637,12 @@ end_loop: + /* put segment on unacknowledged list if length > 0 */ + if (TCP_TCPLEN(seg) > 0) { + seg->next = NULL; ++ ++ /* record segment send time*/ ++ pcb->lsndtime = sys_now(); ++ seg->snd_timestamp = pcb->lsndtime; ++ seg->snd_time++; ++ + /* unacked list is empty? */ + if (pcb->unacked == NULL) { + pcb->unacked = seg; +@@ -1663,6 +1684,7 @@ end_loop: + #endif /* TCP_OVERSIZE */ + + output_done: ++ pcb->is_cwnd_limited = (wnd == pcb->cwnd) && (pcb->unsent != NULL); + pcb->need_tso_send = 0; + if (pcb->unsent == NULL) + pcb->last_unsent = NULL; +@@ -2045,6 +2067,10 @@ tcp_rexmit(struct tcp_pcb *pcb) + { + struct tcp_seg *seg; + struct tcp_seg **cur_seg; ++ u8_t i; ++ struct tcp_seg *p = NULL; ++ struct tcp_seg *next; ++ u32_t timestamp = sys_now(); + + LWIP_ASSERT("tcp_rexmit: invalid pcb", pcb != NULL); + +@@ -2057,18 +2083,46 @@ tcp_rexmit(struct tcp_pcb *pcb) + /* Give up if the segment is still referenced by the netif driver + due to deferred transmission. */ + if (tcp_output_segment_busy(seg)) { +- LWIP_DEBUGF(TCP_RTO_DEBUG, ("tcp_rexmit busy\n")); ++ LWIP_DEBUGF(TCP_RTO_DEBUG, ("tcp_rexmit busy: seqno %u\n", lwip_ntohl(seg->tcphdr->seqno))); ++ if (p != NULL) ++ p->next = seg; + if (seg == pcb->unacked) + return ERR_VAL; + else + break; + } ++ ++ next = seg->next; ++ ++ /* Check for SACK, keep seg in unsent queue if SACKed */ ++ for (i = 0; i < pcb->snd_sacks_valid_count; i++) { ++ if ((TCP_SEQ_LEQ(pcb->snd_sacks[i].left, lwip_ntohl(seg->tcphdr->seqno)) && TCP_SEQ_LEQ(lwip_ntohl(seg->tcphdr->seqno) + seg->len, pcb->snd_sacks[i].right)) ++ || (seg->snd_timestamp + pcb->rttmin >= timestamp)) { ++ seg->next = NULL; ++ if(p != NULL) { ++ p->next = seg; ++ } ++ p = seg; ++ ++ LWIP_DEBUGF(TCP_CWND_DEBUG, ("tcp_rexmit skip: seqno %u, seg_end %u, left %u, right %u, snd_time %u, rttmin %u, now %u\n", ++ lwip_ntohl(seg->tcphdr->seqno), lwip_ntohl(seg->tcphdr->seqno) + seg->len, pcb->snd_sacks[i].left, pcb->snd_sacks[i].right, seg->snd_timestamp, pcb->rttmin, timestamp)); ++ ++ seg = next; ++ break; ++ } ++ } + ++ if (i < pcb->snd_sacks_valid_count) { ++ continue; ++ } ++ + /* Move the first unacked segment to the unsent queue */ + /* Keep the unsent queue sorted. */ +- if (pcb->last_unacked == pcb->unacked) +- pcb->last_unacked = pcb->unacked->next; +- pcb->unacked = pcb->unacked->next; ++ if (p == NULL) { ++ if (pcb->last_unacked == pcb->unacked) ++ pcb->last_unacked = pcb->unacked->next; ++ pcb->unacked = pcb->unacked->next; ++ } + + cur_seg = &(pcb->unsent); + while (*cur_seg && +@@ -2079,6 +2133,8 @@ tcp_rexmit(struct tcp_pcb *pcb) + pcb->last_unsent = seg; + seg->next = *cur_seg; + *cur_seg = seg; ++ ++ LWIP_DEBUGF(TCP_CWND_DEBUG, ("tcp_rexmit move: seqno %u, seg_end %u\n", lwip_ntohl(seg->tcphdr->seqno), lwip_ntohl(seg->tcphdr->seqno) + seg->len)); + #if TCP_OVERSIZE + if (seg->next == NULL) { + /* the retransmitted segment is last in unsent, so reset unsent_oversize */ +@@ -2086,7 +2142,7 @@ tcp_rexmit(struct tcp_pcb *pcb) + } + #endif /* TCP_OVERSIZE */ + +- seg = pcb->unacked; ++ seg = next; + } + + if (pcb->nrtx < 0xFF) { +@@ -2122,22 +2178,15 @@ tcp_rexmit_fast(struct tcp_pcb *pcb) + (u16_t)pcb->dupacks, pcb->lastack, + lwip_ntohl(pcb->unacked->tcphdr->seqno))); + if (tcp_rexmit(pcb) == ERR_OK) { +- /* Set ssthresh to half of the minimum of the current +- * cwnd and the advertised window */ +- pcb->ssthresh = LWIP_MIN(pcb->cwnd, pcb->snd_wnd) / 2; +- +- /* The minimum value for ssthresh should be 2 MSS */ +- if (pcb->ssthresh < (2U * pcb->mss)) { +- LWIP_DEBUGF(TCP_FR_DEBUG, +- ("tcp_receive: The minimum value for ssthresh %"TCPWNDSIZE_F +- " should be min 2 mss %"U16_F"...\n", +- pcb->ssthresh, (u16_t)(2 * pcb->mss))); +- pcb->ssthresh = 2 * pcb->mss; +- } +- ++ /* Update ssthresh by congestion algorithm */ ++ pcb->ssthresh = pcb->cong_ops->ssthresh(pcb); + pcb->cwnd = pcb->ssthresh + 3 * pcb->mss; + tcp_set_flags(pcb, TF_INFR); +- ++ LWIP_DEBUGF(TCP_CWND_DEBUG, ("tcp_rexmit_fast: cwnd %"TCPWNDSIZE_F ++ " ssthresh %"TCPWNDSIZE_F"\n", ++ pcb->cwnd, pcb->ssthresh)); ++ /* Reset accumulate acked here to prevent cwnd burst*/ ++ pcb->bytes_acked = 0; + /* Reset the retransmission timer to prevent immediate rto retransmissions */ + pcb->rtime = 0; + } +diff --git a/src/include/lwip/priv/tcp_priv.h b/src/include/lwip/priv/tcp_priv.h +index ddae3fd..dee99df 100644 +--- a/src/include/lwip/priv/tcp_priv.h ++++ b/src/include/lwip/priv/tcp_priv.h +@@ -88,6 +88,19 @@ void tcp_rexmit_fast (struct tcp_pcb *pcb); + u32_t tcp_update_rcv_ann_wnd(struct tcp_pcb *pcb); + err_t tcp_process_refused_data(struct tcp_pcb *pcb); + ++/* TCP congestion algorithm */ ++/* Events passed to congestion control interface */ ++enum tcp_ca_event { ++ CA_EVENT_TX_START, /* first transmit when no packets in flight */ ++ CA_EVENT_CWND_RESTART, /* congestion window restart */ ++ CA_EVENT_COMPLETE_CWR, /* end of congestion recovery */ ++ CA_EVENT_LOSS, /* loss timeout */ ++ CA_EVENT_ECN_NO_CE, /* ECT set, but not CE marked */ ++ CA_EVENT_ECN_IS_CE /* received CE marked IP packet */ ++}; ++#define tcp_in_slow_start(pcb) (pcb->cwnd < pcb->ssthresh) ++void tcp_cong_avoid_ai(struct tcp_pcb *pcb, u32_t w, u32_t acked); ++ + /** + * This is the Nagle algorithm: try to combine user data to send as few TCP + * segments as possible. Only send if +@@ -251,6 +264,8 @@ struct tcp_seg { + struct tcp_seg *next; /* used when putting segments on a queue */ + struct pbuf *p; /* buffer containing data + TCP header */ + u16_t len; /* the TCP length of this segment */ ++ u32_t snd_time; /* how many times did this segment send*/ ++ u32_t snd_timestamp; /* the send time of this segment*/ + #if TCP_OVERSIZE_DBGCHECK + u16_t oversize_left; /* Extra bytes available at the end of the last + pbuf in unsent (used for asserting vs. +@@ -275,6 +290,7 @@ struct tcp_seg { + #define LWIP_TCP_OPT_MSS 2 + #define LWIP_TCP_OPT_WS 3 + #define LWIP_TCP_OPT_SACK_PERM 4 ++#define LWIP_TCP_OPT_SACK 5 + #define LWIP_TCP_OPT_TS 8 + + #define LWIP_TCP_OPT_LEN_MSS 4 +diff --git a/src/include/lwip/tcp.h b/src/include/lwip/tcp.h +index e13099c..dd882db 100644 +--- a/src/include/lwip/tcp.h ++++ b/src/include/lwip/tcp.h +@@ -50,6 +50,7 @@ + #include "lwip/err.h" + #include "lwip/ip6.h" + #include "lwip/ip6_addr.h" ++#include "lwip/timeouts.h" + + #if GAZELLE_TCP_PCB_HASH + #include "lwip/sys.h" +@@ -211,6 +212,29 @@ struct tcp_pcb_ext_args { + typedef u16_t tcpflags_t; + #define TCP_ALLFLAGS 0xffffU + ++/* TCP congestion algorithm interface*/ ++#define TCP_CA_NAME_MAX 20 ++struct tcp_congestion_ops { ++ /* return slow start threshold (required) */ ++ tcpwnd_size_t (*ssthresh)(struct tcp_pcb *pcb); ++ ++ /* do new cwnd calculation (required) */ ++ void (*cong_avoid)(struct tcp_pcb *pcb, u32_t acked); ++ ++ /* call when cwnd event occurs (optional) */ ++ void (*cwnd_event)(struct tcp_pcb *pcb, u8_t event); ++ ++ /* hook for packet ack accounting (optional) */ ++ void (*pkts_acked)(struct tcp_pcb *pcb, u32_t rtt); ++ ++ char name[TCP_CA_NAME_MAX]; ++ ++ /* initialize private data */ ++ void (*init)(struct tcp_pcb *pcb); ++}; ++extern struct tcp_congestion_ops tcp_ca_reno; ++extern struct tcp_congestion_ops tcp_ca_cubic; ++ + /** + * members common to struct tcp_pcb and struct tcp_listen_pcb + */ +@@ -224,7 +248,7 @@ typedef u16_t tcpflags_t; + u8_t prio; \ + /* ports are in host byte order */ \ + u16_t local_port +- ++ + #else /* GAZELLE_ENABLE */ + #define TCP_PCB_COMMON(type) \ + type *next; /* for the linked list */ \ +@@ -319,6 +343,8 @@ struct tcp_pcb { + #if LWIP_TCP_SACK_OUT + /* SACK ranges to include in ACK packets (entry is invalid if left==right) */ + struct tcp_sack_range rcv_sacks[LWIP_TCP_MAX_SACK_NUM]; ++ struct tcp_sack_range snd_sacks[LWIP_TCP_MAX_SACK_NUM]; ++ u16_t snd_sacks_valid_count; + #define LWIP_TCP_SACK_VALID(pcb, idx) ((pcb)->rcv_sacks[idx].left != (pcb)->rcv_sacks[idx].right) + #endif /* LWIP_TCP_SACK_OUT */ + +@@ -341,6 +367,12 @@ struct tcp_pcb { + u32_t lastack; /* Highest acknowledged seqno. */ + + /* congestion avoidance/control variables */ ++ u16_t is_cwnd_limited; ++ u32_t lsndtime; /* last segment send timestamp in ms*/ ++ u32_t lacktime; /* last segment ack timestamp in ms*/ ++ u32_t rttmin; ++ struct tcp_congestion_ops *cong_ops; ++ u32_t tcp_congestion_priv[18]; + tcpwnd_size_t cwnd; + tcpwnd_size_t ssthresh; + +diff --git a/test/unit/lwipopts.h b/test/unit/lwipopts.h +index e4523fc..b55d2ac 100644 +--- a/test/unit/lwipopts.h ++++ b/test/unit/lwipopts.h +@@ -1,8 +1,8 @@ + /* + * Copyright (c) 2001-2003 Swedish Institute of Computer Science. +- * All rights reserved. +- * +- * Redistribution and use in source and binary forms, with or without modification, ++ * All rights reserved. ++ * ++ * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, +@@ -11,21 +11,21 @@ + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products +- * derived from this software without specific prior written permission. ++ * derived from this software without specific prior written permission. + * +- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED +- * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +- * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT +- * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +- * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT +- * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING +- * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY ++ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED ++ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF ++ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT ++ * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, ++ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT ++ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS ++ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN ++ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING ++ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + * This file is part of the lwIP TCP/IP stack. +- * ++ * + * Author: Simon Goldschmidt + * + */ +@@ -56,10 +56,10 @@ + + /* Minimal changes to opt.h required for tcp unit tests: */ + #define MEM_SIZE 16000 +-#define TCP_SND_QUEUELEN 40 ++#define TCP_SND_QUEUELEN 44 + #define MEMP_NUM_TCP_SEG TCP_SND_QUEUELEN +-#define TCP_SND_BUF (12 * TCP_MSS) +-#define TCP_WND (10 * TCP_MSS) ++#define TCP_SND_BUF (22 * TCP_MSS) ++#define TCP_WND (20 * TCP_MSS) + #define LWIP_WND_SCALE 1 + #define TCP_RCV_SCALE 0 + #define PBUF_POOL_SIZE 400 /* pbuf tests need ~200KByte */ +diff --git a/test/unit/tcp/test_tcp.c b/test/unit/tcp/test_tcp.c +index c7f85f6..2d9a1e2 100644 +--- a/test/unit/tcp/test_tcp.c ++++ b/test/unit/tcp/test_tcp.c +@@ -16,6 +16,29 @@ + #error "This tests needs TCP_SND_BUF to be > TCP_WND" + #endif + ++struct cubictcp { ++ u32_t cnt; /* increase cwnd by 1 after ACKs */ ++ u32_t last_max_cwnd; /* last maximum snd_cwnd */ ++ u32_t last_cwnd; /* the last snd_cwnd */ ++ u32_t last_time; /* time when updated last_cwnd */ ++ u32_t cubic_origin_point;/* origin point of bic function */ ++ u32_t cubic_K; /* time to origin point ++ from the beginning of the current epoch */ ++ u32_t delay_min; /* min delay (msec) */ ++ u32_t epoch_start; /* beginning of an epoch */ ++ u32_t ack_cnt; /* number of acks */ ++ u32_t tcp_cwnd; /* estimated tcp cwnd */ ++ u16_t mss; ++ ++ /* for hystart*/ ++ u8_t sample_cnt; /* number of samples to decide curr_rtt */ ++ u8_t found; /* the exit point is found? */ ++ u32_t round_start; /* beginning of each round */ ++ u32_t end_seq; /* end_seq of the round */ ++ u32_t last_ack; /* last time when the ACK spacing is close */ ++ u32_t curr_rtt; /* the minimum rtt of current round */ ++}; ++ + /* used with check_seqnos() */ + #define SEQNO1 (0xFFFFFF00 - TCP_MSS) + #define ISS 6510 +@@ -25,7 +48,12 @@ static u32_t seqnos[] = { + SEQNO1 + (2 * TCP_MSS), + SEQNO1 + (3 * TCP_MSS), + SEQNO1 + (4 * TCP_MSS), +- SEQNO1 + (5 * TCP_MSS) }; ++ SEQNO1 + (5 * TCP_MSS), ++ SEQNO1 + (6 * TCP_MSS), ++ SEQNO1 + (7 * TCP_MSS), ++ SEQNO1 + (8 * TCP_MSS), ++ SEQNO1 + (9 * TCP_MSS), ++ SEQNO1 + (10 * TCP_MSS) }; + + static u8_t test_tcp_timer; + +@@ -524,7 +552,7 @@ START_TEST(test_tcp_fast_retx_recover) + EXPECT_RET(pcb->dupacks == 3); + memset(&txcounters, 0, sizeof(txcounters)); + /* @todo: check expected data?*/ +- ++ + /* send data5, not output yet */ + err = tcp_write(pcb, data5, sizeof(data5), TCP_WRITE_FLAG_COPY); + EXPECT_RET(err == ERR_OK); +@@ -1090,6 +1118,8 @@ START_TEST(test_tcp_rto_tracking) + pcb->mss = TCP_MSS; + /* Set congestion window large enough to send all our segments */ + pcb->cwnd = 5*TCP_MSS; ++ /* This test rely on lwip reno congestion avoid implementation*/ ++ pcb->cong_ops = &tcp_ca_reno; + + /* send 5 mss-sized segments */ + for (i = 0; i < 5; i++) { +@@ -1263,7 +1293,7 @@ static void test_tcp_rto_timeout_impl(int link_down) + /* check our pcb is no longer active */ + for (cur = tcp_active_pcbs; cur != NULL; cur = cur->next) { + EXPECT(cur != pcb); +- } ++ } + EXPECT_RET(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 0); + } + +@@ -1359,7 +1389,7 @@ static void test_tcp_rto_timeout_syn_sent_impl(int link_down) + /* check our pcb is no longer active */ + for (cur = tcp_active_pcbs; cur != NULL; cur = cur->next) { + EXPECT(cur != pcb); +- } ++ } + EXPECT_RET(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 0); + } + +@@ -1409,7 +1439,7 @@ static void test_tcp_zwp_timeout_impl(int link_down) + EXPECT(err == ERR_OK); + err = tcp_output(pcb); + EXPECT(err == ERR_OK); +- ++ + /* verify segment is in-flight */ + EXPECT(pcb->unsent == NULL); + check_seqnos(pcb->unacked, 1, seqnos); +@@ -1488,7 +1518,7 @@ static void test_tcp_zwp_timeout_impl(int link_down) + /* check our pcb is no longer active */ + for (cur = tcp_active_pcbs; cur != NULL; cur = cur->next) { + EXPECT(cur != pcb); +- } ++ } + EXPECT_RET(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 0); + } + +@@ -1664,6 +1694,798 @@ START_TEST(test_tcp_persist_split) + } + END_TEST + ++START_TEST(test_tcp_ca_cubic_slowstart) ++{ ++ struct netif netif; ++ struct test_tcp_txcounters txcounters; ++ struct test_tcp_counters counters; ++ struct tcp_pcb* pcb; ++ struct pbuf* p; ++ struct cubictcp* ca; ++ err_t err; ++ size_t i; ++ LWIP_UNUSED_ARG(_i); ++ ++ for (i = 0; i < sizeof(tx_data); i++) { ++ tx_data[i] = (u8_t)i; ++ } ++ ++ /* initialize local vars */ ++ test_tcp_init_netif(&netif, &txcounters, &test_local_ip, &test_netmask); ++ memset(&counters, 0, sizeof(counters)); ++ ++ /* create and initialize the pcb */ ++ tcp_ticks = SEQNO1 - ISS; ++ pcb = test_tcp_new_counters_pcb(&counters); ++ EXPECT_RET(pcb != NULL); ++ tcp_set_state(pcb, ESTABLISHED, &test_local_ip, &test_remote_ip, TEST_LOCAL_PORT, TEST_REMOTE_PORT); ++ ca = (struct cubictcp*) pcb->tcp_congestion_priv; ++ if(pcb->cong_ops->init != NULL) ++ pcb->cong_ops->init(pcb); ++ pcb->mss = TCP_MSS; ++ /* Init congestion parameters */ ++ pcb->ssthresh = 3*TCP_MSS; ++ pcb->cwnd = 1*TCP_MSS; ++ pcb->cong_ops = &tcp_ca_cubic; ++ ++ /* send 1 mss-sized segments */ ++ for (i = 0; i < 1; i++) { ++ err = tcp_write(pcb, &tx_data[0], TCP_MSS, TCP_WRITE_FLAG_COPY); ++ EXPECT_RET(err == ERR_OK); ++ } ++ check_seqnos(pcb->unsent, 1, seqnos); ++ EXPECT(pcb->unacked == NULL); ++ lwip_sys_now = 1234; ++ err = tcp_output(pcb); ++ EXPECT_RET(err == ERR_OK); ++ EXPECT(txcounters.num_tx_calls == 1); ++ EXPECT(txcounters.num_tx_bytes == 1 * (TCP_MSS + 40U)); ++ memset(&txcounters, 0, sizeof(txcounters)); ++ /* Check segment in-flight */ ++ EXPECT(pcb->unsent == NULL); ++ check_seqnos(pcb->unacked, 1, seqnos); ++ /* Check recorded last send time*/ ++ EXPECT(pcb->lsndtime == lwip_sys_now); ++ /* Check is_cwnd_limited*/ ++ EXPECT(pcb->is_cwnd_limited == 0); ++ ++ /* ACK first segment, rtt = 1000 ms*/ ++ lwip_sys_now += 1000; ++ p = tcp_create_rx_segment(pcb, NULL, 0, 0, TCP_MSS, TCP_ACK); ++ test_tcp_input(p, &netif); ++ /* Not cwnd limited, do not update cwnd*/ ++ EXPECT(pcb->cwnd == 1*TCP_MSS); ++ EXPECT(ca->epoch_start == 0); ++ EXPECT(ca->delay_min == 1000); ++ ++ /* send 10 mss-sized segments */ ++ for (i = 0; i < 10; i++) { ++ err = tcp_write(pcb, &tx_data[0], TCP_MSS, TCP_WRITE_FLAG_COPY); ++ EXPECT_RET(err == ERR_OK); ++ } ++ check_seqnos(pcb->unsent, 10, &seqnos[1]); ++ EXPECT(pcb->unacked == NULL); ++ lwip_sys_now += 2000; ++ err = tcp_output(pcb); ++ EXPECT_RET(err == ERR_OK); ++ EXPECT(txcounters.num_tx_calls == 1); ++ EXPECT(txcounters.num_tx_bytes == 1 * (TCP_MSS + 40U)); ++ memset(&txcounters, 0, sizeof(txcounters)); ++ /* Check segment in-flight */ ++ check_seqnos(pcb->unsent, 9, &seqnos[2]); ++ check_seqnos(pcb->unacked, 1, &seqnos[1]); ++ /* Check recorded last send time*/ ++ EXPECT(pcb->lsndtime == lwip_sys_now); ++ /* Check is_cwnd_limited*/ ++ EXPECT(pcb->is_cwnd_limited == 1); ++ /* ACK first segment, rtt = 800 ms*/ ++ lwip_sys_now += 800; ++ p = tcp_create_rx_segment(pcb, NULL, 0, 0, TCP_MSS, TCP_ACK); ++ test_tcp_input(p, &netif); ++ /* Check cwnd*/ ++ EXPECT(pcb->cwnd == (tcpwnd_size_t)(2*pcb->mss)); ++ EXPECT(ca->epoch_start == 0); ++ EXPECT(ca->delay_min == 800); ++ ++ /* Ensure next 2 segments are in flight */ ++ EXPECT(txcounters.num_tx_calls == 2); ++ EXPECT(txcounters.num_tx_bytes == 2 * (TCP_MSS + 40U)); ++ memset(&txcounters, 0, sizeof(txcounters)); ++ check_seqnos(pcb->unsent, 7, &seqnos[4]); ++ check_seqnos(pcb->unacked, 2, &seqnos[2]); ++ EXPECT(pcb->lsndtime == lwip_sys_now); ++ EXPECT(pcb->is_cwnd_limited == 1); ++ ++ /* ACK 2 segments, rtt = 900 ms*/ ++ lwip_sys_now += 900; ++ p = tcp_create_rx_segment(pcb, NULL, 0, 0, 2*TCP_MSS, TCP_ACK); ++ test_tcp_input(p, &netif); ++ /* Check cwnd, cwnd is grown to ssthresh*/ ++ EXPECT(pcb->cwnd == (tcpwnd_size_t)(3*pcb->mss)); ++ EXPECT(ca->epoch_start == lwip_sys_now); ++ EXPECT(ca->delay_min == 800); ++ EXPECT(ca->ack_cnt == pcb->mss); ++ EXPECT(ca->cubic_origin_point == 3); ++ ++ /* make sure the pcb is freed */ ++ EXPECT_RET(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 1); ++ tcp_abort(pcb); ++ EXPECT_RET(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 0); ++} ++END_TEST ++ ++START_TEST(test_tcp_ca_cubic_hystart_ack_train) ++{ ++ struct netif netif; ++ struct test_tcp_txcounters txcounters; ++ struct test_tcp_counters counters; ++ struct tcp_pcb* pcb; ++ struct pbuf* p; ++ struct cubictcp* ca; ++ u32_t test_round_start; ++ u32_t test_send_seq; ++ err_t err; ++ size_t i; ++ LWIP_UNUSED_ARG(_i); ++ ++ for (i = 0; i < sizeof(tx_data); i++) { ++ tx_data[i] = (u8_t)i; ++ } ++ ++ /* initialize local vars */ ++ test_tcp_init_netif(&netif, &txcounters, &test_local_ip, &test_netmask); ++ memset(&counters, 0, sizeof(counters)); ++ ++ /* create and initialize the pcb */ ++ tcp_ticks = SEQNO1 - ISS; ++ pcb = test_tcp_new_counters_pcb(&counters); ++ EXPECT_RET(pcb != NULL); ++ tcp_set_state(pcb, ESTABLISHED, &test_local_ip, &test_remote_ip, TEST_LOCAL_PORT, TEST_REMOTE_PORT); ++ ca = (struct cubictcp*) pcb->tcp_congestion_priv; ++ if(pcb->cong_ops->init != NULL) ++ pcb->cong_ops->init(pcb); ++ pcb->mss = TCP_MSS; ++ /* Init congestion parameters */ ++ pcb->ssthresh = 32*TCP_MSS; ++ pcb->cwnd = 16*TCP_MSS; ++ pcb->cong_ops = &tcp_ca_cubic; ++ lwip_sys_now = 1000; ++ ++ /* send and ack 1 mss-sized segments to set delay_min*/ ++ err = tcp_write(pcb, &tx_data[0], TCP_MSS, TCP_WRITE_FLAG_COPY); ++ EXPECT_RET(err == ERR_OK); ++ err = tcp_output(pcb); ++ EXPECT_RET(err == ERR_OK); ++ lwip_sys_now += 10; ++ p = tcp_create_rx_segment(pcb, NULL, 0, 0, TCP_MSS, TCP_ACK); ++ test_tcp_input(p, &netif); ++ /* check delay_min equal 10ms*/ ++ EXPECT(ca->delay_min == 10); ++ EXPECT(txcounters.num_tx_calls == 1); ++ EXPECT(txcounters.num_tx_bytes == 1 * (TCP_MSS + 40U)); ++ memset(&txcounters, 0, sizeof(txcounters)); ++ ++ /* send 20 mss-sized segments in a train */ ++ for (i = 0; i < 20; i++) { ++ err = tcp_write(pcb, &tx_data[0], TCP_MSS, TCP_WRITE_FLAG_COPY); ++ EXPECT_RET(err == ERR_OK); ++ err = tcp_output(pcb); ++ EXPECT_RET(err == ERR_OK); ++ lwip_sys_now += 2; ++ } ++ ++ EXPECT(txcounters.num_tx_calls == 16); ++ EXPECT(txcounters.num_tx_bytes == 16 * (TCP_MSS + 40U)); ++ memset(&txcounters, 0, sizeof(txcounters)); ++ test_send_seq = pcb->snd_nxt; ++ ++ /* Check is_cwnd_limited*/ ++ EXPECT(pcb->is_cwnd_limited == 1); ++ ++ /* ACK first segment, rtt = 20ms, triggers hystart reset*/ ++ p = tcp_create_rx_segment(pcb, NULL, 0, 0, TCP_MSS, TCP_ACK); ++ test_tcp_input(p, &netif); ++ /* Check hystart_reset*/ ++ EXPECT(ca->round_start == lwip_sys_now); ++ EXPECT(ca->last_ack == lwip_sys_now); ++ EXPECT(ca->end_seq == test_send_seq); ++ test_round_start = ca->round_start; ++ test_send_seq = ca->end_seq; ++ ++ /* ACK 5 segment in a train*/ ++ for (i = 0; i < 5; i++) { ++ lwip_sys_now += 2; ++ p = tcp_create_rx_segment(pcb, NULL, 0, 0, TCP_MSS, TCP_ACK); ++ test_tcp_input(p, &netif); ++ EXPECT(ca->round_start == test_round_start); ++ EXPECT(ca->last_ack == lwip_sys_now); ++ EXPECT(ca->end_seq == test_send_seq); ++ } ++ ++ /* ACK 1 more that trigger hystart ack train*/ ++ lwip_sys_now += 2; ++ p = tcp_create_rx_segment(pcb, NULL, 0, 0, TCP_MSS, TCP_ACK); ++ test_tcp_input(p, &netif); ++ EXPECT(ca->found == 1); ++ EXPECT(pcb->ssthresh == pcb->cwnd); ++ ++ /* make sure the pcb is freed */ ++ EXPECT_RET(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 1); ++ tcp_abort(pcb); ++ EXPECT_RET(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 0); ++} ++END_TEST ++ ++START_TEST(test_tcp_ca_cubic_hystart_delay) ++{ ++ struct netif netif; ++ struct test_tcp_txcounters txcounters; ++ struct test_tcp_counters counters; ++ struct tcp_pcb* pcb; ++ struct pbuf* p; ++ struct cubictcp* ca; ++ u32_t test_round_start; ++ u32_t test_send_seq; ++ err_t err; ++ size_t i; ++ LWIP_UNUSED_ARG(_i); ++ ++ for (i = 0; i < sizeof(tx_data); i++) { ++ tx_data[i] = (u8_t)i; ++ } ++ ++ /* initialize local vars */ ++ test_tcp_init_netif(&netif, &txcounters, &test_local_ip, &test_netmask); ++ memset(&counters, 0, sizeof(counters)); ++ ++ /* create and initialize the pcb */ ++ tcp_ticks = SEQNO1 - ISS; ++ pcb = test_tcp_new_counters_pcb(&counters); ++ EXPECT_RET(pcb != NULL); ++ tcp_set_state(pcb, ESTABLISHED, &test_local_ip, &test_remote_ip, TEST_LOCAL_PORT, TEST_REMOTE_PORT); ++ ca = (struct cubictcp*) pcb->tcp_congestion_priv; ++ if(pcb->cong_ops->init != NULL) ++ pcb->cong_ops->init(pcb); ++ pcb->mss = TCP_MSS; ++ /* Init congestion parameters */ ++ pcb->ssthresh = 32*TCP_MSS; ++ pcb->cwnd = 16*TCP_MSS; ++ pcb->cong_ops = &tcp_ca_cubic; ++ lwip_sys_now = 1000; ++ ++ /* send and ack 1 mss-sized segments to set delay_min*/ ++ err = tcp_write(pcb, &tx_data[0], TCP_MSS, TCP_WRITE_FLAG_COPY); ++ EXPECT_RET(err == ERR_OK); ++ err = tcp_output(pcb); ++ EXPECT_RET(err == ERR_OK); ++ lwip_sys_now += 40; ++ p = tcp_create_rx_segment(pcb, NULL, 0, 0, TCP_MSS, TCP_ACK); ++ test_tcp_input(p, &netif); ++ /* check delay_min equal 40ms*/ ++ EXPECT(ca->delay_min == 40); ++ EXPECT(txcounters.num_tx_calls == 1); ++ EXPECT(txcounters.num_tx_bytes == 1 * (TCP_MSS + 40U)); ++ memset(&txcounters, 0, sizeof(txcounters)); ++ ++ /* send 20 mss-sized segments*/ ++ for (i = 0; i < 20; i++) { ++ err = tcp_write(pcb, &tx_data[0], TCP_MSS, TCP_WRITE_FLAG_COPY); ++ EXPECT_RET(err == ERR_OK); ++ err = tcp_output(pcb); ++ EXPECT_RET(err == ERR_OK); ++ } ++ ++ EXPECT(txcounters.num_tx_calls == 16); ++ EXPECT(txcounters.num_tx_bytes == 16 * (TCP_MSS + 40U)); ++ memset(&txcounters, 0, sizeof(txcounters)); ++ test_send_seq = pcb->snd_nxt; ++ ++ /* Check is_cwnd_limited*/ ++ EXPECT(pcb->is_cwnd_limited == 1); ++ ++ /* ACK first segment, rtt = 46ms, triggers hystart reset*/ ++ lwip_sys_now += 46; ++ p = tcp_create_rx_segment(pcb, NULL, 0, 0, TCP_MSS, TCP_ACK); ++ test_tcp_input(p, &netif); ++ /* Check hystart_reset*/ ++ EXPECT(ca->round_start == lwip_sys_now); ++ EXPECT(ca->curr_rtt == 46); ++ EXPECT(ca->sample_cnt == 1); ++ EXPECT(ca->end_seq == test_send_seq); ++ EXPECT(ca->delay_min == 40); ++ test_round_start = ca->round_start; ++ test_send_seq = ca->end_seq; ++ ++ /* ACK 7 segment*/ ++ for (i = 0; i < 7; i++) { ++ p = tcp_create_rx_segment(pcb, NULL, 0, 0, TCP_MSS, TCP_ACK); ++ test_tcp_input(p, &netif); ++ EXPECT(ca->round_start == test_round_start); ++ EXPECT(ca->curr_rtt == 46); ++ EXPECT(ca->sample_cnt == i+2); ++ EXPECT(ca->end_seq == test_send_seq); ++ EXPECT(ca->delay_min == 40); ++ } ++ ++ /* ACK 1 more that trigger hystart delay*/ ++ p = tcp_create_rx_segment(pcb, NULL, 0, 0, TCP_MSS, TCP_ACK); ++ test_tcp_input(p, &netif); ++ EXPECT(ca->found == 1); ++ EXPECT(pcb->ssthresh == pcb->cwnd); ++ ++ /* make sure the pcb is freed */ ++ EXPECT_RET(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 1); ++ tcp_abort(pcb); ++ EXPECT_RET(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 0); ++} ++END_TEST ++ ++START_TEST(test_tcp_ca_cubic) ++{ ++ struct netif netif; ++ struct test_tcp_txcounters txcounters; ++ struct test_tcp_counters counters; ++ struct tcp_pcb* pcb; ++ struct pbuf* p; ++ struct cubictcp* ca; ++ u32_t test_epoch_start; ++ err_t err; ++ size_t i; ++ LWIP_UNUSED_ARG(_i); ++ ++ for (i = 0; i < sizeof(tx_data); i++) { ++ tx_data[i] = (u8_t)i; ++ } ++ ++ /* initialize local vars */ ++ test_tcp_init_netif(&netif, &txcounters, &test_local_ip, &test_netmask); ++ memset(&counters, 0, sizeof(counters)); ++ ++ /* create and initialize the pcb */ ++ tcp_ticks = SEQNO1 - ISS; ++ pcb = test_tcp_new_counters_pcb(&counters); ++ EXPECT_RET(pcb != NULL); ++ tcp_set_state(pcb, ESTABLISHED, &test_local_ip, &test_remote_ip, TEST_LOCAL_PORT, TEST_REMOTE_PORT); ++ ca = (struct cubictcp*) pcb->tcp_congestion_priv; ++ if(pcb->cong_ops->init != NULL) ++ pcb->cong_ops->init(pcb); ++ pcb->mss = TCP_MSS; ++ /* Init congestion parameters */ ++ pcb->ssthresh = 10*TCP_MSS; ++ pcb->cwnd = 10*TCP_MSS; ++ pcb->cong_ops = &tcp_ca_cubic; ++ lwip_sys_now = 1000; ++ ++ /* send and ack 1 mss-sized segments to set delay_min*/ ++ err = tcp_write(pcb, &tx_data[0], TCP_MSS, TCP_WRITE_FLAG_COPY); ++ EXPECT_RET(err == ERR_OK); ++ err = tcp_output(pcb); ++ EXPECT_RET(err == ERR_OK); ++ lwip_sys_now += 40; ++ p = tcp_create_rx_segment(pcb, NULL, 0, 0, TCP_MSS, TCP_ACK); ++ test_tcp_input(p, &netif); ++ /* check delay_min equal 10ms*/ ++ EXPECT(ca->delay_min == 40); ++ EXPECT(txcounters.num_tx_calls == 1); ++ EXPECT(txcounters.num_tx_bytes == 1 * (TCP_MSS + 40U)); ++ memset(&txcounters, 0, sizeof(txcounters)); ++ ++ /* send 20 mss-sized segments*/ ++ for (i = 0; i < 20; i++) { ++ err = tcp_write(pcb, &tx_data[0], TCP_MSS, TCP_WRITE_FLAG_COPY); ++ EXPECT_RET(err == ERR_OK); ++ err = tcp_output(pcb); ++ EXPECT_RET(err == ERR_OK); ++ } ++ ++ EXPECT(txcounters.num_tx_calls == 10); ++ EXPECT(txcounters.num_tx_bytes == 10 * (TCP_MSS + 40U)); ++ memset(&txcounters, 0, sizeof(txcounters)); ++ ++ /* Check is_cwnd_limited*/ ++ EXPECT(pcb->is_cwnd_limited == 1); ++ ++ /* ACK first segment */ ++ p = tcp_create_rx_segment(pcb, NULL, 0, 0, TCP_MSS, TCP_ACK); ++ test_tcp_input(p, &netif); ++ /* Check epoch start*/ ++ EXPECT(ca->epoch_start == lwip_sys_now); ++ EXPECT(ca->ack_cnt == 1 * TCP_MSS); ++ EXPECT(ca->tcp_cwnd == 10); ++ EXPECT(ca->cubic_K == 0); ++ EXPECT(ca->cubic_origin_point == 10); ++ /* initial growth set to 5% */ ++ EXPECT(ca->cnt == 20); ++ /* no change in cwnd */ ++ EXPECT(pcb->bytes_acked == 1 * TCP_MSS); ++ EXPECT(pcb->cwnd == 10 * TCP_MSS); ++ EXPECT(pcb->ssthresh == 10 * TCP_MSS); ++ test_epoch_start = ca->epoch_start; ++ ++ /* ACK next segment */ ++ lwip_sys_now += 1000; ++ p = tcp_create_rx_segment(pcb, NULL, 0, 0, TCP_MSS, TCP_ACK); ++ test_tcp_input(p, &netif); ++ EXPECT(ca->epoch_start == test_epoch_start); ++ EXPECT(ca->ack_cnt == 2 * TCP_MSS); ++ EXPECT(ca->tcp_cwnd == 10); ++ EXPECT(ca->cubic_K == 0); ++ EXPECT(ca->cubic_origin_point == 10); ++ /* initial growth set to 5% */ ++ EXPECT(ca->cnt == 20); ++ /* no change in cwnd */ ++ EXPECT(pcb->bytes_acked == 2 * TCP_MSS); ++ EXPECT(pcb->cwnd == 10 * TCP_MSS); ++ EXPECT(pcb->ssthresh == 10 * TCP_MSS); ++ ++ /* ACK next segment, cwnd grows*/ ++ lwip_sys_now += 1000; ++ p = tcp_create_rx_segment(pcb, NULL, 0, 0, TCP_MSS, TCP_ACK); ++ test_tcp_input(p, &netif); ++ EXPECT(ca->epoch_start == test_epoch_start); ++ EXPECT(ca->ack_cnt == 3 * TCP_MSS); ++ EXPECT(ca->tcp_cwnd == 10); ++ EXPECT(ca->cubic_K == 0); ++ EXPECT(ca->cubic_origin_point == 10); ++ /* calculated raise count */ ++ EXPECT(ca->cnt == 3); ++ /* cwnd plus 1*/ ++ EXPECT(pcb->bytes_acked == 0); ++ EXPECT(pcb->cwnd == 11 * TCP_MSS); ++ EXPECT(pcb->ssthresh == 10 * TCP_MSS); ++ ++ /* 2 duplicated ACK*/ ++ p = tcp_create_rx_segment(pcb, NULL, 0, 0, 0, TCP_ACK); ++ EXPECT_RET(p != NULL); ++ test_tcp_input(p, &netif); ++ EXPECT_RET(pcb->dupacks == 1); ++ p = tcp_create_rx_segment(pcb, NULL, 0, 0, 0, TCP_ACK); ++ EXPECT_RET(p != NULL); ++ test_tcp_input(p, &netif); ++ EXPECT_RET(pcb->dupacks == 2); ++ ++ /* check there is no change after 2 dup ACK*/ ++ EXPECT(ca->epoch_start == test_epoch_start); ++ EXPECT(ca->ack_cnt == 3 * TCP_MSS); ++ EXPECT(ca->tcp_cwnd == 10); ++ EXPECT(ca->cubic_K == 0); ++ EXPECT(ca->cubic_origin_point == 10); ++ EXPECT(ca->cnt == 3); ++ ++ /* 3 duplicated ACK*/ ++ p = tcp_create_rx_segment(pcb, NULL, 0, 0, 0, TCP_ACK); ++ EXPECT_RET(p != NULL); ++ test_tcp_input(p, &netif); ++ EXPECT_RET(pcb->dupacks == 3); ++ /* Check ssthresh update */ ++ EXPECT(ca->epoch_start == 0); ++ EXPECT(ca->last_max_cwnd == 11); ++ EXPECT(pcb->ssthresh == 7 * TCP_MSS); ++ EXPECT(pcb->cwnd == 10 * TCP_MSS); ++ ++ /* ACK next segment, recover from fast retransmit*/ ++ lwip_sys_now += 1000; ++ p = tcp_create_rx_segment(pcb, NULL, 0, 0, TCP_MSS, TCP_ACK); ++ test_tcp_input(p, &netif); ++ /* Check epoch start*/ ++ EXPECT(ca->epoch_start == lwip_sys_now); ++ EXPECT(ca->ack_cnt == 1 * TCP_MSS); ++ EXPECT(ca->tcp_cwnd == 7); ++ EXPECT(ca->cubic_K >= 2200); ++ EXPECT(ca->cubic_K <= 2210); ++ EXPECT(ca->cubic_origin_point == 11); ++ EXPECT(ca->cnt == 7); ++ /* cwnd set to ssthresh*/ ++ EXPECT(pcb->bytes_acked == 1 * TCP_MSS); ++ EXPECT(pcb->cwnd == 7 * TCP_MSS); ++ EXPECT(pcb->ssthresh == 7 * TCP_MSS); ++ test_epoch_start = ca->epoch_start; ++ ++ /* ACK next segment */ ++ lwip_sys_now += 1000; ++ p = tcp_create_rx_segment(pcb, NULL, 0, 0, TCP_MSS, TCP_ACK); ++ test_tcp_input(p, &netif); ++ EXPECT(ca->epoch_start == test_epoch_start); ++ EXPECT(ca->ack_cnt == 2 * TCP_MSS); ++ EXPECT(ca->tcp_cwnd == 7); ++ EXPECT(ca->cubic_K >= 2200); ++ EXPECT(ca->cubic_K <= 2210); ++ EXPECT(ca->cubic_origin_point == 11); ++ /* calculated raise count */ ++ EXPECT(ca->cnt == 2); ++ /* cwnd plus 1*/ ++ EXPECT(pcb->bytes_acked == 0); ++ EXPECT(pcb->cwnd == 8 * TCP_MSS); ++ EXPECT(pcb->ssthresh == 7 * TCP_MSS); ++ ++ /* 3 duplicated ACK*/ ++ p = tcp_create_rx_segment(pcb, NULL, 0, 0, 0, TCP_ACK); ++ EXPECT_RET(p != NULL); ++ test_tcp_input(p, &netif); ++ EXPECT_RET(pcb->dupacks == 1); ++ p = tcp_create_rx_segment(pcb, NULL, 0, 0, 0, TCP_ACK); ++ EXPECT_RET(p != NULL); ++ test_tcp_input(p, &netif); ++ EXPECT_RET(pcb->dupacks == 2); ++ p = tcp_create_rx_segment(pcb, NULL, 0, 0, 0, TCP_ACK); ++ EXPECT_RET(p != NULL); ++ test_tcp_input(p, &netif); ++ EXPECT_RET(pcb->dupacks == 3); ++ ++ /* Check fast convergence*/ ++ EXPECT(ca->epoch_start == 0); ++ EXPECT(ca->last_max_cwnd == 6); ++ EXPECT(pcb->ssthresh == 5 * TCP_MSS); ++ EXPECT(pcb->cwnd == 8 * TCP_MSS); ++ ++ /* make sure the pcb is freed */ ++ EXPECT_RET(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 1); ++ tcp_abort(pcb); ++ EXPECT_RET(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 0); ++} ++END_TEST ++ ++START_TEST(test_tcp_ca_cubic_rto) ++{ ++ struct netif netif; ++ struct test_tcp_txcounters txcounters; ++ struct test_tcp_counters counters; ++ struct tcp_pcb* pcb; ++ struct pbuf* p; ++ struct cubictcp* ca; ++ u32_t test_epoch_start; ++ err_t err; ++ size_t i; ++ LWIP_UNUSED_ARG(_i); ++ ++ for (i = 0; i < sizeof(tx_data); i++) { ++ tx_data[i] = (u8_t)i; ++ } ++ ++ /* initialize local vars */ ++ test_tcp_init_netif(&netif, &txcounters, &test_local_ip, &test_netmask); ++ memset(&counters, 0, sizeof(counters)); ++ ++ /* create and initialize the pcb */ ++ tcp_ticks = SEQNO1 - ISS; ++ pcb = test_tcp_new_counters_pcb(&counters); ++ EXPECT_RET(pcb != NULL); ++ tcp_set_state(pcb, ESTABLISHED, &test_local_ip, &test_remote_ip, TEST_LOCAL_PORT, TEST_REMOTE_PORT); ++ ca = (struct cubictcp*) pcb->tcp_congestion_priv; ++ if(pcb->cong_ops->init != NULL) ++ pcb->cong_ops->init(pcb); ++ pcb->mss = TCP_MSS; ++ /* Init congestion parameters */ ++ pcb->ssthresh = 10*TCP_MSS; ++ pcb->cwnd = 10*TCP_MSS; ++ pcb->cong_ops = &tcp_ca_cubic; ++ lwip_sys_now = 1000; ++ ++ /* send and ack 1 mss-sized segments to set delay_min*/ ++ err = tcp_write(pcb, &tx_data[0], TCP_MSS, TCP_WRITE_FLAG_COPY); ++ EXPECT_RET(err == ERR_OK); ++ err = tcp_output(pcb); ++ EXPECT_RET(err == ERR_OK); ++ lwip_sys_now += 40; ++ p = tcp_create_rx_segment(pcb, NULL, 0, 0, TCP_MSS, TCP_ACK); ++ test_tcp_input(p, &netif); ++ /* check delay_min equal 10ms*/ ++ EXPECT(ca->delay_min == 40); ++ EXPECT(txcounters.num_tx_calls == 1); ++ EXPECT(txcounters.num_tx_bytes == 1 * (TCP_MSS + 40U)); ++ memset(&txcounters, 0, sizeof(txcounters)); ++ ++ /* send 20 mss-sized segments*/ ++ for (i = 0; i < 20; i++) { ++ err = tcp_write(pcb, &tx_data[0], TCP_MSS, TCP_WRITE_FLAG_COPY); ++ EXPECT_RET(err == ERR_OK); ++ err = tcp_output(pcb); ++ EXPECT_RET(err == ERR_OK); ++ } ++ ++ EXPECT(txcounters.num_tx_calls == 10); ++ EXPECT(txcounters.num_tx_bytes == 10 * (TCP_MSS + 40U)); ++ memset(&txcounters, 0, sizeof(txcounters)); ++ ++ /* Check is_cwnd_limited*/ ++ EXPECT(pcb->is_cwnd_limited == 1); ++ ++ /* ACK first segment */ ++ p = tcp_create_rx_segment(pcb, NULL, 0, 0, TCP_MSS, TCP_ACK); ++ test_tcp_input(p, &netif); ++ /* Check epoch start*/ ++ EXPECT(ca->epoch_start == lwip_sys_now); ++ EXPECT(ca->ack_cnt == 1 * TCP_MSS); ++ EXPECT(ca->tcp_cwnd == 10); ++ EXPECT(ca->cubic_K == 0); ++ EXPECT(ca->cubic_origin_point == 10); ++ /* initial growth set to 5% */ ++ EXPECT(ca->cnt == 20); ++ /* no change in cwnd */ ++ EXPECT(pcb->bytes_acked == 1 * TCP_MSS); ++ EXPECT(pcb->cwnd == 10 * TCP_MSS); ++ EXPECT(pcb->ssthresh == 10 * TCP_MSS); ++ test_epoch_start = ca->epoch_start; ++ ++ /* ACK next segment */ ++ lwip_sys_now += 1000; ++ p = tcp_create_rx_segment(pcb, NULL, 0, 0, TCP_MSS, TCP_ACK); ++ test_tcp_input(p, &netif); ++ EXPECT(ca->epoch_start == test_epoch_start); ++ EXPECT(ca->ack_cnt == 2 * TCP_MSS); ++ EXPECT(ca->tcp_cwnd == 10); ++ EXPECT(ca->cubic_K == 0); ++ EXPECT(ca->cubic_origin_point == 10); ++ /* initial growth set to 5% */ ++ EXPECT(ca->cnt == 20); ++ /* no change in cwnd */ ++ EXPECT(pcb->bytes_acked == 2 * TCP_MSS); ++ EXPECT(pcb->cwnd == 10 * TCP_MSS); ++ EXPECT(pcb->ssthresh == 10 * TCP_MSS); ++ ++ /* ACK next segment, cwnd grows*/ ++ lwip_sys_now += 1000; ++ p = tcp_create_rx_segment(pcb, NULL, 0, 0, TCP_MSS, TCP_ACK); ++ test_tcp_input(p, &netif); ++ EXPECT(ca->epoch_start == test_epoch_start); ++ EXPECT(ca->ack_cnt == 3 * TCP_MSS); ++ EXPECT(ca->tcp_cwnd == 10); ++ EXPECT(ca->cubic_K == 0); ++ EXPECT(ca->cubic_origin_point == 10); ++ /* calculated raise count */ ++ EXPECT(ca->cnt == 3); ++ /* cwnd plus 1*/ ++ EXPECT(pcb->bytes_acked == 0); ++ EXPECT(pcb->cwnd == 11 * TCP_MSS); ++ EXPECT(pcb->ssthresh == 10 * TCP_MSS); ++ ++ /* Force us into retransmisson timeout */ ++ while (!(pcb->flags & TF_RTO)) { ++ test_tcp_tmr(); ++ } ++ /* Check rto reset*/ ++ EXPECT(ca->epoch_start == 0); ++ EXPECT(ca->ack_cnt == 0); ++ EXPECT(ca->tcp_cwnd == 0); ++ EXPECT(ca->cubic_K == 0); ++ EXPECT(ca->cubic_origin_point == 0); ++ EXPECT(ca->cnt == 0); ++ EXPECT(ca->delay_min == 0); ++ /* cwnd plus 1*/ ++ EXPECT(pcb->bytes_acked == 0); ++ EXPECT(pcb->cwnd == 1 * TCP_MSS); ++ EXPECT(pcb->ssthresh == 7 * TCP_MSS); ++ ++ /* make sure the pcb is freed */ ++ EXPECT_RET(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 1); ++ tcp_abort(pcb); ++ EXPECT_RET(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 0); ++} ++END_TEST ++ ++START_TEST(test_tcp_ca_cubic_tcp_friendliness) ++{ ++ struct netif netif; ++ struct test_tcp_txcounters txcounters; ++ struct test_tcp_counters counters; ++ struct tcp_pcb* pcb; ++ struct pbuf* p; ++ struct cubictcp* ca; ++ u32_t test_epoch_start; ++ err_t err; ++ size_t i; ++ LWIP_UNUSED_ARG(_i); ++ ++ for (i = 0; i < sizeof(tx_data); i++) { ++ tx_data[i] = (u8_t)i; ++ } ++ ++ /* initialize local vars */ ++ test_tcp_init_netif(&netif, &txcounters, &test_local_ip, &test_netmask); ++ memset(&counters, 0, sizeof(counters)); ++ ++ /* create and initialize the pcb */ ++ tcp_ticks = SEQNO1 - ISS; ++ pcb = test_tcp_new_counters_pcb(&counters); ++ EXPECT_RET(pcb != NULL); ++ tcp_set_state(pcb, ESTABLISHED, &test_local_ip, &test_remote_ip, TEST_LOCAL_PORT, TEST_REMOTE_PORT); ++ ca = (struct cubictcp*) pcb->tcp_congestion_priv; ++ if(pcb->cong_ops->init != NULL) ++ pcb->cong_ops->init(pcb); ++ pcb->mss = TCP_MSS; ++ /* Init congestion parameters */ ++ pcb->ssthresh = 4*TCP_MSS; ++ pcb->cwnd = 4*TCP_MSS; ++ pcb->cong_ops = &tcp_ca_cubic; ++ lwip_sys_now = 1000; ++ ++ /* send and ack 1 mss-sized segments to set delay_min*/ ++ err = tcp_write(pcb, &tx_data[0], TCP_MSS, TCP_WRITE_FLAG_COPY); ++ EXPECT_RET(err == ERR_OK); ++ err = tcp_output(pcb); ++ EXPECT_RET(err == ERR_OK); ++ lwip_sys_now += 40; ++ p = tcp_create_rx_segment(pcb, NULL, 0, 0, TCP_MSS, TCP_ACK); ++ test_tcp_input(p, &netif); ++ /* check delay_min equal 10ms*/ ++ EXPECT(ca->delay_min == 40); ++ EXPECT(txcounters.num_tx_calls == 1); ++ EXPECT(txcounters.num_tx_bytes == 1 * (TCP_MSS + 40U)); ++ memset(&txcounters, 0, sizeof(txcounters)); ++ ++ /* send 20 mss-sized segments*/ ++ for (i = 0; i < 20; i++) { ++ err = tcp_write(pcb, &tx_data[0], TCP_MSS, TCP_WRITE_FLAG_COPY); ++ EXPECT_RET(err == ERR_OK); ++ err = tcp_output(pcb); ++ EXPECT_RET(err == ERR_OK); ++ } ++ ++ EXPECT(txcounters.num_tx_calls == 4); ++ EXPECT(txcounters.num_tx_bytes == 4 * (TCP_MSS + 40U)); ++ memset(&txcounters, 0, sizeof(txcounters)); ++ ++ /* Check is_cwnd_limited*/ ++ EXPECT(pcb->is_cwnd_limited == 1); ++ ++ /* ACK first segment */ ++ lwip_sys_now += 50; ++ p = tcp_create_rx_segment(pcb, NULL, 0, 0, TCP_MSS, TCP_ACK); ++ test_tcp_input(p, &netif); ++ /* Check epoch start*/ ++ EXPECT(ca->epoch_start == lwip_sys_now); ++ EXPECT(ca->ack_cnt == 1 * TCP_MSS); ++ EXPECT(ca->tcp_cwnd == 4); ++ EXPECT(ca->cubic_K == 0); ++ EXPECT(ca->cubic_origin_point == 4); ++ /* initial growth set to 5% */ ++ EXPECT(ca->cnt == 20); ++ /* no change in cwnd */ ++ EXPECT(pcb->bytes_acked == 1 * TCP_MSS); ++ EXPECT(pcb->cwnd == 4 * TCP_MSS); ++ EXPECT(pcb->ssthresh == 4 * TCP_MSS); ++ test_epoch_start = ca->epoch_start; ++ ++ /* ACK 6 segments */ ++ for (i = 0; i < 6; i++) { ++ lwip_sys_now += 50; ++ p = tcp_create_rx_segment(pcb, NULL, 0, 0, TCP_MSS, TCP_ACK); ++ test_tcp_input(p, &netif); ++ EXPECT(ca->epoch_start == test_epoch_start); ++ EXPECT(ca->ack_cnt == (i+2) * TCP_MSS); ++ EXPECT(ca->tcp_cwnd == 4); ++ EXPECT(ca->cubic_K == 0); ++ EXPECT(ca->cubic_origin_point == 4); ++ EXPECT(ca->cnt == 20); ++ EXPECT(pcb->bytes_acked == (i+2) * TCP_MSS); ++ EXPECT(pcb->cwnd == 4 * TCP_MSS); ++ EXPECT(pcb->ssthresh == 4 * TCP_MSS); ++ } ++ ++ /* ACK next segment, go into tcp friendliness*/ ++ lwip_sys_now += 50; ++ p = tcp_create_rx_segment(pcb, NULL, 0, 0, TCP_MSS, TCP_ACK); ++ test_tcp_input(p, &netif); ++ EXPECT(ca->epoch_start == test_epoch_start); ++ EXPECT(ca->ack_cnt == 1 * TCP_MSS); ++ EXPECT(ca->tcp_cwnd == 5); ++ EXPECT(ca->cubic_K == 0); ++ EXPECT(ca->cubic_origin_point == 4); ++ /* calculated raise count */ ++ EXPECT(ca->cnt == 4); ++ /* cwnd plus 1*/ ++ EXPECT(pcb->bytes_acked == TCP_MSS); ++ EXPECT(pcb->cwnd == 5 * TCP_MSS); ++ EXPECT(pcb->ssthresh == 4 * TCP_MSS); ++ ++ ++ /* make sure the pcb is freed */ ++ EXPECT_RET(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 1); ++ tcp_abort(pcb); ++ EXPECT_RET(MEMP_STATS_GET(used, MEMP_TCP_PCB) == 0); ++} ++END_TEST ++ + /** Create the suite including all tests for this module */ + Suite * + tcp_suite(void) +@@ -1689,7 +2511,13 @@ tcp_suite(void) + TESTFUNC(test_tcp_rto_timeout_syn_sent_link_down), + TESTFUNC(test_tcp_zwp_timeout), + TESTFUNC(test_tcp_zwp_timeout_link_down), +- TESTFUNC(test_tcp_persist_split) ++ TESTFUNC(test_tcp_persist_split), ++ TESTFUNC(test_tcp_ca_cubic_slowstart), ++ TESTFUNC(test_tcp_ca_cubic_hystart_ack_train), ++ TESTFUNC(test_tcp_ca_cubic_hystart_delay), ++ TESTFUNC(test_tcp_ca_cubic), ++ TESTFUNC(test_tcp_ca_cubic_rto), ++ TESTFUNC(test_tcp_ca_cubic_tcp_friendliness) + }; + return create_suite("TCP", tests, sizeof(tests)/sizeof(testfunc), tcp_setup, tcp_teardown); + } +-- +2.33.0 + diff --git a/lwip.spec b/lwip.spec index f5301715fb2a7e89618f7b144b9d41083c409dc0..d9e79a8c941064ab4f92acb7fefb273e6ee8830f 100644 --- a/lwip.spec +++ b/lwip.spec @@ -4,7 +4,7 @@ Summary: lwip is a small independent implementation of the TCP/IP protocol suite Name: lwip Version: 2.1.3 -Release: 76 +Release: 77 License: BSD URL: http://savannah.nongnu.org/projects/lwip/ Source0: http://download.savannah.nongnu.org/releases/lwip/%{name}-%{version}.zip @@ -83,6 +83,7 @@ Patch9067: 0068-enable-UDP-CKSUM-in-lwip.patch Patch9068: 0069-add-error-check-in-hugepage_init-and-sys_mbox_free.patch Patch9069: 0070-add-CHECKSUM_UDP-when-not-support-OFFLOAD_UDP_CHECKS.patch Patch9070: 0071-fix-pbuf-tot_len-incorrect-after-pbuf_split_64k-is-c.patch +Patch9071: 0072-add-tcp-cubic-congestion-control-algorithm.patch BuildRequires: gcc-c++ dos2unix dpdk-devel @@ -113,6 +114,9 @@ cd %{_builddir}/%{name}-%{version}/src %{_libdir}/liblwip.a %changelog +* Sat Sep 23 2023 whr - 2.1.3-77 +- add tcp cubic congestion control algorithm + * Fri Sep 15 2023 jiangheng - 2.1.3-76 - fix pbuf->tot_len incorrect after pbuf_split_64k is called