#!/usr/bin/env bash # Traffic control for AmneziaWG (production-ready) set -euo pipefail LOG="/var/log/amnezia-tc.log" LOCK_FILE="/tmp/amnezia-tc.lock" LIMIT="${LIMIT:-70mbit}" CONTAINER="" WG_IF="${WG_IF:-wg0}" # Prefer new container name, fall back to old for c in amnezia-awg2 amnezia-awg; do if docker ps --format '{{.Names}}' 2>/dev/null | grep -xq "$c"; then CONTAINER="$c" break fi done log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG" } # Lock to avoid concurrent runs exec 200>"$LOCK_FILE" flock -n 200 || { log "Script already running, exit"; exit 0; } if [ -z "$CONTAINER" ]; then log "Container amnezia-awg2/amnezia-awg not found, exit" exit 0 fi # detect interface inside container if default not present there if ! docker exec "$CONTAINER" ip link show "$WG_IF" >/dev/null 2>&1; then DET_IF=$(docker exec "$CONTAINER" sh -c "ip -o link show 2>/dev/null | awk -F': ' '{print $2}'" 2>/dev/null \ | egrep -o '^(wg[0-9]+|wg|awg[0-9]*|amnezia[0-9]*|tun[0-9]+)' | head -n1 || true) if [ -n "$DET_IF" ]; then WG_IF="$DET_IF" log "Detected interface inside container: $WG_IF" else log "Interface $WG_IF not found inside container $CONTAINER, exit" exit 0 fi fi log "=== Start updating rules === (container=$CONTAINER, iface=$WG_IF, limit=$LIMIT)" # Get current clients CLIENT_IPS=$(docker exec "$CONTAINER" sh -c "wg show all allowed-ips 2>/dev/null" 2>/dev/null \ | awk '{print $3}' | grep -oE '10\.8\.1\.[0-9]{1,3}' | sort -u || true) # fallback if [ -z "$CLIENT_IPS" ]; then CLIENT_IPS=$(docker exec "$CONTAINER" sh -c "wg show 2>/dev/null" 2>/dev/null \ | grep -Eo '10\.8\.1\.[0-9]{1,3}' | sort -u || true) fi if [ -z "$CLIENT_IPS" ]; then log "No clients found, exit" exit 0 fi log "Clients: $(echo $CLIENT_IPS | tr '\n' ' ')" EXISTING_RULES=$(docker exec "$CONTAINER" sh -c "tc filter show dev $WG_IF parent 1: 2>/dev/null || true" 2>/dev/null \ | grep -oE 'match [0-9a-f]+' | awk '{print $2}' | sort -u || true) NEEDS_UPDATE=false num_ips=$(echo "$CLIENT_IPS" | wc -w) num_rules=$(echo "$EXISTING_RULES" | wc -w) if [ "$num_rules" -ne "$num_ips" ] || [ -z "$EXISTING_RULES" ]; then NEEDS_UPDATE=true else for ip in $CLIENT_IPS; do hex_ip=$(printf '%02x' ${ip//./ } | tr -d '\n' || true) if ! echo "$EXISTING_RULES" | grep -q "$hex_ip"; then NEEDS_UPDATE=true break fi done fi if [ "$NEEDS_UPDATE" = false ]; then log "Rules are already up-to-date, skip" exit 0 fi log "Updating rules (old will be removed)" docker exec "$CONTAINER" sh -c " tc qdisc del dev $WG_IF root 2>/dev/null || true tc qdisc del dev $WG_IF ingress 2>/dev/null || true ip link del ifb0 2>/dev/null || true " 2>/dev/null || true docker exec "$CONTAINER" sh -c " tc qdisc add dev $WG_IF root handle 1: htb 2>/dev/null || true tc class add dev $WG_IF parent 1: classid 1:1 htb rate 1000mbit ceil 1000mbit 2>/dev/null || true " 2>/dev/null || true docker exec "$CONTAINER" sh -c " modprobe ifb 2>/dev/null || true ip link add name ifb0 type ifb 2>/dev/null || true ip link set dev ifb0 up 2>/dev/null || true tc qdisc add dev $WG_IF handle ffff: ingress 2>/dev/null || true tc filter add dev $WG_IF parent ffff: protocol all u32 match u32 0 0 action mirred egress redirect dev ifb0 2>/dev/null || true tc qdisc add dev ifb0 root handle 2: htb 2>/dev/null || true tc class add dev ifb0 parent 2: classid 2:1 htb rate 1000mbit ceil 1000mbit 2>/dev/null || true " 2>/dev/null || true class_id=10 count=0 for ip in $CLIENT_IPS; do log "Adding limit for $ip (class $class_id)" docker exec "$CONTAINER" sh -c " tc class add dev $WG_IF parent 1:1 classid 1:$class_id htb rate $LIMIT ceil $LIMIT 2>/dev/null || true tc qdisc add dev $WG_IF parent 1:$class_id fq_codel 2>/dev/null || true tc filter add dev $WG_IF parent 1: protocol ip u32 match ip dst $ip flowid 1:$class_id 2>/dev/null || true " 2>/dev/null || true docker exec "$CONTAINER" sh -c " tc class add dev ifb0 parent 2:1 classid 2:$class_id htb rate $LIMIT ceil $LIMIT 2>/dev/null || true tc qdisc add dev ifb0 parent 2:$class_id fq_codel 2>/dev/null || true tc filter add dev ifb0 parent 2: protocol ip u32 match ip src $ip flowid 2:$class_id 2>/dev/null || true " 2>/dev/null || true class_id=$((class_id + 1)) count=$((count + 1)) done log "Limits applied for $count clients" log "=== Completed successfully ===" exit 0