- go.mod module path matches repo zkcoi/Meshray-Manager (case-distinct from zkcoi/Meshray/core)
- rewrite internal imports meshray/{internal,web,pkg} -> Meshray-Manager/... (core refs kept)
- sync README.md / install.sh repo URLs; add CHANGELOG entry
319 lines
8.5 KiB
Go
319 lines
8.5 KiB
Go
package service
|
||
|
||
import (
|
||
"crypto/rand"
|
||
"crypto/sha256"
|
||
"encoding/hex"
|
||
"errors"
|
||
"fmt"
|
||
"net"
|
||
|
||
"git.zkcoi.com/zkcoi/Meshray-Manager/internal/ctr"
|
||
"git.zkcoi.com/zkcoi/Meshray-Manager/internal/model"
|
||
"git.zkcoi.com/zkcoi/Meshray-Manager/internal/store/sqlite"
|
||
"git.zkcoi.com/zkcoi/Meshray-Manager/pkg/idutil"
|
||
"go.uber.org/zap"
|
||
"gorm.io/gorm"
|
||
)
|
||
|
||
// NetworkService 网络管理服务
|
||
type NetworkService struct {
|
||
store *sqlite.Store
|
||
ctrClient ctr.Client // meshray-ctr 客户端
|
||
logger *zap.Logger
|
||
}
|
||
|
||
// NewNetworkService 创建网络服务实例
|
||
func NewNetworkService(store *sqlite.Store, ctrClient ctr.Client, logger *zap.Logger) *NetworkService {
|
||
return &NetworkService{
|
||
store: store,
|
||
ctrClient: ctrClient,
|
||
logger: logger,
|
||
}
|
||
}
|
||
|
||
// ListNetworks 获取网络列表(Service 层方法)
|
||
func (s *NetworkService) ListNetworks() ([]model.Network, error) {
|
||
var networks []model.Network
|
||
err := s.store.DB().Preload("Devices").Find(&networks).Error
|
||
return networks, err
|
||
}
|
||
|
||
// GetNetworkByID 根据 ID 获取网络
|
||
func (s *NetworkService) GetNetworkByID(id uint64) (*model.Network, error) {
|
||
var network model.Network
|
||
err := s.store.DB().Preload("Devices").First(&network, id).Error
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
return &network, nil
|
||
}
|
||
|
||
// CreateNetwork 创建网络
|
||
func (s *NetworkService) CreateNetwork(req *model.Network) (*model.Network, error) {
|
||
// 验证子网格式
|
||
if err := s.validateSubnet(req.SubnetIPv4); err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
// 检查网络名称是否重复
|
||
var existing model.Network
|
||
if err := s.store.DB().Where("name = ?", req.Name).First(&existing).Error; err == nil {
|
||
return nil, errors.New("网络名称已存在")
|
||
}
|
||
|
||
// 生成雪花算法 ID
|
||
if req.ID == 0 {
|
||
gen, err := idutil.GetGenerator()
|
||
if err != nil {
|
||
return nil, fmt.Errorf("failed to get snowflake generator: %w", err)
|
||
}
|
||
req.ID, err = gen.NextID()
|
||
if err != nil {
|
||
return nil, fmt.Errorf("failed to generate network id: %w", err)
|
||
}
|
||
}
|
||
|
||
// 计算 listenPort:51820 + hash(networkID)(先不保存,用于 ctr 调用)
|
||
listenPort := 51820 + hashUint64(req.ID)%1000
|
||
|
||
// 根据组网模式决定是否启动 Core
|
||
// 原生模式:仅创建 WG 设备
|
||
// 增强模式:创建 WG 设备 + 启动 Core 实例
|
||
if s.ctrClient != nil {
|
||
// P2 阶段 - 先调用 meshray-ctr 创建 WireGuard 设备(失败则不回滚数据库)
|
||
// 策略:宽松模式 - ctr 失败只记录警告,不影响数据库操作
|
||
if err := s.ctrClient.CreateNetwork(req.ID, req.SubnetIPv4, listenPort, req.Mode); err != nil {
|
||
// 记录错误但不影响数据库操作(允许降级)
|
||
s.logger.Warn("调用 ctr 创建网络失败,将手动启动",
|
||
zap.Uint64("network_id", req.ID),
|
||
zap.Error(err))
|
||
}
|
||
}
|
||
|
||
// 保存到数据库
|
||
if err := s.store.DB().Create(req).Error; err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
s.logger.Info("网络创建成功",
|
||
zap.Uint64("network_id", req.ID),
|
||
zap.String("mode", req.Mode))
|
||
|
||
return req, nil
|
||
}
|
||
|
||
// GetNetwork 获取网络详情
|
||
func (s *NetworkService) GetNetwork(id uint64) (*model.Network, error) {
|
||
var network model.Network
|
||
if err := s.store.DB().First(&network, id).Error; err != nil {
|
||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||
return nil, errors.New("网络不存在")
|
||
}
|
||
return nil, err
|
||
}
|
||
return &network, nil
|
||
}
|
||
|
||
// UpdateNetwork 更新网络
|
||
func (s *NetworkService) UpdateNetwork(id uint64, updates map[string]interface{}) (*model.Network, error) {
|
||
var network model.Network
|
||
if err := s.store.DB().First(&network, id).Error; err != nil {
|
||
return nil, errors.New("网络不存在")
|
||
}
|
||
|
||
if err := s.store.DB().Model(&network).Updates(updates).Error; err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
return &network, nil
|
||
}
|
||
|
||
// DeleteNetwork 删除网络
|
||
func (s *NetworkService) DeleteNetwork(id uint64, force bool) error {
|
||
// ✅ 使用事务保证数据一致性
|
||
tx := s.store.DB().Begin()
|
||
defer func() {
|
||
if r := recover(); r != nil {
|
||
tx.Rollback()
|
||
}
|
||
}()
|
||
|
||
var network model.Network
|
||
if err := tx.First(&network, id).Error; err != nil {
|
||
tx.Rollback()
|
||
return errors.New("网络不存在")
|
||
}
|
||
|
||
// 检查是否有关联的设备
|
||
var deviceCount int64
|
||
tx.Model(&model.Device{}).Where("network_id = ?", id).Count(&deviceCount)
|
||
if deviceCount > 0 {
|
||
if !force {
|
||
tx.Rollback()
|
||
return errors.New("该网络下仍有设备,为避免误操作,请确认后强制删除")
|
||
}
|
||
// 级联删除所有关联设备
|
||
if err := tx.Where("network_id = ?", id).Delete(&model.Device{}).Error; err != nil {
|
||
tx.Rollback()
|
||
return fmt.Errorf("级联删除设备失败:%w", err)
|
||
}
|
||
s.logger.Info("级联删除了关联设备", zap.Uint64("network_id", id), zap.Int64("device_count", deviceCount))
|
||
}
|
||
|
||
// 先调用 ctr 删除 WG 设备(如果存在)
|
||
if s.ctrClient != nil {
|
||
if err := s.ctrClient.DeleteNetwork(id); err != nil {
|
||
// 记录错误但不中断删除流程(设备可能已经不存在)
|
||
s.logger.Warn("删除 WG 设备失败(可能已不存在)",
|
||
zap.Uint64("network_id", id),
|
||
zap.Error(err))
|
||
// 继续删除数据库记录
|
||
}
|
||
}
|
||
|
||
// 再删除数据库记录
|
||
if err := tx.Delete(&network, id).Error; err != nil {
|
||
tx.Rollback()
|
||
return err
|
||
}
|
||
|
||
// 提交事务
|
||
if err := tx.Commit().Error; err != nil {
|
||
return err
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// validateSubnet 验证子网格式
|
||
func (s *NetworkService) validateSubnet(subnet string) error {
|
||
// 只调用一次 ParseCIDR
|
||
ip, ipNet, err := net.ParseCIDR(subnet)
|
||
if err != nil {
|
||
return fmt.Errorf("无效的子网格式:%s", subnet)
|
||
}
|
||
|
||
// 检查是否是有效的 WireGuard 子网(至少 /24 或更小)
|
||
ones, bits := ipNet.Mask.Size()
|
||
|
||
if bits == 32 && ones > 24 {
|
||
return errors.New("IPv4 子网掩码不能大于 /24")
|
||
}
|
||
if bits == 128 && ones > 64 {
|
||
return errors.New("IPv6 子网掩码不能大于 /64")
|
||
}
|
||
|
||
// 检查是否是私有地址段
|
||
if !isPrivateSubnet(ip, ipNet) {
|
||
return errors.New("请使用私有地址段(如 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16)")
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// isPrivateSubnet 检查是否是私有地址段
|
||
func isPrivateSubnet(ip net.IP, ipNet *net.IPNet) bool {
|
||
// IPv4 私有地址段
|
||
privateRanges := []string{
|
||
"10.0.0.0/8",
|
||
"172.16.0.0/12",
|
||
"192.168.0.0/16",
|
||
"100.64.0.0/10", // CGNAT
|
||
}
|
||
|
||
for _, privRange := range privateRanges {
|
||
_, privNet, _ := net.ParseCIDR(privRange)
|
||
if privNet.Contains(ip) {
|
||
return true
|
||
}
|
||
}
|
||
|
||
return false
|
||
}
|
||
|
||
// generateNetworkSecret 生成 Network Secret
|
||
func generateNetworkSecret() string {
|
||
// 使用加密安全的随机数生成器
|
||
bytes := make([]byte, 32)
|
||
if _, err := rand.Read(bytes); err != nil {
|
||
// ✅ 随机数生成失败时 panic,而不是返回弱密码
|
||
panic(fmt.Sprintf("生成安全随机数失败:%v", err))
|
||
}
|
||
return hex.EncodeToString(bytes)
|
||
}
|
||
|
||
// hashUint64 计算 uint64 哈希值
|
||
func hashUint64(id uint64) int {
|
||
h := sha256.Sum256([]byte(fmt.Sprintf("%d", id)))
|
||
// 取前 4 字节转换为 int
|
||
return int(h[0])<<24 | int(h[1])<<16 | int(h[2])<<8 | int(h[3])
|
||
}
|
||
|
||
// StartNetwork 启动网络
|
||
func (s *NetworkService) StartNetwork(id uint64) error {
|
||
network, err := s.GetNetworkByID(id)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
// 如果已经有 ctr,直接调用 CreateNetwork
|
||
if s.ctrClient != nil {
|
||
listenPort := 51820 + hashUint64(network.ID)%1000
|
||
if err := s.ctrClient.CreateNetwork(network.ID, network.SubnetIPv4, listenPort, network.Mode); err != nil {
|
||
s.logger.Error("启动网络失败",
|
||
zap.Uint64("network_id", network.ID),
|
||
zap.Error(err))
|
||
return fmt.Errorf("启动网络失败:%w", err)
|
||
}
|
||
|
||
s.logger.Info("网络已启动",
|
||
zap.Uint64("network_id", network.ID),
|
||
zap.Int("listen_port", listenPort))
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// StopNetwork 停止网络
|
||
func (s *NetworkService) StopNetwork(id uint64) error {
|
||
network, err := s.GetNetworkByID(id)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
// 调用 ctr 删除网络(清理 WG 设备)
|
||
if s.ctrClient != nil {
|
||
if err := s.ctrClient.DeleteNetwork(network.ID); err != nil {
|
||
s.logger.Error("停止网络失败",
|
||
zap.Uint64("network_id", network.ID),
|
||
zap.Error(err))
|
||
return fmt.Errorf("停止网络失败:%w", err)
|
||
}
|
||
|
||
s.logger.Info("网络已停止",
|
||
zap.Uint64("network_id", network.ID))
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// SwitchMode 切换组网模式
|
||
func (s *NetworkService) SwitchMode(id uint64, meshMode string) error {
|
||
network, err := s.GetNetworkByID(id)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
// 更新数据库
|
||
if err := s.store.DB().Model(network).Update("mode", meshMode).Error; err != nil {
|
||
return err
|
||
}
|
||
|
||
s.logger.Info("组网模式已切换",
|
||
zap.Uint64("network_id", network.ID),
|
||
zap.String("mode", meshMode))
|
||
|
||
return nil
|
||
}
|