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

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

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

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

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

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

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

#!/bin/bash
# NAT 转发管理

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\; }
        # 虽然 forward 一般放在 filter 表,但这里保留你的设计
        $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 tcp_rule udp_rule rip rport packets bytes mb

        tcp_rule=$(echo "$rules" | grep "comment \"NAT-${port}-tcp\"" | head -1)
        if [[ -n "$tcp_rule" ]]; then
            rip=$(echo "$tcp_rule" | grep -oE 'ip daddr [0-9.]+' | awk '{print $3}')
            rport=$(echo "$tcp_rule" | grep -oE 'dport [0-9]+' | awk '{print $2}')
            packets=$(echo "$tcp_rule" | grep -oE 'packets [0-9]+' | awk '{print $2}' || echo "0")
            bytes=$(echo "$tcp_rule" | grep -oE 'bytes [0-9]+' | awk '{print $2}' || echo "0")
            mb=$(echo "scale=2; $bytes/1024/1024" | bc -l 2>/dev/null || echo "0.00")
            echo "本地端口 ${port}/tcp → ${rip}:${rport} (${mb} MB, ${packets} 包)"
        fi

        udp_rule=$(echo "$rules" | grep "comment \"NAT-${port}-udp\"" | head -1)
        if [[ -n "$udp_rule" ]]; then
            rip=$(echo "$udp_rule" | grep -oE 'ip daddr [0-9.]+' | awk '{print $3}')
            rport=$(echo "$udp_rule" | grep -oE 'dport [0-9]+' | awk '{print $2}')
            packets=$(echo "$udp_rule" | grep -oE 'packets [0-9]+' | awk '{print $2}' || echo "0")
            bytes=$(echo "$udp_rule" | grep -oE 'bytes [0-9]+' | awk '{print $2}' || echo "0")
            mb=$(echo "scale=2; $bytes/1024/1024" | bc -l 2>/dev/null || echo "0.00")
            echo "本地端口 ${port}/udp → ${rip}:${rport} (${mb} MB, ${packets} 包)"
        fi
    done
    echo "=============================================="
}

del_rule() {
    local lport=$1
    echo "[*] 删除端口 $lport 相关规则"
    local deleted=0

    for chain in prerouting postrouting forward; do
        # -a 输出带 handle
        local handles
        # 只匹配前缀 NAT-<port>- 避免 80/8080 交叉
        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
}

add_rule() {
    local lport=$1
    local remote=$2
    local rip=${remote%%:*}
    local rport=${remote##*:}

    if [[ -z "$rip" || -z "$rport" ]]; then
        echo "目标格式错误,应为 IP:PORT"
        exit 1
    fi

    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"

    # FORWARD
    $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"

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

cleanup_rules() {
    echo "[*] 清理重复规则..."
    local ports
    ports=$($NFT list chain ip $TABLE forward 2>/dev/null | \
        grep -oE 'comment "NAT-[0-9]+-tcp"' | \
        sed -n 's/comment "NAT-\([0-9]*\)-tcp"/\1/p' | sort -nu || true)

    local cleaned=0
    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 1 || $udp_count -gt 1 ]]; then
            echo "[*] 清理端口 $port 的重复规则 (TCP:$tcp_count UDP:$udp_count)"
            # 获取目标
            local rule_info rip rport
            rule_info=$($NFT list chain ip $TABLE forward 2>/dev/null | grep "comment \"NAT-${port}-tcp\"" | head -1)
            rip=$(echo "$rule_info" | grep -oE 'ip daddr [0-9.]+' | awk '{print $3}')
            rport=$(echo "$rule_info" | grep -oE 'dport [0-9]+' | awk '{print $2}')
            if [[ -n "$rip" && -n "$rport" ]]; then
                del_rule "$port"
                add_rule "$port" "$rip:$rport"
                cleaned=$((cleaned+1))
            fi
        fi
    done

    if [[ $cleaned -eq 0 ]]; then
        echo "[*] 没有发现重复规则"
    else
        echo "[+] 清理了 $cleaned 个端口的重复规则"
    fi

    show_rules
}

list_all() {
    echo "============== 详细规则列表 =============="
    echo ""
    echo "--- PREROUTING 链 ---"
    $NFT list chain ip $TABLE prerouting 2>/dev/null | grep -E "(dnat|comment)" || echo "无规则"
    echo ""
    echo "--- POSTROUTING 链 ---"
    $NFT list chain ip $TABLE postrouting 2>/dev/null | grep -E "(masquerade|comment)" || echo "无规则"
    echo ""
    echo "--- FORWARD 链 ---"
    $NFT list chain ip $TABLE forward 2>/dev/null | grep -E "(counter|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                          显示当前 NAT 转发情况
  $0 add <LOCAL_PORT> <IP:PORT>    添加 NAT 转发规则
  $0 del <LOCAL_PORT>              删除 NAT 转发规则
  $0 cleanup                       清理重复规则
  $0 list-all                      显示所有规则详情
  $0 reset                         重置所有规则

示例:
  $0 add 8080 192.168.1.100:80     # 将本地8080转发到192.168.1.100:80
  $0 del 8080                      # 删除8080端口的转发规则
EOF
}

init_table

case "$1" in
    show)
        show_rules
        ;;
    add)
        if [[ $# -ne 3 ]]; then
            echo "错误:参数不足"
            show_help
            exit 1
        fi
        add_rule "$2" "$3"
        ;;
    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

使用上非常简单,假设脚本名为 add-nat.sh,赋予可执行权限:

chmod +x add-nat.sh

然后执行以下命令:

# 添加转发规则,将本地 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

留言

发表留言