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:
@@ -49,6 +49,9 @@ func (m *AllocationManager) Allocate(userID uint) (net.IP, error) {
|
||||
}
|
||||
|
||||
count := cidr.AddressCount(m.net)
|
||||
if count == 0 && m.net.IP.To4() == nil {
|
||||
count = 65536
|
||||
}
|
||||
maxIndex := int(count - 1)
|
||||
for i := 2; i < maxIndex; i++ {
|
||||
ip, err := cidr.Host(m.net, i)
|
||||
@@ -105,6 +108,9 @@ func (m *AllocationManager) UsedCount() int {
|
||||
|
||||
func (m *AllocationManager) Capacity() uint64 {
|
||||
count := cidr.AddressCount(m.net)
|
||||
if count == 0 && m.net.IP.To4() == nil {
|
||||
return 65533
|
||||
}
|
||||
if count < 3 {
|
||||
return 0
|
||||
}
|
||||
|
||||
+15
-11
@@ -6,17 +6,21 @@ import (
|
||||
)
|
||||
|
||||
type DiagResult struct {
|
||||
Platform string `json:"platform"`
|
||||
IsRoot bool `json:"is_root"`
|
||||
HasCapNetAdmin *bool `json:"has_cap_net_admin"`
|
||||
CapNetAdminNote string `json:"cap_net_admin_note,omitempty"`
|
||||
IPForward *bool `json:"ip_forward"`
|
||||
IPForwardNote string `json:"ip_forward_note,omitempty"`
|
||||
Masquerade *bool `json:"masquerade"`
|
||||
MasqueradeNote string `json:"masquerade_note,omitempty"`
|
||||
TUNCreate string `json:"tun_create"`
|
||||
TUNRunning bool `json:"tun_running"`
|
||||
TUNName string `json:"tun_name,omitempty"`
|
||||
Platform string `json:"platform"`
|
||||
IsRoot bool `json:"is_root"`
|
||||
HasCapNetAdmin *bool `json:"has_cap_net_admin"`
|
||||
CapNetAdminNote string `json:"cap_net_admin_note,omitempty"`
|
||||
IPForward *bool `json:"ip_forward"`
|
||||
IPForwardNote string `json:"ip_forward_note,omitempty"`
|
||||
Masquerade *bool `json:"masquerade"`
|
||||
MasqueradeNote string `json:"masquerade_note,omitempty"`
|
||||
IP6Forward *bool `json:"ip6_forward"`
|
||||
IP6ForwardNote string `json:"ip6_forward_note,omitempty"`
|
||||
Masquerade6 *bool `json:"masquerade6"`
|
||||
Masquerade6Note string `json:"masquerade6_note,omitempty"`
|
||||
TUNCreate string `json:"tun_create"`
|
||||
TUNRunning bool `json:"tun_running"`
|
||||
TUNName string `json:"tun_name,omitempty"`
|
||||
}
|
||||
|
||||
func Diag(svc *VpnService) DiagResult {
|
||||
|
||||
@@ -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 规则"
|
||||
}
|
||||
|
||||
@@ -9,4 +9,8 @@ func fillPlatformDiag(r *DiagResult) {
|
||||
r.IPForwardNote = "仅 Linux 适用"
|
||||
r.Masquerade = nil
|
||||
r.MasqueradeNote = "仅 Linux 适用"
|
||||
r.IP6Forward = nil
|
||||
r.IP6ForwardNote = "仅 Linux 适用"
|
||||
r.Masquerade6 = nil
|
||||
r.Masquerade6Note = "仅 Linux 适用"
|
||||
}
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
package vpn
|
||||
|
||||
type initMessage struct {
|
||||
Type string `json:"type"`
|
||||
IP string `json:"ip"`
|
||||
Prefix int `json:"prefix"`
|
||||
MTU int `json:"mtu"`
|
||||
ServerIP string `json:"server_ip"`
|
||||
Type string `json:"type"`
|
||||
IP string `json:"ip"`
|
||||
Prefix int `json:"prefix"`
|
||||
MTU int `json:"mtu"`
|
||||
ServerIP string `json:"server_ip"`
|
||||
IP6 string `json:"ip6,omitempty"`
|
||||
Prefix6 int `json:"prefix6,omitempty"`
|
||||
ServerIP6 string `json:"server_ip6,omitempty"`
|
||||
}
|
||||
|
||||
type controlMessage struct {
|
||||
|
||||
+97
-12
@@ -19,6 +19,10 @@ type VpnService struct {
|
||||
serverIP net.IP
|
||||
prefix int
|
||||
alloc *AllocationManager
|
||||
net6 *net.IPNet
|
||||
serverIP6 net.IP
|
||||
prefix6 int
|
||||
alloc6 *AllocationManager
|
||||
switchx *PacketSwitch
|
||||
tun *TUNInterface
|
||||
tunDone chan struct{}
|
||||
@@ -57,7 +61,7 @@ func (s *VpnService) parseNet(subnet string) (*net.IPNet, net.IP, int, error) {
|
||||
return ipNet, serverIP, ones, nil
|
||||
}
|
||||
|
||||
func (s *VpnService) ApplySettings(settings model.VpnSetting, reservations map[uint]string) error {
|
||||
func (s *VpnService) ApplySettings(settings model.VpnSetting, reservations4, reservations6 map[uint]string) error {
|
||||
s.mu.Lock()
|
||||
if s.running {
|
||||
s.mu.Unlock()
|
||||
@@ -76,6 +80,17 @@ func (s *VpnService) ApplySettings(settings model.VpnSetting, reservations map[u
|
||||
return err
|
||||
}
|
||||
|
||||
var ipNet6 *net.IPNet
|
||||
var serverIP6 net.IP
|
||||
var prefix6 int
|
||||
var alloc6 *AllocationManager
|
||||
if settings.Subnet6 != "" {
|
||||
ipNet6, serverIP6, prefix6, err = s.parseNet(settings.Subnet6)
|
||||
if err != nil {
|
||||
return fmt.Errorf("IPv6 子网错误: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
tun, err := CreateTUN(settings.InterfaceName)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -84,11 +99,22 @@ func (s *VpnService) ApplySettings(settings model.VpnSetting, reservations map[u
|
||||
if settings.DoLocalIPConfig {
|
||||
if err := tun.Configure(serverIP, prefix, nil); err != nil {
|
||||
_ = tun.Close()
|
||||
return fmt.Errorf("配置 TUN 失败: %w", err)
|
||||
return fmt.Errorf("配置 TUN IPv4 失败: %w", err)
|
||||
}
|
||||
}
|
||||
if err := tun.AddSubnetRoute(ipNet); err != nil {
|
||||
log.Printf("警告: 添加子网路由失败: %v", err)
|
||||
log.Printf("警告: 添加 IPv4 子网路由失败: %v", err)
|
||||
}
|
||||
if ipNet6 != nil {
|
||||
if settings.DoLocalIPConfig {
|
||||
if err := tun.Configure(serverIP6, prefix6, nil); err != nil {
|
||||
log.Printf("警告: 配置 TUN IPv6 失败: %v", err)
|
||||
}
|
||||
}
|
||||
if err := tun.AddSubnetRoute(ipNet6); err != nil {
|
||||
log.Printf("警告: 添加 IPv6 子网路由失败: %v", err)
|
||||
}
|
||||
alloc6 = NewAllocationManager(ipNet6, serverIP6, reservations6)
|
||||
}
|
||||
if err := tun.SetMTU(settings.MTU); err != nil {
|
||||
log.Printf("警告: 设置 MTU 失败: %v", err)
|
||||
@@ -98,7 +124,11 @@ func (s *VpnService) ApplySettings(settings model.VpnSetting, reservations map[u
|
||||
s.net = ipNet
|
||||
s.serverIP = serverIP
|
||||
s.prefix = prefix
|
||||
s.alloc = NewAllocationManager(ipNet, serverIP, reservations)
|
||||
s.alloc = NewAllocationManager(ipNet, serverIP, reservations4)
|
||||
s.net6 = ipNet6
|
||||
s.serverIP6 = serverIP6
|
||||
s.prefix6 = prefix6
|
||||
s.alloc6 = alloc6
|
||||
s.switchx = NewPacketSwitch(settings.AllowClientToClient)
|
||||
s.tun = tun
|
||||
s.tunDone = make(chan struct{})
|
||||
@@ -106,7 +136,10 @@ func (s *VpnService) ApplySettings(settings model.VpnSetting, reservations map[u
|
||||
s.mu.Unlock()
|
||||
|
||||
go s.serveTUN()
|
||||
log.Printf("VPN 服务已启动: tun=%s subnet=%s server=%s mtu=%d", tun.Name(), ipNet.String(), serverIP.String(), settings.MTU)
|
||||
log.Printf("VPN 服务已启动: tun=%s subnet=%s server=%s", tun.Name(), ipNet.String(), serverIP.String())
|
||||
if ipNet6 != nil {
|
||||
log.Printf(" IPv6: subnet=%s server=%s", ipNet6.String(), serverIP6.String())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -162,14 +195,27 @@ func (s *VpnService) Stop() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *VpnService) Allocate(user *model.User) (net.IP, error) {
|
||||
func (s *VpnService) Allocate(user *model.User) (net.IP, net.IP, error) {
|
||||
s.mu.RLock()
|
||||
alloc := s.alloc
|
||||
alloc6 := s.alloc6
|
||||
s.mu.RUnlock()
|
||||
if alloc == nil {
|
||||
return nil, errors.New("VPN 服务未运行")
|
||||
return nil, nil, errors.New("VPN 服务未运行")
|
||||
}
|
||||
return alloc.Allocate(user.ID)
|
||||
ip4, err := alloc.Allocate(user.ID)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
var ip6 net.IP
|
||||
if alloc6 != nil {
|
||||
ip6, err = alloc6.Allocate(user.ID)
|
||||
if err != nil {
|
||||
alloc.Release(ip4)
|
||||
return ip4, nil, fmt.Errorf("IPv6 分配失败: %w", err)
|
||||
}
|
||||
}
|
||||
return ip4, ip6, nil
|
||||
}
|
||||
|
||||
func (s *VpnService) WriteToTUN(packet []byte) error {
|
||||
@@ -209,6 +255,9 @@ func (s *VpnService) unregisterClient(c *tunnelConn) {
|
||||
if s.alloc != nil {
|
||||
s.alloc.Release(c.assignedIP)
|
||||
}
|
||||
if s.alloc6 != nil && c.assignedIP6 != nil {
|
||||
s.alloc6.Release(c.assignedIP6)
|
||||
}
|
||||
s.mu.Unlock()
|
||||
}
|
||||
|
||||
@@ -224,14 +273,36 @@ func (s *VpnService) Prefix() int {
|
||||
return s.prefix
|
||||
}
|
||||
|
||||
func (s *VpnService) AllocStats() (used int, capacity uint64) {
|
||||
func (s *VpnService) ServerIP6() net.IP {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
return s.serverIP6
|
||||
}
|
||||
|
||||
func (s *VpnService) Prefix6() int {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
return s.prefix6
|
||||
}
|
||||
|
||||
func (s *VpnService) HasIPv6() bool {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
return s.alloc6 != nil
|
||||
}
|
||||
|
||||
func (s *VpnService) AllocStats() (used4 int, cap4 uint64, used6 int, cap6 uint64) {
|
||||
s.mu.RLock()
|
||||
alloc := s.alloc
|
||||
alloc6 := s.alloc6
|
||||
s.mu.RUnlock()
|
||||
if alloc == nil {
|
||||
return 0, 0
|
||||
if alloc != nil {
|
||||
used4, cap4 = alloc.UsedCount(), alloc.Capacity()
|
||||
}
|
||||
return alloc.UsedCount(), alloc.Capacity()
|
||||
if alloc6 != nil {
|
||||
used6, cap6 = alloc6.UsedCount(), alloc6.Capacity()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *VpnService) AddReservation(userID uint, ipStr string) {
|
||||
@@ -243,13 +314,26 @@ func (s *VpnService) AddReservation(userID uint, ipStr string) {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *VpnService) AddReservation6(userID uint, ipStr string) {
|
||||
s.mu.RLock()
|
||||
alloc6 := s.alloc6
|
||||
s.mu.RUnlock()
|
||||
if alloc6 != nil {
|
||||
alloc6.AddReservation(userID, ipStr)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *VpnService) RemoveReservation(userID uint) {
|
||||
s.mu.RLock()
|
||||
alloc := s.alloc
|
||||
alloc6 := s.alloc6
|
||||
s.mu.RUnlock()
|
||||
if alloc != nil {
|
||||
alloc.RemoveReservation(userID)
|
||||
}
|
||||
if alloc6 != nil {
|
||||
alloc6.RemoveReservation(userID)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *VpnService) ClientList() []ClientInfo {
|
||||
@@ -265,6 +349,7 @@ func (s *VpnService) ClientList() []ClientInfo {
|
||||
type ClientInfo struct {
|
||||
Username string `json:"username"`
|
||||
IP string `json:"ip"`
|
||||
IP6 string `json:"ip6,omitempty"`
|
||||
ConnectedAt string `json:"connected_at"`
|
||||
}
|
||||
|
||||
|
||||
+25
-8
@@ -10,6 +10,7 @@ import (
|
||||
type SwitchConn interface {
|
||||
WritePacket(data []byte) error
|
||||
AssignedIP() net.IP
|
||||
AssignedIP6() net.IP
|
||||
}
|
||||
|
||||
type ipKey [16]byte
|
||||
@@ -40,17 +41,24 @@ func (s *PacketSwitch) SetAllowClientToClient(v bool) {
|
||||
}
|
||||
|
||||
func (s *PacketSwitch) Register(c SwitchConn) {
|
||||
k := ipToKey(c.AssignedIP())
|
||||
s.mu.Lock()
|
||||
s.table[k] = c
|
||||
s.table[ipToKey(c.AssignedIP())] = c
|
||||
if ip6 := c.AssignedIP6(); ip6 != nil {
|
||||
s.table[ipToKey(ip6)] = c
|
||||
}
|
||||
s.mu.Unlock()
|
||||
}
|
||||
|
||||
func (s *PacketSwitch) Unregister(c SwitchConn) {
|
||||
k := ipToKey(c.AssignedIP())
|
||||
s.mu.Lock()
|
||||
if cur, ok := s.table[k]; ok && cur == c {
|
||||
delete(s.table, k)
|
||||
if cur, ok := s.table[ipToKey(c.AssignedIP())]; ok && cur == c {
|
||||
delete(s.table, ipToKey(c.AssignedIP()))
|
||||
}
|
||||
if ip6 := c.AssignedIP6(); ip6 != nil {
|
||||
k := ipToKey(ip6)
|
||||
if cur, ok := s.table[k]; ok && cur == c {
|
||||
delete(s.table, k)
|
||||
}
|
||||
}
|
||||
s.mu.Unlock()
|
||||
}
|
||||
@@ -110,9 +118,18 @@ func (s *PacketSwitch) RouteFromClient(src SwitchConn, packet []byte) []SwitchCo
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
// anti-spoof: enforce assigned source IP
|
||||
if srcIP != nil && !srcIP.Equal(src.AssignedIP()) {
|
||||
return nil
|
||||
// anti-spoof: enforce assigned source IP by version
|
||||
if srcIP != nil {
|
||||
if srcIP.To4() != nil {
|
||||
if !srcIP.Equal(src.AssignedIP()) {
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
assigned6 := src.AssignedIP6()
|
||||
if assigned6 == nil || !srcIP.Equal(assigned6) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
if dest.IsGlobalUnicast() {
|
||||
if c := s.findByIP(dest); c != nil && s.allowC2C() {
|
||||
|
||||
+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