1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249
| cat > /root/port_limit.sh << 'EOF'
RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' CYAN='\033[0;36m' BLUE='\033[0;34m' BOLD='\033[1m' NC='\033[0m'
IFACE=$(ip route show default 2>/dev/null | awk '/default/ {print $5}' | head -1) IFACE=${IFACE:-eth0}
check_root() { if [[ $EUID -ne 0 ]]; then echo -e "${RED}错误:此脚本需要 root 权限运行!${NC}" exit 1 fi }
check_deps() { local missing=() for cmd in tc iptables ip6tables ip; do command -v "$cmd" &>/dev/null || missing+=("$cmd") done if [[ ${#missing[@]} -gt 0 ]]; then echo -e "${RED}缺少依赖命令: ${missing[*]}${NC}" exit 1 fi }
ensure_root_qdisc() { if ! tc qdisc show dev "$IFACE" 2>/dev/null | grep -q "htb"; then tc qdisc add dev "$IFACE" root handle 1: htb default 999 2>/dev/null tc class add dev "$IFACE" parent 1: classid 1:999 htb rate 10gbit 2>/dev/null fi }
port_to_classid() { printf "1:%x" "$1"; } port_to_mark() { printf "0x%x" "$1"; } mbps_to_tc() { echo "${1}mbit"; }
show_limited_ports() { echo "" echo -e "${CYAN}${BOLD}══════════════════════════════════════════${NC}" echo -e "${CYAN}${BOLD} 当前限速端口列表 [接口: $IFACE] ${NC}" echo -e "${CYAN}${BOLD}══════════════════════════════════════════${NC}" local found=0 while IFS= read -r line; do local classid hex_port port rate_info rate_display classid=$(echo "$line" | awk '{print $3}') [[ -z "$classid" ]] && continue [[ "$classid" == "1:1" || "$classid" == "1:999" ]] && continue hex_port=$(echo "$classid" | cut -d: -f2) port=$((16#${hex_port})) [[ "$port" -lt 1 || "$port" -gt 65535 ]] && continue rate_info=$(echo "$line" | grep -oP 'rate \K[^ ]+') [[ -z "$rate_info" ]] && continue rate_display=$(echo "$rate_info" | sed 's/[Mm]bit/Mbps/;s/[Kk]bit/Kbps/;s/[Gg]bit/Gbps/') echo -e " ${GREEN}▶${NC} 端口 ${BOLD}${port}${NC} → 限速 ${YELLOW}${rate_display}${NC} ${CYAN}[IPv4+IPv6]${NC}" found=$((found + 1)) done < <(tc class show dev "$IFACE" 2>/dev/null) if [[ $found -eq 0 ]]; then echo -e " ${YELLOW}暂无限速端口${NC}" fi echo -e "${CYAN}══════════════════════════════════════════${NC}" echo "" }
add_limit() { local port=$1 rate_mbps=$2 local classid mark tc_rate classid=$(port_to_classid "$port") mark=$(port_to_mark "$port") tc_rate=$(mbps_to_tc "$rate_mbps")
if tc class show dev "$IFACE" classid "$classid" 2>/dev/null | grep -q "htb"; then echo -e "${YELLOW}端口 $port 已存在,正在更新...${NC}" tc class change dev "$IFACE" parent 1: classid "$classid" htb rate "$tc_rate" burst 15k else tc class add dev "$IFACE" parent 1: classid "$classid" htb rate "$tc_rate" burst 15k tc filter add dev "$IFACE" parent 1: protocol ip handle "$mark" fw flowid "$classid" tc filter add dev "$IFACE" parent 1: protocol ipv6 handle "$mark" fw flowid "$classid" fi
for proto in tcp udp; do iptables -t mangle -C OUTPUT -p "$proto" --sport "$port" \ -m comment --comment "LIMIT_PORT_${port}" -j MARK --set-mark "$mark" 2>/dev/null \ || iptables -t mangle -A OUTPUT -p "$proto" --sport "$port" \ -m comment --comment "LIMIT_PORT_${port}" -j MARK --set-mark "$mark" done
for proto in tcp udp; do ip6tables -t mangle -C OUTPUT -p "$proto" --sport "$port" \ -m comment --comment "LIMIT_PORT_${port}" -j MARK --set-mark "$mark" 2>/dev/null \ || ip6tables -t mangle -A OUTPUT -p "$proto" --sport "$port" \ -m comment --comment "LIMIT_PORT_${port}" -j MARK --set-mark "$mark" done
echo -e "${GREEN}✔ 端口 ${BOLD}$port${NC}${GREEN} 限速已设置为 ${BOLD}${rate_mbps}Mbps${NC}${GREEN}(IPv4+IPv6)!${NC}" }
remove_limit() { local port=$1 local classid mark removed=0 classid=$(port_to_classid "$port") mark=$(port_to_mark "$port")
for proto in tcp udp; do while iptables -t mangle -C OUTPUT -p "$proto" --sport "$port" \ -m comment --comment "LIMIT_PORT_${port}" -j MARK --set-mark "$mark" 2>/dev/null; do iptables -t mangle -D OUTPUT -p "$proto" --sport "$port" \ -m comment --comment "LIMIT_PORT_${port}" -j MARK --set-mark "$mark" removed=1 done done
for proto in tcp udp; do while ip6tables -t mangle -C OUTPUT -p "$proto" --sport "$port" \ -m comment --comment "LIMIT_PORT_${port}" -j MARK --set-mark "$mark" 2>/dev/null; do ip6tables -t mangle -D OUTPUT -p "$proto" --sport "$port" \ -m comment --comment "LIMIT_PORT_${port}" -j MARK --set-mark "$mark" removed=1 done done
tc filter del dev "$IFACE" parent 1: protocol ip handle "$mark" fw 2>/dev/null && removed=1 tc filter del dev "$IFACE" parent 1: protocol ipv6 handle "$mark" fw 2>/dev/null && removed=1
if tc class show dev "$IFACE" classid "$classid" 2>/dev/null | grep -q "htb"; then tc class del dev "$IFACE" classid "$classid" 2>/dev/null removed=1 fi
if [[ $removed -eq 1 ]]; then echo -e "${GREEN}✔ 端口 ${BOLD}$port${NC}${GREEN} 限速已解除!${NC}" else echo -e "${YELLOW}⚠ 未找到端口 $port 的限速规则${NC}" fi }
validate_port() { [[ "$1" =~ ^[0-9]+$ ]] && [[ "$1" -ge 1 && "$1" -le 65535 ]] && return 0 echo -e "${RED}错误:端口号必须为 1~65535${NC}"; return 1 }
validate_rate() { [[ "$1" =~ ^[0-9]+(\.[0-9]+)?$ ]] && [[ "$1" != "0" ]] && return 0 echo -e "${RED}错误:速率必须为正数(Mbps)${NC}"; return 1 }
print_header() { clear echo -e "${BLUE}${BOLD}" echo " ╔══════════════════════════════════════╗" echo " ║ 端口限速管理工具 ║" echo " ║ Port Bandwidth Limiter v1.2 ║" echo " ╚══════════════════════════════════════╝" echo -e "${NC}" echo -e " 网络接口: ${CYAN}${IFACE}${NC}" echo "" }
print_menu() { echo -e "${BOLD} ┌─────────────────────────────────────┐${NC}" echo -e "${BOLD} │ 主菜单 │${NC}" echo -e "${BOLD} ├─────────────────────────────────────┤${NC}" echo -e " │ ${GREEN}1${NC} 查看当前限速端口及速率 │" echo -e " │ ${GREEN}2${NC} 添加限速端口 │" echo -e " │ ${GREEN}3${NC} 删除限速端口 │" echo -e " │ ${RED}9${NC} 退出 │" echo -e "${BOLD} └─────────────────────────────────────┘${NC}" echo "" }
wait_return() { echo -e " 按 ${YELLOW}0${NC} 返回主菜单..." while true; do read -r -s -n1 k; [[ "$k" == "0" ]] && break; done }
menu_view() { print_header; show_limited_ports; wait_return }
menu_add() { print_header echo -e "${BOLD} ── 添加限速端口 ──${NC}"; echo "" while true; do echo -ne " 请输入端口号 (1-65535,0返回): "; read -r port [[ "$port" == "0" ]] && return validate_port "$port" && break done while true; do echo -ne " 请输入限速速率 (Mbps,如 100): "; read -r rate [[ "$rate" == "0" ]] && return validate_rate "$rate" && break done echo "" ensure_root_qdisc add_limit "$port" "$rate" echo "" show_limited_ports wait_return }
menu_remove() { print_header echo -e "${BOLD} ── 删除限速端口 ──${NC}"; echo "" show_limited_ports while true; do echo -ne " 请输入要解除限速的端口号 (0返回): "; read -r port [[ "$port" == "0" ]] && return if validate_port "$port"; then echo ""; remove_limit "$port"; echo ""; show_limited_ports fi done }
main() { check_root check_deps ensure_root_qdisc while true; do print_header; print_menu echo -ne " 请选择操作 [1/2/3/9]: "; read -r choice case "$choice" in 1) menu_view ;; 2) menu_add ;; 3) menu_remove ;; 9) echo -e "\n ${GREEN}再见!${NC}\n"; exit 0 ;; *) echo -e " ${RED}无效选项${NC}"; sleep 1 ;; esac done }
main EOF echo "✔ 写入完成" chmod +x /root/port_limit.sh
|