-
-
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)
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 | |
| 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