Created
February 15, 2025 19:13
-
-
Save mikkorantalainen/24153c60cc08766da7e7f6e81229acc6 to your computer and use it in GitHub Desktop.
traffic-shaping script for 1Gbps fiber connection (Ubuntu 22.04 LTS)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
# Traffic shaping script (AQM, fq_codel+tbf) | |
# Copyright 2018,2019 Mikko Rantalainen <[email protected]> | |
# License: MIT (X11) | |
# Usage: | |
# 21/0.8 Mbps connection (ADSL2): DOWNLINK_RATE=21.7Mbit UPLINK_RATE=0.8Mbit TBF_LATENCY=500ms DOWNLINK_BURST=1500 UPLINK_BURST=1500 bin/traffic-shaping start | |
# 100/100 Mbps connection: ./traffic-shaping | |
# 1/1 GBps connection: DOWNLINK_RATE=1Gbit UPLINK_RATE=1Gbit TBF_LATENCY=15ms bin/traffic-shaping start | |
# Note that using low TBF_LATENCY will require powerful CPU. | |
# | |
# See also: https://www.bufferbloat.net/projects/codel/wiki/Best_practices_for_benchmarking_Codel_and_FQ_Codel/ | |
# See also: http://www.jfcarter.net/~jimc/documents/voip-qos-1609.html | |
# TODO: man 7 tc-hfcs (instead of tbf) | |
# TODO: try to limit bandwidth using fq_codel only (get rid of tbf) - https://gist.github.com/eqhmcow/939373/8d2e8ad745a7e0a8ddb21abde42538034c2ea65b | |
# | |
# | |
# TODO: check if following would make more sense nowadays (2022): | |
# | |
# ## no bandwidth limits, just fq_codel with default settings | |
# # modprobe tcp_cdg | |
# # grep . /proc/sys/net/ipv4/tcp_congestion_control | |
# cdg | |
# | |
# # /sys/module/tcp_cdg/parameters# grep . * | |
# backoff_beta:724 | |
# backoff_factor:100 | |
# hystart_detect:2 | |
# use_ineff:2 | |
# use_shadow:N | |
# use_tolerance:N | |
# window:8 | |
# | |
# Ubuntu defaults: | |
# backoff_beta:724 | |
# backoff_factor:42 | |
# hystart_detect:3 | |
# use_ineff:5 | |
# use_shadow:Y | |
# use_tolerance:N | |
# window:8 | |
# | |
# sudo tc qdisc del dev enp3s0 root | |
# sudo tc qdisc add dev enp3s0 root fq_codel limit 5000 target 3.0ms interval 50.0ms memory_limit 8Mb ecn | |
# | |
# | |
set -e # abort if a command returns non-zero status (failed) | |
set -x # verbose execution | |
# Note: ip route sometimes outputs multiple lines with prefix "default", use the first one | |
DEV="${DEV:=$(ip route | grep "^default " | head -n1 | grep -Po "(?<=dev )[^ ]+")}" | |
# ingress: | |
DOWNLINK_RATE="${DOWNLINK_RATE:=900000kbit}" # or e.g. "21.5Mbit" | |
# egress: | |
UPLINK_RATE="${UPLINK_RATE:=900000kbit}" | |
CODEL_INTERVAL="${CODEL_INTERVAL:=50ms}" # usually 100ms, high speed links with low latency may need lower values | |
CODEL_TARGET="${CODEL_TARGET:=3ms}" # unit "us" is also available, usually 5%-10% of CODEL_INTERVAL | |
CODEL_LIMIT="${CODEL_LIMIT:=1001}" # decrease to reduce latency, too low values will limit throughput | |
CODEL_FLOWS="${CODEL_FLOWS:=1024}" | |
# set burst as high as possible without causing dropped packets at the start of the connections | |
DOWNLINK_BURST="${DOWNLINK_BURST:=16000}" | |
UPLINK_BURST="${UPLINK_BURST:=55000}" | |
TBF_LATENCY="${TBF_LATENCY:=10ms}" # set to lower latency to improve control over bandwidth limiting, UPLINK_BURST bytes must be able to be sent in this time | |
IFB="$DEV.in" # logically this should be $DEV.ingress but max limit might be exceeded (e.g. dev = enp0s29u1u6 -> enp0s29u1u6.ingress is too long | |
INITCWND="${INITCWND:=20}" # initial congestion window, decrease if packet loss is seen | |
INITRWND="${INITRWND:=40}" # initial receiving window (advertised from client to servers), can be safely pretty high if you have lots of bandwidth (Windows and OS X have this near 40) | |
# See also: https://www.cdnplanet.com/blog/tune-tcp-initcwnd-for-optimum-performance/ | |
# See also: https://www.acc.umu.se/~maswan/linux-netperf.txt | |
# See also: http://intronetworks.cs.luc.edu/1/html/newtcps.html | |
# See also: https://www.ietf.org/proceedings/84/slides/slides-84-iccrg-1.pdf | |
configure_shaping() | |
{ | |
# EGRESS (outgoing traffic, "uploads"): | |
# setup bandwidth limiting: | |
tc qdisc add dev "$DEV" root handle 1: tbf rate "$UPLINK_RATE" burst "$UPLINK_BURST" latency "$TBF_LATENCY" | |
# setup fq_codel for bandwidth shaping | |
tc qdisc add dev "$DEV" parent 1: fq_codel quantum 300 limit "$CODEL_LIMIT" target "$CODEL_TARGET" interval "$CODEL_INTERVAL" flows "$CODEL_FLOWS" noecn | |
# INGRESS (incoming traffic, "downloads"): | |
ip link show ifb0 >&/dev/null && HAD_IFB0=1 || HAD_IFB0=0 | |
ip link show ifb1 >&/dev/null && HAD_IFB1=1 || HAD_IFB1=0 | |
# setup bandwidth limiting (ingress limiting needs IFB or Intermediate Functional Block, see https://wiki.linuxfoundation.org/networking/ifb): | |
tc qdisc add dev "$DEV" handle ffff: ingress | |
ip link add name "$IFB" type ifb | |
tc qdisc add dev "$IFB" root handle 1: tbf rate "$DOWNLINK_RATE" burst "$DOWNLINK_BURST" latency "$TBF_LATENCY" | |
# setup fq_codel for bandwidth shaping | |
tc qdisc add dev "$IFB" parent 1: fq_codel quantum 300 limit "$CODEL_LIMIT" target "$CODEL_TARGET" interval "$CODEL_INTERVAL" flows "$CODEL_FLOWS" ecn | |
ip link set dev "$IFB" up | |
# connect ingress filtering to actual WAN device | |
tc filter add dev "$DEV" parent ffff: protocol all prio 10 u32 match u32 0 0 flowid 1:1 action mirred egress redirect dev "$IFB" | |
# Configure initcwnd and initrwnd | |
# Note that "ip route" sometimes emit multiple lines with prefix "default" - we'll use first one always | |
ip route change $(ip route | grep ^default | head -n1) initcwnd "$INITCWND" initrwnd "$INITRWND" | |
## configure CDG congestion control algorithm | |
##modprobe tcp_cdg && echo cdg > /proc/sys/net/ipv4/tcp_congestion_control | |
# cubic seems to be better overall with AQM, let's tune it | |
echo cubic > /proc/sys/net/ipv4/tcp_congestion_control || true | |
echo 13 > /sys/module/tcp_cubic/parameters/hystart_low_window | |
echo 0 > /proc/sys/net/ipv4/tcp_slow_start_after_idle | |
# TODO: try modprobe tcp_westwood | |
# Remove any offloading that increases latency (Note that if you don't have enough CPU power, this may reduce max bandwith!) | |
# Note that due ethtool braindamage, the names used here do not match with ethtool --show-offload, see 'man ethtool' for details! | |
# ignore possible errors and keep going | |
ethtool --offload "$DEV" gso off || true | |
ethtool --offload "$DEV" gro off || true | |
ethtool --offload "$DEV" tx off || true | |
ethtool --offload "$DEV" rx off || true | |
ethtool --offload "$DEV" rxvlan off || true | |
ethtool --offload "$DEV" txvlan off || true | |
# cleanup broken ip link add ... type ifb sometimes creating extra ifb links (called "ifb0" and "ifb1") | |
test "$HAD_IFB0" = "0" && ip link show ifb0 >&/dev/null && ip link del ifb0 || true | |
test "$HAD_IFB1" = "0" && ip link show ifb1 >&/dev/null && ip link del ifb1 || true | |
} | |
remove_shaping() | |
{ | |
#set -x | |
tc qdisc list | grep -q "ingress" && tc qdisc del dev "$DEV" ingress || true | |
# Note: we need to avoid removing root qdisc in case this kernel defaults to fq_codel, "qdisc list" will output "fq_codel 0:" for root qdisc so we look for something different | |
tc qdisc list | grep -q "fq_codel [1-9]" && tc qdisc del dev "$DEV" root || true | |
ip link show | grep -q "$IFB" && ip link del "$IFB" || true | |
# configure CDG congestion control algorithm | |
modprobe tcp_cdg && echo cdg > /proc/sys/net/ipv4/tcp_congestion_control || true | |
#set +x | |
} | |
status() | |
{ | |
echo "─── queue discipline configuration: ──────────────────" | |
tc qdisc list | |
echo " TIP: use e.g. 'sudo tc qdisc del dev $DEV ingress' to remove ingress filtering" | |
echo " TIP: use e.g. 'sudo tc qdisc del dev $DEV root' to remove egress filtering" | |
echo "─── ip link show: ────────────────────────────────────" | |
ip link show | |
echo " TIP: use e.g. 'sudo ip link del $IFB' to remove ingress device" | |
} | |
color_status() | |
{ | |
status | grep --color=auto -E "^|$DEV|$IFB|rate [^ ]+" | |
} | |
# handle parameters | |
ACTION="$1" | |
shift || true | |
while [ ! -z "$1" ] | |
do | |
case "$1" in | |
-v|--verbose) | |
echo "Device: $DEV" | |
echo "Downlink rate (ingress): $DOWNLINK_RATE" | |
echo "Uplink rate (egress): $UPLINK_RATE" | |
set -x | |
;; | |
*) | |
if [ ! -z "$2" ]; then | |
echo "Unknown parameter: '$2'" 1>&2 | |
exit 1 | |
fi | |
;; | |
esac | |
shift || true | |
done | |
case "$ACTION" in | |
start) | |
remove_shaping | |
configure_shaping | |
;; | |
stop) | |
remove_shaping | |
;; | |
status) | |
color_status | |
;; | |
restart) | |
remove_shaping | |
configure_shaping | |
;; | |
*) | |
echo "Unknown action: $1" 1>&2 | |
echo "Usage: $0 <start|stop|restart|status> [--verbose|-v]" 1>&2 | |
exit 1 | |
esac |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment