139 lines
4.4 KiB
Plaintext
139 lines
4.4 KiB
Plaintext
#!/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
|