package ctr import ( "fmt" "net" "os/exec" "runtime" "strings" "sync" contract "git.zkcoi.com/zkcoi/meshray-contract" "go.uber.org/zap" "golang.zx2c4.com/wireguard/device" "golang.zx2c4.com/wireguard/tun" "golang.zx2c4.com/wireguard/wgctrl" "golang.zx2c4.com/wireguard/wgctrl/wgtypes" ) // WGManager WireGuard 管理器(P2 阶段骨架) type WGManager struct { devices map[string]*WGDevice // network_id -> device mu sync.RWMutex logger *zap.Logger wgMode string // "kernel" | "userspace"(当前运行模式) } // WGDevice WireGuard 设备 type WGDevice struct { NetworkID string Name string // wg0, wg1, ... Config *DeviceConfig Peers []PeerInfo Running bool // 用户态模式下使用的资源引用 tunDevice tun.Device // TUN 设备引用(用户态模式) wgDevice *device.Device // WireGuard 设备引用(用户态模式) } // NewWGManager 创建 WireGuard 管理器(固定使用用户态) func NewWGManager(logger *zap.Logger) *WGManager { logger.Info("使用 wireguard-go 用户态模式(跨平台统一)") return &WGManager{ devices: make(map[string]*WGDevice), logger: logger, wgMode: "userspace", // 固定为用户态 } } // CreateDevice 创建 WireGuard 设备 // meshMode: "native" 使用 wireguard-go 默认 Bind 直连;"enhanced" 使用 meshray-core 注册的自定义 Bind 接管收发 func (m *WGManager) CreateDevice(networkID string, subnet string, listenPort int, meshMode string) error { m.mu.Lock() defer m.mu.Unlock() // 检查是否已存在 if _, ok := m.devices[networkID]; ok { return fmt.Errorf("网络 %s 的设备已存在", networkID) } deviceName := fmt.Sprintf("wg%s", networkID) m.logger.Info("开始创建 WireGuard 设备", zap.String("network_id", networkID), zap.String("device", deviceName), zap.String("subnet", subnet), zap.Int("listen_port", listenPort), zap.String("mesh_mode", meshMode)) // 生成密钥对 privateKey, err := wgtypes.GeneratePrivateKey() if err != nil { return fmt.Errorf("生成私钥失败:%w", err) } publicKey := privateKey.PublicKey() m.logger.Debug("生成 WG 密钥对", zap.String("public_key", publicKey.String())) // 使用用户态模式启动 wireguard-go 进程,并获取资源引用 tunDev, wgDev, err := m.startUserModeWGProcessWithRefs(deviceName, privateKey, listenPort, networkID, meshMode) if err != nil { return err } // 4. ✅ Core 模块使用回环地址拦截,无需配置 TUN 设备 IP // WireGuard 用户态设备会自动管理自己的网络接口 m.logger.Info("WireGuard 用户态设备已创建,使用回环地址拦截模式", zap.String("device", deviceName)) // 5. 启动设备(带回滚) if err := m.bringUpDevice(deviceName); err != nil { m.cleanupDevice(deviceName) // 回滚 return fmt.Errorf("启动设备失败:%w", err) } // 7. 记录到内存 device := &WGDevice{ NetworkID: networkID, Name: deviceName, Config: &DeviceConfig{ PrivateKey: privateKey.String(), PublicKey: publicKey.String(), ListenPort: listenPort, Subnet: subnet, }, Running: true, } // ⚠️ 关键:保存用户态模式资源引用 device.tunDevice = tunDev device.wgDevice = wgDev m.logger.Debug("已保存用户态模式资源引用", zap.String("device", deviceName)) m.devices[networkID] = device m.logger.Info("WireGuard 设备创建成功(用户态模式)", zap.String("network_id", networkID), zap.String("public_key", publicKey.String())) return nil } // AddPeer 添加 Peer func (m *WGManager) AddPeer(networkID string, publicKey, allowedIP string) error { m.mu.Lock() defer m.mu.Unlock() device, ok := m.devices[networkID] if !ok { return fmt.Errorf("网络 %s 的设备不存在", networkID) } m.logger.Info("添加 Peer", zap.String("network_id", networkID), zap.String("public_key", truncatePublicKey(publicKey)+"..."), zap.String("allowed_ip", allowedIP)) // 1. 解析公钥 peerKey, err := wgtypes.ParseKey(publicKey) if err != nil { return fmt.Errorf("解析公钥失败:%w", err) } // 2. 解析 AllowedIP _, ipNet, err := net.ParseCIDR(allowedIP) if err != nil { return fmt.Errorf("解析 AllowedIP 失败:%w", err) } // 3. 连接 wgctrl 并配置 client, err := wgctrl.New() if err != nil { return fmt.Errorf("wgctrl 连接失败:%w", err) } defer client.Close() // 4. 配置 Peer config := wgtypes.Config{ Peers: []wgtypes.PeerConfig{ { PublicKey: peerKey, ReplaceAllowedIPs: true, AllowedIPs: []net.IPNet{*ipNet}, }, }, } if err := client.ConfigureDevice(device.Name, config); err != nil { return fmt.Errorf("配置 Peer 失败:%w", err) } m.logger.Info("Peer 配置成功", zap.String("network_id", networkID), zap.String("public_key", truncatePublicKey(publicKey)+"..."), zap.String("allowed_ip", allowedIP)) // 5. 同时更新内存(保持向后兼容) peer := PeerInfo{ PublicKey: publicKey, AllowedIPs: []string{allowedIP}, } device.Peers = append(device.Peers, peer) return nil } // RemovePeer 移除 Peer func (m *WGManager) RemovePeer(networkID string, publicKey string) error { m.mu.Lock() defer m.mu.Unlock() device, ok := m.devices[networkID] if !ok { return fmt.Errorf("网络 %s 的设备不存在", networkID) } m.logger.Info("移除 Peer", zap.String("network_id", networkID), zap.String("public_key", truncatePublicKey(publicKey)+"...")) // 1. 解析公钥 peerKey, err := wgtypes.ParseKey(publicKey) if err != nil { return fmt.Errorf("解析公钥失败:%w", err) } // 2. 连接 wgctrl 并移除 client, err := wgctrl.New() if err != nil { return fmt.Errorf("wgctrl 连接失败:%w", err) } defer client.Close() // 3. 配置移除 Peer(使用 Remove: true) config := wgtypes.Config{ Peers: []wgtypes.PeerConfig{ { PublicKey: peerKey, Remove: true, }, }, } if err := client.ConfigureDevice(device.Name, config); err != nil { return fmt.Errorf("移除 Peer 失败:%w", err) } m.logger.Info("Peer 移除成功", zap.String("network_id", networkID), zap.String("public_key", truncatePublicKey(publicKey)+"...")) // 4. 同时从内存删除(保持向后兼容) newPeers := []PeerInfo{} for _, p := range device.Peers { if p.PublicKey != publicKey { newPeers = append(newPeers, p) } } device.Peers = newPeers return nil } // DeleteDevice 删除 WireGuard 设备 func (m *WGManager) DeleteDevice(networkID string) error { m.mu.Lock() defer m.mu.Unlock() device, ok := m.devices[networkID] if !ok { return fmt.Errorf("网络 %s 的设备不存在", networkID) } m.logger.Info("删除 WireGuard 设备", zap.String("network_id", networkID), zap.String("device", device.Name)) // 1. 清理真实设备(内核态或用户态) m.cleanupDevice(device.Name) // 2. 从内存删除 delete(m.devices, networkID) m.logger.Info("WireGuard 设备已删除", zap.String("network_id", networkID)) return nil } // GetStatus 获取设备状态 func (m *WGManager) GetStatus(networkID string) (*WGStatus, error) { m.mu.RLock() defer m.mu.RUnlock() device, ok := m.devices[networkID] if !ok { return nil, fmt.Errorf("网络 %s 的设备不存在", networkID) } status := &WGStatus{ DeviceName: device.Name, Running: device.Running, PeerCount: len(device.Peers), Subnet: device.Config.Subnet, } return status, nil } // GetWGMode 获取当前 WG 模式 func (m *WGManager) GetWGMode() string { m.mu.RLock() defer m.mu.RUnlock() return m.wgMode } // Stop 停止管理器并释放所有资源 func (m *WGManager) Stop() error { m.mu.Lock() defer m.mu.Unlock() m.logger.Info("正在停止 WireGuard 管理器...") // 关闭所有设备 for networkID, device := range m.devices { m.logger.Info("正在关闭设备", zap.String("network_id", networkID), zap.String("device_name", device.Name)) // 如果是用户态模式,关闭相关资源 if device.wgDevice != nil { m.logger.Debug("关闭用户态 WireGuard 设备", zap.String("device", device.Name)) device.wgDevice.Close() } if device.tunDevice != nil { m.logger.Debug("关闭 TUN 设备", zap.String("device", device.Name)) device.tunDevice.Close() } // 清理内核态设备(忽略返回值) m.cleanupDevice(device.Name) delete(m.devices, networkID) } m.logger.Info("WireGuard 管理器已停止") return nil } // WGDevice WireGuard 设备(结构已在上面定义) // DeviceConfig 设备配置 type DeviceConfig struct { PrivateKey string PublicKey string ListenPort int Subnet string } // PeerInfo Peer 信息 type PeerInfo struct { PublicKey string Endpoint string AllowedIPs []string } // WGStatus WireGuard 状态 type WGStatus struct { DeviceName string `json:"device_name"` Running bool `json:"running"` PeerCount int `json:"peer_count"` Subnet string `json:"subnet"` } // ListPeers 获取设备下所有 Peer 信息 // 除内存记录外,还会从 wgctrl 读取对端真实 Endpoint,作为增强模式 P2P 建连的候选地址来源 func (m *WGManager) ListPeers(networkID string) ([]PeerInfo, error) { m.mu.RLock() device, ok := m.devices[networkID] if !ok { m.mu.RUnlock() return nil, fmt.Errorf("网络 %s 的设备不存在", networkID) } // 复制内存中的 peer 列表,避免返回内部共享引用 peers := make([]PeerInfo, len(device.Peers)) copy(peers, device.Peers) m.mu.RUnlock() // 从 wgctrl 读取真实 Endpoint(若已配置) client, err := wgctrl.New() if err == nil { defer client.Close() if dev, err := client.Device(device.Name); err == nil { for i := range peers { for _, wp := range dev.Peers { if wp.PublicKey.String() == peers[i].PublicKey { if wp.Endpoint != nil { peers[i].Endpoint = wp.Endpoint.String() } break } } } } } return peers, nil } // UpdatePeerEndpoint 更新 Peer 的 Endpoint // 注意:增强模式(meshray-core 接管收发)下不应再改写 peer Endpoint 为回环端口, // 否则 WG 入站源地址匹配失败;此方法仅用于设置对端真实地址或原生模式。 func (m *WGManager) UpdatePeerEndpoint(networkID string, publicKey string, newEndpoint string) error { m.mu.Lock() defer m.mu.Unlock() device, ok := m.devices[networkID] if !ok { return fmt.Errorf("网络 %s 的设备不存在", networkID) } m.logger.Info("更新 Peer Endpoint", zap.String("network_id", networkID), zap.String("public_key", truncatePublicKey(publicKey)+"..."), zap.String("new_endpoint", newEndpoint)) // 解析公钥 peerKey, err := wgtypes.ParseKey(publicKey) if err != nil { return fmt.Errorf("解析公钥失败:%w", err) } // 解析 Endpoint 地址 var udpAddr *net.UDPAddr if newEndpoint != "" { addr, err := net.ResolveUDPAddr("udp", newEndpoint) if err != nil { return fmt.Errorf("解析 Endpoint 地址 %s 失败:%w", newEndpoint, err) } udpAddr = addr } // 连接 wgctrl 并配置 client, err := wgctrl.New() if err != nil { return fmt.Errorf("wgctrl 连接失败:%w", err) } defer client.Close() // 配置更新 Peer config := wgtypes.Config{ Peers: []wgtypes.PeerConfig{ { PublicKey: peerKey, UpdateOnly: true, Endpoint: udpAddr, }, }, } if err := client.ConfigureDevice(device.Name, config); err != nil { return fmt.Errorf("更新 Peer 失败:%w", err) } // 同时更新内存 for i, p := range device.Peers { if p.PublicKey == publicKey { device.Peers[i].Endpoint = newEndpoint break } } return nil } // startUserModeWGProcess 用户态模式启动 wireguard-go(保存资源引用) func (m *WGManager) startUserModeWGProcess(deviceName string, privateKey wgtypes.Key, listenPort int) error { tunDev, wgDev, err := m.startUserModeWGProcessWithRefs(deviceName, privateKey, listenPort, "", "native") if err != nil { return err } // 兼容旧接口,忽略返回值 _ = tunDev _ = wgDev return nil } // startUserModeWGProcessWithRefs 用户态模式启动 wireguard-go,返回资源引用 // meshMode 决定 Bind 来源:native 用 wireguard-go 默认 Bind;enhanced 用 meshray-core 注册的自定义 Bind func (m *WGManager) startUserModeWGProcessWithRefs(deviceName string, privateKey wgtypes.Key, listenPort int, networkID string, meshMode string) (tun.Device, *device.Device, error) { m.logger.Info("使用用户态模式启动 WireGuard", zap.String("device", deviceName), zap.Int("port", listenPort), zap.String("mesh_mode", meshMode)) // 1. 创建 TUN 设备并保存引用 tunDevice, err := tun.CreateTUN(deviceName, 1420) if err != nil { return nil, nil, fmt.Errorf("创建 TUN 设备失败:%w", err) } // 2. 创建 UDP bind:根据组网模式选择 // - 原生模式:wireguard-go 默认 Bind(标准直连,无需 meshray-core) // - 增强模式:优先使用 meshray-core 注册的自定义 Bind(接管收发,根治 Endpoint 旁路缺陷); // 若默认构建未引入 meshray-core,则返回错误,增强模式不可用。 bindFactory, err := contract.GetBindFactory(meshMode == "enhanced") if err != nil { tunDevice.Close() return nil, nil, fmt.Errorf("获取 Bind 工厂失败(增强模式需 meshray-core):%w", err) } bind, err := bindFactory(listenPort, networkID, m.logger) if err != nil { tunDevice.Close() return nil, nil, fmt.Errorf("创建 Bind 失败:%w", err) } // 3. 创建 WireGuard device 并保存引用 logger := &device.Logger{ Verbosef: func(format string, args ...interface{}) { m.logger.Debug(fmt.Sprintf(format, args...)) }, Errorf: func(format string, args ...interface{}) { m.logger.Error(fmt.Sprintf(format, args...)) }, } wgDevice := device.NewDevice(tunDevice, bind, logger) // 4. 配置设备 config := fmt.Sprintf("private_key=%s\nlisten_port=%d\n", privateKey.String(), listenPort) if err := wgDevice.IpcSet(config); err != nil { wgDevice.Close() return nil, nil, fmt.Errorf("配置 WireGuard 设备失败:%w", err) } // 5. 启动设备 if err := wgDevice.Up(); err != nil { wgDevice.Close() return nil, nil, fmt.Errorf("启动 WireGuard 设备失败:%w", err) } m.logger.Info("用户态 WireGuard 启动成功", zap.String("device", deviceName), zap.Int("port", listenPort)) // 6. 返回资源引用 return tunDevice, wgDevice, nil } // truncatePublicKey 截断公钥用于日志显示(避免数组越界) func truncatePublicKey(publicKey string) string { if len(publicKey) <= 8 { return publicKey } return publicKey[:8] } // bringUpDevice 启动设备(跨平台实现) func (m *WGManager) bringUpDevice(deviceName string) error { m.logger.Info("启动设备", zap.String("device", deviceName)) var cmd *exec.Cmd var output []byte var err error switch runtime.GOOS { case "linux": // Linux: ip link set up device cmd = exec.Command("ip", "link", "set", "up", deviceName) output, err = cmd.CombinedOutput() case "windows": // Windows: 用户态模式下 wireguard-go 会自动管理设备状态 // 这里不需要额外操作 m.logger.Debug("Windows 平台用户态模式无需手动启动设备") return nil case "darwin": // macOS: ifconfig device up cmd = exec.Command("ifconfig", deviceName, "up") output, err = cmd.CombinedOutput() default: return fmt.Errorf("不支持的操作系统:%s", runtime.GOOS) } if err != nil { return fmt.Errorf("启动设备失败 (%s): %w", string(output), err) } m.logger.Info("设备已启动", zap.String("device", deviceName)) return nil } // cleanupDevice 清理残留设备(回滚用,跨平台实现) func (m *WGManager) cleanupDevice(deviceName string) { m.logger.Warn("清理残留设备", zap.String("device", deviceName)) var cmd *exec.Cmd var output []byte switch runtime.GOOS { case "linux": // Linux: ip link delete device cmd = exec.Command("ip", "link", "delete", deviceName) output, _ = cmd.CombinedOutput() case "windows": // Windows: 无法通过命令行直接删除,需要用户手动操作 m.logger.Warn("Windows 平台需要通过 WireGuard 客户端删除设备", zap.String("device", deviceName)) return case "darwin": // macOS: ifconfig device down delete cmd = exec.Command("ifconfig", deviceName, "down") _, _ = cmd.CombinedOutput() // macOS 可能需要额外步骤,这里简化处理 default: m.logger.Warn("不支持的操作系统,跳过清理", zap.String("os", runtime.GOOS)) return } // 检查是否是"设备不存在"错误 if strings.Contains(string(output), "does not exist") || strings.Contains(string(output), "cannot find device") { m.logger.Debug("设备不存在,无需清理", zap.String("device", deviceName)) return } m.logger.Debug("设备清理完成", zap.String("device", deviceName)) }