#!/bin/bash # Traffic shaping script (AQM, fq_codel+tbf) # Copyright 2018 Mikko Rantalainen # License: MIT (X11) # Usage: # 21/0.8 Mbps connection (ADSL2): DOWNLINK_RATE=21.7Mbit UPLINK_RATE=0.8Mbit TBF_LATENCY=500ms bin/traffic-shaping start # 100/100 Mbps connection: ./traffic-shaping # 1/1 GBps connection: DOWNLINK_RATE=1Gbit UPLINK_RATE=1Gbit TBF_LATENCY=10ms 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 # set -e # abort if a command returns non-zero status (failed) #set -x # verbose execution DEV="${DEV:=$(ip route | grep "^default " | grep -Po "(?<=dev )[^ ]+" | uniq)}" # ingress: DOWNLINK_RATE="${DOWNLINK_RATE:=106000kbit}" # or e.g. "21.5Mbit" # egress: UPLINK_RATE="${UPLINK_RATE:=101000kbit}" CODEL_INTERVAL="${CODEL_INTERVAL:=100ms}" # usually 100ms, high speed links with low latency may need lower values CODEL_TARGET="${CODEL_TARGET:=5ms}" # 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:=6500}" UPLINK_BURST="${UPLINK_BURST:=6500}" TBF_LATENCY="${TBF_LATENCY:=14ms}" # set to lower latency to improve control over bandwidth limiting, UPLINK_BURST bytes must be able to be sent in this time IFB="$DEV.ingress" INITCWND="${INITCWND:=15}" # 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"): # 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 ip route change $(ip route | grep ^default) 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_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 [--verbose|-v]" 1>&2 exit 1 esac