7 Commits

Author SHA1 Message Date
Lukas Prause 52cb1fe1f1 Adds minimum RTT probing. 2026-06-12 14:01:25 +02:00
Lukas Prause 96fed81e57 Update the README file to reflect the current state of the project. 2026-03-27 14:52:29 +01:00
Lukas Prause ac8787ed10 Merge branch 'make-formatting-linux-conform' into 'master'
Make formatting linux conform

See merge request lukas.prause/tcp-roccet-kernel-module!1
2026-02-10 14:21:08 +01:00
Tim Füchsel 27d181962d Make formatting linux conform 2026-02-10 14:21:08 +01:00
Lukas Prause c24b02231a Adds ECN handling managed by ROCCET. 2026-02-09 13:51:17 +01:00
Lukas Prause 5ebd5c0cb7 Adjusts the comment about the ROCCET paper. 2026-01-30 13:34:47 +01:00
Lukas Prause a4cce7378f Update paper state in README.md 2026-01-29 11:35:20 +01:00
3 changed files with 246 additions and 263 deletions
+32 -37
View File
@@ -1,35 +1,44 @@
# TCP ROCCET :rocket: (RTT Oriented CUBIC Congestion control ExTension) # TCP ROCCET :rocket: (RTT Oriented CUBIC Congestion control ExTension)
TCP ROCCET is a new TCP congestion control TCP ROCCET is a new TCP congestion control algorithm that reduces latency by detecting queuing.
algorithm suited for current cellular 5G NR beyond networks. Additionally, it is specially suited for 4G/5G cellular networks.
It extends the kernel default congestion control CUBIC
and improves its performance, and additionally solves
unwanted side effects of CUBIC’s implementation.
ROCCET uses its own Slow Start, called LAUNCH, where loss
is not considered as a congestion event.
The congestion avoidance phase, called ORBITER, uses
CUBIC's window growth function and adds, based on RTT
and ACK rate, congestion events.
NOTE: A paper for TCP ROCCET is currently under review. A peer-reviewed [paper on TCP ROCCET](https://opendl.ifip-tc6.org/db/conf/wons/wons2026/1571217211.pdf) was presented at the WONS 2026 conference.
Paper draft:
https://arxiv.org/abs/2510.25281
The oldest Linux kernel version we tested with ROCCET is 6.1.
## Setup ## Install with apt
Add tcp-roccet-dkms to your source lists and install:
```
echo "deb [trusted=yes] https://apt.fury.io/timfuchs/ /" | sudo tee /etc/apt/sources.list.d/tcp-roccet.list
sudo apt update
sudo apt install tcp-roccet-dkms
```
### Loading and using the congestion control
Note: This change is not persistent, the module will not be loaded at boot.
```
sudo modprobe tcp_roccet
sudo sysctl net.ipv4.tcp_congestion_control=roccet
```
### Versions
- release-2026-06-12-v1
- Adds minimum RTT probing
## Build from source
### Setup
Kernel headers are required:
* Debian: `sudo apt install linux-headers-generic` * Debian: `sudo apt install linux-headers-generic`
* Fedora: `sudo dnf install kernel-devel` * Fedora: `sudo dnf install kernel-devel`
(`sudo reboot`) (`sudo reboot`)
## Build ### Build
`make` `make`
## Loading & Unloading the Module ## Loading & Unloading the Module
**1.** Insert into Kernel: `sudo insmod tcp_roccet.ko` **1.** Insert into Kernel: `sudo insmod tcp_roccet.ko`
In case you get the "Invalid module Format" error, it can help reinstalling the kernel-headers. In case you get the "Invalid module Format" error, it can help to reinstall the kernel-headers.
**2.** Use the Algorithm: **2.** Use the Algorithm:
* Either via globally loading it: `sudo sysctl net.ipv4.tcp_congestion_control=roccet` * Either via globally loading it: `sudo sysctl net.ipv4.tcp_congestion_control=roccet`
@@ -39,12 +48,12 @@ In case you get the "Invalid module Format" error, it can help reinstalling the
## Debugging (Using kprobe) ## Debugging (Using kprobe)
In order to debug the `tcp_roccet` congestion control algorithm, there exists a Kprobe module (`roccet_kprobe.c`). Using this it is possible to inspect events generated by the algorithm. In order to debug the `tcp_roccet` congestion control algorithm, there exists a Kprobe module (`roccet_kprobe.c`). Using this, it is possible to inspect events generated by the algorithm.
In order to use the Kprobe module the following steps are necessary: In order to use the Kprobe module, the following steps are necessary:
**1. Specify the event to inspect** **1. Specify the event to inspect**
For this head into the `tcp_roccet.c` source code and find the function the Kprobe should attach to. For this, head into the `tcp_roccet.c` source code and find the function the Kprobe should attach to.
For Example: For Example:
``` ```
@@ -69,7 +78,7 @@ Required files for this are:
* `tcp_roccet.c` (Optional; Need to remove corresponding entry in Makefile if missing) * `tcp_roccet.c` (Optional; Need to remove corresponding entry in Makefile if missing)
**3. Load Kprobe module** **3. Load Kprobe module**
To then load the Module use To then load the module use
`sudo insmod roccet_kprobe.ko` `sudo insmod roccet_kprobe.ko`
If you are seeing the error "Unknown symbol in module" you need to first load the roccet algorithm. If you are seeing the error "Unknown symbol in module" you need to first load the roccet algorithm.
@@ -78,21 +87,7 @@ To see the trace output use
`sudo cat /sys/kernel/tracing/trace_pipe` `sudo cat /sys/kernel/tracing/trace_pipe`
**5. Unload Kprobe module** **5. Unload Kprobe module**
To remove the module again use To remove the module again, use
`sudo rmmod roccet_kprobe` `sudo rmmod roccet_kprobe`
_See more info at_ https://docs.kernel.org/trace/kprobes.html and https://www.kernel.org/doc/Documentation/trace/kprobetrace.rst _See more info at_ https://docs.kernel.org/trace/kprobes.html and https://www.kernel.org/doc/Documentation/trace/kprobetrace.rst
## Further Info
* On TCP-CC Ops:
* https://www.yonch.com/tech/linux-tcp-congestion-control-internals
* https://docs.ebpf.io/linux/program-type/BPF_PROG_TYPE_STRUCT_OPS/tcp_congestion_ops/
# Setup Development Environment
For specific Kernel:
1. Download linux source (the version you want to develop for)
2. Create Config via `make defconfig`
3. Compile kernel via `make`
4. Generate clangd Config via `python scripts/clang-tools/gen_compile_commands.py`
5. Copy `compile_commands.json` to development directory
+179 -186
View File
@@ -1,4 +1,4 @@
// SPDX-License-Identifier: GPL-2.0-only // SPDX-License-Identifier: GPL-2.0
/* /*
* TCP ROCCET: An RTT-Oriented CUBIC Congestion Control * TCP ROCCET: An RTT-Oriented CUBIC Congestion Control
* Extension for 5G and Beyond Networks * Extension for 5G and Beyond Networks
@@ -10,19 +10,20 @@
* unwanted side effects of CUBIC’s implementation. * unwanted side effects of CUBIC’s implementation.
* ROCCET uses its own Slow Start, called LAUNCH, where loss * ROCCET uses its own Slow Start, called LAUNCH, where loss
* is not considered as a congestion event. * is not considered as a congestion event.
* The congestion avoidance phase, called ORBITER, uses * The congestion avoidance phase, called ORBITER, uses
* CUBIC's window growth function and adds, based on RTT * CUBIC's window growth function and adds, based on RTT
* and ACK rate, congestion events. * and ACK rate, congestion events.
* *
* A peer-reviewed paper on TCP ROCCET will be presented at the WONS 2026 conference. * A peer-reviewed paper on TCP ROCCET will be presented
* at the WONS 2026 conference.
* A draft of the paper is available here: * A draft of the paper is available here:
* https://arxiv.org/abs/2510.25281 * https://arxiv.org/abs/2510.25281
* *
* *
* Further information about CUBIC: * Further information about CUBIC:
* TCP CUBIC: Binary Increase Congestion control for TCP v2.3 * TCP CUBIC: Binary Increase Congestion control for TCP v2.3
* Home page: * Home page:
* http://netsrv.csc.ncsu.edu/twiki/bin/view/Main/BIC * http://netsrv.csc.ncsu.edu/twiki/bin/view/Main/BIC
* This is from the implementation of CUBIC TCP in * This is from the implementation of CUBIC TCP in
* Sangtae Ha, Injong Rhee and Lisong Xu, * Sangtae Ha, Injong Rhee and Lisong Xu,
* "CUBIC: A New TCP-Friendly High-Speed TCP Variant" * "CUBIC: A New TCP-Friendly High-Speed TCP Variant"
@@ -45,12 +46,12 @@
*/ */
#include "tcp_roccet.h" #include "tcp_roccet.h"
#include "linux/printk.h"
#include <linux/btf.h> #include <linux/btf.h>
#include <linux/btf_ids.h> #include <linux/btf_ids.h>
#include <linux/math64.h> #include <linux/math64.h>
#include <linux/mm.h> #include <linux/mm.h>
#include <linux/module.h> #include <linux/module.h>
#include <linux/moduleparam.h>
#include <net/tcp.h> #include <net/tcp.h>
/* Scale factor beta calculation (max_cwnd = snd_cwnd * beta) */ /* Scale factor beta calculation (max_cwnd = snd_cwnd * beta) */
@@ -63,39 +64,18 @@
*/ */
#define ROCCET_ALPHA_TIMES_100 20 #define ROCCET_ALPHA_TIMES_100 20
/* The amount of seconds ROCCET stores a minRTT. /* min RTT probe period in seconds */
* Enable "calculate_min_rtt" first. #define ROCCET_NEXT_MIN_RTT_PROBE 5000
*/
#define ROCCET_RTT_LOOKBACK_S 10
/* Parameters that are specific to the ROCCET-Algorithm */ /* Parameters that are specific to the ROCCET-Algorithm */
static int sr_rtt_upper_bound __read_mostly = 100; static int sr_rtt_upper_bound __read_mostly = 100;
static int ack_rate_diff_ss __read_mostly = 10; static int ack_rate_diff_ss __read_mostly = 10;
static int ack_rate_diff_ca __read_mostly = 200;
static int calculate_min_rtt __read_mostly = 0;
static int ignore_loss __read_mostly = 0;
static int roccet_minRTT_interpolation_factor __read_mostly = 70;
module_param(sr_rtt_upper_bound, int, 0644); module_param(sr_rtt_upper_bound, int, 0644);
MODULE_PARM_DESC(sr_rtt_upper_bound, "ROCCET's upper bound for srRTT."); MODULE_PARM_DESC(sr_rtt_upper_bound, "ROCCET's upper bound for srRTT.");
module_param(ack_rate_diff_ss, int, 0644); module_param(ack_rate_diff_ss, int, 0644);
MODULE_PARM_DESC(ack_rate_diff_ss, MODULE_PARM_DESC(ack_rate_diff_ss,
"ROCCET's threshold to exit slow start if ACK-rate defer by " "ROCCET's threshold to exit slow start if ACK-rate defer by given amount of segments.");
"given amount of segments.");
module_param(ack_rate_diff_ca, int, 0644);
MODULE_PARM_DESC(ack_rate_diff_ca,
"ROCCET's threshold for ack-rate and cum_cwnd, in percantage "
"of the current cwnd.");
module_param(calculate_min_rtt, int, 0644);
MODULE_PARM_DESC(calculate_min_rtt,
"Calculate min RTT if no lower RTT occurs after 10 sec.");
module_param(ignore_loss, int, 0644);
MODULE_PARM_DESC(ignore_loss, "Ignore loss as a congestion event.");
module_param(roccet_minRTT_interpolation_factor, int, 0644);
MODULE_PARM_DESC(
roccet_minRTT_interpolation_factor,
"ROCCET factor for interpolating the current RTT with the last minRTT "
"(minRTT = (factor * currRTT + (100-factor) * minRTT) / 100)");
static int fast_convergence __read_mostly = 1; static int fast_convergence __read_mostly = 1;
static int beta __read_mostly = 717; /* = 717/1024 (BICTCP_BETA_SCALE) */ static int beta __read_mostly = 717; /* = 717/1024 (BICTCP_BETA_SCALE) */
@@ -115,99 +95,126 @@ MODULE_PARM_DESC(beta, "beta for multiplicative increase");
module_param(initial_ssthresh, int, 0644); module_param(initial_ssthresh, int, 0644);
MODULE_PARM_DESC(initial_ssthresh, "initial value of slow start threshold"); MODULE_PARM_DESC(initial_ssthresh, "initial value of slow start threshold");
module_param(bic_scale, int, 0444); module_param(bic_scale, int, 0444);
MODULE_PARM_DESC( MODULE_PARM_DESC(bic_scale,
bic_scale, "scale (scaled by 1024) value for bic function (bic_scale/1024)");
"scale (scaled by 1024) value for bic function (bic_scale/1024)");
module_param(tcp_friendliness, int, 0644); module_param(tcp_friendliness, int, 0644);
MODULE_PARM_DESC(tcp_friendliness, "turn on/off tcp friendliness"); MODULE_PARM_DESC(tcp_friendliness, "turn on/off tcp friendliness");
static inline void roccettcp_reset(struct roccettcp *ca) static __always_inline void roccettcp_reset(struct roccettcp *ca)
{ {
memset(ca, 0, offsetof(struct roccettcp, curr_rtt)); memset(ca, 0, sizeof(struct roccettcp));
ca->bw_limit.sum_cwnd = 1; ca->next_srrtt_check = 0;
ca->bw_limit.sum_acked = 1; ca->curr_min_rtt = ~0U;
ca->bw_limit.next_check = 0; ca->last_rtt = 0;
ca->curr_min_rtt_timed.rtt = ~0U; ca->ece_received = false;
ca->curr_min_rtt_timed.time = ~0U;
ca->ece_srrtt = 0;
ca->ece_cwnd = 2;
} }
static inline void update_min_rtt(struct sock *sk) /* Return true if ROCCET is in min RTT probing.
*/
static __always_inline bool is_in_min_rtt_probing(struct roccettcp *ca, u32 now)
{
if (ca->probe_min_rtt_until == 0)
return false;
return before(now, ca->probe_min_rtt_until);
}
static __always_inline void update_min_rtt(struct sock *sk)
{ {
struct roccettcp *ca = inet_csk_ca(sk); struct roccettcp *ca = inet_csk_ca(sk);
u32 now = jiffies_to_usecs(tcp_jiffies32);
if (now - ca->curr_min_rtt_timed.time >
ROCCET_RTT_LOOKBACK_S * USEC_PER_SEC &&
calculate_min_rtt) {
u32 new_min_rtt = max(ca->curr_rtt, 1);
u32 old_min_rtt = ca->curr_min_rtt_timed.rtt;
u32 interpolated_min_rtt =
(new_min_rtt * roccet_minRTT_interpolation_factor +
old_min_rtt *
(100 - roccet_minRTT_interpolation_factor)) /
100;
ca->curr_min_rtt_timed.rtt = interpolated_min_rtt;
ca->curr_min_rtt_timed.time = now;
}
/* Check if new lower min RTT was found. If so, set it directly */ /* Check if new lower min RTT was found. If so, set it directly */
if (ca->curr_rtt < ca->curr_min_rtt_timed.rtt) { if (ca->curr_rtt < ca->curr_min_rtt) {
ca->curr_min_rtt_timed.rtt = max(ca->curr_rtt, 1); ca->curr_min_rtt = max(ca->curr_rtt, 1);
ca->curr_min_rtt_timed.time = now; /* Probe for the min RTT in ROCCET_NEXT_MIN_RTT_PROBE seconds
* if no other update occurs.
*/
ca->next_min_rtt_probe = jiffies_to_usecs(tcp_jiffies32) +
ROCCET_NEXT_MIN_RTT_PROBE * USEC_PER_MSEC;
} }
} }
/* Return difference between last and current ack rate. /* Return difference between last and current ack rate.
*/ */
static inline int get_ack_rate_diff(struct roccettcp *ca) static __always_inline int get_ack_rate_diff(struct roccettcp *ca)
{ {
return ca->ack_rate.last_rate - ca->ack_rate.curr_rate; if (ca->ack_rate.curr_rate < ca->ack_rate.last_rate)
return 0;
return ca->ack_rate.curr_rate - ca->ack_rate.last_rate;
} }
/* Update ack rate sampled by 100ms. /* Update ack rate sampled by 100ms.
*/ */
static inline void update_ack_rate(struct sock *sk) static __always_inline void update_ack_rate(struct sock *sk, u32 acked, u32 now)
{ {
struct roccettcp *ca = inet_csk_ca(sk); struct roccettcp *ca = inet_csk_ca(sk);
u32 now = jiffies_to_usecs(tcp_jiffies32);
u32 interval = USEC_PER_MSEC * 100; u32 interval = USEC_PER_MSEC * 100;
if ((u32)(now - ca->ack_rate.last_rate_time) >= interval) { if (after(now, ca->ack_rate.last_rate_time + interval)) {
ca->ack_rate.last_rate_time = now; ca->ack_rate.last_rate_time = now;
ca->ack_rate.last_rate = ca->ack_rate.curr_rate; ca->ack_rate.last_rate = ca->ack_rate.curr_rate;
ca->ack_rate.curr_rate = ca->ack_rate.cnt; ca->ack_rate.curr_rate = ca->ack_rate.cnt;
ca->ack_rate.cnt = 0; ca->ack_rate.cnt = acked; // start counting for the new interval
} else { } else {
ca->ack_rate.cnt += 1; ca->ack_rate.cnt += acked;
} }
} }
/* Compute srRTT. /* Compute srRTT.
*/ */
static inline void update_srrtt(struct sock *sk) static __always_inline void update_srrtt(struct sock *sk)
{ {
struct roccettcp *ca = inet_csk_ca(sk); struct roccettcp *ca = inet_csk_ca(sk);
if (ca->curr_min_rtt_timed.rtt == 0) /* Avoid integer overflow in the calculation below.
return; * This could occur in cases where we have not yet
* received an RTT sample. In these cases, set the
* rtt to a safe value.
*/
if (ca->curr_rtt < ca->curr_min_rtt) {
ca->curr_rtt = max(ca->curr_rtt, 1);
ca->curr_min_rtt = ca->curr_rtt;
}
/* Avoid division by zero */
if (ca->curr_min_rtt == 0) {
ca->curr_min_rtt = max(ca->curr_min_rtt, 1);
return; // skip srRTT update
}
/* Calculate the new rRTT (Scaled by 100). /* Calculate the new rRTT (Scaled by 100).
* 100 * ((sRTT - sRTT_min) / sRTT_min) * 100 * ((sRTT - sRTT_min) / sRTT_min).
*
* curr_min_rtt_timed.rtt is always <= than curr_rtt,
* since this is the minimum of the rtt.
*
* 0 is a valid value for rrtt.
*/ */
u32 rRTT = (100 * (ca->curr_rtt - ca->curr_min_rtt_timed.rtt)) / u32 rrtt = (100 * (ca->curr_rtt - ca->curr_min_rtt)) /
ca->curr_min_rtt_timed.rtt; ca->curr_min_rtt;
// (1 - alpha) * srRTT + alpha * rRTT // (1 - alpha) * srRTT + alpha * rRTT
ca->curr_srRTT = ((100 - ROCCET_ALPHA_TIMES_100) * ca->curr_srRTT + ca->curr_srrtt = ((100 - ROCCET_ALPHA_TIMES_100) * ca->curr_srrtt +
ROCCET_ALPHA_TIMES_100 * rRTT) / ROCCET_ALPHA_TIMES_100 * rrtt) / 100;
100;
} }
__bpf_kfunc static void roccettcp_init(struct sock *sk) /* Do a ROCCET congestion event.
*/
static __always_inline void roccet_congestion_event(struct sock *sk, u32 now)
{
struct tcp_sock *tp = tcp_sk(sk);
struct roccettcp *ca = inet_csk_ca(sk);
ca->epoch_start = 0;
ca->roccet_last_event_time_us = now;
ca->cnt = 100 * tcp_snd_cwnd(tp);
ca->last_max_cwnd = tcp_snd_cwnd(tp);
tcp_snd_cwnd_set(tp, min(tp->snd_cwnd_clamp,
max((tcp_snd_cwnd(tp) * beta) /
BICTCP_BETA_SCALE, 2U)));
tp->snd_ssthresh = tcp_snd_cwnd(tp);
}
static void roccettcp_init(struct sock *sk)
{ {
struct roccettcp *ca = inet_csk_ca(sk); struct roccettcp *ca = inet_csk_ca(sk);
@@ -216,16 +223,15 @@ __bpf_kfunc static void roccettcp_init(struct sock *sk)
if (initial_ssthresh) if (initial_ssthresh)
tcp_sk(sk)->snd_ssthresh = initial_ssthresh; tcp_sk(sk)->snd_ssthresh = initial_ssthresh;
/* Initial roccet paramters */ /* Initial roccet parameters */
ca->roccet_last_event_time_us = 0; ca->roccet_last_event_time_us = 0;
ca->curr_min_rtt = ~0U;
ca->ack_rate.last_rate = 0; ca->ack_rate.last_rate = 0;
ca->ack_rate.last_rate_time = 0; ca->ack_rate.last_rate_time = 0;
ca->ack_rate.curr_rate = 0; ca->ack_rate.curr_rate = 0;
ca->ack_rate.cnt = 0; ca->ack_rate.cnt = 0;
} }
__bpf_kfunc static void roccettcp_cwnd_event(struct sock *sk, static void roccettcp_cwnd_event(struct sock *sk,
enum tcp_ca_event event) enum tcp_ca_event event)
{ {
if (event == CA_EVENT_TX_START) { if (event == CA_EVENT_TX_START) {
@@ -295,7 +301,8 @@ static u32 cubic_root(u64 a)
/* Compute congestion window to use. /* Compute congestion window to use.
*/ */
static inline void bictcp_update(struct roccettcp *ca, u32 cwnd, u32 acked) static __always_inline void bictcp_update(struct roccettcp *ca, u32 cwnd,
u32 acked)
{ {
u32 delta, bic_target, max_cnt; u32 delta, bic_target, max_cnt;
u64 offs, t; u64 offs, t;
@@ -340,7 +347,7 @@ static inline void bictcp_update(struct roccettcp *ca, u32 cwnd, u32 acked)
* (so time^3 is done by using 64 bit) * (so time^3 is done by using 64 bit)
* and without the support of division of 64bit numbers * and without the support of division of 64bit numbers
* (so all divisions are done by using 32 bit) * (so all divisions are done by using 32 bit)
* also NOTE the unit of those veriables * also NOTE the unit of those variables
* time = (t - K) / 2^bictcp_HZ * time = (t - K) / 2^bictcp_HZ
* c = bic_scale >> 10 * c = bic_scale >> 10
* rtt = (srtt >> 3) / HZ * rtt = (srtt >> 3) / HZ
@@ -368,11 +375,10 @@ static inline void bictcp_update(struct roccettcp *ca, u32 cwnd, u32 acked)
bic_target = ca->bic_origin_point + delta; bic_target = ca->bic_origin_point + delta;
/* cubic function - calc bictcp_cnt*/ /* cubic function - calc bictcp_cnt*/
if (bic_target > cwnd) { if (bic_target > cwnd)
ca->cnt = cwnd / (bic_target - cwnd); ca->cnt = cwnd / (bic_target - cwnd);
} else { else
ca->cnt = 100 * cwnd; /* very small increment*/ ca->cnt = 100 * cwnd; /* very small increment*/
}
/* The initial growth of cubic function may be too conservative /* The initial growth of cubic function may be too conservative
* when the available bandwidth is still unknown. * when the available bandwidth is still unknown.
@@ -405,38 +411,45 @@ tcp_friendliness:
ca->cnt = max(ca->cnt, 2U); ca->cnt = max(ca->cnt, 2U);
} }
__bpf_kfunc static void roccettcp_cong_avoid(struct sock *sk, u32 ack, static void roccettcp_cong_avoid(struct sock *sk, u32 ack,
u32 acked) u32 acked)
{ {
struct tcp_sock *tp = tcp_sk(sk); struct tcp_sock *tp = tcp_sk(sk);
struct roccettcp *ca = inet_csk_ca(sk); struct roccettcp *ca = inet_csk_ca(sk);
u32 now = jiffies_to_usecs(tcp_jiffies32); u32 now = jiffies_to_usecs(tcp_jiffies32);
u32 bw_limit_detect = 0; bool evaluate_srrtt = false;
u32 roccet_xj; u32 roccet_xj;
u32 jitter; u32 jitter;
if (ca->last_rtt > ca->curr_rtt) {
jitter = ca->last_rtt - ca->curr_rtt;
} else {
jitter = ca->curr_rtt - ca->last_rtt;
}
/* Update roccet paramters */ if (after(ca->last_rtt, ca->curr_rtt))
update_ack_rate(sk); jitter = ca->last_rtt - ca->curr_rtt;
else
jitter = ca->curr_rtt - ca->last_rtt;
/* Update roccet parameters */
update_ack_rate(sk, acked, now);
update_min_rtt(sk); update_min_rtt(sk);
update_srrtt(sk); update_srrtt(sk);
/* Reset ECE handling if we already have more bandwidth /* do not increase the cwnd if in RTT probe */
* than we received the last ECE. if (is_in_min_rtt_probing(ca, now))
return;
/* Probe for min RTT if the last min RTT update is
* ROCCET_NEXT_MIN_RTT_PROBE seconds ago.
*/ */
if(ca->ece_srrtt > 0){ if (after(now, ca->next_min_rtt_probe)){
if(tcp_snd_cwnd(tp) >= ca->ece_cwnd){ ca->probe_min_rtt_until = now + min(200, 2 * ca->curr_rtt) * USEC_PER_MSEC;
ca->ece_srrtt = 0; ca->cwnd_before_min_rtt_probe = tcp_snd_cwnd(tp);
} tcp_snd_cwnd_set(tp, max(tcp_snd_cwnd(tp) / 2, TCP_INIT_CWND));
ca->curr_min_rtt = ~0U;
return;
} }
/* ROCCET drain. /* ROCCET drain.
* Do not increase the cwnd for 100ms after a roccet congestion event * Do not increase the cwnd for 100ms after a roccet congestion event
* because the buffer is still not drained.
*/ */
if (now - ca->roccet_last_event_time_us <= 100 * USEC_PER_MSEC) if (now - ca->roccet_last_event_time_us <= 100 * USEC_PER_MSEC)
return; return;
@@ -445,19 +458,23 @@ __bpf_kfunc static void roccettcp_cong_avoid(struct sock *sk, u32 ack,
* in networks with large buffers of multiple BDP * in networks with large buffers of multiple BDP
* Like in cellular networks (5G, ...). * Like in cellular networks (5G, ...).
* Or exit LAUNCH if cwnd is too large for application layer * Or exit LAUNCH if cwnd is too large for application layer
* data rate. * data rate (tcp cwnd validation).
*/ */
if ((tcp_in_slow_start(tp) && ca->curr_srRTT > sr_rtt_upper_bound && if ((tcp_in_slow_start(tp) && ca->curr_srrtt > sr_rtt_upper_bound &&
get_ack_rate_diff(ca) >= ack_rate_diff_ss) || get_ack_rate_diff(ca) <= ack_rate_diff_ss) ||
(!tcp_is_cwnd_limited(sk) && tcp_in_slow_start(tp)) (!tcp_is_cwnd_limited(sk) && tcp_in_slow_start(tp))) {
) {
ca->epoch_start = 0; ca->epoch_start = 0;
/* Handle inital slow start. Here we observe the most problems */ /* Handle initial slow start. Here occur most bufferbloat */
if (tp->snd_ssthresh == TCP_INFINITE_SSTHRESH) { if (tp->snd_ssthresh == TCP_INFINITE_SSTHRESH) {
tcp_sk(sk)->snd_ssthresh = tcp_snd_cwnd(tp) / 2; tcp_sk(sk)->snd_ssthresh = tcp_snd_cwnd(tp) / 2;
tcp_snd_cwnd_set(tp, tcp_snd_cwnd(tp) / 2); /* since this is the initial slow start,
* the min cwnd won't be 1, so the window
* can't be set to 0 by accident.
*/
tcp_snd_cwnd_set(tp, max(tcp_snd_cwnd(tp) / 2,
TCP_INIT_CWND));
} else { } else {
tcp_sk(sk)->snd_ssthresh = tcp_sk(sk)->snd_ssthresh =
tcp_snd_cwnd(tp) - (tcp_snd_cwnd(tp) / 3); tcp_snd_cwnd(tp) - (tcp_snd_cwnd(tp) / 3);
@@ -474,61 +491,39 @@ __bpf_kfunc static void roccettcp_cong_avoid(struct sock *sk, u32 ack,
return; return;
} }
if (ca->bw_limit.next_check == 0) if (ca->next_srrtt_check == 0)
ca->bw_limit.next_check = now + 5 * ca->curr_rtt; ca->next_srrtt_check = now + 5 * ca->curr_rtt;
ca->bw_limit.sum_cwnd += tcp_snd_cwnd(tp); /* Check if it's time to evaluate the srRTT */
ca->bw_limit.sum_acked += acked; if (after(now, ca->next_srrtt_check)) {
evaluate_srrtt = true;
if (ca->bw_limit.next_check < now) {
/* We send more data as we got acked in the last 5 RTTs */
if ((ca->bw_limit.sum_cwnd * 100) / ca->bw_limit.sum_acked >=
ack_rate_diff_ca)
bw_limit_detect = 1;
/* reset struct and set next end of period */ /* reset struct and set next end of period */
ca->bw_limit.sum_cwnd = 1; ca->next_srrtt_check = now + 5 * ca->curr_rtt;
/* set to 1 to avoid division by zero */
ca->bw_limit.sum_acked = 1;
ca->bw_limit.next_check = now + 5 * ca->curr_rtt;
} }
/* Respects the jitter of the connection and add it on top of the upper bound /* Respects the jitter of the connection and add it on top of
* for the srRTT * the upper bound for the srRTT.
*/ */
roccet_xj = ((jitter * 100) / ca->curr_min_rtt_timed.rtt) + roccet_xj = ((jitter * 100) / ca->curr_min_rtt) +
sr_rtt_upper_bound; sr_rtt_upper_bound;
if (roccet_xj < sr_rtt_upper_bound) if (roccet_xj < sr_rtt_upper_bound)
roccet_xj = sr_rtt_upper_bound; roccet_xj = sr_rtt_upper_bound;
/* This is true if we recently received an ECE bit. /* The srRTT exceeds the upper bound if bufferbloat happens.
* Therefore we should respect the srRTT at this piont. * Here, we want to reduce the cwnd and drain the buffer.
*/ */
if(ca->ece_srrtt < roccet_xj && ca->ece_srrtt > 0) if (ca->curr_srrtt > roccet_xj && evaluate_srrtt) {
roccet_xj = ca->ece_srrtt; roccet_congestion_event(sk, now);
if (ca->curr_srRTT > roccet_xj && (bw_limit_detect || ca->ece_srrtt > 0)) {
ca->epoch_start = 0;
ca->roccet_last_event_time_us = now;
ca->cnt = 100 * tcp_snd_cwnd(tp);
/* Set Wmax if cwnd is larger than the old Wmax */
if (tcp_snd_cwnd(tp) > ca->last_max_cwnd)
ca->last_max_cwnd = tcp_snd_cwnd(tp);
tcp_snd_cwnd_set(tp, min(tp->snd_cwnd_clamp,
max((tcp_snd_cwnd(tp) * beta) / BICTCP_BETA_SCALE, 2U)));
tp->snd_ssthresh = tcp_snd_cwnd(tp);
return; return;
} }
/* Terminates this function if cwnd is not fully utilized. /* Terminates this function if cwnd is not fully utilized.
* In mobile networks like 5G, this termination causes the cwnd to be frozen at * In mobile networks like 5G, this termination causes the cwnd to be
* an excessively high value. This is because slow start or HyStart massively * frozen at an excessively high value. This is because slow start or
* exceed the available bandwidth and leave the cwnd at an excessively high * HyStart massively exceed the available bandwidth and leave the cwnd
* value. The cwnd cannot therefore be fully utilized because it is limited by * at an excessively high value. The cwnd cannot therefore be fully
* the connection capacity. * utilized because it is limited by the connection capacity.
*/ */
if (!tcp_is_cwnd_limited(sk)) if (!tcp_is_cwnd_limited(sk))
return; return;
@@ -537,18 +532,24 @@ __bpf_kfunc static void roccettcp_cong_avoid(struct sock *sk, u32 ack,
tcp_cong_avoid_ai(tp, max(1, ca->cnt), acked); tcp_cong_avoid_ai(tp, max(1, ca->cnt), acked);
} }
__bpf_kfunc static u32 roccettcp_recalc_ssthresh(struct sock *sk) static u32 roccettcp_recalc_ssthresh(struct sock *sk)
{ {
const struct tcp_sock *tp = tcp_sk(sk); const struct tcp_sock *tp = tcp_sk(sk);
struct roccettcp *ca = inet_csk_ca(sk); struct roccettcp *ca = inet_csk_ca(sk);
if (ignore_loss) /* Handle ECN as ROCCET congestion event. */
if (ca->ece_received) {
ca->ece_received = false;
roccet_congestion_event(sk, jiffies_to_usecs(tcp_jiffies32));
return tcp_snd_cwnd(tp); return tcp_snd_cwnd(tp);
}
/* Don't exit slow start if loss occurs. */ /* On loss in slow start enter congestion avoidance
* without a cwnd reduction.
*/
if (tcp_in_slow_start(tp)) if (tcp_in_slow_start(tp))
return tcp_snd_cwnd(tp); return tcp_snd_cwnd(tp);
ca->epoch_start = 0; /* end of epoch */ ca->epoch_start = 0; /* end of epoch */
/* Wmax and fast convergence */ /* Wmax and fast convergence */
@@ -562,14 +563,15 @@ __bpf_kfunc static u32 roccettcp_recalc_ssthresh(struct sock *sk)
return max((tcp_snd_cwnd(tp) * beta) / BICTCP_BETA_SCALE, 2U); return max((tcp_snd_cwnd(tp) * beta) / BICTCP_BETA_SCALE, 2U);
} }
__bpf_kfunc static void roccettcp_state(struct sock *sk, u8 new_state) static void roccettcp_state(struct sock *sk, u8 new_state)
{ {
struct roccettcp *ca = inet_csk_ca(sk); struct roccettcp *ca = inet_csk_ca(sk);
if (new_state == TCP_CA_Loss) if (new_state == TCP_CA_Loss)
roccettcp_reset(ca); roccettcp_reset(ca);
} }
__bpf_kfunc static void roccettcp_acked(struct sock *sk, static void roccettcp_acked(struct sock *sk,
const struct ack_sample *sample) const struct ack_sample *sample)
{ {
struct roccettcp *ca = inet_csk_ca(sk); struct roccettcp *ca = inet_csk_ca(sk);
@@ -583,11 +585,12 @@ __bpf_kfunc static void roccettcp_acked(struct sock *sk,
return; return;
u32 delay = sample->rtt_us; u32 delay = sample->rtt_us;
if (delay == 0) if (delay == 0)
delay = 1; delay = 1;
/* first time call or link delay decreases */ /* first time call or link delay decreases */
if (ca->delay_min == 0 || ca->delay_min > delay) if (ca->delay_min == 0 || after(ca->delay_min, delay))
ca->delay_min = delay; ca->delay_min = delay;
/* Get valid sample for roccet */ /* Get valid sample for roccet */
@@ -597,18 +600,15 @@ __bpf_kfunc static void roccettcp_acked(struct sock *sk,
} }
} }
__bpf_kfunc static void roccet_in_ack_event(struct sock *sk, u32 flags) static void roccet_in_ack_event(struct sock *sk, u32 flags)
{ {
struct tcp_sock *tp = tcp_sk(sk);
struct roccettcp *ca = inet_csk_ca(sk); struct roccettcp *ca = inet_csk_ca(sk);
/* Handle ECE bit. /* Handle ECE bit.
* Pocessing of ECE events is done in roccettcp_cong_avoid() * Processing of ECE events is done in roccettcp_recalc_ssthresh()
*/ */
if (flags & CA_ACK_ECE) { if (flags & CA_ACK_ECE)
ca->ece_srrtt = ca->curr_srRTT; ca->ece_received = true;
ca->ece_cwnd = tcp_snd_cwnd(tp);
}
} }
static struct tcp_congestion_ops roccet_tcp __read_mostly = { static struct tcp_congestion_ops roccet_tcp __read_mostly = {
@@ -624,26 +624,24 @@ static struct tcp_congestion_ops roccet_tcp __read_mostly = {
.name = "roccet", .name = "roccet",
}; };
BTF_KFUNCS_START(tcp_roccet_check_kfunc_ids)
BTF_ID_FLAGS(func, roccettcp_init)
BTF_ID_FLAGS(func, roccettcp_recalc_ssthresh)
BTF_ID_FLAGS(func, roccettcp_cong_avoid)
BTF_ID_FLAGS(func, roccettcp_state)
BTF_ID_FLAGS(func, roccettcp_cwnd_event)
BTF_ID_FLAGS(func, roccettcp_acked)
BTF_KFUNCS_END(tcp_roccet_check_kfunc_ids)
static const struct btf_kfunc_id_set tcp_roccet_kfunc_set = {
.owner = THIS_MODULE,
.set = &tcp_roccet_check_kfunc_ids,
};
static int __init roccettcp_register(void) static int __init roccettcp_register(void)
{ {
int ret;
BUILD_BUG_ON(sizeof(struct roccettcp) > ICSK_CA_PRIV_SIZE); BUILD_BUG_ON(sizeof(struct roccettcp) > ICSK_CA_PRIV_SIZE);
/*
* Validate parameters to avoid division by zero errors.
*/
if (beta <= 0 || beta >= BICTCP_BETA_SCALE) {
pr_err("roccet: beta must be between 0 and %d\n",
BICTCP_BETA_SCALE);
return -EINVAL;
}
if (bic_scale <= 0) {
pr_err("roccet: bic_scale must be positive\n");
return -EINVAL;
}
/* Precompute a bunch of the scaling factors that are used per-packet /* Precompute a bunch of the scaling factors that are used per-packet
* based on SRTT of 100ms * based on SRTT of 100ms
*/ */
@@ -672,10 +670,6 @@ static int __init roccettcp_register(void)
/* divide by bic_scale and by constant Srtt (100ms) */ /* divide by bic_scale and by constant Srtt (100ms) */
do_div(cube_factor, bic_scale * 10); do_div(cube_factor, bic_scale * 10);
ret = register_btf_kfunc_id_set(BPF_PROG_TYPE_STRUCT_OPS,
&tcp_roccet_kfunc_set);
if (ret < 0)
return ret;
return tcp_register_congestion_control(&roccet_tcp); return tcp_register_congestion_control(&roccet_tcp);
} }
@@ -690,4 +684,3 @@ module_exit(roccettcp_unregister);
MODULE_AUTHOR("Lukas Prause, Tim Füchsel"); MODULE_AUTHOR("Lukas Prause, Tim Füchsel");
MODULE_LICENSE("GPL"); MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("ROCCET TCP"); MODULE_DESCRIPTION("ROCCET TCP");
MODULE_VERSION("1.0");
+35 -40
View File
@@ -1,3 +1,4 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* /*
* TCP ROCCET congestion control interface * TCP ROCCET congestion control interface
*/ */
@@ -6,49 +7,43 @@
#include <linux/math64.h> #include <linux/math64.h>
struct AckRate { struct ack_rate {
u16 last_rate; /* Last ACK-rate */ u32 last_rate; /* Last ACK-rate */
u32 last_rate_time; /* Timestamp of the last ACK-rate */ u32 last_rate_time; /* Timestamp of the last ACK-rate */
u16 curr_rate; /* Current ACK-rate */ u32 curr_rate; /* Current ACK-rate */
u16 cnt; /* Used for counting acks */ u32 cnt; /* Used for counting acks */
}; };
struct BandwidthLimitDetect { /* Based on the BICTCP struct with additions specific
u32 sum_cwnd; /* sum of cwnd during time interval */ * for the ROCCET-Algorithm
u32 sum_acked; /* sum of received acks during time interval */ */
u32 next_check; /* end/upper bound of time interval */
};
struct TimedRTT {
u32 time; /* Time of recoding */
u32 rtt; /* Measured RTT */
};
/* Based on the BICTCP struct with additions specific for the ROCCET-Algorithm */
struct roccettcp { struct roccettcp {
u32 cnt; /* increase cwnd by 1 after ACKs */ u32 cnt; /* increase cwnd by 1 after ACKs */
u32 last_max_cwnd; /* last maximum snd_cwnd */ u32 last_max_cwnd; /* last maximum snd_cwnd */
u32 last_cwnd; /* the last snd_cwnd */ u32 last_cwnd; /* the last snd_cwnd */
u32 last_time; /* time when updated last_cwnd */ u32 last_time; /* time when updated last_cwnd */
u32 bic_origin_point; /* origin point of bic function */ u32 bic_origin_point; /* origin point of bic function */
u32 bic_K; /* time to origin point from the u32 bic_K; /* time to origin point from the
beginning of the current epoch */ * beginning of the current epoch
u32 delay_min; /* min delay (usec) */ */
u32 epoch_start; /* beginning of an epoch */ u32 delay_min; /* min delay (usec) */
u32 ack_cnt; /* number of acks */ u32 epoch_start; /* beginning of an epoch */
u32 tcp_cwnd; /* estimated tcp cwnd */ u32 ack_cnt; /* number of acks */
u32 curr_rtt; /* the minimum rtt of current round */ u32 tcp_cwnd; /* estimated tcp cwnd */
u32 curr_rtt; /* the minimum rtt of current round */
u32 roccet_last_event_time_us; /* The last time ROCCET was triggered */ u32 roccet_last_event_time_us; /* The last time ROCCET was
u32 ece_cwnd; /* The cwnd when a ECE bit was received */ * triggered
u32 ece_srrtt; /* The srRTT whent the ECE was received */ */
u32 curr_min_rtt; /* The current observed minRTT */ bool ece_received; /* Set to true if an ECE bit was received */
struct TimedRTT curr_min_rtt_timed; /* The current observed minRTT with u32 curr_min_rtt; /* The current observed minRTT */
the timestamp when it was observed */ u32 next_min_rtt_probe; /* Next time to probe the minRTT */
u32 curr_srRTT; /* The srRTT calculated based on the latest ACK */ u32 probe_min_rtt_until; /* End of minRTT probing period */
struct AckRate ack_rate; /* The last and the current ACK rate */ u32 cwnd_before_min_rtt_probe; /* cwnd before min RTT probeing */
struct BandwidthLimitDetect bw_limit; u32 curr_srrtt; /* srRTT calculated based on the latest ACK */
u32 last_rtt; /* Used for jitter calculation */ u32 next_srrtt_check; /* Next check for srRTT */
struct ack_rate ack_rate; /* The last and the current ACK rate */
u32 last_rtt; /* Used for jitter calculation */
}; };
#endif /* __TCP_ROCCET_H */ #endif /* __TCP_ROCCET_H */