· 约 22 分钟 ·

使用 nftables 在 IPLC 中转服务器上管理 NAT 转发

利用内核集成的 nftables 编写脚本,便捷添加、删除与管理 NAT 转发规则,适用于 IPLC 中转服务器的端口映射与流量转发。

购买了 IPLC 中转服务器,用于加速代理速度,可以借助 nftables 来做简单的 NAT 转发

因为 nftables 本身就集成在 Linux 内核中,无需额外安装,且性能优异

无需担心协议被识别的问题,NAT 转发只会修改 IP 和端口,不会改变数据包的内容,加密依旧是加密,只要你的代理协议本身足够隐蔽即可

数据包依旧在本地加密,在落地端解密,中间的 IPLC 机器不会看到任何明文数据(运营商和 IPLC 服务商也看不到)

为了方便添加多个落地加速,撰写一个脚本用于方便添加新的中转服务

编辑 nat-shaper.sh ,其内容如下:

#!/bin/bash
# NAT 转发管理 (纯 nftables 版本 - 修复/加固 + 支持端口限速 + 双向限速)
# - add <PORT> <IP:PORT> [--rate Mbps]
# - 限速=去程(→rip:rport) + 回程(←rip:rport) 各自 limit+drop,TCP/UDP 同步
# - nft 不支持 bit/s;将 Mbps 换算为 kbytes/second(1 Mbps = 125 kB/s)

set -e

NFT="/usr/sbin/nft"
TABLE="natmgr"
CONFIG_FILE="/etc/nftables-natmgr.conf"

need_cmd() { command -v "$1" >/dev/null 2>&1 || { echo "缺少命令: $1"; exit 1; }; }
need_cmd "$NFT"

if [[ $EUID -ne 0 ]]; then echo "请使用 root 权限运行。"; exit 1; fi

init_table() {
    if ! $NFT list table ip $TABLE >/dev/null 2>&1; then
        echo "[+] 创建表 $TABLE 和必要的链"
        $NFT add table ip $TABLE
        $NFT add chain ip $TABLE prerouting  { type nat hook prerouting  priority -100\; }
        $NFT add chain ip $TABLE postrouting { type nat hook postrouting priority 100\; }
        $NFT add chain ip $TABLE forward     { type filter hook forward  priority 0\; }
    fi
}

save_rules() {
    echo "#!/usr/sbin/nft -f" > "$CONFIG_FILE"
    $NFT list table ip $TABLE >> "$CONFIG_FILE"
    echo "[*] 规则已保存到 $CONFIG_FILE"
}

show_rules() {
    echo ""
    echo "============== 当前 NAT 转发情况 =============="
    local rules
    rules=$($NFT list chain ip $TABLE forward 2>/dev/null | grep -E 'comment "NAT-[0-9]+-.*"' || true)
    if [[ -z "$rules" ]]; then
        echo "暂无 NAT 转发规则"
        echo "=============================================="
        return
    fi
    local ports
    ports=$(echo "$rules" | sed -n 's/.*comment "NAT-\([0-9]*\)-.*/\1/p' | sort -nu)
    for port in $ports; do
        local any_rule rip rport
        any_rule=$(echo "$rules" | grep -E "comment \"NAT-${port}-.*\"" | head -1)
        rip=$(echo "$any_rule" | grep -oE 'ip (saddr|daddr) [0-9.]+' | awk '{print $3}')
        rport=$(echo "$any_rule" | grep -oE '(dport|sport) [0-9]+' | awk '{print $2}')
        [[ -n "$rip" && -n "$rport" ]] && echo "本地端口 ${port}${rip}:${rport}"
    done
    echo "=============================================="
}

del_rule() {
    local lport=$1
    echo "[*] 删除端口 $lport 相关规则"
    local deleted=0
    for chain in prerouting postrouting forward; do
        local handles
        handles=$($NFT -a list chain ip $TABLE "$chain" 2>/dev/null | \
            awk -v p="NAT-"$lport"-" '
                /comment/ && $0 ~ p {
                    for(i=1;i<=NF;i++) if($i=="handle"){print $(i+1)}
                }' || true)
        if [[ -n "$handles" ]]; then
            for h in $handles; do
                $NFT delete rule ip $TABLE "$chain" handle "$h" 2>/dev/null || true
                echo "[-] 删除 $chain 链规则 handle $h"
                deleted=$((deleted+1))
            done
        fi
    done
    if [[ $deleted -eq 0 ]]; then
        echo "[-] 未找到端口 $lport 的相关规则"
    else
        echo "[*] 共删除 $deleted 条规则"
        save_rules
    fi
}

to_kbytes_per_sec() { awk -v r="$1" 'BEGIN { printf("%d\n", (r*125)+0.5) }'; }

parse_add_args() {
    ADD_LPORT=""; ADD_REMOTE=""; ADD_RATE=""
    if [[ $# -lt 2 ]]; then echo "错误:add 需要至少 2 个参数"; show_help; exit 1; fi
    ADD_LPORT="$1"; shift
    ADD_REMOTE="$1"; shift
    while [[ $# -gt 0 ]]; do
        case "$1" in
            --rate) shift
                [[ -z "$1" ]] && { echo "错误:--rate 需要数值(Mbps)"; exit 1; }
                [[ ! "$1" =~ ^[0-9]+(\.[0-9]+)?$ ]] && { echo "错误:--rate 仅支持数字(可小数)"; exit 1; }
                ADD_RATE="$1"; shift;;
            *) echo "未知参数:$1"; exit 1;;
        esac
    done
}

# 生成一组限速规则(方向由 direction 决定:dst=去程 daddr/dport;src=回程 saddr/sport)
add_limit_rules_pair() {
    local direction="$1" proto="$2" ipaddr="$3" port="$4" rate_kbytes="$5" lport="$6"
    local tag mid verb1 verb2
    if [[ "$direction" == "dst" ]]; then
        tag="NAT-${lport}-${proto}-limit"  # 去程
        if [[ "$proto" == "tcp" ]]; then
            verb1="ip daddr $ipaddr tcp dport $port"
        else
            verb1="ip daddr $ipaddr udp dport $port"
        fi
    else
        tag="NAT-${lport}-${proto}-limit-rev"  # 回程
        if [[ "$proto" == "tcp" ]]; then
            verb1="ip saddr $ipaddr tcp sport $port"
        else
            verb1="ip saddr $ipaddr udp sport $port"
        fi
    fi
    # burst:TCP=5MB,UDP=2MB
    if [[ "$proto" == "tcp" ]]; then mid="burst 5 mbytes"; else mid="burst 2 mbytes"; fi

    # limit -> accept;随后 drop 兜底
    $NFT add rule ip $TABLE forward $verb1 limit rate ${rate_kbytes} kbytes/second $mid counter accept comment "$tag"
    $NFT add rule ip $TABLE forward $verb1 counter drop comment "${tag/-limit/-over}"
}

add_rule() {
    local lport="$1" remote="$2" rate="$3"
    local rip=${remote%%:*} rport=${remote##*:}
    [[ -z "$rip" || -z "$rport" ]] && { echo "目标格式错误,应为 IP:PORT"; exit 1; }

    echo "[+] 添加规则: $lport$rip:$rport"
    del_rule "$lport"

    # PREROUTING DNAT
    $NFT add rule ip $TABLE prerouting tcp dport $lport dnat to $rip:$rport comment "NAT-${lport}-tcp-preroute"
    $NFT add rule ip $TABLE prerouting udp dport $lport dnat to $rip:$rport comment "NAT-${lport}-udp-preroute"

    # POSTROUTING MASQUERADE
    $NFT add rule ip $TABLE postrouting ip daddr $rip masquerade comment "NAT-${lport}-post"

    if [[ -n "$rate" ]]; then
        local rate_kbytes; rate_kbytes=$(to_kbytes_per_sec "$rate")
        [[ "$rate_kbytes" -lt 1 ]] && { echo "错误:--rate 太小(<$rate Mbps)导致 <1 kB/s"; exit 1; }

        # 去程(client->server)+ 回程(server->client),TCP/UDP 各两条 => 共 8 条
        add_limit_rules_pair dst tcp "$rip" "$rport" "$rate_kbytes" "$lport"
        add_limit_rules_pair dst udp "$rip" "$rport" "$rate_kbytes" "$lport"
        add_limit_rules_pair src tcp "$rip" "$rport" "$rate_kbytes" "$lport"
        add_limit_rules_pair src udp "$rip" "$rport" "$rate_kbytes" "$lport"

        echo "[*] 端口 $lport 已启用双向限速 ${rate}Mbps(≈ ${rate_kbytes} kB/s,TCP/UDP)"
    else
        # 不限速:双协议直接放行(去/回不区分,回程反正还会经过其他链)
        $NFT add rule ip $TABLE forward ip daddr $rip tcp dport $rport counter accept comment "NAT-${lport}-tcp"
        $NFT add rule ip $TABLE forward ip daddr $rip udp dport $rport counter accept comment "NAT-${lport}-udp"
        echo "[*] 端口 $lport 不限速"
    fi

    save_rules
    echo "[+] 规则添加完成"
    show_rules
}

cleanup_rules() {
    echo "[*] 清理重复规则..."
    local ports cleaned=0
    ports=$($NFT list chain ip $TABLE forward 2>/dev/null | \
        grep -oE 'comment "NAT-[0-9]+-' | sed -n 's/comment "NAT-\([0-9]*\)-/\1/p' | sort -nu || true)
    for port in $ports; do
        local tcp_count udp_count
        tcp_count=$($NFT list chain ip $TABLE forward 2>/dev/null | grep -c "comment \"NAT-${port}-tcp" || echo 0)
        udp_count=$($NFT list chain ip $TABLE forward 2>/dev/null | grep -c "comment \"NAT-${port}-udp" || echo 0)
        if [[ $tcp_count -gt 4 || $udp_count -gt 4 ]]; then
            echo "[*] 清理端口 $port 的可能重复规则"
            local info rip rport
            info=$($NFT list chain ip $TABLE forward 2>/dev/null | grep -E "comment \"NAT-${port}-tcp.*\"" | head -1)
            rip=$(echo "$info" | grep -oE 'ip (saddr|daddr) [0-9.]+' | awk '{print $3}')
            rport=$(echo "$info" | grep -oE '(dport|sport) [0-9]+' | awk '{print $2}')
            if [[ -n "$rip" && -n "$rport" ]]; then
                del_rule "$port"
                add_rule "$port" "$rip:$rport"   # 默认不限速重建;需要限速再 add --rate
                cleaned=$((cleaned+1))
            fi
        fi
    done
    [[ $cleaned -eq 0 ]] && echo "[*] 没有发现需要清理的重复规则" || echo "[+] 清理了 $cleaned 个端口"
    show_rules
}

list_all() {
    echo "============== 详细规则列表 =============="
    echo "--- PREROUTING ---";  $NFT list chain ip $TABLE prerouting  2>/dev/null | grep -E "(dnat|comment)" || echo "无规则"
    echo "--- POSTROUTING ---"; $NFT list chain ip $TABLE postrouting 2>/dev/null | grep -E "(masquerade|comment)" || echo "无规则"
    echo "--- FORWARD ---";     $NFT list chain ip $TABLE forward     2>/dev/null | grep -E "(limit|accept|drop|comment)" || echo "无规则"
    echo "========================================"
}

reset_all() {
    echo "[!] 警告:这将删除所有 NAT 规则!"
    read -r -p "确认继续?(y/N): " confirm
    if [[ "$confirm" == [yY] ]]; then
        $NFT delete table ip $TABLE 2>/dev/null || echo "[*] 表不存在或已删除"
        init_table; save_rules; echo "[+] 所有规则已清空"
    else
        echo "[*] 操作已取消"
    fi
}

show_help() {
    cat <<EOF
NAT 转发管理脚本 (nftables + 端口限速,双向)
用法:
  $0 show
  $0 add <LOCAL_PORT> <IP:PORT> [--rate Mbps]
     示例:
       $0 add 4443 1.1.1.1:443           # 不限速
       $0 add 4443 1.1.1.1:443 --rate 10 # 上/下行各限 10Mbps
  $0 del <LOCAL_PORT>
  $0 cleanup
  $0 list-all
  $0 reset

说明:
  - 限速=两套规则:去程(daddr:rport) 与 回程(saddr:sport);超额 drop(硬卡带宽)。
  - 单位:Mbps -> kbytes/second(1 Mbps = 125 kB/s),burst: TCP=5MB, UDP=2MB。
EOF
}

init_table
case "$1" in
  show) show_rules ;;
  add) shift; parse_add_args "$@"; add_rule "$ADD_LPORT" "$ADD_REMOTE" "$ADD_RATE" ;;
  del)
    if [[ $# -ne 2 ]]; then echo "错误:参数不足"; show_help; exit 1; fi
    del_rule "$2"; show_rules ;;
  cleanup) cleanup_rules ;;
  list-all) list_all ;;
  reset) reset_all ;;
  *) show_help; exit 1 ;;
esac

使用说明:

~ ./nat-shaper.sh --help
NAT 转发管理脚本 (nftables + 端口限速,双向)
用法:
  ./nat-shaper.sh show
  ./nat-shaper.sh add <LOCAL_PORT> <IP:PORT> [--rate Mbps]
     示例:
       ./nat-shaper.sh add 4443 1.1.1.1:443           # 不限速
       ./nat-shaper.sh add 4443 1.1.1.1:443 --rate 10 # 上/下行各限 10Mbps
  ./nat-shaper.sh del <LOCAL_PORT>
  ./nat-shaper.sh cleanup
  ./nat-shaper.sh list-all
  ./nat-shaper.sh reset

说明:
  - 限速=两套规则:去程(daddr:rport)  回程(saddr:sport);超额 drop(硬卡带宽)。
  - 单位:Mbps -> kbytes/second(1 Mbps = 125 kB/s),burst: TCP=5MB, UDP=2MB。

然后执行以下命令:

# 添加转发规则,将本地 8080 端口转发到 1.2.3.4:1234
add-nat.sh add 8080 1.2.3.4:1234
# 删除转发规则,删除本地 8080 端口的转发
add-nat.sh del 8080
# 查看当前转发情况
add-nat.sh show
# 清理重复规则
add-nat.sh cleanup
# 显示所有规则详情
add-nat.sh list-all
# 重置所有规则
add-nat.sh reset

留言交流

发表留言

支持 Markdown 语法 0/1000