Ubuntu 20针对除特定端口外的全局上行自动限速脚本

一、方案概述

  • 限速工具:Linux 内核tc(Traffic Control)+iptables标记
  • 限速对象:指定网卡的上行流量(出口方向)
  • 限速粒度:全局上行带宽,可排除特定端口(如 SSH、Web 管理等)
  • 持久化:通过 systemd 服务实现开机自动限速
  • 定时控制:通过 cron 实现按需开启/关闭或切换限速值
  • 适用系统:Ubuntu 20.04 LTS(也兼容 Debian 11/12、飞牛 fnOS)

二、安装必要软件包

Ubuntu 20.04 默认已安装 iproute2(提供 tc)和 iptables,但为了确保环境完整,执行:

bash

sudo apt update
sudo apt install -y iproute2 iptables

三、部署限速脚本

1. 创建脚本目录并编写主脚本

bash

sudo mkdir -p /usr/local/bin
sudo nano /usr/local/bin/speedlimit.sh

将以下完整脚本内容粘贴进去(已修复 Illegal "match" 错误,使用 fw 分类器):

bash

#!/bin/bash
# ============================================
# Ubuntu 20.04 上行带宽限速脚本
# 功能:对指定网卡上行总带宽进行限速,支持排除端口
# 用法:speedlimit.sh {start|stop|status|restart}
# ============================================

# ========== 用户配置区域 ==========
INTERFACE="eth0"               # 你的出口网卡,用 ip link show 查看
LIMIT_RATE="10mbit"            # 限速值,例如 2mbit / 10mbit / 100mbit
EXCLUDE_PORTS="22,443,8080"    # 排除不限速的端口,逗号分隔,无则留空 ""
# =================================

IPMARK="100"
TC_CLSID="1:${IPMARK}"

start_limit() {
    echo "正在为网卡 $INTERFACE 设置上行限速: $LIMIT_RATE ..."

    # 清除旧规则
    tc qdisc del dev $INTERFACE root 2>/dev/null

    # 绑定 HTB 队列树
    tc qdisc add dev $INTERFACE root handle 1: htb default 20
    tc class add dev $INTERFACE parent 1: classid 1:1 htb rate $LIMIT_RATE
    tc class add dev $INTERFACE parent 1:1 classid 1:20 htb rate $LIMIT_RATE
    tc class add dev $INTERFACE parent 1:1 classid $TC_CLSID htb rate 10000mbit

    # 使用 fw 分类器匹配防火墙标记(避免 u32 match 语法问题)
    tc filter add dev $INTERFACE parent 1: protocol ip prio 1 handle $IPMARK fw flowid $TC_CLSID

    # 配置 iptables 标记链
    iptables -t mangle -D OUTPUT -j SPEEDLIMIT 2>/dev/null
    iptables -t mangle -F SPEEDLIMIT 2>/dev/null
    iptables -t mangle -X SPEEDLIMIT 2>/dev/null

    iptables -t mangle -N SPEEDLIMIT
    iptables -t mangle -A OUTPUT -j SPEEDLIMIT

    # 排除端口:打上特殊标记 $IPMARK,绕过限速
    IFS=',' read -ra PORTS <<< "$EXCLUDE_PORTS"
    for PORT in "${PORTS[@]}"; do
        if [[ -n "$PORT" ]]; then
            iptables -t mangle -A SPEEDLIMIT -p tcp --sport $PORT -j MARK --set-mark $IPMARK
            iptables -t mangle -A SPEEDLIMIT -p udp --sport $PORT -j MARK --set-mark $IPMARK
            echo "已添加排除端口: $PORT"
        fi
    done

    # 其余流量默认标记 0,被限速
    iptables -t mangle -A SPEEDLIMIT -j MARK --set-mark 0

    echo "限速规则已生效。"
}

stop_limit() {
    echo "正在停止所有限速规则..."
    tc qdisc del dev $INTERFACE root 2>/dev/null
    iptables -t mangle -D OUTPUT -j SPEEDLIMIT 2>/dev/null
    iptables -t mangle -F SPEEDLIMIT 2>/dev/null
    iptables -t mangle -X SPEEDLIMIT 2>/dev/null
    echo "限速规则已清除。"
}

status_check() {
    echo "========== 限速状态检查 =========="
    if tc qdisc show dev $INTERFACE | grep -q "htb 1:"; then
        echo -e "[状态] 限速: 已启用"
        echo "当前限速设置:"
        tc class show dev $INTERFACE | grep "htb rate"
    else
        echo "[状态] 限速: 已停止"
    fi
    echo "排除端口: $EXCLUDE_PORTS"
    echo "================================="
}

case "$1" in
    start)
        start_limit
        ;;
    stop)
        stop_limit
        ;;
    status)
        status_check
        ;;
    restart)
        stop_limit
        start_limit
        ;;
    *)
        echo "使用方法: $0 {start|stop|status|restart}"
        exit 1
        ;;
esac

exit 0
2. 保存并赋予执行权限

bash

sudo chmod +x /usr/local/bin/speedlimit.sh
3. 测试脚本

先修改脚本开头的 INTERFACE 为你的真实网卡名(用 ip link show 查看),例如 enp0s3ens33eth0 等。

bash

# 查看网卡名称
ip link show

# 编辑脚本改 INTERFACE
sudo nano /usr/local/bin/speedlimit.sh

然后测试:

bash

sudo /usr/local/bin/speedlimit.sh start
sudo /usr/local/bin/speedlimit.sh status
sudo /usr/local/bin/speedlimit.sh stop

确保没有 Illegal "match" 错误,且 status 显示正常。


四、设置开机自动限速(systemd 服务)

创建 systemd 服务文件:

bash

sudo nano /etc/systemd/system/speedlimit.service

粘贴以下内容:

ini

[Unit]
Description=Ubuntu Bandwidth Limiter
After=network-online.target
Wants=network-online.target

[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/local/bin/speedlimit.sh start
ExecStop=/usr/local/bin/speedlimit.sh stop
StandardOutput=journal

[Install]
WantedBy=multi-user.target

启用并启动服务:

bash

sudo systemctl daemon-reload
sudo systemctl enable speedlimit.service
sudo systemctl start speedlimit.service

检查状态:

bash

sudo systemctl status speedlimit.service
sudo /usr/local/bin/speedlimit.sh status

重启系统验证:sudo reboot 后再执行 status 应依然为“已启用”。

五、配置定时开关限速(cron)

如果你需要每天固定时间开启/关闭限速,或者在不同时段使用不同限速值,使用 cron。

1. 编辑 root 用户的 crontab

bash

sudo crontab -e
2. 添加 PATH 和定时任务

在文件开头添加(确保 tc/iptables 命令可被找到):

cron

PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin

然后在下方添加你的调度规则,例如:

cron

# 每天 22:30 开启限速
30 22 * * * /usr/local/bin/speedlimit.sh start

# 每天 08:00 关闭限速
0 8 * * * /usr/local/bin/speedlimit.sh stop

保存退出。

3. 高级用法:不同时段不同限速值

如果需要白天限速较宽松、夜间严格限制,可以复制多个脚本并修改 LIMIT_RATE

bash

sudo cp /usr/local/bin/speedlimit.sh /usr/local/bin/speedlimit-night.sh
sudo nano /usr/local/bin/speedlimit-night.sh   # 修改 LIMIT_RATE="2mbit"

然后在 crontab 中:

cron

# 白天 8:00 使用 10mbit
0 8 * * * /usr/local/bin/speedlimit.sh start

# 夜间 23:00 切换为 2mbit
0 23 * * * /usr/local/bin/speedlimit-night.sh start

# 凌晨 2:00 完全取消限速
0 2 * * * /usr/local/bin/speedlimit.sh stop

注意切换不同脚本前最好先执行 stop,或者直接调用新脚本的 start(它会自动清除旧规则重建)。


六、验证限速是否真实生效

  1. 本地查看队列统计:bashtc -s qdisc show dev eth0 tc -s class show dev eth0观察Sent字节数是否增长,dropped是否出现。
  2. 通过另一台主机进行 iperf3 上行测试
    • 在限速服务器上启动 iperf3 服务端:iperf3 -s
    • 在外网客户端(或同一局域网的另一台机器)执行:iperf3 -c <服务器IP> -R(-R 表示反向测试,即测试服务器上行)
    • 观察带宽是否被限制到设定值。
  3. 检查排除端口效果
    • 从服务器访问外网某服务,源端口为排除端口(如 443),应不受限速影响。
    • 可以使用curl --local-port 443 //example.com测试。

七、常见问题与解决

Q1:执行 start 时出现 RTNETLINK answers: No such file or directory

原因:网卡名称错误或 tc 模块未加载。
解决:确认 INTERFACE 名称正确;执行 modprobe sch_htb 加载 HTB 模块。

Q2:限速不生效,查看 tc -s class 显示 rate 为 0 或巨大

原因tc filter 未能正确匹配流量。
解决:检查 iptables -t mangle -L -v 是否有包计数;确保使用 fw 分类器而非 u32 match

Q3:重启后限速规则丢失(systemd 未启动)

原因:服务未启用或网络依赖未解决。
解决:执行 sudo systemctl enable speedlimit.service;检查 After=network-online.target 是否生效,必要时改为 After=network.target 并增加 Restart=on-failure

Q4:cron 任务未执行

原因:环境变量缺失或脚本权限问题。
解决:在 crontab 中显式设置 PATH;使用绝对路径调用脚本;查看日志:grep CRON /var/log/syslog

Q5:排除端口无效

原因:iptables 链顺序或标记被覆盖。
解决:确认标记链在所有 OUTPUT 规则之前插入;可以添加 iptables -t mangle -L -n -v 检查计数器。


八、完整部署脚本(一键安装)

为了方便,你也可以将以上所有步骤整合为一个自动化安装脚本,保存为 deploy_speedlimit.sh

bash

#!/bin/bash
# Ubuntu 20.04 限速方案一键部署脚本

set -e

# 1. 安装依赖
apt update
apt install -y iproute2 iptables

# 2. 创建主脚本
cat > /usr/local/bin/speedlimit.sh << 'EOF'
(这里粘贴上面完整脚本内容)
EOF

chmod +x /usr/local/bin/speedlimit.sh

# 3. 提示修改网卡名称
echo "请编辑 /usr/local/bin/speedlimit.sh,设置 INTERFACE 为你的出口网卡名($(ip link show | grep -oP '^[0-9]+: \K[^:]+' | grep -v lo | head -1))和 LIMIT_RATE。"
echo "完成后执行:sudo /usr/local/bin/speedlimit.sh start"

# 4. 创建 systemd 服务
cat > /etc/systemd/system/speedlimit.service << 'EOF'
[Unit]
Description=Ubuntu Bandwidth Limiter
After=network-online.target
Wants=network-online.target

[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/local/bin/speedlimit.sh start
ExecStop=/usr/local/bin/speedlimit.sh stop
StandardOutput=journal

[Install]
WantedBy=multi-user.target
EOF

systemctl daemon-reload
systemctl enable speedlimit.service

echo "部署完成。请编辑 /usr/local/bin/speedlimit.sh 后执行 systemctl start speedlimit.service"

执行此一键脚本前请先修改其中的脚本内容占位符为实际完整脚本。


九、总结

通过以上步骤,你可以在 Ubuntu 20.04 系统上获得一个功能完整、持久化、定时可调的 上行带宽限速方案,核心特点:

  • 使用tc+iptables实现精准上行限速
  • 支持排除任意端口(如管理端口、P2P 端口)
  • 开机自动恢复限速规则(systemd)
  • 支持 crontab 灵活调度(按时间开启/关闭或切换限速值)
  • 兼容飞牛 fnOS(基于 Debian 12)及其他 Debian 系系统