- go.mod: require git.zkcoi.com/zkcoi/meshray/core, replace => ../Meshray - internal/ctr/corebind: EnhancedBind + registry + adapter (wg-go integration isolated) - cmd/mr-wg migrated from old core/cmd/meshray-core - core/ removed; CHANGELOG updated; fix checkdb vet warning
365 lines
10 KiB
Go
365 lines
10 KiB
Go
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 = <SERVER_PUBLIC_KEY>\n"
|
||
}
|
||
if settings.ServerIP != "" {
|
||
config += "Endpoint = " + settings.ServerIP + ":" + strconv.Itoa(settings.ServerPort) + "\n"
|
||
} else {
|
||
config += fmt.Sprintf("Endpoint = <SERVER_IP>:%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
|
||
}
|