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

利用内核集成的 nftables 编写脚本,便捷添加、删除与管理 NAT 转发规则,适用于 IPLC 中转服务器的端口映射与流量转发。
2025-09-30
8778 字 · 约 22 分钟阅读

购买了 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/second1 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

留言

发表留言