Skip to content

Instantly share code, notes, and snippets.

@majek
Created July 8, 2022 08:52
Show Gist options
  • Save majek/ae3f85666b977ef0155059b4a57e1ea6 to your computer and use it in GitHub Desktop.
Save majek/ae3f85666b977ef0155059b4a57e1ea6 to your computer and use it in GitHub Desktop.
Bash script to selectively slow down traffic to given IP's - using tc netem
#!/bin/bash
set -e
set -u
set -o pipefail
IFACE="lo"
LATENCY=200
CLEAN=""
IPS=()
for i in "$@"; do
case $i in
--help)
cat <<EOF
Usage:
./slow_network.sh [--clean] [--iface=IFACE] [--latency=LATENCYMS] [ADDRESS]...
Cleans and sets a tc (traffic control) policy on a given IFACE, adding
latency on traffic going to specific IP addresses. The script first
cleans up existing tc policy, and sets up a simple netem/u32
configuration.
By default the IFACE=lo and LATENCYMS=200. IFACE can also be set to a
"default" string which will cause the script to resolve the default
gateway and use the appropriate interface.
When ADDRESSES are not given over command line, they are read from
stdin until EOF.
The --clean option just clears the tc config.
Example:
$ sudo ./slow_network.sh --interface=default 1.1.1.1 8.8.8.8
[ ] Setting tc on enx381428a758c2, latency 200 ms
$ ping -nc2 1.1.1.1
PING 1.1.1.1 (1.1.1.1) 56(84) bytes of data.
64 bytes from 1.1.1.1: icmp_seq=1 ttl=64 time=201 ms
64 bytes from 1.1.1.1: icmp_seq=2 ttl=64 time=201 ms
If IFACE is lo, then the LATENCY might be doubled since both ingress
and egress go via tc::
$ sudo ./slow_network.sh --latency=100 ::1 127.0.0.1
[ ] Setting tc on lo, latency 100 ms
$ ping -nc2 127.0.0.1
PING 127.0.0.1 (127.0.0.1) 56(84) bytes of data.
64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=200 ms
64 bytes from 127.0.0.1: icmp_seq=2 ttl=64 time=200 ms
You can give the script a hostname to resolve:
$ sudo ./slow_network.sh --iface=default speed.cloudflare.com
[ ] Setting tc on enxf6beece96820, latency 200 ms
- 104.18.47.225
- 172.64.156.31
- 2606:4700:440e::6812:2fe1
- 2606:4700:440e::ac40:9c1f
EOF
exit 1
;;
--clean|--clear)
CLEAN="yes"
shift # past argument with no value
;;
-i=*|--interface=*|--iface=*)
IFACE="${i#*=}"
shift
;;
-l=*|--latency=*)
LATENCY="${i#*=}"
shift
;;
-*|--*)
echo "Unknown option $i"
exit 1
;;
*)
IPS[${#IPS[@]}]="$i"
shift
;;
esac
done
if [ "$IFACE" == "default" ]; then
IFACE=`ip r show default|awk '/default/ {print $5}'|head -n1`
fi
if [ "$CLEAN" == "yes" ]; then
echo "[ ] Cleaning up tc on $IFACE"
tc qdisc del dev $IFACE root
exit 0
fi
echo "[ ] Setting tc on $IFACE, latency $LATENCY ms"
tc qdisc del dev $IFACE root || true
tc qdisc add dev $IFACE root handle 1: prio bands 2 priomap 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
tc qdisc add dev $IFACE parent 1:1 handle 2: fq_codel
tc qdisc add dev $IFACE parent 1:2 handle 3: netem delay ${LATENCY}ms
if [ "${#IPS[@]}" -gt "0" ]; then
for LINE in "${IPS[@]}"; do
echo $LINE
done
else
while read LINE; do
echo $LINE
done
fi | while read IP; do
echo $IP | \
python3 -c "import socket as s;print('\n'.join(x[4][0] for x in s.getaddrinfo(input(), 0, 0,s.SOCK_STREAM)))" \
| while read XIP; do
echo " - $XIP"
if [[ "$XIP" =~ .*":".* ]]; then
tc filter add dev $IFACE protocol ipv6 parent 1: u32 match ip6 dst $XIP flowid 1:2
else
tc filter add dev $IFACE protocol ip parent 1: prio 1 u32 match ip dst $XIP flowid 1:2
fi
done
done
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment