fix: 网卡更换后 NAT/诊断规则绑定失效网卡导致客户端无法上网 #1
+2
-1
@@ -53,7 +53,8 @@ echo ">>> 安装 systemd 服务..."
|
||||
cat > /etc/systemd/system/lmvpn.service << 'EOF'
|
||||
[Unit]
|
||||
Description=LMVPN Server
|
||||
After=network.target
|
||||
Wants=network-online.target
|
||||
After=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
package vpn
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
@@ -103,10 +104,18 @@ func checkMasquerade() (*bool, string) {
|
||||
out, err := exec.Command(nftPath, "list", "ruleset").Output()
|
||||
if err == nil {
|
||||
has := strings.Contains(string(out), "masquerade")
|
||||
if has {
|
||||
return &has, ""
|
||||
if !has {
|
||||
return &has, "未检测到 masquerade 规则,客户端无法出网"
|
||||
}
|
||||
return &has, "未检测到 masquerade 规则,客户端无法出网"
|
||||
// masquerade 规则存在,但若绑定到已不存在的网卡名(如更换网卡后),
|
||||
// 规则永远不会命中,客户端仍然无法上网——需要单独校验。
|
||||
if iface := masqueradeIface(string(out)); iface != "" {
|
||||
if err := exec.Command("ip", "link", "show", iface).Run(); err != nil {
|
||||
return ptrBool(false), fmt.Sprintf(
|
||||
"masquerade 规则绑定到不存在的网卡 %s(网卡已更换?),请在管理后台保存 VPN 设置或重启服务以重新配置防火墙", iface)
|
||||
}
|
||||
}
|
||||
return &has, ""
|
||||
}
|
||||
// nft 存在但执行失败(权限不足等),仍回退 iptables 尝试
|
||||
}
|
||||
@@ -141,16 +150,39 @@ func readIP6Forward() *bool {
|
||||
return &v
|
||||
}
|
||||
|
||||
// masqueradeIface 从 nft ruleset 文本中提取 masquerade 规则绑定的出口网卡名;
|
||||
// 若规则未绑定网卡(对所有出口生效)则返回空字符串。
|
||||
func masqueradeIface(ruleset string) string {
|
||||
for _, line := range strings.Split(ruleset, "\n") {
|
||||
if !strings.Contains(line, "masquerade") {
|
||||
continue
|
||||
}
|
||||
if i := strings.Index(line, `oifname "`); i >= 0 {
|
||||
rest := line[i+len(`oifname "`):]
|
||||
if j := strings.Index(rest, `"`); j > 0 {
|
||||
return rest[:j]
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func checkMasquerade6() (*bool, string) {
|
||||
nftPath := findExecutable("nft")
|
||||
if nftPath != "" {
|
||||
out, err := exec.Command(nftPath, "list", "ruleset").Output()
|
||||
if err == nil {
|
||||
s := string(out)
|
||||
if strings.Contains(s, "ip6 saddr") && strings.Contains(s, "masquerade") {
|
||||
return ptrBool(true), ""
|
||||
if !(strings.Contains(s, "ip6 saddr") && strings.Contains(s, "masquerade")) {
|
||||
return ptrBool(false), "未检测到 IPv6 masquerade 规则,IPv6 客户端无法出网"
|
||||
}
|
||||
return ptrBool(false), "未检测到 IPv6 masquerade 规则,IPv6 客户端无法出网"
|
||||
if iface := masqueradeIface(s); iface != "" {
|
||||
if err := exec.Command("ip", "link", "show", iface).Run(); err != nil {
|
||||
return ptrBool(false), fmt.Sprintf(
|
||||
"masquerade 规则绑定到不存在的网卡 %s(网卡已更换?),请在管理后台保存 VPN 设置或重启服务以重新配置防火墙", iface)
|
||||
}
|
||||
}
|
||||
return ptrBool(true), ""
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"log"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// configureFirewall dynamically configures NAT masquerade, forward accept rules,
|
||||
@@ -26,16 +27,21 @@ func configureFirewall(ipNet, ipNet6 string, tunName string) {
|
||||
configureUFWForward(ipNet, ipNet6)
|
||||
}
|
||||
|
||||
// detectWANInterface 通过默认路由确定出口网卡。
|
||||
// 开机初期(如 systemd 尚未完成网络在线、DHCP 未取得租约)可能没有默认路由,
|
||||
// 此时短暂重试,避免在网卡变更/开机竞态下留下绑定旧网卡名的陈旧防火墙规则。
|
||||
func detectWANInterface() string {
|
||||
out, err := exec.Command("ip", "route", "show", "default").Output()
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
fields := strings.Fields(string(out))
|
||||
for i, f := range fields {
|
||||
if f == "dev" && i+1 < len(fields) {
|
||||
return fields[i+1]
|
||||
for attempt := 0; attempt < 10; attempt++ {
|
||||
out, err := exec.Command("ip", "route", "show", "default").Output()
|
||||
if err == nil {
|
||||
fields := strings.Fields(string(out))
|
||||
for i, f := range fields {
|
||||
if f == "dev" && i+1 < len(fields) {
|
||||
return fields[i+1]
|
||||
}
|
||||
}
|
||||
}
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user