Initial commit
This commit is contained in:
@@ -0,0 +1,318 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"git.zkcoi.com/zkcoi/meshray/internal/ctr"
|
||||
"git.zkcoi.com/zkcoi/meshray/internal/model"
|
||||
"git.zkcoi.com/zkcoi/meshray/internal/store/sqlite"
|
||||
"git.zkcoi.com/zkcoi/meshray/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
|
||||
}
|
||||
Reference in New Issue
Block a user