diff --git a/install_linux.sh b/install_linux.sh index 99754e3..f9f2f90 100755 --- a/install_linux.sh +++ b/install_linux.sh @@ -48,7 +48,7 @@ net.ipv4.ip_forward = 1 EOF sysctl -p /etc/sysctl.d/99-lmvpn.conf >/dev/null -echo ">>> 配置 iptables NAT 与转发规则..." +echo ">>> 配置 NAT 与转发规则..." # 自动检测默认路由出口网卡 WAN_IFACE=$(ip route show default 2>/dev/null | awk '{print $5; exit}') if [ -z "$WAN_IFACE" ]; then @@ -56,18 +56,38 @@ if [ -z "$WAN_IFACE" ]; then else echo "出口网卡: $WAN_IFACE" - # 选择可用的防火墙前端:优先 iptables,回退 nft + # 选择可用的防火墙前端:优先 nft(原生,Debian 12+ 的 iptables 是 nft 包装器,操作原生 nft nat 表会不兼容) NAT_TOOL="" - if command -v iptables >/dev/null 2>&1; then - NAT_TOOL="iptables" - elif command -v nft >/dev/null 2>&1; then + if command -v nft >/dev/null 2>&1; then NAT_TOOL="nft" + elif command -v iptables >/dev/null 2>&1; then + NAT_TOOL="iptables" fi if [ -z "$NAT_TOOL" ]; then - echo "警告: 未找到 iptables 或 nft,跳过 NAT 配置" - echo " 客户端将无法上外网。请安装: apt install iptables iptables-persistent" + echo "警告: 未找到 nft 或 iptables,跳过 NAT 配置" + echo " 客户端将无法上外网。请安装: apt install nftables" echo " 安装后重新执行本脚本即可自动配置" + 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 + # 确保主配置文件 include 该目录 + if [ -f /etc/nftables.conf ] && ! grep -q 'include "/etc/nftables.d' /etc/nftables.conf; then + echo 'include "/etc/nftables.d/*.nft"' >> /etc/nftables.conf + fi + systemctl enable nftables 2>/dev/null || true + echo "nft 规则已写入 /etc/nftables.d/lmvpn.nft" elif [ "$NAT_TOOL" = "iptables" ]; then echo "使用 iptables 配置 NAT..." # 幂等:先删除旧规则(忽略错误),再添加 @@ -91,22 +111,6 @@ else 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 e16895f..f050b66 100644 --- a/internal/vpn/diag_linux.go +++ b/internal/vpn/diag_linux.go @@ -75,14 +75,32 @@ func findExecutable(names ...string) string { return "" } -// checkMasquerade 检测 NAT masquerade 规则,优先 iptables,回退 nft +// checkMasquerade 检测 NAT masquerade 规则,优先 nft(原生、无兼容问题),回退 iptables // 返回 (结果, 说明);结果为 nil 表示无法判定 func checkMasquerade() (*bool, string) { - // 优先 iptables + // 优先 nft:Debian 12+ 的 iptables 是 nft 包装器,操作原生 nft nat 表会 "incompatible" + nftPath := findExecutable("nft") + if nftPath != "" { + out, err := exec.Command(nftPath, "list", "ruleset").Output() + if err == nil { + has := strings.Contains(string(out), "masquerade") + if has { + return &has, "" + } + return &has, "未检测到 masquerade 规则,客户端无法出网" + } + // nft 存在但执行失败(权限不足等),仍回退 iptables 尝试 + } + + // 回退 iptables(老系统或 nft 不可用时) iptPath := findExecutable("iptables") if iptPath != "" { out, err := exec.Command(iptPath, "-t", "nat", "-L", "POSTROUTING", "-n").Output() if err != nil { + // iptables-nft 与原生 nft 表不兼容时,若 nft 也读不到则判定为无法检测 + if nftPath != "" { + return nil, "iptables 与原生 nft 表不兼容且 nft 不可执行,无法检测 MASQUERADE" + } return nil, "iptables 不可执行(权限不足?),无法检测 MASQUERADE" } has := strings.Contains(string(out), "MASQUERADE") @@ -92,19 +110,5 @@ func checkMasquerade() (*bool, string) { 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" + return nil, "iptables 与 nft 均未安装,无法检测 NAT 规则。Debian/Ubuntu 安装: apt install nftables" }