From 691ae99451df7d128fbf639731026ce1c7d9a098 Mon Sep 17 00:00:00 2001 From: kevin Date: Mon, 6 Jul 2026 14:53:30 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20NAT=20=E6=A3=80=E6=B5=8B=E4=B8=8E?= =?UTF-8?q?=E5=AE=89=E8=A3=85=E8=84=9A=E6=9C=AC=E9=80=82=E9=85=8D=E6=97=A0?= =?UTF-8?q?=20iptables=20=E7=9A=84=E7=B3=BB=E7=BB=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - diag_linux: 用 findExecutable 在 /usr/sbin /sbin 等常见路径查找 iptables/nft,弥补 systemd 服务 PATH 不含这些目录 - diag_linux: iptables 不存在时回退 nft list ruleset 检测 masquerade,两者均无时返回明确安装提示 - diag_linux: 区分"未安装"与"权限不足",MASQUERADE 检测说明更精确 - install_linux.sh: NAT 段加 NAT_TOOL 选择,优先 iptables 回退 nft,无则提示安装而非 set -e 退出 - install_linux.sh: 新增 nftables 配置分支(lmvpn_nat 表,postrouting masquerade + forward accept),持久化到 /etc/nftables.d/lmvpn.nft --- install_linux.sh | 71 +++++++++++++++++++++++++++----------- internal/vpn/diag_linux.go | 62 ++++++++++++++++++++++++++------- 2 files changed, 99 insertions(+), 34 deletions(-) diff --git a/install_linux.sh b/install_linux.sh index d46d607..99754e3 100755 --- a/install_linux.sh +++ b/install_linux.sh @@ -52,32 +52,61 @@ echo ">>> 配置 iptables NAT 与转发规则..." # 自动检测默认路由出口网卡 WAN_IFACE=$(ip route show default 2>/dev/null | awk '{print $5; exit}') if [ -z "$WAN_IFACE" ]; then - echo "警告: 未能自动检测出口网卡,跳过 iptables 配置,请手动设置" + echo "警告: 未能自动检测出口网卡,跳过 NAT 配置,请手动设置" else echo "出口网卡: $WAN_IFACE" - # 幂等:先删除旧规则(忽略错误),再添加 - # NAT: VPN 子网经出口网卡做源地址转换 - iptables -t nat -D POSTROUTING -s "$VPN_SUBNET" -o "$WAN_IFACE" -j MASQUERADE 2>/dev/null || true - iptables -t nat -A POSTROUTING -s "$VPN_SUBNET" -o "$WAN_IFACE" -j MASQUERADE + # 选择可用的防火墙前端:优先 iptables,回退 nft + NAT_TOOL="" + if command -v iptables >/dev/null 2>&1; then + NAT_TOOL="iptables" + elif command -v nft >/dev/null 2>&1; then + NAT_TOOL="nft" + fi - # FORWARD: 放行 VPN 子网进出 - iptables -D FORWARD -s "$VPN_SUBNET" -j ACCEPT 2>/dev/null || true - iptables -D FORWARD -d "$VPN_SUBNET" -j ACCEPT 2>/dev/null || true - iptables -A FORWARD -s "$VPN_SUBNET" -j ACCEPT - iptables -A FORWARD -d "$VPN_SUBNET" -j ACCEPT + if [ -z "$NAT_TOOL" ]; then + echo "警告: 未找到 iptables 或 nft,跳过 NAT 配置" + echo " 客户端将无法上外网。请安装: apt install iptables iptables-persistent" + echo " 安装后重新执行本脚本即可自动配置" + elif [ "$NAT_TOOL" = "iptables" ]; then + echo "使用 iptables 配置 NAT..." + # 幂等:先删除旧规则(忽略错误),再添加 + iptables -t nat -D POSTROUTING -s "$VPN_SUBNET" -o "$WAN_IFACE" -j MASQUERADE 2>/dev/null || true + iptables -t nat -A POSTROUTING -s "$VPN_SUBNET" -o "$WAN_IFACE" -j MASQUERADE - # 持久化 iptables 规则 - if command -v netfilter-persistent >/dev/null 2>&1; then - netfilter-persistent save - echo "iptables 规则已通过 netfilter-persistent 持久化" - elif command -v iptables-save >/dev/null 2>&1; then - mkdir -p /etc/iptables - iptables-save > /etc/iptables/rules.v4 - echo "iptables 规则已写入 /etc/iptables/rules.v4" - echo "注意: 重启后规则持久化需配合 iptables-persistent 包,建议安装: apt install iptables-persistent" - else - echo "警告: 未找到 iptables-save,规则仅在本次运行有效,请手动持久化" + iptables -D FORWARD -s "$VPN_SUBNET" -j ACCEPT 2>/dev/null || true + iptables -D FORWARD -d "$VPN_SUBNET" -j ACCEPT 2>/dev/null || true + iptables -A FORWARD -s "$VPN_SUBNET" -j ACCEPT + iptables -A FORWARD -d "$VPN_SUBNET" -j ACCEPT + + # 持久化 iptables 规则 + if command -v netfilter-persistent >/dev/null 2>&1; then + netfilter-persistent save + echo "iptables 规则已通过 netfilter-persistent 持久化" + elif command -v iptables-save >/dev/null 2>&1; then + mkdir -p /etc/iptables + iptables-save > /etc/iptables/rules.v4 + echo "iptables 规则已写入 /etc/iptables/rules.v4" + echo "注意: 重启后规则持久化需配合 iptables-persistent 包,建议安装: apt install iptables-persistent" + else + echo "警告: 未找到 iptables-save,规则仅在本次运行有效,请手动持久化" + fi + elif [ "$NAT_TOOL" = "nft" ]; then + echo "使用 nft 配置 NAT..." + # 幂等:先删除旧表(忽略错误),再创建 + nft delete table inet lmvpn_nat 2>/dev/null || true + nft add table inet lmvpn_nat + nft 'add chain inet lmvpn_nat postrouting { type nat hook postrouting priority 100 ; }' + nft add rule inet lmvpn_nat postrouting oifname "$WAN_IFACE" ip saddr "$VPN_SUBNET" masquerade + nft 'add chain inet lmvpn_nat forward { type filter hook forward priority 0 ; policy accept ; }' + nft add rule inet lmvpn_nat forward ip saddr "$VPN_SUBNET" accept + nft add rule inet lmvpn_nat forward ip daddr "$VPN_SUBNET" accept + + # 持久化 nft 规则 + mkdir -p /etc/nftables.d + nft list ruleset > /etc/nftables.d/lmvpn.nft + echo "nft 规则已写入 /etc/nftables.d/lmvpn.nft" + echo "注意: 需确保系统启动时加载 nftables 规则,编辑 /etc/nftables.conf 添加: include \"/etc/nftables.d/lmvpn.nft\"" fi fi diff --git a/internal/vpn/diag_linux.go b/internal/vpn/diag_linux.go index da2b28e..e16895f 100644 --- a/internal/vpn/diag_linux.go +++ b/internal/vpn/diag_linux.go @@ -21,13 +21,9 @@ func fillPlatformDiag(r *DiagResult) { r.IPForwardNote = "未开启,执行: sysctl -w net.ipv4.ip_forward=1" } - m := checkMasquerade() + m, note := checkMasquerade() r.Masquerade = m - if m == nil { - r.MasqueradeNote = "iptables 未安装或不可执行" - } else if !*m { - r.MasqueradeNote = "未检测到 MASQUERADE 规则,客户端无法出网" - } + r.MasqueradeNote = note } func ptrBool(b bool) *bool { return &b } @@ -63,12 +59,52 @@ func readIPForward() *bool { return &v } -func checkMasquerade() *bool { - cmd := exec.Command("iptables", "-t", "nat", "-L", "POSTROUTING", "-n") - out, err := cmd.Output() - if err != nil { - return nil +// findExecutable 在常见路径中查找可执行文件,弥补 systemd 服务 PATH 不含 /usr/sbin、/sbin 的问题 +func findExecutable(names ...string) string { + for _, name := range names { + if p, err := exec.LookPath(name); err == nil { + return p + } + for _, dir := range []string{"/usr/sbin", "/sbin", "/usr/bin", "/bin"} { + full := dir + "/" + name + if fi, err := os.Stat(full); err == nil && !fi.IsDir() { + return full + } + } } - has := strings.Contains(string(out), "MASQUERADE") - return &has + return "" +} + +// checkMasquerade 检测 NAT masquerade 规则,优先 iptables,回退 nft +// 返回 (结果, 说明);结果为 nil 表示无法判定 +func checkMasquerade() (*bool, string) { + // 优先 iptables + iptPath := findExecutable("iptables") + if iptPath != "" { + out, err := exec.Command(iptPath, "-t", "nat", "-L", "POSTROUTING", "-n").Output() + if err != nil { + return nil, "iptables 不可执行(权限不足?),无法检测 MASQUERADE" + } + has := strings.Contains(string(out), "MASQUERADE") + if has { + return &has, "" + } + return &has, "未检测到 MASQUERADE 规则,客户端无法出网" + } + + // 回退 nft + nftPath := findExecutable("nft") + if nftPath != "" { + out, err := exec.Command(nftPath, "list", "ruleset").Output() + if err != nil { + return nil, "nft 不可执行(权限不足?),无法检测 MASQUERADE" + } + has := strings.Contains(string(out), "masquerade") + if has { + return &has, "" + } + return &has, "未检测到 masquerade 规则,客户端无法出网" + } + + return nil, "iptables 与 nft 均未安装,无法检测 NAT 规则。Debian/Ubuntu 安装: apt install iptables iptables-persistent" }