feat: 支持 IPv6 双栈地址派发

- model: VpnSetting 新增 Subnet6 字段,VpnReservation 新增 IPAddress6
- alloc: 处理 /64 等大子网 cidr.AddressCount 溢出,回退 65536 扫描上限
- protocol: initMessage 新增 ip6/prefix6/server_ip6(omitempty 向后兼容)
- service: 双分配器 alloc4+alloc6,ApplySettings 配置双 IP 与双路由,
  Allocate 返回 v4+v6 一对地址
- switch: SwitchConn 新增 AssignedIP6(),Register/Unregister 注册双 key,
  反欺骗按 IP 版本分别校验 v4/v6 源地址
- tunnel: tunnelConn 新增 assignedIP6,init 消息填充 v6 字段
- handler: 新增 validateSubnet6(/64~/126),settings/预留 API 全面支持 v6
- diag: 新增 IPv6 forwarding 与 NAT66 masquerade 检测
- install_linux.sh: 新增 VPN_SUBNET6、ipv6 forwarding、nft/ip6tables NAT66
- 前端: v6 子网输入框、v6 预留、v6 诊断卡片、客户端列表 v6 列
- 文档: init 消息、IP 分配、子网约束、TUN 配置、NAT、反欺骗全部更新
This commit is contained in:
2026-07-07 11:38:21 +08:00
parent 808b991483
commit a770862c7c
13 changed files with 561 additions and 145 deletions
+51
View File
@@ -24,6 +24,16 @@ func fillPlatformDiag(r *DiagResult) {
m, note := checkMasquerade()
r.Masquerade = m
r.MasqueradeNote = note
v6 := readIP6Forward()
r.IP6Forward = v6
if v6 == nil || !*v6 {
r.IP6ForwardNote = "未开启,执行: sysctl -w net.ipv6.conf.all.forwarding=1"
}
m6, note6 := checkMasquerade6()
r.Masquerade6 = m6
r.Masquerade6Note = note6
}
func ptrBool(b bool) *bool { return &b }
@@ -112,3 +122,44 @@ func checkMasquerade() (*bool, string) {
return nil, "iptables 与 nft 均未安装,无法检测 NAT 规则。Debian/Ubuntu 安装: apt install nftables"
}
func readIP6Forward() *bool {
data, err := os.ReadFile("/proc/sys/net/ipv6/conf/all/forwarding")
if err != nil {
return nil
}
v := strings.TrimSpace(string(data)) == "1"
return &v
}
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), ""
}
return ptrBool(false), "未检测到 IPv6 masquerade 规则,IPv6 客户端无法出网"
}
}
ip6tPath := findExecutable("ip6tables")
if ip6tPath != "" {
out, err := exec.Command(ip6tPath, "-t", "nat", "-L", "POSTROUTING", "-n").Output()
if err != nil {
if nftPath != "" {
return nil, "ip6tables 与原生 nft 表不兼容且 nft 不可执行,无法检测 IPv6 MASQUERADE"
}
return nil, "ip6tables 不可执行(权限不足?),无法检测 IPv6 MASQUERADE"
}
has := strings.Contains(string(out), "MASQUERADE")
if has {
return &has, ""
}
return &has, "未检测到 IPv6 MASQUERADE 规则,IPv6 客户端无法出网"
}
return nil, "ip6tables 与 nft 均未安装,无法检测 IPv6 NAT 规则"
}