#!/usr/bin/env bash set -e # 必须 root if [[ "$EUID" -ne 0 ]]; then echo "❌ 请使用 root 或在前面加 sudo 再运行这个脚本。" exit 1 fi echo "=== Debian/Ubuntu:按【对方IP(dst)】白名单决定上行限速(tc+iptables)一键脚本 v4 (VIP版) ===" echo "白名单支持:" echo " - 前缀:117 182 171 223 (自动转 117.0.0.0/8 等)" echo " - CIDR:1.2.3.0/24 10.0.0.0/8" echo "逻辑更新:" echo " - 白名单流量强制打标 MARK 20 (VIP)" echo " - 非白名单流量强制打标 MARK 10 (Limit)" echo " - TC 显式通过 handle 20 放行白名单,彻底解决默认路由失效问题" echo # 自动检测默认出口网卡 detect_iface() { local dev dev=$(ip route get 1.1.1.1 2>/dev/null | awk '{for (i=1;i<=NF;i++) if ($i=="dev") {print $(i+1); exit}}') if [[ -z "$dev" ]]; then dev=$(ip route 2>/dev/null | awk '/default/ {print $5; exit}') fi echo "$dev" } DEFAULT_IFACE="$(detect_iface)" echo "检测到的默认出口网卡: ${DEFAULT_IFACE:-<未检测到>}" read -rp "请输入需要限速的网卡名 [${DEFAULT_IFACE}]: " IFACE < /dev/tty IFACE="${IFACE:-$DEFAULT_IFACE}" if [[ -z "$IFACE" ]] || ! ip link show "$IFACE" &>/dev/null; then echo "❌ 网卡无效:$IFACE" exit 1 fi echo echo "请输入【对方IP白名单(不限速)】(可写前缀或CIDR),空格分隔。" echo "例:117 182 223 192.168.1.0/24" read -rp "白名单: " WHITELIST_INPUT < /dev/tty # 解析白名单输入 -> CIDR 列表 WHITELIST_CIDRS=() if [[ -n "$WHITELIST_INPUT" ]]; then for tok in $WHITELIST_INPUT; do # 纯数字:当作 /8 if [[ "$tok" =~ ^[0-9]{1,3}$ ]]; then if (( tok < 0 || tok > 255 )); then echo "❌ 前缀必须在 0-255:$tok" exit 1 fi WHITELIST_CIDRS+=("${tok}.0.0.0/8") continue fi # CIDR:a.b.c.d/n if [[ "$tok" =~ ^([0-9]{1,3}.){3}[0-9]{1,3}/[0-9]{1,2}$ ]]; then WHITELIST_CIDRS+=("$tok") continue fi echo "❌ 无法识别的条目:$tok(请输入前缀或CIDR)" exit 1 done fi echo read -rp "请输入【非白名单对方IP】的上行限速(单位 M,回车=100M): " RATE_M < /dev/tty RATE_M="${RATE_M//[!0-9]/}" [[ -z "$RATE_M" ]] && RATE_M=100 (( RATE_M > 0 )) || { echo "❌ 限速必须 >0"; exit 1; } RATE="${RATE_M}mbit" # 定义标记常量 MARK_LIMIT=10 MARK_VIP=20 WL_SHOW="<空=无白名单>" (( ${#WHITELIST_CIDRS[@]} > 0 )) && WL_SHOW="${WHITELIST_CIDRS[*]}" echo echo "====== 配置确认 ======" echo " 网卡: $IFACE" echo " 对方白名单(dst): $WL_SHOW" echo " 非白名单限速: $RATE" echo " 策略逻辑: 白名单 -> Mark $MARK_VIP -> 不限速类" echo " 其他人 -> Mark $MARK_LIMIT -> 限速类" echo "=======================" read -rp "确认执行?[Y/n]: " CONFIRM < /dev/tty CONFIRM=${CONFIRM:-Y} [[ "$CONFIRM" =~ ^[Yy]$ ]] || { echo "已取消。"; exit 0; } TC_BIN="$(command -v tc || echo /sbin/tc)" IPT_BIN="$(command -v iptables || echo /sbin/iptables)" SERVICE_NAME="netlimit-peerip-${IFACE}.service" SERVICE_PATH="/etc/systemd/system/${SERVICE_NAME}" CHAIN_NAME="NETLIMIT_PEERIP_${IFACE}" CHAIN_NAME="${CHAIN_NAME//[^a-zA-Z0-9_]/_}" # 白名单规则段(按 dst):先打 VIP 标记,再 RETURN WL_RULES="" if (( ${#WHITELIST_CIDRS[@]} > 0 )); then for c in "${WHITELIST_CIDRS[@]}"; do # 关键修改:白名单显式打标 20 WL_RULES+=$' '"ExecStart=${IPT_BIN} -t mangle -A ${CHAIN_NAME} -d ${c} -j MARK --set-mark ${MARK_VIP}" WL_RULES+=$' '"ExecStart=${IPT_BIN} -t mangle -A ${CHAIN_NAME} -d ${c} -j RETURN" done else WL_RULES+=$' '"# (empty whitelist) throttle all peers" fi echo echo "➡ 正在写入 systemd 服务:${SERVICE_PATH}" cat > "${SERVICE_PATH}" </dev/null; do :; done' ExecStartPre=-${IPT_BIN} -t mangle -F ${CHAIN_NAME} ExecStartPre=-${IPT_BIN} -t mangle -X ${CHAIN_NAME} # ---- 建立新 iptables 链 ---- ExecStart=${IPT_BIN} -t mangle -N ${CHAIN_NAME} ExecStart=${IPT_BIN} -t mangle -A POSTROUTING -o ${IFACE} -j ${CHAIN_NAME} # 1. 白名单规则(命中则 Mark 20 并 RETURN) ${WL_RULES} # 2. 剩下的流量(非白名单):打 Mark 10 ExecStart=${IPT_BIN} -t mangle -A ${CHAIN_NAME} -j MARK --set-mark ${MARK_LIMIT} # ---- tc 配置 ---- # 根队列 HTB,默认走 20 (虽然有兜底,但我们现在依靠显式 filter) ExecStart=${TC_BIN} qdisc add dev ${IFACE} root handle 1: htb default 20 ExecStart=${TC_BIN} class add dev ${IFACE} parent 1: classid 1:1 htb rate 1000mbit ceil 1000mbit # 1:10 限速类 (Mark 10) ExecStart=${TC_BIN} class add dev ${IFACE} parent 1:1 classid 1:10 htb rate ${RATE} ceil ${RATE} ExecStart=${TC_BIN} qdisc add dev ${IFACE} parent 1:10 handle 10: fq_codel # 1:20 不限速类 (Mark 20 / Default) ExecStart=${TC_BIN} class add dev ${IFACE} parent 1:1 classid 1:20 htb rate 1000mbit ceil 1000mbit ExecStart=${TC_BIN} qdisc add dev ${IFACE} parent 1:20 handle 20: fq_codel # ---- Filter 绑定 ---- # 匹配 Mark 10 -> 送入 1:10 (限速) ExecStart=${TC_BIN} filter add dev ${IFACE} parent 1: protocol ip prio 10 handle ${MARK_LIMIT} fw flowid 1:10 # [关键修复] 匹配 Mark 20 -> 送入 1:20 (不限速) ExecStart=${TC_BIN} filter add dev ${IFACE} parent 1: protocol ip prio 10 handle ${MARK_VIP} fw flowid 1:20 # 兜底:未匹配任何 filter 的流量强制走 1:20 ExecStart=${TC_BIN} filter add dev ${IFACE} parent 1: protocol ip prio 999 u32 match u32 0 0 flowid 1:20 # 停止时清理 ExecStop=-${TC_BIN} qdisc del dev ${IFACE} root ExecStop=/bin/bash -c 'while ${IPT_BIN} -t mangle -D POSTROUTING -o ${IFACE} -j ${CHAIN_NAME} 2>/dev/null; do :; done' ExecStop=-${IPT_BIN} -t mangle -F ${CHAIN_NAME} ExecStop=-${IPT_BIN} -t mangle -X ${CHAIN_NAME} RemainAfterExit=yes [Install] WantedBy=multi-user.target EOF echo "✅ systemd 服务已生成:${SERVICE_PATH}" echo "➡ 重新加载并重启服务..." systemctl daemon-reload systemctl enable --now "${SERVICE_NAME}" systemctl restart "${SERVICE_NAME}" echo echo "=== 验证 iptables 标记情况 ===" echo "预期:白名单行应显示 MARK set 0x14 (即20)" ${IPT_BIN} -t mangle -L ${CHAIN_NAME} -v -n || true echo echo "=== 验证 TC 流量分流 ===" echo "预期:测速时,流量应主要出现在 1:20 (不限速类)" ${TC_BIN} -s class show dev "${IFACE}" || true echo echo "✅ v4 VIP版已应用。请再次测试 iperf3。"