Skip to content

Instantly share code, notes, and snippets.

@guangmuzhu
Forked from ole1986/traffic-control.sh
Last active September 9, 2025 02:14
Show Gist options
  • Select an option

  • Save guangmuzhu/cfbf176fe9d1ec933521f883d26d2f6d to your computer and use it in GitHub Desktop.

Select an option

Save guangmuzhu/cfbf176fe9d1ec933521f883d26d2f6d to your computer and use it in GitHub Desktop.
Traffic control script for incoming and outgoing packages using TC (on a specific ip address)
#!/bin/bash
VERSION="1.2.3"
INTERFACE="eth0"
VIRTUAL="ifb0"
CHANGE=0
DIRECTION=3
SPEED_DOWNLOAD="10mbit"
SPEED_UPLOAD="10mbit"
LIMIT=10
LOSS="0%"
DELAY="40ms"
BURST_TIME="100ms"
BURST=""
CBURST=""
CEIL=""
# Enhanced safety checks
function validate_interface {
if ! ip link show "$1" >/dev/null 2>&1; then
echo "ERROR: Interface $1 does not exist!" >&2
exit 1
fi
}
function validate_ip {
if ! [[ "$1" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]; then
echo "ERROR: Invalid IP address format: $1" >&2
exit 1
fi
}
function validate_speed {
if ! [[ "$1" =~ ^[0-9]+(kbit|mbit|gbit)$ ]]; then
echo "ERROR: Invalid speed format: $1. Valid formats: 10kbit, 100mbit, 1gbit" >&2
exit 1
fi
}
function validate_positive_number {
if ! [[ "$1" =~ ^[0-9]+$ ]] || [ "$1" -le 0 ]; then
echo "ERROR: $2 must be a positive integer: $1" >&2
exit 1
fi
}
function validate_loss {
if ! [[ "$1" =~ ^[0-9]+%$ ]] || [ "${1%\%}" -gt 100 ]; then
echo "ERROR: Loss must be between 0% and 100%: $1" >&2
exit 1
fi
}
function validate_time_unit {
if ! [[ "$1" =~ ^[0-9]+ms$ ]]; then
echo "ERROR: $2 must be in milliseconds with 'ms' unit (e.g., 40ms): $1" >&2
exit 1
fi
}
function extract_time_value {
echo "${1%ms}"
}
function multiply_and_concat {
local input="$1"
local mul="$2"
if [[ "$input" =~ ^([0-9]+\.?[0-9]*)([^0-9].*)$ ]]; then
local number="${BASH_REMATCH[1]}"
local suffix="${BASH_REMATCH[2]}"
local multiplied=$(echo "$number * $mul" | bc -l)
local result=$(printf "%g%s" "$multiplied" "$suffix")
echo "$result"
else
echo "$input"
fi
}
function ip_u32 {
IFS=. read -r a b c d <<< "$1"
printf "%08x\n" $(( (10#$a << 24) | (10#$b << 16) | (10#$c << 8) | 10#$d ))
}
function show_usage {
echo
echo "Bandwidth Control using TC (Production Safe)"
echo "Version: $VERSION | Author: ole1986, cambda, DeepSeek"
echo
echo "Usage: $1 [-h|--help] [-I|--if=] [-c|--change] [-r|--remove] [-i|--incoming] [-o|--outgoing] [-L|--limit=] [-d|--delay=] [-l|--loss=] [-U|--uspeed=] [-D|--dspeed=] [-t|--burst-time=] [-b|--burst=] [-B|--cburst=] [-C|--ceil=] <IP>"
echo
echo "Arguments:"
echo " -h|--help : show usage"
echo " -I|--if=<if> : the interface to connect (default: eth0)"
echo " -c|--change : update existing rules instead of removing"
echo " -r|--remove : remove all traffic control rules"
echo " -i|--incoming : limit incoming traffic only"
echo " -o|--outgoing : limit outgoing traffic only"
echo " -L|--limit=10 : queue length in packets (1-1000, default:10)"
echo " -d|--delay=40ms : network delay (1-1000ms, must include 'ms', default:40ms)"
echo " -l|--loss=0% : packet loss (0-100%, must include '%', default:0%)"
echo " -U|--uspeed=<speed>: upload speed (e.g., 10mbit, default:10mbit)"
echo " -D|--dspeed=<speed>: download speed (e.g., 10mbit, default:10mbit)"
echo " -t|--burst-time=100ms: burst duration (10-1000ms, must include 'ms', default:100ms)"
echo " -b|--burst=<bytes> : burst size in bytes (auto-calculated)"
echo " -B|--cburst=<bytes>: ceil burst size (default: 2x burst)"
echo " -C|--ceil=<speed> : peak rate (default: 1.5x rate)"
echo " <IP> : IP address to limit (required)"
echo
echo "Example (safe defaults):"
echo " $0 -U 10mbit -D 10mbit --limit=5 --delay=50ms --burst-time=150ms 192.168.1.100"
echo
echo "Changelog:"
echo "v1.2.3 - Fix tc filter IP match in CHANGE mode"
echo "v1.2.2 - Strict time unit validation (must include 'ms')"
echo "v1.2.1 - Enhanced safety checks and validation"
echo "v1.2.0 - Added burst-time parameter"
}
# Safe burst calculation with bounds
calculate_burst() {
local rate=$1
# Validate input
validate_speed "$rate"
# Extract numeric value and unit
local value=$(echo $rate | grep -oE '[0-9]+')
local unit=$(echo $rate | grep -oE '[a-zA-Z]+')
# Convert to bits per second
case $unit in
kbit) value=$((value * 1000)) ;;
mbit) value=$((value * 1000000)) ;;
gbit) value=$((value * 1000000000)) ;;
*) value=$((value * 1000000)) ;;
esac
# Extract burst time value (remove 'ms' unit)
local burst_time_value=$(extract_time_value "$BURST_TIME")
# Calculate burst: (rate * burst_time) / 8 (bytes)
local burst=$(( (value * burst_time_value) / (8 * 1000) ))
# Apply safety bounds
local min_burst=$((1500 * 2)) # 2 MTUs
local max_burst=$((100 * 1024 * 1024)) # 100MB
if [ $burst -lt $min_burst ]; then
burst=$min_burst
elif [ $burst -gt $max_burst ]; then
burst=$max_burst
fi
echo $burst
}
# Safe removal function
function remove_tc {
echo "Removing traffic control rules..."
# Outgoing rules
if tc qdisc show dev $INTERFACE | grep -q 'htb 1:'; then
tc qdisc del root dev $INTERFACE 2>/dev/null || true
fi
# Incoming rules
if tc qdisc show dev $VIRTUAL | grep -q 'htb 2:'; then
tc qdisc del dev $INTERFACE handle ffff: ingress 2>/dev/null || true
tc qdisc del root dev $VIRTUAL 2>/dev/null || true
fi
# Cleanup virtual interface
if ip link show $VIRTUAL >/dev/null 2>&1; then
ip link set dev $VIRTUAL down 2>/dev/null
modprobe -r ifb 2>/dev/null || true
fi
echo "Traffic control rules removed"
}
# Safe TC command execution
safe_tc() {
local cmd=$1
shift
echo "Executing: tc $cmd $@"
tc $cmd "$@" || {
echo "WARNING: tc command failed (rc=$?): tc $cmd $@" >&2
return 1
}
}
# Outgoing traffic control with safety
function tc_outgoing {
echo "Configuring OUTGOING traffic control (safe mode)"
# Validate parameters with explicit units
validate_speed "$SPEED_UPLOAD"
validate_positive_number "$LIMIT" "Queue limit"
validate_loss "$LOSS"
validate_time_unit "$DELAY" "Delay"
validate_time_unit "$BURST_TIME" "Burst time"
# Calculate burst if needed
if [ -z "$BURST" ]; then
BURST=$(calculate_burst "$SPEED_UPLOAD")
fi
# Set safe defaults
: ${CBURST:=$((BURST * 2))}
: ${CEIL:=$(multiply_and_concat "$SPEED_UPLOAD" "1.5")}
# Validate calculated values
validate_positive_number "$BURST" "Burst size"
validate_positive_number "$CBURST" "Ceil burst"
validate_speed "$CEIL"
echo "- Safe parameters:"
echo " Rate: $SPEED_UPLOAD, Ceil: $CEIL"
echo " Burst: $BURST bytes, CBurst: $CBURST bytes"
echo " Burst Time: $BURST_TIME"
echo " Queue: $LIMIT pkts, Delay: $DELAY, Loss: $LOSS"
# Create or change HTB
if [[ $CHANGE -ne 0 ]] && tc class show dev $INTERFACE | grep -q 'htb 1:1'; then
echo "Updating existing HTB class..."
safe_tc class change dev $INTERFACE parent 1: classid 1:1 htb \
rate "$SPEED_UPLOAD" \
ceil "$CEIL" \
burst ${BURST} \
cburst ${CBURST} \
quantum 1600
else
echo "Creating new HTB qdisc..."
safe_tc qdisc add dev $INTERFACE root handle 1: htb direct_qlen 2
safe_tc class add dev $INTERFACE parent 1: classid 1:1 htb \
rate "$SPEED_UPLOAD" \
ceil "$CEIL" \
burst ${BURST} \
cburst ${CBURST} \
quantum 1600
fi
# Netem configuration
if [[ $CHANGE -ne 0 ]] && tc qdisc show dev $INTERFACE | grep -q 'netem 10:'; then
echo "Updating existing netem..."
safe_tc qdisc change dev $INTERFACE parent 1:1 handle 10: netem \
limit $LIMIT \
delay "$DELAY" \
loss "$LOSS"
else
safe_tc qdisc add dev $INTERFACE parent 1:1 handle 10: netem \
limit $LIMIT \
delay "$DELAY" \
loss "$LOSS"
fi
# Filter configuration
if [[ $CHANGE -ne 0 ]] && tc filter show dev $INTERFACE | grep -q "$(ip_u32 "$ADDRESS")"; then
echo "Updating existing filter..."
safe_tc filter change dev $INTERFACE protocol ip parent 1: \
handle 800::801 prio 1 u32 match ip dst "$ADDRESS" flowid 1:1
else
safe_tc filter add dev $INTERFACE protocol ip parent 1: \
handle 800::801 prio 1 u32 match ip dst "$ADDRESS" flowid 1:1
fi
echo "OUTGOING configuration complete"
}
# Incoming traffic control with safety
function tc_incoming {
echo "Configuring INCOMING traffic control (safe mode)"
# Validate parameters with explicit units
validate_speed "$SPEED_DOWNLOAD"
validate_positive_number "$LIMIT" "Queue limit"
validate_loss "$LOSS"
validate_time_unit "$DELAY" "Delay"
validate_time_unit "$BURST_TIME" "Burst time"
# Calculate burst if needed
if [ -z "$BURST" ]; then
BURST=$(calculate_burst "$SPEED_DOWNLOAD")
fi
# Set safe defaults
: ${CBURST:=$((BURST * 2))}
: ${CEIL:=$(multiply_and_concat "$SPEED_DOWNLOAD" "1.5")}
# Validate calculated values
validate_positive_number "$BURST" "Burst size"
validate_positive_number "$CBURST" "Ceil burst"
validate_speed "$CEIL"
echo "- Safe parameters:"
echo " Rate: $SPEED_DOWNLOAD, Ceil: $CEIL"
echo " Burst: $BURST bytes, CBurst: $CBURST bytes"
echo " Burst Time: $BURST_TIME"
echo " Queue: $LIMIT pkts, Delay: $DELAY, Loss: $LOSS"
# Setup virtual interface
if [ $CHANGE -eq 0 ]; then
echo "Initializing virtual interface..."
modprobe ifb numifbs=1 || {
echo "ERROR: Failed to load ifb module" >&2
return 1
}
ip link set dev $VIRTUAL up || {
echo "ERROR: Failed to bring up $VIRTUAL" >&2
return 1
}
fi
# Create ingress if needed
if ! tc qdisc show dev $INTERFACE | grep -q 'ingress'; then
safe_tc qdisc add dev $INTERFACE handle ffff: ingress
safe_tc filter add dev $INTERFACE parent ffff: protocol ip u32 \
match u32 0 0 action mirred egress redirect dev $VIRTUAL
fi
# Create or change HTB
if [[ $CHANGE -ne 0 ]] && tc class show dev $VIRTUAL | grep -q 'htb 2:1'; then
echo "Updating existing HTB class..."
safe_tc class change dev $VIRTUAL parent 2: classid 2:1 htb \
rate "$SPEED_DOWNLOAD" \
ceil "$CEIL" \
burst ${BURST} \
cburst ${CBURST} \
quantum 1600
else
echo "Creating new HTB qdisc..."
safe_tc qdisc add dev $VIRTUAL root handle 2: htb direct_qlen 2
safe_tc class add dev $VIRTUAL parent 2: classid 2:1 htb \
rate "$SPEED_DOWNLOAD" \
ceil "$CEIL" \
burst ${BURST} \
cburst ${CBURST} \
quantum 1600
fi
# Netem configuration
if [[ $CHANGE -ne 0 ]] && tc qdisc show dev $VIRTUAL | grep -q 'netem 20:'; then
echo "Updating existing netem..."
safe_tc qdisc change dev $VIRTUAL parent 2:1 handle 20: netem \
limit $LIMIT \
delay "$DELAY" \
loss "$LOSS"
else
safe_tc qdisc add dev $VIRTUAL parent 2:1 handle 20: netem \
limit $LIMIT \
delay "$DELAY" \
loss "$LOSS"
fi
# Filter configuration
if [[ $CHANGE -ne 0 ]] && tc filter show dev $VIRTUAL | grep -q "$(ip_u32 "$ADDRESS")"; then
echo "Updating existing filter..."
safe_tc filter change dev $VIRTUAL protocol ip parent 2: \
handle 800::802 prio 1 u32 match ip src "$ADDRESS" flowid 2:1
else
safe_tc filter add dev $VIRTUAL protocol ip parent 2: \
handle 800::802 prio 1 u32 match ip src "$ADDRESS" flowid 2:1
fi
echo "INCOMING configuration complete"
}
# --- Main Script (with strict unit validation) ---
# Check required commands
for cmd in tc ip modprobe awk grep; do
if ! command -v $cmd >/dev/null 2>&1; then
echo "ERROR: Required command '$cmd' not found" >&2
exit 1
fi
done
# Validate root privileges
if [ "$(id -u)" -ne 0 ]; then
echo "ERROR: This script must be run as root" >&2
exit 1
fi
# Parse arguments
while [ $# -gt 0 ]; do
case "$1" in
-h|--help)
show_usage "$0"
exit 0
;;
-c|--change)
CHANGE=1
;;
-r|--remove)
DIRECTION=0
;;
-o|--outgoing)
DIRECTION=1
;;
-i|--incoming)
DIRECTION=2
;;
-I=*|--if=*)
INTERFACE="${1#*=}"
validate_interface "$INTERFACE"
;;
-L=*|--limit=*)
LIMIT="${1#*=}"
validate_positive_number "$LIMIT" "Queue limit"
;;
-l=*|--loss=*)
LOSS="${1#*=}"
validate_loss "$LOSS"
;;
-d=*|--delay=*)
DELAY="${1#*=}"
validate_time_unit "$DELAY" "Delay"
;;
-U=*|--uspeed=*)
SPEED_UPLOAD="${1#*=}"
validate_speed "$SPEED_UPLOAD"
;;
-D=*|--dspeed=*)
SPEED_DOWNLOAD="${1#*=}"
validate_speed "$SPEED_DOWNLOAD"
;;
-t=*|--burst-time=*)
BURST_TIME="${1#*=}"
validate_time_unit "$BURST_TIME" "Burst time"
;;
-b=*|--burst=*)
BURST="${1#*=}"
validate_positive_number "$BURST" "Burst size"
;;
-B=*|--cburst=*)
CBURST="${1#*=}"
validate_positive_number "$CBURST" "Ceil burst"
;;
-C=*|--ceil=*)
CEIL="${1#*=}"
validate_speed "$CEIL"
;;
*)
# Assume it's the IP address
if [ -z "$ADDRESS" ]; then
ADDRESS="$1"
validate_ip "$ADDRESS"
else
echo "WARNING: Ignoring extra argument: $1" >&2
fi
;;
esac
shift
done
# Apply safe defaults with explicit units
: ${DELAY:="40ms"}
: ${BURST_TIME:="100ms"}
# Extract numerical values for validation
burst_time_value=$(extract_time_value "$BURST_TIME")
delay_value=$(extract_time_value "$DELAY")
# Apply safe time boundaries
if [ "$burst_time_value" -lt 10 ]; then
echo "WARNING: Burst time <10ms is too low, setting to 10ms" >&2
BURST_TIME="10ms"
elif [ "$burst_time_value" -gt 1000 ]; then
echo "WARNING: Burst time >1000ms is too high, setting to 1000ms" >&2
BURST_TIME="1000ms"
fi
if [ "$delay_value" -lt 0 ]; then
echo "WARNING: Negative delay is invalid, setting to 0ms" >&2
DELAY="0ms"
elif [ "$delay_value" -gt 1000 ]; then
echo "WARNING: Delay >1000ms is too high, setting to 1000ms" >&2
DELAY="1000ms"
fi
# Apply safe queue limits
LIMIT=${LIMIT:-10}
if [ "$LIMIT" -lt 2 ]; then
echo "WARNING: Queue limit <2 may cause instability, setting to 2" >&2
LIMIT=2
elif [ "$LIMIT" -gt 10000 ]; then
echo "WARNING: Queue limit >1000 may cause high latency, setting to 1000" >&2
LIMIT=10000
fi
# Remove rules if requested
if [ $DIRECTION -eq 0 ]; then
remove_tc
exit 0
fi
# Validate required IP address
if [ -z "$ADDRESS" ]; then
echo "ERROR: IP address is required" >&2
show_usage "$0"
exit 1
fi
# Initialize if needed
[ $CHANGE -eq 0 ] && remove_tc
# Execute traffic control
echo "Starting safe traffic control for $ADDRESS"
if [ $DIRECTION -eq 1 ]; then
tc_outgoing
elif [ $DIRECTION -eq 2 ]; then
tc_incoming
elif [ $DIRECTION -eq 3 ]; then
tc_outgoing
tc_incoming
fi
echo "Traffic control applied successfully"
echo "Verify with:"
echo " tc -s qdisc show dev $INTERFACE"
echo " tc -s class show dev $INTERFACE"
[ "$DIRECTION" -ne 1 ] && echo " tc -s qdisc show dev $VIRTUAL"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment