fixes
This commit is contained in:
@@ -9,13 +9,25 @@
|
||||
# - P2P protocols have backpressure - if you're slow to respond, peers back off
|
||||
# - Limiting outgoing is usually sufficient to control your bandwidth usage
|
||||
#
|
||||
# IP-based limiting: When using --limit-by-ip, a cronjob is automatically created
|
||||
# to update iptables rules if the container IP changes (e.g., after restart)
|
||||
#
|
||||
# Usage:
|
||||
# ./limit-bandwidth.sh <compose-file> [start|stop|status] [--limit BANDWIDTH]
|
||||
# ./limit-bandwidth.sh <compose-file> [start|stop|status] [OPTIONS]
|
||||
# ./limit-bandwidth.sh rpc/gnosis/reth/gnosis-mainnet-reth-pruned-trace start
|
||||
# ./limit-bandwidth.sh rpc/gnosis/reth/gnosis-mainnet-reth-pruned-trace start --limit 20mbit
|
||||
# ./limit-bandwidth.sh rpc/gnosis/reth/gnosis-mainnet-reth-pruned-trace start --limit 5mbit --total-limit
|
||||
# ./limit-bandwidth.sh rpc/gnosis/reth/gnosis-mainnet-reth-pruned-trace start --limit 5mbit --per-port-limit
|
||||
#
|
||||
# Environment variable:
|
||||
# Options:
|
||||
# --limit BANDWIDTH Set bandwidth limit (e.g., 20mbit, 100mbit)
|
||||
# --total-limit All ports share the limit (default)
|
||||
# --per-port-limit Each port gets its own limit
|
||||
#
|
||||
# Environment variables:
|
||||
# BANDWIDTH_LIMIT=20mbit ./limit-bandwidth.sh <compose-file> start
|
||||
# TOTAL_LIMIT=false BANDWIDTH_LIMIT=5mbit ./limit-bandwidth.sh <compose-file> start
|
||||
# (TOTAL_LIMIT=false for per-port limits, true is default)
|
||||
#
|
||||
# For cronjob:
|
||||
# */5 * * * * /path/to/rpc/limit-bandwidth.sh /path/to/compose.yml start
|
||||
@@ -28,6 +40,14 @@ BASEPATH="$(cd "$(dirname "$0")" && pwd)"
|
||||
# Bandwidth limit (can be overridden via BANDWIDTH_LIMIT env var or --limit parameter)
|
||||
# Default: 100mbit
|
||||
BANDWIDTH_LIMIT="${BANDWIDTH_LIMIT:-100mbit}"
|
||||
# TOTAL_LIMIT: If "true", limit is shared across all ports (total bandwidth)
|
||||
# If "false", each port gets its own limit (per-port bandwidth)
|
||||
# Default: true (total limit is now the default behavior)
|
||||
TOTAL_LIMIT="${TOTAL_LIMIT:-true}"
|
||||
# LIMIT_BY_IP: If "true", limit all traffic from container IP (not just specific ports)
|
||||
# This catches ephemeral ports but requires detecting container IP dynamically
|
||||
# Default: false (use port-based limiting)
|
||||
LIMIT_BY_IP="${LIMIT_BY_IP:-false}"
|
||||
BURST_MULTIPLIER="${BURST_MULTIPLIER:-0.1}" # Burst is 10% of limit by default
|
||||
LATENCY="50ms" # Latency for shaping
|
||||
|
||||
@@ -61,16 +81,24 @@ fi
|
||||
|
||||
# Parse arguments
|
||||
if [ $# -lt 1 ]; then
|
||||
echo "Usage: $0 <compose-file> [start|stop|status] [--limit BANDWIDTH]"
|
||||
echo "Usage: $0 <compose-file> [start|stop|status] [OPTIONS]"
|
||||
echo ""
|
||||
echo "Options:"
|
||||
echo " --limit BANDWIDTH Set bandwidth limit (e.g., 20mbit, 100mbit)"
|
||||
echo " Can also use BANDWIDTH_LIMIT environment variable"
|
||||
echo " --total-limit Use total limit mode: all ports share the limit (default)"
|
||||
echo " --per-port-limit Use per-port limit mode: each port gets its own limit"
|
||||
echo " --limit-by-ip Limit by container IP (catches all ports including ephemeral)"
|
||||
echo " Traffic between containers on same network is NOT limited"
|
||||
echo " --limit-by-port Limit by source port only (default, misses ephemeral ports)"
|
||||
echo " Can also use LIMIT_BY_IP=true/false environment variable"
|
||||
echo ""
|
||||
echo "Examples:"
|
||||
echo " $0 rpc/gnosis/reth/gnosis-mainnet-reth-pruned-trace start"
|
||||
echo " $0 rpc/gnosis/reth/gnosis-mainnet-reth-pruned-trace.yml start"
|
||||
echo " $0 rpc/gnosis/reth/gnosis-mainnet-reth-pruned-trace start --limit 20mbit"
|
||||
echo " $0 rpc/gnosis/reth/gnosis-mainnet-reth-pruned-trace start --limit 5mbit --total-limit"
|
||||
echo " $0 rpc/gnosis/reth/gnosis-mainnet-reth-pruned-trace start --limit 5mbit --per-port-limit"
|
||||
echo " BANDWIDTH_LIMIT=20mbit $0 rpc/gnosis/reth/gnosis-mainnet-reth-pruned-trace start"
|
||||
echo " $0 rpc/ethereum/geth/ethereum-mainnet-geth-pruned-pebble-path status"
|
||||
exit 1
|
||||
@@ -79,19 +107,54 @@ fi
|
||||
COMPOSE_FILE="$1"
|
||||
ACTION=${2:-start}
|
||||
|
||||
# Parse optional --limit parameter
|
||||
if [ "$ACTION" = "--limit" ] && [ $# -ge 3 ]; then
|
||||
BANDWIDTH_LIMIT="$3"
|
||||
ACTION=${4:-start}
|
||||
elif [ $# -ge 3 ] && [ "$2" = "--limit" ]; then
|
||||
BANDWIDTH_LIMIT="$3"
|
||||
ACTION=${4:-start}
|
||||
elif [ $# -ge 4 ] && [ "$3" = "--limit" ]; then
|
||||
BANDWIDTH_LIMIT="$4"
|
||||
elif [ "$ACTION" = "--limit" ]; then
|
||||
# Parse optional parameters
|
||||
# Special handling for update-ip command (it has its own parameters)
|
||||
if [ "$ACTION" = "update-ip" ]; then
|
||||
# For update-ip, skip normal argument parsing and let the case statement handle it
|
||||
shift 2 # Remove compose file and action
|
||||
else
|
||||
# Normal argument parsing for start/stop/status
|
||||
shift # Remove compose file
|
||||
shift # Remove action (or use default)
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--limit)
|
||||
if [ -z "$2" ]; then
|
||||
echo -e "${RED}Error: --limit requires a bandwidth value${NC}"
|
||||
echo "Example: $0 $COMPOSE_FILE start --limit 20mbit"
|
||||
exit 1
|
||||
fi
|
||||
BANDWIDTH_LIMIT="$2"
|
||||
shift 2
|
||||
;;
|
||||
--total-limit)
|
||||
TOTAL_LIMIT="true"
|
||||
shift
|
||||
;;
|
||||
--per-port-limit)
|
||||
TOTAL_LIMIT="false"
|
||||
shift
|
||||
;;
|
||||
--limit-by-ip)
|
||||
LIMIT_BY_IP="true"
|
||||
shift
|
||||
;;
|
||||
--limit-by-port)
|
||||
LIMIT_BY_IP="false"
|
||||
shift
|
||||
;;
|
||||
*)
|
||||
# Unknown option, might be old-style --limit usage
|
||||
if [ "$1" = "--limit" ] && [ -n "$2" ]; then
|
||||
BANDWIDTH_LIMIT="$2"
|
||||
shift 2
|
||||
else
|
||||
echo -e "${YELLOW}Warning: Unknown option '$1', ignoring${NC}"
|
||||
shift
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
done
|
||||
fi
|
||||
|
||||
# Handle .yml extension (like latest.sh does)
|
||||
@@ -216,6 +279,76 @@ find_bridge() {
|
||||
echo "eth0" # Final fallback
|
||||
}
|
||||
|
||||
# Function to find the physical egress interface (where traffic actually leaves the system)
|
||||
find_egress_interface() {
|
||||
# Method 1: Find interface from default route (most reliable)
|
||||
local default_route=$(ip route show default 2>/dev/null | head -1)
|
||||
if [ -n "$default_route" ]; then
|
||||
local egress=$(echo "$default_route" | awk '{for(i=1;i<=NF;i++) if($i=="dev") print $(i+1)}' | head -1)
|
||||
if [ -n "$egress" ] && ip link show "$egress" >/dev/null 2>&1; then
|
||||
# Verify it's not a virtual interface
|
||||
if ! echo "$egress" | grep -qE "^(lo|docker|br-|veth)"; then
|
||||
echo "$egress"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# Method 2: Find interface used for a test route (works even without default route)
|
||||
# Try multiple common IPs to find egress interface
|
||||
for test_ip in 8.8.8.8 1.1.1.1 208.67.222.222; do
|
||||
local route_output=$(ip route get "$test_ip" 2>/dev/null | head -1)
|
||||
if [ -n "$route_output" ]; then
|
||||
local egress=$(echo "$route_output" | awk '{for(i=1;i<=NF;i++) if($i=="dev") print $(i+1)}' | head -1)
|
||||
if [ -n "$egress" ] && ip link show "$egress" >/dev/null 2>&1; then
|
||||
# Verify it's not a virtual interface
|
||||
if ! echo "$egress" | grep -qE "^(lo|docker|br-|veth)"; then
|
||||
echo "$egress"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
# Method 3: Find first physical interface (non-virtual, non-loopback)
|
||||
# Look for interfaces that are UP and not virtual
|
||||
local iface=$(ip link show | grep -E "^[0-9]+:" | grep -vE "lo:|docker|br-|veth" | \
|
||||
while read -r line; do
|
||||
ifname=$(echo "$line" | cut -d: -f2 | tr -d ' ')
|
||||
# Check if interface is UP and not a virtual type
|
||||
if ip link show "$ifname" 2>/dev/null | grep -q "state UP" && \
|
||||
! ip link show "$ifname" 2>/dev/null | grep -qE "link/loopback|link/none"; then
|
||||
echo "$ifname"
|
||||
break
|
||||
fi
|
||||
done | head -1)
|
||||
|
||||
if [ -n "$iface" ]; then
|
||||
echo "$iface"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Method 4: Simple fallback - find first non-virtual interface name
|
||||
local iface=$(ip link show | grep -E "^[0-9]+:" | grep -vE "lo:|docker|br-|veth" | \
|
||||
head -1 | cut -d: -f2 | tr -d ' ')
|
||||
|
||||
if [ -n "$iface" ]; then
|
||||
echo "$iface"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Final fallback: common interface names (in order of likelihood)
|
||||
for fallback in eth0 enp0s3 enp0s8 ens33 ens3; do
|
||||
if ip link show "$fallback" >/dev/null 2>&1; then
|
||||
echo "$fallback"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
|
||||
# Last resort
|
||||
echo "eth0"
|
||||
}
|
||||
|
||||
# Extract ports from compose file
|
||||
PORTS_RAW=$(extract_ports "$COMPOSE_FILE")
|
||||
|
||||
@@ -237,24 +370,160 @@ fi
|
||||
|
||||
echo -e "${GREEN}Found ${#PORTS[@]} public port(s): ${PORTS[*]}${NC}"
|
||||
|
||||
# Find the bridge interface
|
||||
# Find the bridge interface (for iptables marking)
|
||||
BRIDGE=$(find_bridge "$COMPOSE_DIR")
|
||||
echo -e "${BLUE}Using network interface: ${BRIDGE}${NC}"
|
||||
echo -e "${BLUE}Using bridge interface: ${BRIDGE}${NC}"
|
||||
|
||||
# Verify interface exists
|
||||
# Find the physical egress interface (where traffic actually leaves)
|
||||
EGRESS_IFACE=$(find_egress_interface)
|
||||
echo -e "${BLUE}Using egress interface: ${EGRESS_IFACE}${NC}"
|
||||
|
||||
# Function to get network subnet from .env file or environment
|
||||
get_network_subnet() {
|
||||
# Try to get from .env file in the same directory as compose file first
|
||||
local compose_dir=$(dirname "$COMPOSE_FILE")
|
||||
local env_file="$compose_dir/.env"
|
||||
|
||||
# Also check root .env file (where CHAINS_SUBNET is typically defined)
|
||||
if [ ! -f "$env_file" ] || ! grep -q "^[[:space:]]*CHAINS_SUBNET[[:space:]]*=" "$env_file" 2>/dev/null; then
|
||||
env_file="$BASEPATH/.env"
|
||||
fi
|
||||
|
||||
# Try to read CHAINS_SUBNET from .env file
|
||||
if [ -f "$env_file" ]; then
|
||||
# Handle various .env formats: CHAINS_SUBNET=value, CHAINS_SUBNET="value", CHAINS_SUBNET='value', CHAINS_SUBNET = value
|
||||
local subnet=$(grep -E "^[[:space:]]*CHAINS_SUBNET[[:space:]]*=" "$env_file" 2>/dev/null | head -1 | sed 's/^[^=]*=[[:space:]]*//' | sed 's/^["'\'']//' | sed 's/["'\'']$//' | tr -d ' ')
|
||||
if [ -n "$subnet" ]; then
|
||||
echo "$subnet"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
# Fallback: try environment variable (allows override)
|
||||
if [ -n "$CHAINS_SUBNET" ]; then
|
||||
echo "$CHAINS_SUBNET"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Last resort: try to detect from Docker network (dynamic detection)
|
||||
local network="rpc_chains"
|
||||
local subnet=$(docker network inspect "$network" --format '{{range .IPAM.Config}}{{.Subnet}}{{end}}' 2>/dev/null | head -1)
|
||||
if [ -n "$subnet" ]; then
|
||||
echo "$subnet"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Final fallback: default from base.yml (matches base.yml default)
|
||||
echo "192.168.0.0/26"
|
||||
}
|
||||
|
||||
# Function to get container IP and network subnet (for IP-based limiting)
|
||||
get_container_network_info() {
|
||||
# Try docker compose first
|
||||
local container_name=$(docker compose -f "$COMPOSE_FILE" ps --format json 2>/dev/null | \
|
||||
jq -r '.[0].Name' 2>/dev/null | head -1)
|
||||
|
||||
# Fallback: try to find container by compose file path
|
||||
if [ -z "$container_name" ] || [ "$container_name" = "null" ]; then
|
||||
# Extract service name from compose file path (e.g., gnosis-mainnet-erigon3 from path)
|
||||
local service_name=$(basename "$COMPOSE_FILE" .yml | sed 's/-pruned-trace$//' | sed 's/-archive-trace$//' | sed 's/-minimal-trace$//')
|
||||
container_name=$(docker ps --filter "name=$service_name" --format "{{.Names}}" 2>/dev/null | head -1)
|
||||
fi
|
||||
|
||||
if [ -z "$container_name" ] || [ "$container_name" = "null" ]; then
|
||||
echo ""
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Get container IP
|
||||
local container_ip=$(docker inspect "$container_name" --format '{{range $net, $conf := .NetworkSettings.Networks}}{{$conf.IPAddress}}{{end}}' 2>/dev/null | head -1)
|
||||
|
||||
if [ -z "$container_ip" ]; then
|
||||
echo ""
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Get network subnet from .env or environment (not hardcoded)
|
||||
local subnet=$(get_network_subnet)
|
||||
|
||||
echo "$container_ip|$subnet"
|
||||
return 0
|
||||
}
|
||||
|
||||
# Get container IP and network info if IP-based limiting is enabled
|
||||
CONTAINER_IP=""
|
||||
NETWORK_SUBNET=""
|
||||
if [ "$LIMIT_BY_IP" = "true" ] || [ "$LIMIT_BY_IP" = "1" ] || [ "$LIMIT_BY_IP" = "yes" ]; then
|
||||
NETWORK_INFO=$(get_container_network_info)
|
||||
if [ -n "$NETWORK_INFO" ]; then
|
||||
CONTAINER_IP=$(echo "$NETWORK_INFO" | cut -d'|' -f1)
|
||||
NETWORK_SUBNET=$(echo "$NETWORK_INFO" | cut -d'|' -f2)
|
||||
if [ -n "$CONTAINER_IP" ]; then
|
||||
echo -e "${BLUE}Container IP: ${CONTAINER_IP}${NC}"
|
||||
if [ -n "$NETWORK_SUBNET" ]; then
|
||||
echo -e "${BLUE}Network subnet: ${NETWORK_SUBNET} (inter-container traffic will NOT be limited)${NC}"
|
||||
fi
|
||||
else
|
||||
echo -e "${YELLOW}Warning: Could not detect container IP, falling back to port-based limiting${NC}"
|
||||
LIMIT_BY_IP="false"
|
||||
fi
|
||||
else
|
||||
echo -e "${YELLOW}Warning: Could not detect container IP, falling back to port-based limiting${NC}"
|
||||
LIMIT_BY_IP="false"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Verify interfaces exist
|
||||
if ! ip link show "$BRIDGE" >/dev/null 2>&1; then
|
||||
echo -e "${RED}Error: Network interface '$BRIDGE' not found${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! ip link show "$EGRESS_IFACE" >/dev/null 2>&1; then
|
||||
echo -e "${RED}Error: Egress interface '$EGRESS_IFACE' not found${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
case "$ACTION" in
|
||||
start)
|
||||
echo -e "${YELLOW}Setting up bandwidth limiting...${NC}"
|
||||
|
||||
# Remove existing qdisc if any
|
||||
# Clean up existing iptables rules first (to avoid duplicates/conflicts)
|
||||
echo -e "${BLUE}Cleaning up existing iptables rules...${NC}"
|
||||
PORT_ID=10
|
||||
for port in "${PORTS[@]}"; do
|
||||
# Remove OUTPUT rules (outgoing from host)
|
||||
iptables -t mangle -D OUTPUT -p tcp --sport ${port} -j MARK 2>/dev/null || true
|
||||
iptables -t mangle -D OUTPUT -p udp --sport ${port} -j MARK 2>/dev/null || true
|
||||
|
||||
# Remove FORWARD rules (outgoing from containers)
|
||||
iptables -t mangle -D FORWARD -p tcp --sport ${port} -j MARK 2>/dev/null || true
|
||||
iptables -t mangle -D FORWARD -p udp --sport ${port} -j MARK 2>/dev/null || true
|
||||
|
||||
# Remove POSTROUTING rules
|
||||
iptables -t mangle -D POSTROUTING -o "$EGRESS_IFACE" -p tcp --sport ${port} -j MARK 2>/dev/null || true
|
||||
iptables -t mangle -D POSTROUTING -o "$EGRESS_IFACE" -p udp --sport ${port} -j MARK 2>/dev/null || true
|
||||
|
||||
PORT_ID=$((PORT_ID + 1))
|
||||
done
|
||||
|
||||
# Also clean up shared class mark (20) if it exists
|
||||
SHARED_MARK=20
|
||||
for port in "${PORTS[@]}"; do
|
||||
iptables -t mangle -D OUTPUT -p tcp --sport ${port} -j MARK --set-mark ${SHARED_MARK} 2>/dev/null || true
|
||||
iptables -t mangle -D OUTPUT -p udp --sport ${port} -j MARK --set-mark ${SHARED_MARK} 2>/dev/null || true
|
||||
iptables -t mangle -D FORWARD -p tcp --sport ${port} -j MARK --set-mark ${SHARED_MARK} 2>/dev/null || true
|
||||
iptables -t mangle -D FORWARD -p udp --sport ${port} -j MARK --set-mark ${SHARED_MARK} 2>/dev/null || true
|
||||
iptables -t mangle -D POSTROUTING -o "$EGRESS_IFACE" -p tcp --sport ${port} -j MARK --set-mark ${SHARED_MARK} 2>/dev/null || true
|
||||
iptables -t mangle -D POSTROUTING -o "$EGRESS_IFACE" -p udp --sport ${port} -j MARK --set-mark ${SHARED_MARK} 2>/dev/null || true
|
||||
done
|
||||
|
||||
# Remove existing qdisc if any (on both bridge and egress interface)
|
||||
tc qdisc del dev "$BRIDGE" root 2>/dev/null || true
|
||||
tc qdisc del dev "$EGRESS_IFACE" root 2>/dev/null || true
|
||||
|
||||
# Create HTB (Hierarchical Token Bucket) qdisc for egress (outgoing traffic only)
|
||||
# Apply to the physical egress interface where traffic actually leaves the system
|
||||
# We only limit outgoing because:
|
||||
# - You control what you send (outgoing)
|
||||
# - You can't control what others send (incoming)
|
||||
@@ -265,18 +534,102 @@ case "$ACTION" in
|
||||
# Higher r2q = smaller quantum = better for low bandwidths
|
||||
# Quantum = rate / r2q, so higher r2q means smaller quantums
|
||||
# Adaptive r2q based on bandwidth limit to avoid quantum warnings
|
||||
tc qdisc add dev "$BRIDGE" root handle 1: htb r2q ${R2Q_VALUE} default 30
|
||||
tc qdisc add dev "$EGRESS_IFACE" root handle 1: htb r2q ${R2Q_VALUE} default 30
|
||||
|
||||
# Create root class with high bandwidth
|
||||
# Set explicit quantum to avoid warnings
|
||||
# Quantum should be between 1500-60000 bytes for optimal performance
|
||||
# For 1000mbit (125MB/s), use quantum of 50000 bytes (within recommended range)
|
||||
tc class add dev "$BRIDGE" parent 1: classid 1:1 htb rate 1000mbit quantum 50000
|
||||
tc class add dev "$EGRESS_IFACE" parent 1: classid 1:1 htb rate 1000mbit quantum 50000
|
||||
|
||||
# Create unlimited class for non-limited traffic
|
||||
tc class add dev "$BRIDGE" parent 1:1 classid 1:30 htb rate 1000mbit quantum 50000
|
||||
tc class add dev "$EGRESS_IFACE" parent 1:1 classid 1:30 htb rate 1000mbit quantum 50000
|
||||
|
||||
# Check if we should use total limit (shared across all ports) or per-port limit
|
||||
if [ "$TOTAL_LIMIT" = "true" ] || [ "$TOTAL_LIMIT" = "1" ] || [ "$TOTAL_LIMIT" = "yes" ]; then
|
||||
# TOTAL LIMIT MODE: All ports share a single class with the specified limit
|
||||
echo -e "${BLUE}Using TOTAL limit mode: All ${#PORTS[@]} ports share ${BANDWIDTH_LIMIT} total${NC}"
|
||||
|
||||
# Calculate quantum for the shared class
|
||||
if [[ "$BANDWIDTH_LIMIT" =~ ^([0-9]+)mbit$ ]]; then
|
||||
RATE_NUM="${BASH_REMATCH[1]}"
|
||||
RATE_BYTES=$((RATE_NUM * 125000))
|
||||
QUANTUM=$((RATE_BYTES / 2000))
|
||||
if [ "$QUANTUM" -lt 1500 ]; then
|
||||
QUANTUM=1500
|
||||
fi
|
||||
if [ "$QUANTUM" -gt 60000 ]; then
|
||||
QUANTUM=60000
|
||||
fi
|
||||
else
|
||||
QUANTUM=1500
|
||||
fi
|
||||
|
||||
# Create a single shared class for all ports (class ID 20)
|
||||
SHARED_CLASS_ID=20
|
||||
MARK_VALUE=${SHARED_CLASS_ID}
|
||||
tc class add dev "$EGRESS_IFACE" parent 1:1 classid 1:${SHARED_CLASS_ID} htb rate ${BANDWIDTH_LIMIT} burst ${BURST} ceil ${BANDWIDTH_LIMIT} quantum ${QUANTUM}
|
||||
|
||||
# Add filter to route marked packets to shared class (only once, not per port)
|
||||
tc filter add dev "$EGRESS_IFACE" parent 1: protocol ip prio ${MARK_VALUE} handle ${MARK_VALUE} fw flowid 1:${SHARED_CLASS_ID} 2>/dev/null || true
|
||||
|
||||
# IP-based limiting: Mark all traffic from container IP (excluding inter-container traffic)
|
||||
if [ "$LIMIT_BY_IP" = "true" ] && [ -n "$CONTAINER_IP" ]; then
|
||||
echo -e "${BLUE} Adding IP-based limiting for ${CONTAINER_IP}...${NC}"
|
||||
|
||||
# Mark outgoing traffic in FORWARD chain (traffic from container going to internet)
|
||||
# Exclude traffic going to other containers in the same network
|
||||
if [ -n "$NETWORK_SUBNET" ]; then
|
||||
echo -e "${BLUE} Excluding traffic to ${NETWORK_SUBNET} (inter-container traffic)${NC}"
|
||||
iptables -t mangle -C FORWARD -s "$CONTAINER_IP" ! -d "$NETWORK_SUBNET" -j MARK --set-mark ${MARK_VALUE} 2>/dev/null || \
|
||||
iptables -t mangle -A FORWARD -s "$CONTAINER_IP" ! -d "$NETWORK_SUBNET" -j MARK --set-mark ${MARK_VALUE}
|
||||
else
|
||||
iptables -t mangle -C FORWARD -s "$CONTAINER_IP" -j MARK --set-mark ${MARK_VALUE} 2>/dev/null || \
|
||||
iptables -t mangle -A FORWARD -s "$CONTAINER_IP" -j MARK --set-mark ${MARK_VALUE}
|
||||
fi
|
||||
|
||||
# Mark outgoing traffic in POSTROUTING chain (critical for Docker NAT)
|
||||
if [ -n "$NETWORK_SUBNET" ]; then
|
||||
iptables -t mangle -C POSTROUTING -o "$EGRESS_IFACE" -s "$CONTAINER_IP" ! -d "$NETWORK_SUBNET" -j MARK --set-mark ${MARK_VALUE} 2>/dev/null || \
|
||||
iptables -t mangle -A POSTROUTING -o "$EGRESS_IFACE" -s "$CONTAINER_IP" ! -d "$NETWORK_SUBNET" -j MARK --set-mark ${MARK_VALUE}
|
||||
else
|
||||
iptables -t mangle -C POSTROUTING -o "$EGRESS_IFACE" -s "$CONTAINER_IP" -j MARK --set-mark ${MARK_VALUE} 2>/dev/null || \
|
||||
iptables -t mangle -A POSTROUTING -o "$EGRESS_IFACE" -s "$CONTAINER_IP" -j MARK --set-mark ${MARK_VALUE}
|
||||
fi
|
||||
fi
|
||||
|
||||
# Port-based limiting: Mark traffic from specific ports (if not using IP-only mode)
|
||||
if [ "$LIMIT_BY_IP" != "true" ] || [ -z "$CONTAINER_IP" ]; then
|
||||
# Process each port - all route to the same shared class
|
||||
for port in "${PORTS[@]}"; do
|
||||
echo -e "${BLUE} Adding port ${port} to shared limit of ${BANDWIDTH_LIMIT}...${NC}"
|
||||
|
||||
# Mark outgoing traffic in OUTPUT chain (traffic from host)
|
||||
iptables -t mangle -C OUTPUT -p tcp --sport ${port} -j MARK --set-mark ${MARK_VALUE} 2>/dev/null || \
|
||||
iptables -t mangle -A OUTPUT -p tcp --sport ${port} -j MARK --set-mark ${MARK_VALUE}
|
||||
iptables -t mangle -C OUTPUT -p udp --sport ${port} -j MARK --set-mark ${MARK_VALUE} 2>/dev/null || \
|
||||
iptables -t mangle -A OUTPUT -p udp --sport ${port} -j MARK --set-mark ${MARK_VALUE}
|
||||
|
||||
# Mark outgoing traffic in FORWARD chain (traffic from containers)
|
||||
iptables -t mangle -C FORWARD -p tcp --sport ${port} -j MARK --set-mark ${MARK_VALUE} 2>/dev/null || \
|
||||
iptables -t mangle -A FORWARD -p tcp --sport ${port} -j MARK --set-mark ${MARK_VALUE}
|
||||
iptables -t mangle -C FORWARD -p udp --sport ${port} -j MARK --set-mark ${MARK_VALUE} 2>/dev/null || \
|
||||
iptables -t mangle -A FORWARD -p udp --sport ${port} -j MARK --set-mark ${MARK_VALUE}
|
||||
|
||||
# Mark outgoing traffic in POSTROUTING chain (critical for Docker NAT)
|
||||
iptables -t mangle -C POSTROUTING -o "$EGRESS_IFACE" -p tcp --sport ${port} -j MARK --set-mark ${MARK_VALUE} 2>/dev/null || \
|
||||
iptables -t mangle -A POSTROUTING -o "$EGRESS_IFACE" -p tcp --sport ${port} -j MARK --set-mark ${MARK_VALUE}
|
||||
iptables -t mangle -C POSTROUTING -o "$EGRESS_IFACE" -p udp --sport ${port} -j MARK --set-mark ${MARK_VALUE} 2>/dev/null || \
|
||||
iptables -t mangle -A POSTROUTING -o "$EGRESS_IFACE" -p udp --sport ${port} -j MARK --set-mark ${MARK_VALUE}
|
||||
done
|
||||
fi
|
||||
|
||||
echo -e "${GREEN}✓ Outgoing bandwidth limiting configured!${NC}"
|
||||
echo -e "${GREEN}All ${#PORTS[@]} ports share a TOTAL limit of ${BANDWIDTH_LIMIT} outgoing traffic${NC}"
|
||||
else
|
||||
# PER-PORT LIMIT MODE: Each port gets its own class (original behavior)
|
||||
echo -e "${BLUE}Using PER-PORT limit mode: Each port limited to ${BANDWIDTH_LIMIT}${NC}"
|
||||
|
||||
# Process each port
|
||||
PORT_ID=10
|
||||
for port in "${PORTS[@]}"; do
|
||||
echo -e "${BLUE} Limiting port ${port} to ${BANDWIDTH_LIMIT}...${NC}"
|
||||
@@ -297,14 +650,14 @@ case "$ACTION" in
|
||||
if [ "$QUANTUM" -gt 60000 ]; then
|
||||
QUANTUM=60000
|
||||
fi
|
||||
tc class add dev "$BRIDGE" parent 1:1 classid 1:${PORT_ID} htb rate ${BANDWIDTH_LIMIT} burst ${BURST} ceil ${BANDWIDTH_LIMIT} quantum ${QUANTUM}
|
||||
tc class add dev "$EGRESS_IFACE" parent 1:1 classid 1:${PORT_ID} htb rate ${BANDWIDTH_LIMIT} burst ${BURST} ceil ${BANDWIDTH_LIMIT} quantum ${QUANTUM}
|
||||
else
|
||||
# Fallback: use safe default quantum
|
||||
tc class add dev "$BRIDGE" parent 1:1 classid 1:${PORT_ID} htb rate ${BANDWIDTH_LIMIT} burst ${BURST} ceil ${BANDWIDTH_LIMIT} quantum 1500
|
||||
tc class add dev "$EGRESS_IFACE" parent 1:1 classid 1:${PORT_ID} htb rate ${BANDWIDTH_LIMIT} burst ${BURST} ceil ${BANDWIDTH_LIMIT} quantum 1500
|
||||
fi
|
||||
|
||||
# Add filter to route marked packets to this class (outgoing only)
|
||||
tc filter add dev "$BRIDGE" parent 1: protocol ip prio ${PORT_ID} handle ${PORT_ID} fw flowid 1:${PORT_ID}
|
||||
tc filter add dev "$EGRESS_IFACE" parent 1: protocol ip prio ${PORT_ID} handle ${PORT_ID} fw flowid 1:${PORT_ID}
|
||||
|
||||
# Mark outgoing traffic in OUTPUT chain (traffic from host)
|
||||
iptables -t mangle -C OUTPUT -p tcp --sport ${port} -j MARK --set-mark ${PORT_ID} 2>/dev/null || \
|
||||
@@ -319,17 +672,344 @@ case "$ACTION" in
|
||||
iptables -t mangle -C FORWARD -p udp --sport ${port} -j MARK --set-mark ${PORT_ID} 2>/dev/null || \
|
||||
iptables -t mangle -A FORWARD -p udp --sport ${port} -j MARK --set-mark ${PORT_ID}
|
||||
|
||||
# Mark outgoing traffic in POSTROUTING chain (critical for Docker NAT)
|
||||
# This ensures marks are preserved through NAT and applied on the egress interface
|
||||
iptables -t mangle -C POSTROUTING -o "$EGRESS_IFACE" -p tcp --sport ${port} -j MARK --set-mark ${PORT_ID} 2>/dev/null || \
|
||||
iptables -t mangle -A POSTROUTING -o "$EGRESS_IFACE" -p tcp --sport ${port} -j MARK --set-mark ${PORT_ID}
|
||||
iptables -t mangle -C POSTROUTING -o "$EGRESS_IFACE" -p udp --sport ${port} -j MARK --set-mark ${PORT_ID} 2>/dev/null || \
|
||||
iptables -t mangle -A POSTROUTING -o "$EGRESS_IFACE" -p udp --sport ${port} -j MARK --set-mark ${PORT_ID}
|
||||
|
||||
PORT_ID=$((PORT_ID + 1))
|
||||
done
|
||||
|
||||
echo -e "${GREEN}✓ Outgoing bandwidth limiting configured!${NC}"
|
||||
echo -e "${GREEN}All ports are now limited to ${BANDWIDTH_LIMIT} outgoing traffic each${NC}"
|
||||
fi
|
||||
|
||||
echo -e "${YELLOW}Note: Only OUTGOING traffic is limited. Incoming traffic is not limited.${NC}"
|
||||
|
||||
# If using IP-based limiting, set up cronjob to update IP if container restarts
|
||||
if [ "$LIMIT_BY_IP" = "true" ] && [ -n "$CONTAINER_IP" ]; then
|
||||
echo -e "${BLUE}Setting up cronjob to update IP-based rules if container IP changes...${NC}"
|
||||
|
||||
# Create a unique identifier for this cronjob based on compose file
|
||||
CRON_ID=$(echo "$COMPOSE_FILE" | md5sum | cut -d' ' -f1 | head -c 8)
|
||||
CRON_TAG="limit-bandwidth-${CRON_ID}"
|
||||
|
||||
# Remove existing cronjob if any
|
||||
(crontab -l 2>/dev/null | grep -v "$CRON_TAG" || true) | crontab -
|
||||
|
||||
# Add new cronjob (every 5 minutes)
|
||||
CRON_CMD="*/5 * * * * $BASEPATH/limit-bandwidth.sh \"$COMPOSE_FILE\" update-ip --cron-id $CRON_ID >> /var/log/limit-bandwidth-${CRON_ID}.log 2>&1"
|
||||
(crontab -l 2>/dev/null | grep -v "$CRON_TAG"; echo "$CRON_CMD # $CRON_TAG") | crontab -
|
||||
|
||||
echo -e "${GREEN}✓ Cronjob registered (runs every 5 minutes to update IP if changed)${NC}"
|
||||
echo -e "${BLUE} To view logs: tail -f /var/log/limit-bandwidth-${CRON_ID}.log${NC}"
|
||||
fi
|
||||
;;
|
||||
update-ip)
|
||||
# Internal command to update IP-based rules (called by cronjob)
|
||||
# Parse --cron-id parameter from remaining arguments
|
||||
CRON_ID=""
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--cron-id)
|
||||
if [ -n "$2" ]; then
|
||||
CRON_ID="$2"
|
||||
shift 2
|
||||
else
|
||||
echo "$(date): Error: --cron-id requires a value"
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
--cron-id=*)
|
||||
CRON_ID="${1#*=}"
|
||||
shift
|
||||
;;
|
||||
*)
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [ -z "$CRON_ID" ]; then
|
||||
echo "$(date): Error: update-ip requires --cron-id parameter"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Need to re-initialize variables for update-ip command
|
||||
EGRESS_IFACE=$(find_egress_interface)
|
||||
|
||||
# Get expected service name from compose file to verify container matches
|
||||
EXPECTED_SERVICE=$(grep -E "^[[:space:]]*[a-zA-Z0-9_-]+:" "$COMPOSE_FILE" | grep -v "^[[:space:]]*x-" | head -1 | cut -d: -f1 | tr -d ' ')
|
||||
|
||||
# Get ports from compose file (needed for cleanup)
|
||||
PORTS_RAW=$(extract_ports "$COMPOSE_FILE")
|
||||
PORTS=()
|
||||
while IFS= read -r line; do
|
||||
[ -n "$line" ] && PORTS+=("$line")
|
||||
done <<< "$PORTS_RAW"
|
||||
|
||||
# Get current container IP and verify container exists
|
||||
NETWORK_INFO=$(get_container_network_info)
|
||||
if [ -z "$NETWORK_INFO" ]; then
|
||||
echo "$(date): WARNING: Container not found for compose file $COMPOSE_FILE"
|
||||
echo "$(date): Container may have been removed. Removing all bandwidth limiting rules (IP and port-based)."
|
||||
|
||||
# Find and remove any existing IP-based rules
|
||||
EXISTING_IP=$(iptables -t mangle -L FORWARD -n 2>/dev/null | grep "MARK set 0x14" | grep -oE "192\.168\.[0-9]+\.[0-9]+" | head -1)
|
||||
if [ -z "$EXISTING_IP" ]; then
|
||||
EXISTING_IP=$(iptables -t mangle -L POSTROUTING -n 2>/dev/null | grep "MARK set 0x14" | grep -oE "192\.168\.[0-9]+\.[0-9]+" | head -1)
|
||||
fi
|
||||
|
||||
if [ -n "$EXISTING_IP" ]; then
|
||||
SHARED_MARK=20
|
||||
CURRENT_SUBNET=$(get_network_subnet)
|
||||
if [ -n "$CURRENT_SUBNET" ]; then
|
||||
iptables -t mangle -D FORWARD -s "$EXISTING_IP" ! -d "$CURRENT_SUBNET" -j MARK --set-mark ${SHARED_MARK} 2>/dev/null || true
|
||||
iptables -t mangle -D POSTROUTING -o "$EGRESS_IFACE" -s "$EXISTING_IP" ! -d "$CURRENT_SUBNET" -j MARK --set-mark ${SHARED_MARK} 2>/dev/null || true
|
||||
else
|
||||
iptables -t mangle -D FORWARD -s "$EXISTING_IP" -j MARK --set-mark ${SHARED_MARK} 2>/dev/null || true
|
||||
iptables -t mangle -D POSTROUTING -o "$EGRESS_IFACE" -s "$EXISTING_IP" -j MARK --set-mark ${SHARED_MARK} 2>/dev/null || true
|
||||
fi
|
||||
echo "$(date): Removed IP-based rules for $EXISTING_IP"
|
||||
fi
|
||||
|
||||
# Remove port-based rules (they add overhead to iptables if left orphaned)
|
||||
if [ ${#PORTS[@]} -gt 0 ]; then
|
||||
echo "$(date): Removing port-based rules for ports: ${PORTS[*]}"
|
||||
SHARED_MARK=20
|
||||
PORT_ID=10
|
||||
for port in "${PORTS[@]}"; do
|
||||
# Remove OUTPUT rules
|
||||
iptables -t mangle -D OUTPUT -p tcp --sport ${port} -j MARK --set-mark ${SHARED_MARK} 2>/dev/null || true
|
||||
iptables -t mangle -D OUTPUT -p udp --sport ${port} -j MARK --set-mark ${SHARED_MARK} 2>/dev/null || true
|
||||
# Remove FORWARD rules
|
||||
iptables -t mangle -D FORWARD -p tcp --sport ${port} -j MARK --set-mark ${SHARED_MARK} 2>/dev/null || true
|
||||
iptables -t mangle -D FORWARD -p udp --sport ${port} -j MARK --set-mark ${SHARED_MARK} 2>/dev/null || true
|
||||
# Remove POSTROUTING rules
|
||||
iptables -t mangle -D POSTROUTING -o "$EGRESS_IFACE" -p tcp --sport ${port} -j MARK --set-mark ${SHARED_MARK} 2>/dev/null || true
|
||||
iptables -t mangle -D POSTROUTING -o "$EGRESS_IFACE" -p udp --sport ${port} -j MARK --set-mark ${SHARED_MARK} 2>/dev/null || true
|
||||
PORT_ID=$((PORT_ID + 1))
|
||||
done
|
||||
echo "$(date): Removed port-based rules for ${#PORTS[@]} ports"
|
||||
fi
|
||||
|
||||
# Optionally remove cronjob if container is permanently gone
|
||||
# (User can manually restart if container comes back)
|
||||
echo "$(date): NOTE: If container is permanently removed, run 'stop' command to remove cronjob"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
CURRENT_IP=$(echo "$NETWORK_INFO" | cut -d'|' -f1)
|
||||
CURRENT_SUBNET=$(echo "$NETWORK_INFO" | cut -d'|' -f2)
|
||||
|
||||
# Verify the container matches expected service (safety check)
|
||||
# The IP we got came from get_container_network_info, which already verified the container exists
|
||||
# But we should double-check that the container is still running and matches
|
||||
|
||||
# Get container name that was used to find the IP (re-use the same logic)
|
||||
CONTAINER_NAME=$(docker compose -f "$COMPOSE_FILE" ps --format json 2>/dev/null | \
|
||||
jq -r '.[0].Name' 2>/dev/null | head -1)
|
||||
|
||||
if [ -z "$CONTAINER_NAME" ] || [ "$CONTAINER_NAME" = "null" ]; then
|
||||
# Fallback: try to find container by service name pattern
|
||||
if [ -n "$EXPECTED_SERVICE" ]; then
|
||||
CONTAINER_NAME=$(docker ps --filter "name=$EXPECTED_SERVICE" --format "{{.Names}}" 2>/dev/null | head -1)
|
||||
fi
|
||||
fi
|
||||
|
||||
# If we can't find the container name, but we got an IP, verify by checking which container has that IP
|
||||
if [ -z "$CONTAINER_NAME" ]; then
|
||||
# Find container by IP address
|
||||
CONTAINER_NAME=$(docker ps --format "{{.Names}}" 2>/dev/null | while read name; do
|
||||
container_ip=$(docker inspect "$name" --format '{{range $net, $conf := .NetworkSettings.Networks}}{{$conf.IPAddress}}{{end}}' 2>/dev/null | head -1)
|
||||
if [ "$container_ip" = "$CURRENT_IP" ]; then
|
||||
echo "$name"
|
||||
break
|
||||
fi
|
||||
done | head -1)
|
||||
fi
|
||||
|
||||
# Verify container exists and is running
|
||||
if [ -z "$CONTAINER_NAME" ]; then
|
||||
echo "$(date): WARNING: Could not find container with IP $CURRENT_IP"
|
||||
echo "$(date): Container may have been removed. Removing IP-based rules to prevent affecting other containers."
|
||||
|
||||
# Remove rules for this IP to prevent affecting wrong container
|
||||
SHARED_MARK=20
|
||||
if [ -n "$CURRENT_SUBNET" ]; then
|
||||
iptables -t mangle -D FORWARD -s "$CURRENT_IP" ! -d "$CURRENT_SUBNET" -j MARK --set-mark ${SHARED_MARK} 2>/dev/null || true
|
||||
iptables -t mangle -D POSTROUTING -o "$EGRESS_IFACE" -s "$CURRENT_IP" ! -d "$CURRENT_SUBNET" -j MARK --set-mark ${SHARED_MARK} 2>/dev/null || true
|
||||
else
|
||||
iptables -t mangle -D FORWARD -s "$CURRENT_IP" -j MARK --set-mark ${SHARED_MARK} 2>/dev/null || true
|
||||
iptables -t mangle -D POSTROUTING -o "$EGRESS_IFACE" -s "$CURRENT_IP" -j MARK --set-mark ${SHARED_MARK} 2>/dev/null || true
|
||||
fi
|
||||
echo "$(date): Removed rules for $CURRENT_IP. Run 'start' command again when correct container is running."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Verify IP actually belongs to this container
|
||||
CONTAINER_IP_CHECK=$(docker inspect "$CONTAINER_NAME" --format '{{range $net, $conf := .NetworkSettings.Networks}}{{$conf.IPAddress}}{{end}}' 2>/dev/null | head -1)
|
||||
if [ "$CONTAINER_IP_CHECK" != "$CURRENT_IP" ]; then
|
||||
echo "$(date): WARNING: IP mismatch! Container $CONTAINER_NAME has IP $CONTAINER_IP_CHECK but found $CURRENT_IP"
|
||||
echo "$(date): This IP may have been reassigned to a different container. Removing rules to prevent affecting wrong container."
|
||||
|
||||
# Remove old rules for the IP we found
|
||||
SHARED_MARK=20
|
||||
if [ -n "$CURRENT_SUBNET" ]; then
|
||||
iptables -t mangle -D FORWARD -s "$CURRENT_IP" ! -d "$CURRENT_SUBNET" -j MARK --set-mark ${SHARED_MARK} 2>/dev/null || true
|
||||
iptables -t mangle -D POSTROUTING -o "$EGRESS_IFACE" -s "$CURRENT_IP" ! -d "$CURRENT_SUBNET" -j MARK --set-mark ${SHARED_MARK} 2>/dev/null || true
|
||||
else
|
||||
iptables -t mangle -D FORWARD -s "$CURRENT_IP" -j MARK --set-mark ${SHARED_MARK} 2>/dev/null || true
|
||||
iptables -t mangle -D POSTROUTING -o "$EGRESS_IFACE" -s "$CURRENT_IP" -j MARK --set-mark ${SHARED_MARK} 2>/dev/null || true
|
||||
fi
|
||||
echo "$(date): Removed rules for $CURRENT_IP. Run 'start' command again when correct container is running."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Additional verification: check if container is actually running
|
||||
CONTAINER_STATE=$(docker inspect "$CONTAINER_NAME" --format '{{.State.Status}}' 2>/dev/null)
|
||||
if [ "$CONTAINER_STATE" != "running" ]; then
|
||||
echo "$(date): WARNING: Container $CONTAINER_NAME is not running (state: $CONTAINER_STATE)"
|
||||
echo "$(date): Removing IP-based rules to prevent affecting other containers if IP is reassigned."
|
||||
|
||||
SHARED_MARK=20
|
||||
if [ -n "$CURRENT_SUBNET" ]; then
|
||||
iptables -t mangle -D FORWARD -s "$CURRENT_IP" ! -d "$CURRENT_SUBNET" -j MARK --set-mark ${SHARED_MARK} 2>/dev/null || true
|
||||
iptables -t mangle -D POSTROUTING -o "$EGRESS_IFACE" -s "$CURRENT_IP" ! -d "$CURRENT_SUBNET" -j MARK --set-mark ${SHARED_MARK} 2>/dev/null || true
|
||||
else
|
||||
iptables -t mangle -D FORWARD -s "$CURRENT_IP" -j MARK --set-mark ${SHARED_MARK} 2>/dev/null || true
|
||||
iptables -t mangle -D POSTROUTING -o "$EGRESS_IFACE" -s "$CURRENT_IP" -j MARK --set-mark ${SHARED_MARK} 2>/dev/null || true
|
||||
fi
|
||||
echo "$(date): Removed rules for $CURRENT_IP. Rules will be re-added when container is running again."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Verify container name matches expected service (additional safety check)
|
||||
if [ -n "$EXPECTED_SERVICE" ] && ! echo "$CONTAINER_NAME" | grep -qi "$EXPECTED_SERVICE"; then
|
||||
echo "$(date): WARNING: Container name '$CONTAINER_NAME' doesn't match expected service '$EXPECTED_SERVICE'"
|
||||
echo "$(date): However, IP $CURRENT_IP belongs to this container. Proceeding with update."
|
||||
fi
|
||||
|
||||
# Check if IP-based rules exist and get the IP they're using
|
||||
# Look for rules with MARK set 0x14 (20 in decimal) that have source IP
|
||||
EXISTING_IP=$(iptables -t mangle -L FORWARD -n 2>/dev/null | grep "MARK set 0x14" | grep -oE "192\.168\.[0-9]+\.[0-9]+" | head -1)
|
||||
|
||||
# If no existing IP found, try POSTROUTING chain
|
||||
if [ -z "$EXISTING_IP" ]; then
|
||||
EXISTING_IP=$(iptables -t mangle -L POSTROUTING -n 2>/dev/null | grep "MARK set 0x14" | grep -oE "192\.168\.[0-9]+\.[0-9]+" | head -1)
|
||||
fi
|
||||
|
||||
# If IP hasn't changed, no update needed
|
||||
if [ -n "$EXISTING_IP" ] && [ "$CURRENT_IP" = "$EXISTING_IP" ]; then
|
||||
echo "$(date): IP unchanged ($CURRENT_IP), no update needed"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ -n "$EXISTING_IP" ]; then
|
||||
echo "$(date): IP changed from $EXISTING_IP to $CURRENT_IP, updating rules..."
|
||||
else
|
||||
echo "$(date): No existing IP rules found, adding rules for $CURRENT_IP..."
|
||||
fi
|
||||
|
||||
# Remove old rules
|
||||
if [ -n "$EXISTING_IP" ]; then
|
||||
SHARED_MARK=20
|
||||
# Try to find subnet from existing rules
|
||||
EXISTING_SUBNET=$(iptables -t mangle -L FORWARD -n 2>/dev/null | grep "$EXISTING_IP" | grep -oE "192\.168\.[0-9]+\.[0-9]+/[0-9]+" | head -1)
|
||||
if [ -z "$EXISTING_SUBNET" ]; then
|
||||
EXISTING_SUBNET="$CURRENT_SUBNET"
|
||||
fi
|
||||
|
||||
if [ -n "$EXISTING_SUBNET" ]; then
|
||||
iptables -t mangle -D FORWARD -s "$EXISTING_IP" ! -d "$EXISTING_SUBNET" -j MARK --set-mark ${SHARED_MARK} 2>/dev/null || true
|
||||
iptables -t mangle -D POSTROUTING -o "$EGRESS_IFACE" -s "$EXISTING_IP" ! -d "$EXISTING_SUBNET" -j MARK --set-mark ${SHARED_MARK} 2>/dev/null || true
|
||||
else
|
||||
iptables -t mangle -D FORWARD -s "$EXISTING_IP" -j MARK --set-mark ${SHARED_MARK} 2>/dev/null || true
|
||||
iptables -t mangle -D POSTROUTING -o "$EGRESS_IFACE" -s "$EXISTING_IP" -j MARK --set-mark ${SHARED_MARK} 2>/dev/null || true
|
||||
fi
|
||||
fi
|
||||
|
||||
# Add new rules with current IP
|
||||
SHARED_MARK=20
|
||||
if [ -n "$CURRENT_SUBNET" ]; then
|
||||
iptables -t mangle -C FORWARD -s "$CURRENT_IP" ! -d "$CURRENT_SUBNET" -j MARK --set-mark ${SHARED_MARK} 2>/dev/null || \
|
||||
iptables -t mangle -A FORWARD -s "$CURRENT_IP" ! -d "$CURRENT_SUBNET" -j MARK --set-mark ${SHARED_MARK}
|
||||
iptables -t mangle -C POSTROUTING -o "$EGRESS_IFACE" -s "$CURRENT_IP" ! -d "$CURRENT_SUBNET" -j MARK --set-mark ${SHARED_MARK} 2>/dev/null || \
|
||||
iptables -t mangle -A POSTROUTING -o "$EGRESS_IFACE" -s "$CURRENT_IP" ! -d "$CURRENT_SUBNET" -j MARK --set-mark ${SHARED_MARK}
|
||||
else
|
||||
iptables -t mangle -C FORWARD -s "$CURRENT_IP" -j MARK --set-mark ${SHARED_MARK} 2>/dev/null || \
|
||||
iptables -t mangle -A FORWARD -s "$CURRENT_IP" -j MARK --set-mark ${SHARED_MARK}
|
||||
iptables -t mangle -C POSTROUTING -o "$EGRESS_IFACE" -s "$CURRENT_IP" -j MARK --set-mark ${SHARED_MARK} 2>/dev/null || \
|
||||
iptables -t mangle -A POSTROUTING -o "$EGRESS_IFACE" -s "$CURRENT_IP" -j MARK --set-mark ${SHARED_MARK}
|
||||
fi
|
||||
|
||||
echo "$(date): Successfully updated IP-based rules to $CURRENT_IP"
|
||||
;;
|
||||
stop)
|
||||
echo -e "${YELLOW}Removing bandwidth limiting...${NC}"
|
||||
|
||||
# Remove cronjob if it exists
|
||||
CRON_ID=$(echo "$COMPOSE_FILE" | md5sum | cut -d' ' -f1 | head -c 8)
|
||||
CRON_TAG="limit-bandwidth-${CRON_ID}"
|
||||
if crontab -l 2>/dev/null | grep -q "$CRON_TAG"; then
|
||||
echo -e "${BLUE} Removing cronjob...${NC}"
|
||||
(crontab -l 2>/dev/null | grep -v "$CRON_TAG" || true) | crontab -
|
||||
echo -e "${GREEN}✓ Cronjob removed${NC}"
|
||||
fi
|
||||
|
||||
# Get container IP for cleanup (if IP-based limiting was used)
|
||||
# Try to find IP from existing iptables rules first
|
||||
EXISTING_IP=$(iptables -t mangle -L FORWARD -n 2>/dev/null | grep "MARK set 0x14" | grep -oE "192\.168\.[0-9]+\.[0-9]+" | head -1)
|
||||
|
||||
CONTAINER_IP_CLEANUP=""
|
||||
NETWORK_SUBNET_CLEANUP=""
|
||||
if [ -z "$EXISTING_IP" ]; then
|
||||
# Fallback: try to get from container
|
||||
NETWORK_INFO=$(get_container_network_info 2>/dev/null)
|
||||
if [ -n "$NETWORK_INFO" ]; then
|
||||
CONTAINER_IP_CLEANUP=$(echo "$NETWORK_INFO" | cut -d'|' -f1)
|
||||
NETWORK_SUBNET_CLEANUP=$(echo "$NETWORK_INFO" | cut -d'|' -f2)
|
||||
fi
|
||||
else
|
||||
CONTAINER_IP_CLEANUP="$EXISTING_IP"
|
||||
# Try to get subnet from existing rules
|
||||
EXISTING_SUBNET=$(iptables -t mangle -L FORWARD -n 2>/dev/null | grep "MARK set 0x14" | grep -oE "192\.168\.[0-9]+\.[0-9]+/[0-9]+" | head -1)
|
||||
if [ -n "$EXISTING_SUBNET" ]; then
|
||||
NETWORK_SUBNET_CLEANUP="$EXISTING_SUBNET"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Remove IP-based iptables rules if they exist
|
||||
if [ -n "$CONTAINER_IP_CLEANUP" ]; then
|
||||
echo -e "${BLUE} Removing IP-based limits for ${CONTAINER_IP_CLEANUP}...${NC}"
|
||||
SHARED_MARK=20
|
||||
if [ -n "$NETWORK_SUBNET_CLEANUP" ]; then
|
||||
iptables -t mangle -D FORWARD -s "$CONTAINER_IP_CLEANUP" ! -d "$NETWORK_SUBNET_CLEANUP" -j MARK --set-mark ${SHARED_MARK} 2>/dev/null || true
|
||||
iptables -t mangle -D POSTROUTING -o "$EGRESS_IFACE" -s "$CONTAINER_IP_CLEANUP" ! -d "$NETWORK_SUBNET_CLEANUP" -j MARK --set-mark ${SHARED_MARK} 2>/dev/null || true
|
||||
else
|
||||
iptables -t mangle -D FORWARD -s "$CONTAINER_IP_CLEANUP" -j MARK --set-mark ${SHARED_MARK} 2>/dev/null || true
|
||||
iptables -t mangle -D POSTROUTING -o "$EGRESS_IFACE" -s "$CONTAINER_IP_CLEANUP" -j MARK --set-mark ${SHARED_MARK} 2>/dev/null || true
|
||||
fi
|
||||
fi
|
||||
|
||||
# Remove iptables rules for each port
|
||||
# Check if we need to remove shared class (20) or individual classes (10+)
|
||||
if tc class show dev "$EGRESS_IFACE" 2>/dev/null | grep -q "classid 1:20"; then
|
||||
# Shared class mode - all ports use mark 20
|
||||
SHARED_MARK=20
|
||||
for port in "${PORTS[@]}"; do
|
||||
echo -e "${BLUE} Removing limits for port ${port}...${NC}"
|
||||
iptables -t mangle -D OUTPUT -p tcp --sport ${port} -j MARK --set-mark ${SHARED_MARK} 2>/dev/null || true
|
||||
iptables -t mangle -D OUTPUT -p udp --sport ${port} -j MARK --set-mark ${SHARED_MARK} 2>/dev/null || true
|
||||
iptables -t mangle -D FORWARD -p tcp --sport ${port} -j MARK --set-mark ${SHARED_MARK} 2>/dev/null || true
|
||||
iptables -t mangle -D FORWARD -p udp --sport ${port} -j MARK --set-mark ${SHARED_MARK} 2>/dev/null || true
|
||||
iptables -t mangle -D POSTROUTING -o "$EGRESS_IFACE" -p tcp --sport ${port} -j MARK --set-mark ${SHARED_MARK} 2>/dev/null || true
|
||||
iptables -t mangle -D POSTROUTING -o "$EGRESS_IFACE" -p udp --sport ${port} -j MARK --set-mark ${SHARED_MARK} 2>/dev/null || true
|
||||
done
|
||||
else
|
||||
# Per-port mode - each port has its own mark
|
||||
PORT_ID=10
|
||||
for port in "${PORTS[@]}"; do
|
||||
echo -e "${BLUE} Removing limits for port ${port}...${NC}"
|
||||
@@ -342,22 +1022,31 @@ case "$ACTION" in
|
||||
iptables -t mangle -D FORWARD -p tcp --sport ${port} -j MARK --set-mark ${PORT_ID} 2>/dev/null || true
|
||||
iptables -t mangle -D FORWARD -p udp --sport ${port} -j MARK --set-mark ${PORT_ID} 2>/dev/null || true
|
||||
|
||||
# Remove POSTROUTING rules
|
||||
iptables -t mangle -D POSTROUTING -o "$EGRESS_IFACE" -p tcp --sport ${port} -j MARK --set-mark ${PORT_ID} 2>/dev/null || true
|
||||
iptables -t mangle -D POSTROUTING -o "$EGRESS_IFACE" -p udp --sport ${port} -j MARK --set-mark ${PORT_ID} 2>/dev/null || true
|
||||
|
||||
PORT_ID=$((PORT_ID + 1))
|
||||
done
|
||||
fi
|
||||
|
||||
# Remove tc qdisc
|
||||
# Remove tc qdisc from both interfaces
|
||||
tc qdisc del dev "$BRIDGE" root 2>/dev/null || true
|
||||
tc qdisc del dev "$EGRESS_IFACE" root 2>/dev/null || true
|
||||
|
||||
echo -e "${GREEN}✓ Bandwidth limiting removed${NC}"
|
||||
;;
|
||||
status)
|
||||
echo -e "${YELLOW}Current bandwidth limiting status:${NC}"
|
||||
echo ""
|
||||
echo -e "${BLUE}TC Qdisc on ${BRIDGE}:${NC}"
|
||||
tc qdisc show dev "$BRIDGE" 2>/dev/null || echo " No qdisc configured"
|
||||
echo -e "${BLUE}TC Qdisc on ${EGRESS_IFACE} (egress interface):${NC}"
|
||||
tc qdisc show dev "$EGRESS_IFACE" 2>/dev/null || echo " No qdisc configured"
|
||||
echo ""
|
||||
echo -e "${BLUE}TC Classes:${NC}"
|
||||
tc class show dev "$BRIDGE" 2>/dev/null || echo " No classes configured"
|
||||
echo -e "${BLUE}TC Classes on ${EGRESS_IFACE}:${NC}"
|
||||
tc class show dev "$EGRESS_IFACE" 2>/dev/null || echo " No classes configured"
|
||||
echo ""
|
||||
echo -e "${BLUE}TC Statistics on ${EGRESS_IFACE}:${NC}"
|
||||
tc -s class show dev "$EGRESS_IFACE" 2>/dev/null | head -20 || echo " No statistics available"
|
||||
echo ""
|
||||
echo -e "${BLUE}Iptables rules (OUTPUT - outgoing from host):${NC}"
|
||||
iptables -t mangle -L OUTPUT -n --line-numbers | grep -E "MARK|${PORTS[0]}" || echo " No rules found"
|
||||
@@ -365,6 +1054,9 @@ case "$ACTION" in
|
||||
echo -e "${BLUE}Iptables rules (FORWARD - outgoing from containers):${NC}"
|
||||
iptables -t mangle -L FORWARD -n --line-numbers | grep -E "MARK|${PORTS[0]}" || echo " No rules found"
|
||||
echo ""
|
||||
echo -e "${BLUE}Iptables rules (POSTROUTING - after NAT):${NC}"
|
||||
iptables -t mangle -L POSTROUTING -n --line-numbers | grep -E "MARK|${PORTS[0]}" || echo " No rules found"
|
||||
echo ""
|
||||
echo -e "${YELLOW}Note: Only outgoing traffic is limited. Incoming traffic is not limited${NC}"
|
||||
echo -e "${YELLOW} because you can't control what other nodes send you.${NC}"
|
||||
echo ""
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"hash": "0xccb16eb07b7a718c2ee374df57b0e28c9ac9d8d18ca6d3204cfbba661067855a",
|
||||
"number": 12241700
|
||||
},
|
||||
"l2_time": 1760699568,
|
||||
"l2_time": 1760700537,
|
||||
"system_config": {
|
||||
"batcherAddr": "0x8edf9b54e1c693b7b0caea85e6a005c35e229124",
|
||||
"overhead": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||
@@ -16,7 +16,8 @@
|
||||
"gasLimit": 30000000,
|
||||
"eip1559Params": "0x0000000000000000",
|
||||
"operatorFeeParams": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"minBaseFee": 0
|
||||
"minBaseFee": 0,
|
||||
"daFootprintGasScalar": 0
|
||||
}
|
||||
},
|
||||
"block_time": 1,
|
||||
@@ -33,6 +34,7 @@
|
||||
"granite_time": 0,
|
||||
"holocene_time": 0,
|
||||
"isthmus_time": 0,
|
||||
"jovian_time": 1764327600,
|
||||
"batch_inbox_address": "0x006737cc6980a7786a477ce46b491845509b19dc",
|
||||
"deposit_contract_address": "0x1529a34331d7d85c8868fc88ec730ae56d3ec9c0",
|
||||
"l1_system_config_address": "0x06be4b4a9a28ff8eed6da09447bc5daa676efac3",
|
||||
@@ -42,4 +44,4 @@
|
||||
"eip1559Denominator": 100000000,
|
||||
"eip1559DenominatorCanyon": 250
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user