package service import ( "crypto/rand" "encoding/base64" "errors" "fmt" "net" "strconv" "git.zkcoi.com/zkcoi/meshray/internal/ctr" "git.zkcoi.com/zkcoi/meshray/internal/model" "git.zkcoi.com/zkcoi/meshray/internal/store/sqlite" "golang.org/x/crypto/curve25519" "gorm.io/gorm" ) // DeviceService 设备管理服务 type DeviceService struct { store *sqlite.Store ctrClient ctr.Client // meshray-ctr 客户端 } // NewDeviceService 创建设备服务实例 func NewDeviceService(store *sqlite.Store, ctrClient ctr.Client) *DeviceService { return &DeviceService{ store: store, ctrClient: ctrClient, } } // GetDevice 获取设备详情 func (s *DeviceService) GetDevice(id uint64) (*model.Device, error) { var device model.Device err := s.store.DB().First(&device, id).Error if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil, errors.New("设备不存在") } return nil, err } return &device, nil } // ListAllDevices 获取所有设备列表 func (s *DeviceService) ListAllDevices() ([]model.Device, error) { var devices []model.Device err := s.store.DB().Find(&devices).Error return devices, err } // ListDevicesByNetwork 获取网络下的设备列表 func (s *DeviceService) ListDevicesByNetwork(networkID uint64) ([]model.Device, error) { var devices []model.Device err := s.store.DB().Where("network_id = ?", networkID).Find(&devices).Error return devices, err } // CreateDeviceRequest 创建设备请求 type CreateDeviceRequest struct { NetworkID uint64 `json:"network_id"` Name string `json:"name"` VirtualIP string `json:"virtual_ip"` // 可选,留空则自动分配 Description string `json:"description"` // 可选 } // CreateDeviceResult 创建设备结果 type CreateDeviceResult struct { Device *model.Device `json:"device"` PrivateKey string `json:"private_key"` // 仅首次返回 ConfigText string `json:"config_text"` // WireGuard 配置文本 } // CreateDevice 创建设备 func (s *DeviceService) CreateDevice(req *CreateDeviceRequest) (*CreateDeviceResult, error) { // 验证网络是否存在 var network model.Network if err := s.store.DB().First(&network, req.NetworkID).Error; err != nil { return nil, errors.New("网络不存在") } // 检查设备名称是否重复 var existing model.Device if err := s.store.DB().Where("network_id = ? AND name = ?", req.NetworkID, req.Name).First(&existing).Error; err == nil { return nil, errors.New("设备名称已存在") } // 生成 WireGuard 密钥对(同时获取私钥) privateKey, publicKey, err := generateWireGuardKeys() if err != nil { return nil, fmt.Errorf("生成密钥失败:%w", err) } // 生成预共享密钥 preSharedKey := generatePreSharedKey() // 自动分配 IP(如果未提供) virtualIP := req.VirtualIP if virtualIP == "" { virtualIP, err = s.allocateIP(req.NetworkID, network.SubnetIPv4) if err != nil { return nil, fmt.Errorf("分配 IP 失败:%w", err) } } // 创建设备 device := &model.Device{ NetworkID: req.NetworkID, Name: req.Name, VirtualIP: virtualIP, PublicKey: publicKey, PresharedKey: preSharedKey, Status: "offline", } if err := s.store.DB().Create(device).Error; err != nil { return nil, err } // P2 阶段 - 调用 meshray-ctr 添加 Peer if s.ctrClient != nil { allowedIP := device.VirtualIP + "/32" if err := s.ctrClient.AddPeer(device.NetworkID, device.PublicKey, allowedIP); err != nil { // 记录错误但不影响数据库操作(允许降级) } } // 生成 WireGuard 配置文本 configText := s.deviceGenerateConfig(device, &network, privateKey) return &CreateDeviceResult{ Device: device, PrivateKey: privateKey, ConfigText: configText, }, nil } // UpdateDevice 更新设备 func (s *DeviceService) UpdateDevice(id uint64, updates map[string]interface{}) (*model.Device, error) { var device model.Device if err := s.store.DB().First(&device, id).Error; err != nil { return nil, errors.New("设备不存在") } if err := s.store.DB().Model(&device).Updates(updates).Error; err != nil { return nil, err } return &device, nil } // DeleteDevice 删除设备 func (s *DeviceService) DeleteDevice(id uint64) error { // ✅ 使用事务保证数据一致性 tx := s.store.DB().Begin() defer func() { if r := recover(); r != nil { tx.Rollback() } }() var device model.Device if err := tx.First(&device, id).Error; err != nil { tx.Rollback() return errors.New("设备不存在") } // ✅ 如果设备在线,先断开连接(P2 阶段) if s.ctrClient != nil { if err := s.ctrClient.RemovePeer(device.NetworkID, device.PublicKey); err != nil { // 记录警告但不阻断删除流程 fmt.Printf("⚠️ 从 WireGuard 移除 Peer 失败:network_id=%d, error=%v\n", device.NetworkID, err) } } // ✅ 清理相关路由和配置(P2 阶段) // ✅ P3 阶段 - 当前无额外路由配置,暂不实现 // 未来如果需要,可以在这里添加 // 删除设备 if err := tx.Delete(&device, id).Error; err != nil { tx.Rollback() return err } // 提交事务 if err := tx.Commit().Error; err != nil { return err } return nil } // allocateIP 自动分配 IP 地址 func (s *DeviceService) allocateIP(networkID uint64, subnet string) (string, error) { // 解析子网 _, ipNet, err := net.ParseCIDR(subnet) if err != nil { return "", fmt.Errorf("解析子网失败:%w", err) } // 检查是否是 IPv4 地址 if ipNet.IP.To4() == nil { return "", errors.New("暂不支持 IPv6 地址分配") } // 查询已分配的所有 IP var devices []model.Device if err := s.store.DB().Where("network_id = ?", networkID).Find(&devices).Error; err != nil { return "", fmt.Errorf("查询已分配 IP 失败:%w", err) } // 构建已占用 IP 集合 occupiedIPs := make(map[string]bool) for _, device := range devices { occupiedIPs[device.VirtualIP] = true } // 从 .2 开始分配(.1 通常留给网关) // 遍历整个子网范围查找可用 IP ip := ipNet.IP.To4() startIP := ip.Mask(ipNet.Mask).To4() startIP[3]++ // 从 .1 开始 // 最多尝试 254 个 IP(/24 子网) for i := 1; i < 255; i++ { candidateIP := make(net.IP, len(startIP)) copy(candidateIP, startIP) candidateIP[3] = byte(i + 1) // 从 .2 开始 // 检查是否被占用 if !occupiedIPs[candidateIP.String()] { return candidateIP.String(), nil } } return "", errors.New("IP 地址已耗尽") } // getSettings 获取系统设置(辅助方法) func (s *DeviceService) getSettings() (*model.SystemSetting, error) { var setting model.SystemSetting err := s.store.DB().First(&setting, 1).Error if err != nil { // 如果不存在,返回默认值 return &model.SystemSetting{ ServerPort: 51820, }, nil } return &setting, nil } // generateWireGuardKeys 生成 WireGuard 密钥对 func generateWireGuardKeys() (privateKey, publicKey string, err error) { // 生成私钥(32 字节随机数) var privKeyBytes [32]byte if _, err := rand.Read(privKeyBytes[:]); err != nil { return "", "", err } // 确保私钥符合 Curve25519 要求 privKeyBytes[0] &= 248 privKeyBytes[31] &= 127 privKeyBytes[31] |= 64 // 从私钥推导公钥 var pubKeyBytes [32]byte curve25519.ScalarBaseMult(&pubKeyBytes, &privKeyBytes) // Base64 编码 privateKey = base64.StdEncoding.EncodeToString(privKeyBytes[:]) publicKey = base64.StdEncoding.EncodeToString(pubKeyBytes[:]) return privateKey, publicKey, nil } // generatePreSharedKey 生成预共享密钥 func generatePreSharedKey() string { bytes := make([]byte, 32) rand.Read(bytes) return base64.StdEncoding.EncodeToString(bytes) } // deviceGenerateConfig 生成 WireGuard 配置文本(设备创建时使用) func (s *DeviceService) deviceGenerateConfig(device *model.Device, network *model.Network, privateKey string) string { settings, _ := s.getSettings() config := "[Interface]\n" config += "PrivateKey = " + privateKey + "\n" config += "Address = " + device.VirtualIP + "/32\n" config += fmt.Sprintf("MTU = %d\n\n", network.MTU) config += "[Peer]\n" if settings.ServerPublicKey != "" { config += "PublicKey = " + settings.ServerPublicKey + "\n" } else { config += "PublicKey = \n" } if settings.ServerIP != "" { config += "Endpoint = " + settings.ServerIP + ":" + strconv.Itoa(settings.ServerPort) + "\n" } else { config += fmt.Sprintf("Endpoint = :%d\n", settings.ServerPort) } config += "AllowedIPs = " + network.SubnetIPv4 + "\n" config += "PersistentKeepalive = 25\n" return config } // GenerateDeviceConfig 生成设备配置文件 func (s *DeviceService) GenerateDeviceConfig(deviceID uint64) (string, error) { // 获取设备信息(包含网络) device, err := s.GetDevice(deviceID) if err != nil { return "", err } // 生成 WireGuard 密钥对 privateKey, publicKey, err := generateWireGuardKeys() if err != nil { return "", fmt.Errorf("生成密钥失败:%w", err) } // 更新设备的公钥到数据库 if err := s.store.DB().Model(device).Update("public_key", publicKey).Error; err != nil { return "", fmt.Errorf("保存公钥失败:%w", err) } // 生成完整的 WireGuard 配置 config := "# MeshRay Generated Configuration\n" config += "# Device: " + device.Name + "\n" config += "# Created: " + device.CreatedAt.Format("2006-01-02 15:04:05") + "\n\n" // [Interface] 部分 config += "[Interface]\n" config += "PrivateKey = " + privateKey + "\n" config += "Address = " + device.VirtualIP + "/32\n" config += "DNS = 8.8.8.8, 8.8.4.4\n\n" // [Peer] 部分(服务端配置) config += "# Server (MeshRay)\n" config += "[Peer]\n" // 从 Settings 读取服务端公钥 settings, _ := s.getSettings() if settings.ServerPublicKey == "" { return "", errors.New("请先在系统设置中配置服务端公钥") } config += "PublicKey = " + settings.ServerPublicKey + "\n" if device.PresharedKey != "" { config += "PresharedKey = " + device.PresharedKey + "\n" } config += "AllowedIPs = 0.0.0.0/0\n" // 使用 Settings 中的 ServerIP 和 ServerPort if settings.ServerIP == "" { return "", errors.New("请先在系统设置中配置服务端 IP 地址") } config += "Endpoint = " + settings.ServerIP + ":" + strconv.Itoa(settings.ServerPort) + "\n" config += "PersistentKeepalive = 25\n" return config, nil }