feat: 新增 IPv6 双栈支持
- 协议: InitMessage 增加 ip6/prefix6/server_ip6 字段(omitempty,向后兼容) - TUN: Device 接口新增 ConfigureIPv6, macOS 用 ifconfig inet6, Linux 用 ip -6 addr add - 路由: full 模式自动添加 ::/1+8000::/1, v6 服务器旁路 /128, resolveHosts 双栈解析 - 会话: setupTUN 解析 v6 字段并配置 TUN+路由, connectOnce 传 v6 给 stats/日志 - 统计: SetConnected(ip,ip6), Snapshot.AssignedIP6 - DB: connection_logs 增加 assigned_ip6 列 + migrateV3 幂等迁移 - UI: 状态卡片新增独立 IPv6 行, 始终可见(未获取显示 IPv6: —) - 文档: 同步 client-development.md 至服务端 IPv6 版本
This commit is contained in:
+24
-14
@@ -10,11 +10,11 @@ import (
|
||||
type State string
|
||||
|
||||
const (
|
||||
StateDisconnected State = "disconnected"
|
||||
StateConnecting State = "connecting"
|
||||
StateConnected State = "connected"
|
||||
StateReconnecting State = "reconnecting"
|
||||
StateError State = "error"
|
||||
StateDisconnected State = "disconnected"
|
||||
StateConnecting State = "connecting"
|
||||
StateConnected State = "connected"
|
||||
StateReconnecting State = "reconnecting"
|
||||
StateError State = "error"
|
||||
)
|
||||
|
||||
// Stats holds live session statistics. Counters are atomic for
|
||||
@@ -23,8 +23,9 @@ type Stats struct {
|
||||
RxBytes atomic.Int64
|
||||
TxBytes atomic.Int64
|
||||
ConnectedAt atomic.Int64 // unix timestamp, 0 = not connected
|
||||
state atomic.Value // State
|
||||
assignedIP atomic.Value // string
|
||||
state atomic.Value // State
|
||||
assignedIP atomic.Value // string (IPv4)
|
||||
assignedIP6 atomic.Value // string (IPv6, may be empty)
|
||||
}
|
||||
|
||||
// New creates a Stats instance initialised to the disconnected state.
|
||||
@@ -32,6 +33,7 @@ func New() *Stats {
|
||||
s := &Stats{}
|
||||
s.state.Store(StateDisconnected)
|
||||
s.assignedIP.Store("")
|
||||
s.assignedIP6.Store("")
|
||||
return s
|
||||
}
|
||||
|
||||
@@ -41,10 +43,12 @@ func (s *Stats) SetState(st State) { s.state.Store(st) }
|
||||
// State returns the current state.
|
||||
func (s *Stats) State() State { return s.state.Load().(State) }
|
||||
|
||||
// SetConnected marks the session as connected, recording the time and IP.
|
||||
func (s *Stats) SetConnected(ip string) {
|
||||
// SetConnected marks the session as connected, recording the time and
|
||||
// assigned IP addresses. ip6 may be empty for an IPv4-only server.
|
||||
func (s *Stats) SetConnected(ip, ip6 string) {
|
||||
s.ConnectedAt.Store(time.Now().Unix())
|
||||
s.assignedIP.Store(ip)
|
||||
s.assignedIP6.Store(ip6)
|
||||
s.state.Store(StateConnected)
|
||||
}
|
||||
|
||||
@@ -52,18 +56,23 @@ func (s *Stats) SetConnected(ip string) {
|
||||
func (s *Stats) SetDisconnected() {
|
||||
s.ConnectedAt.Store(0)
|
||||
s.assignedIP.Store("")
|
||||
s.assignedIP6.Store("")
|
||||
s.state.Store(StateDisconnected)
|
||||
}
|
||||
|
||||
// AssignedIP returns the server-assigned tunnel IP.
|
||||
// AssignedIP returns the server-assigned tunnel IPv4.
|
||||
func (s *Stats) AssignedIP() string { return s.assignedIP.Load().(string) }
|
||||
|
||||
// AssignedIP6 returns the server-assigned tunnel IPv6 (may be empty).
|
||||
func (s *Stats) AssignedIP6() string { return s.assignedIP6.Load().(string) }
|
||||
|
||||
// Snapshot returns a point-in-time copy of all counters.
|
||||
type Snapshot struct {
|
||||
RxBytes int64
|
||||
TxBytes int64
|
||||
ConnectedAt time.Time
|
||||
AssignedIP string
|
||||
AssignedIP6 string
|
||||
State State
|
||||
Uptime time.Duration
|
||||
}
|
||||
@@ -71,10 +80,11 @@ type Snapshot struct {
|
||||
// Snapshot returns a point-in-time copy of the statistics.
|
||||
func (s *Stats) Snapshot() Snapshot {
|
||||
snap := Snapshot{
|
||||
RxBytes: s.RxBytes.Load(),
|
||||
TxBytes: s.TxBytes.Load(),
|
||||
AssignedIP: s.AssignedIP(),
|
||||
State: s.State(),
|
||||
RxBytes: s.RxBytes.Load(),
|
||||
TxBytes: s.TxBytes.Load(),
|
||||
AssignedIP: s.AssignedIP(),
|
||||
AssignedIP6: s.AssignedIP6(),
|
||||
State: s.State(),
|
||||
}
|
||||
ts := s.ConnectedAt.Load()
|
||||
if ts > 0 {
|
||||
|
||||
Reference in New Issue
Block a user