Files
Meshray-Manager/internal/service/network.go
T
zkcoi e9ca2f7d70 fix: rename manager module to git.zkcoi.com/zkcoi/Meshray-Manager
- 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
2026-07-15 16:25:46 +08:00

319 lines
8.5 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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)
}
}
// 计算 listenPort51820 + 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
}