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:
+30
-15
@@ -28,18 +28,20 @@ var (
|
||||
)
|
||||
|
||||
type tunnelConn struct {
|
||||
conn *websocket.Conn
|
||||
user *model.User
|
||||
svc *VpnService
|
||||
assignedIP net.IP
|
||||
connectedAt time.Time
|
||||
writeMu sync.Mutex
|
||||
ready atomic.Bool
|
||||
rxBytes atomic.Int64
|
||||
txBytes atomic.Int64
|
||||
conn *websocket.Conn
|
||||
user *model.User
|
||||
svc *VpnService
|
||||
assignedIP net.IP
|
||||
assignedIP6 net.IP
|
||||
connectedAt time.Time
|
||||
writeMu sync.Mutex
|
||||
ready atomic.Bool
|
||||
rxBytes atomic.Int64
|
||||
txBytes atomic.Int64
|
||||
}
|
||||
|
||||
func (c *tunnelConn) AssignedIP() net.IP { return c.assignedIP }
|
||||
func (c *tunnelConn) AssignedIP6() net.IP { return c.assignedIP6 }
|
||||
|
||||
func (c *tunnelConn) WritePacket(data []byte) error {
|
||||
if !c.ready.Load() || len(data) == 0 {
|
||||
@@ -73,11 +75,15 @@ func (c *tunnelConn) close() {
|
||||
}
|
||||
|
||||
func (c *tunnelConn) info() ClientInfo {
|
||||
return ClientInfo{
|
||||
ci := ClientInfo{
|
||||
Username: c.user.Username,
|
||||
IP: c.assignedIP.String(),
|
||||
ConnectedAt: c.connectedAt.Format("2006-01-02 15:04:05"),
|
||||
}
|
||||
if c.assignedIP6 != nil {
|
||||
ci.IP6 = c.assignedIP6.String()
|
||||
}
|
||||
return ci
|
||||
}
|
||||
|
||||
func runTunnel(conn *websocket.Conn, user *model.User) {
|
||||
@@ -106,7 +112,7 @@ func runTunnel(conn *websocket.Conn, user *model.User) {
|
||||
activeConnsMu.Unlock()
|
||||
}()
|
||||
|
||||
ip, err := VPN.Allocate(user)
|
||||
ip4, ip6, err := VPN.Allocate(user)
|
||||
if err != nil {
|
||||
_ = sendJSON(conn, controlMessage{Type: "error", Message: "分配 IP 失败: " + err.Error()})
|
||||
return
|
||||
@@ -116,7 +122,8 @@ func runTunnel(conn *websocket.Conn, user *model.User) {
|
||||
conn: conn,
|
||||
user: user,
|
||||
svc: VPN,
|
||||
assignedIP: ip,
|
||||
assignedIP: ip4,
|
||||
assignedIP6: ip6,
|
||||
connectedAt: time.Now(),
|
||||
}
|
||||
|
||||
@@ -126,17 +133,25 @@ func runTunnel(conn *websocket.Conn, user *model.User) {
|
||||
settings := VPN.Settings()
|
||||
initMsg := initMessage{
|
||||
Type: "init",
|
||||
IP: ip.String(),
|
||||
IP: ip4.String(),
|
||||
Prefix: VPN.Prefix(),
|
||||
MTU: settings.MTU,
|
||||
ServerIP: VPN.ServerIP().String(),
|
||||
}
|
||||
if ip6 != nil {
|
||||
initMsg.IP6 = ip6.String()
|
||||
initMsg.Prefix6 = VPN.Prefix6()
|
||||
initMsg.ServerIP6 = VPN.ServerIP6().String()
|
||||
}
|
||||
if err := tc.writeControl(initMsg); err != nil {
|
||||
log.Printf("用户 %s 发送 init 失败: %v", user.Username, err)
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("用户 %s 已连接,分配 IP %s", user.Username, ip.String())
|
||||
log.Printf("用户 %s 已连接,分配 IP %s", user.Username, ip4.String())
|
||||
if ip6 != nil {
|
||||
log.Printf(" IPv6: %s", ip6.String())
|
||||
}
|
||||
|
||||
conn.SetReadLimit(maxMessageSize)
|
||||
conn.SetReadDeadline(time.Now().Add(readyTimeout))
|
||||
@@ -175,7 +190,7 @@ func runTunnel(conn *websocket.Conn, user *model.User) {
|
||||
if msg.Type == "ready" && !tc.ready.Load() {
|
||||
tc.ready.Store(true)
|
||||
conn.SetReadDeadline(time.Now().Add(readTimeout))
|
||||
log.Printf("用户 %s 就绪 (IP %s)", user.Username, ip.String())
|
||||
log.Printf("用户 %s 就绪 (IP %s)", user.Username, ip4.String())
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user