Initial commit

This commit is contained in:
2026-06-30 15:14:37 +08:00
commit 15dab96872
311 changed files with 95639 additions and 0 deletions
+364
View File
@@ -0,0 +1,364 @@
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().Preload("Network").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().Preload("Network").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
}