Files
Meshray-Manager/internal/ctr/wg.go
T
zkcoi 59e3059246 rebuild: split core into separate repo; move wg-go integration to internal/ctr/corebind
- 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
2026-07-15 16:08:13 +08:00

622 lines
17 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 ctr
import (
"fmt"
"net"
"os/exec"
"runtime"
"strings"
"sync"
contract "git.zkcoi.com/zkcoi/meshray-contract"
"go.uber.org/zap"
"golang.zx2c4.com/wireguard/device"
"golang.zx2c4.com/wireguard/tun"
"golang.zx2c4.com/wireguard/wgctrl"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
)
// WGManager WireGuard 管理器(P2 阶段骨架)
type WGManager struct {
devices map[string]*WGDevice // network_id -> device
mu sync.RWMutex
logger *zap.Logger
wgMode string // "kernel" | "userspace"(当前运行模式)
}
// WGDevice WireGuard 设备
type WGDevice struct {
NetworkID string
Name string // wg0, wg1, ...
Config *DeviceConfig
Peers []PeerInfo
Running bool
// 用户态模式下使用的资源引用
tunDevice tun.Device // TUN 设备引用(用户态模式)
wgDevice *device.Device // WireGuard 设备引用(用户态模式)
}
// NewWGManager 创建 WireGuard 管理器(固定使用用户态)
func NewWGManager(logger *zap.Logger) *WGManager {
logger.Info("使用 wireguard-go 用户态模式(跨平台统一)")
return &WGManager{
devices: make(map[string]*WGDevice),
logger: logger,
wgMode: "userspace", // 固定为用户态
}
}
// CreateDevice 创建 WireGuard 设备
// meshMode: "native" 使用 wireguard-go 默认 Bind 直连;"enhanced" 使用 meshray-core 注册的自定义 Bind 接管收发
func (m *WGManager) CreateDevice(networkID string, subnet string, listenPort int, meshMode string) error {
m.mu.Lock()
defer m.mu.Unlock()
// 检查是否已存在
if _, ok := m.devices[networkID]; ok {
return fmt.Errorf("网络 %s 的设备已存在", networkID)
}
deviceName := fmt.Sprintf("wg%s", networkID)
m.logger.Info("开始创建 WireGuard 设备",
zap.String("network_id", networkID),
zap.String("device", deviceName),
zap.String("subnet", subnet),
zap.Int("listen_port", listenPort),
zap.String("mesh_mode", meshMode))
// 生成密钥对
privateKey, err := wgtypes.GeneratePrivateKey()
if err != nil {
return fmt.Errorf("生成私钥失败:%w", err)
}
publicKey := privateKey.PublicKey()
m.logger.Debug("生成 WG 密钥对",
zap.String("public_key", publicKey.String()))
// 使用用户态模式启动 wireguard-go 进程,并获取资源引用
tunDev, wgDev, err := m.startUserModeWGProcessWithRefs(deviceName, privateKey, listenPort, networkID, meshMode)
if err != nil {
return err
}
// 4. ✅ Core 模块使用回环地址拦截,无需配置 TUN 设备 IP
// WireGuard 用户态设备会自动管理自己的网络接口
m.logger.Info("WireGuard 用户态设备已创建,使用回环地址拦截模式",
zap.String("device", deviceName))
// 5. 启动设备(带回滚)
if err := m.bringUpDevice(deviceName); err != nil {
m.cleanupDevice(deviceName) // 回滚
return fmt.Errorf("启动设备失败:%w", err)
}
// 7. 记录到内存
device := &WGDevice{
NetworkID: networkID,
Name: deviceName,
Config: &DeviceConfig{
PrivateKey: privateKey.String(),
PublicKey: publicKey.String(),
ListenPort: listenPort,
Subnet: subnet,
},
Running: true,
}
// ⚠️ 关键:保存用户态模式资源引用
device.tunDevice = tunDev
device.wgDevice = wgDev
m.logger.Debug("已保存用户态模式资源引用",
zap.String("device", deviceName))
m.devices[networkID] = device
m.logger.Info("WireGuard 设备创建成功(用户态模式)",
zap.String("network_id", networkID),
zap.String("public_key", publicKey.String()))
return nil
}
// AddPeer 添加 Peer
func (m *WGManager) AddPeer(networkID string, publicKey, allowedIP string) error {
m.mu.Lock()
defer m.mu.Unlock()
device, ok := m.devices[networkID]
if !ok {
return fmt.Errorf("网络 %s 的设备不存在", networkID)
}
m.logger.Info("添加 Peer",
zap.String("network_id", networkID),
zap.String("public_key", truncatePublicKey(publicKey)+"..."),
zap.String("allowed_ip", allowedIP))
// 1. 解析公钥
peerKey, err := wgtypes.ParseKey(publicKey)
if err != nil {
return fmt.Errorf("解析公钥失败:%w", err)
}
// 2. 解析 AllowedIP
_, ipNet, err := net.ParseCIDR(allowedIP)
if err != nil {
return fmt.Errorf("解析 AllowedIP 失败:%w", err)
}
// 3. 连接 wgctrl 并配置
client, err := wgctrl.New()
if err != nil {
return fmt.Errorf("wgctrl 连接失败:%w", err)
}
defer client.Close()
// 4. 配置 Peer
config := wgtypes.Config{
Peers: []wgtypes.PeerConfig{
{
PublicKey: peerKey,
ReplaceAllowedIPs: true,
AllowedIPs: []net.IPNet{*ipNet},
},
},
}
if err := client.ConfigureDevice(device.Name, config); err != nil {
return fmt.Errorf("配置 Peer 失败:%w", err)
}
m.logger.Info("Peer 配置成功",
zap.String("network_id", networkID),
zap.String("public_key", truncatePublicKey(publicKey)+"..."),
zap.String("allowed_ip", allowedIP))
// 5. 同时更新内存(保持向后兼容)
peer := PeerInfo{
PublicKey: publicKey,
AllowedIPs: []string{allowedIP},
}
device.Peers = append(device.Peers, peer)
return nil
}
// RemovePeer 移除 Peer
func (m *WGManager) RemovePeer(networkID string, publicKey string) error {
m.mu.Lock()
defer m.mu.Unlock()
device, ok := m.devices[networkID]
if !ok {
return fmt.Errorf("网络 %s 的设备不存在", networkID)
}
m.logger.Info("移除 Peer",
zap.String("network_id", networkID),
zap.String("public_key", truncatePublicKey(publicKey)+"..."))
// 1. 解析公钥
peerKey, err := wgtypes.ParseKey(publicKey)
if err != nil {
return fmt.Errorf("解析公钥失败:%w", err)
}
// 2. 连接 wgctrl 并移除
client, err := wgctrl.New()
if err != nil {
return fmt.Errorf("wgctrl 连接失败:%w", err)
}
defer client.Close()
// 3. 配置移除 Peer(使用 Remove: true
config := wgtypes.Config{
Peers: []wgtypes.PeerConfig{
{
PublicKey: peerKey,
Remove: true,
},
},
}
if err := client.ConfigureDevice(device.Name, config); err != nil {
return fmt.Errorf("移除 Peer 失败:%w", err)
}
m.logger.Info("Peer 移除成功",
zap.String("network_id", networkID),
zap.String("public_key", truncatePublicKey(publicKey)+"..."))
// 4. 同时从内存删除(保持向后兼容)
newPeers := []PeerInfo{}
for _, p := range device.Peers {
if p.PublicKey != publicKey {
newPeers = append(newPeers, p)
}
}
device.Peers = newPeers
return nil
}
// DeleteDevice 删除 WireGuard 设备
func (m *WGManager) DeleteDevice(networkID string) error {
m.mu.Lock()
defer m.mu.Unlock()
device, ok := m.devices[networkID]
if !ok {
return fmt.Errorf("网络 %s 的设备不存在", networkID)
}
m.logger.Info("删除 WireGuard 设备",
zap.String("network_id", networkID),
zap.String("device", device.Name))
// 1. 清理真实设备(内核态或用户态)
m.cleanupDevice(device.Name)
// 2. 从内存删除
delete(m.devices, networkID)
m.logger.Info("WireGuard 设备已删除",
zap.String("network_id", networkID))
return nil
}
// GetStatus 获取设备状态
func (m *WGManager) GetStatus(networkID string) (*WGStatus, error) {
m.mu.RLock()
defer m.mu.RUnlock()
device, ok := m.devices[networkID]
if !ok {
return nil, fmt.Errorf("网络 %s 的设备不存在", networkID)
}
status := &WGStatus{
DeviceName: device.Name,
Running: device.Running,
PeerCount: len(device.Peers),
Subnet: device.Config.Subnet,
}
return status, nil
}
// GetWGMode 获取当前 WG 模式
func (m *WGManager) GetWGMode() string {
m.mu.RLock()
defer m.mu.RUnlock()
return m.wgMode
}
// Stop 停止管理器并释放所有资源
func (m *WGManager) Stop() error {
m.mu.Lock()
defer m.mu.Unlock()
m.logger.Info("正在停止 WireGuard 管理器...")
// 关闭所有设备
for networkID, device := range m.devices {
m.logger.Info("正在关闭设备",
zap.String("network_id", networkID),
zap.String("device_name", device.Name))
// 如果是用户态模式,关闭相关资源
if device.wgDevice != nil {
m.logger.Debug("关闭用户态 WireGuard 设备",
zap.String("device", device.Name))
device.wgDevice.Close()
}
if device.tunDevice != nil {
m.logger.Debug("关闭 TUN 设备",
zap.String("device", device.Name))
device.tunDevice.Close()
}
// 清理内核态设备(忽略返回值)
m.cleanupDevice(device.Name)
delete(m.devices, networkID)
}
m.logger.Info("WireGuard 管理器已停止")
return nil
}
// WGDevice WireGuard 设备(结构已在上面定义)
// DeviceConfig 设备配置
type DeviceConfig struct {
PrivateKey string
PublicKey string
ListenPort int
Subnet string
}
// PeerInfo Peer 信息
type PeerInfo struct {
PublicKey string
Endpoint string
AllowedIPs []string
}
// WGStatus WireGuard 状态
type WGStatus struct {
DeviceName string `json:"device_name"`
Running bool `json:"running"`
PeerCount int `json:"peer_count"`
Subnet string `json:"subnet"`
}
// ListPeers 获取设备下所有 Peer 信息
// 除内存记录外,还会从 wgctrl 读取对端真实 Endpoint,作为增强模式 P2P 建连的候选地址来源
func (m *WGManager) ListPeers(networkID string) ([]PeerInfo, error) {
m.mu.RLock()
device, ok := m.devices[networkID]
if !ok {
m.mu.RUnlock()
return nil, fmt.Errorf("网络 %s 的设备不存在", networkID)
}
// 复制内存中的 peer 列表,避免返回内部共享引用
peers := make([]PeerInfo, len(device.Peers))
copy(peers, device.Peers)
m.mu.RUnlock()
// 从 wgctrl 读取真实 Endpoint(若已配置)
client, err := wgctrl.New()
if err == nil {
defer client.Close()
if dev, err := client.Device(device.Name); err == nil {
for i := range peers {
for _, wp := range dev.Peers {
if wp.PublicKey.String() == peers[i].PublicKey {
if wp.Endpoint != nil {
peers[i].Endpoint = wp.Endpoint.String()
}
break
}
}
}
}
}
return peers, nil
}
// UpdatePeerEndpoint 更新 Peer 的 Endpoint
// 注意:增强模式(meshray-core 接管收发)下不应再改写 peer Endpoint 为回环端口,
// 否则 WG 入站源地址匹配失败;此方法仅用于设置对端真实地址或原生模式。
func (m *WGManager) UpdatePeerEndpoint(networkID string, publicKey string, newEndpoint string) error {
m.mu.Lock()
defer m.mu.Unlock()
device, ok := m.devices[networkID]
if !ok {
return fmt.Errorf("网络 %s 的设备不存在", networkID)
}
m.logger.Info("更新 Peer Endpoint",
zap.String("network_id", networkID),
zap.String("public_key", truncatePublicKey(publicKey)+"..."),
zap.String("new_endpoint", newEndpoint))
// 解析公钥
peerKey, err := wgtypes.ParseKey(publicKey)
if err != nil {
return fmt.Errorf("解析公钥失败:%w", err)
}
// 解析 Endpoint 地址
var udpAddr *net.UDPAddr
if newEndpoint != "" {
addr, err := net.ResolveUDPAddr("udp", newEndpoint)
if err != nil {
return fmt.Errorf("解析 Endpoint 地址 %s 失败:%w", newEndpoint, err)
}
udpAddr = addr
}
// 连接 wgctrl 并配置
client, err := wgctrl.New()
if err != nil {
return fmt.Errorf("wgctrl 连接失败:%w", err)
}
defer client.Close()
// 配置更新 Peer
config := wgtypes.Config{
Peers: []wgtypes.PeerConfig{
{
PublicKey: peerKey,
UpdateOnly: true,
Endpoint: udpAddr,
},
},
}
if err := client.ConfigureDevice(device.Name, config); err != nil {
return fmt.Errorf("更新 Peer 失败:%w", err)
}
// 同时更新内存
for i, p := range device.Peers {
if p.PublicKey == publicKey {
device.Peers[i].Endpoint = newEndpoint
break
}
}
return nil
}
// startUserModeWGProcess 用户态模式启动 wireguard-go(保存资源引用)
func (m *WGManager) startUserModeWGProcess(deviceName string, privateKey wgtypes.Key, listenPort int) error {
tunDev, wgDev, err := m.startUserModeWGProcessWithRefs(deviceName, privateKey, listenPort, "", "native")
if err != nil {
return err
}
// 兼容旧接口,忽略返回值
_ = tunDev
_ = wgDev
return nil
}
// startUserModeWGProcessWithRefs 用户态模式启动 wireguard-go,返回资源引用
// meshMode 决定 Bind 来源:native 用 wireguard-go 默认 Bindenhanced 用 meshray-core 注册的自定义 Bind
func (m *WGManager) startUserModeWGProcessWithRefs(deviceName string, privateKey wgtypes.Key, listenPort int, networkID string, meshMode string) (tun.Device, *device.Device, error) {
m.logger.Info("使用用户态模式启动 WireGuard",
zap.String("device", deviceName),
zap.Int("port", listenPort),
zap.String("mesh_mode", meshMode))
// 1. 创建 TUN 设备并保存引用
tunDevice, err := tun.CreateTUN(deviceName, 1420)
if err != nil {
return nil, nil, fmt.Errorf("创建 TUN 设备失败:%w", err)
}
// 2. 创建 UDP bind:根据组网模式选择
// - 原生模式:wireguard-go 默认 Bind(标准直连,无需 meshray-core
// - 增强模式:优先使用 meshray-core 注册的自定义 Bind(接管收发,根治 Endpoint 旁路缺陷);
// 若默认构建未引入 meshray-core,则返回错误,增强模式不可用。
bindFactory, err := contract.GetBindFactory(meshMode == "enhanced")
if err != nil {
tunDevice.Close()
return nil, nil, fmt.Errorf("获取 Bind 工厂失败(增强模式需 meshray-core):%w", err)
}
bind, err := bindFactory(listenPort, networkID, m.logger)
if err != nil {
tunDevice.Close()
return nil, nil, fmt.Errorf("创建 Bind 失败:%w", err)
}
// 3. 创建 WireGuard device 并保存引用
logger := &device.Logger{
Verbosef: func(format string, args ...interface{}) {
m.logger.Debug(fmt.Sprintf(format, args...))
},
Errorf: func(format string, args ...interface{}) {
m.logger.Error(fmt.Sprintf(format, args...))
},
}
wgDevice := device.NewDevice(tunDevice, bind, logger)
// 4. 配置设备
config := fmt.Sprintf("private_key=%s\nlisten_port=%d\n",
privateKey.String(), listenPort)
if err := wgDevice.IpcSet(config); err != nil {
wgDevice.Close()
return nil, nil, fmt.Errorf("配置 WireGuard 设备失败:%w", err)
}
// 5. 启动设备
if err := wgDevice.Up(); err != nil {
wgDevice.Close()
return nil, nil, fmt.Errorf("启动 WireGuard 设备失败:%w", err)
}
m.logger.Info("用户态 WireGuard 启动成功",
zap.String("device", deviceName),
zap.Int("port", listenPort))
// 6. 返回资源引用
return tunDevice, wgDevice, nil
}
// truncatePublicKey 截断公钥用于日志显示(避免数组越界)
func truncatePublicKey(publicKey string) string {
if len(publicKey) <= 8 {
return publicKey
}
return publicKey[:8]
}
// bringUpDevice 启动设备(跨平台实现)
func (m *WGManager) bringUpDevice(deviceName string) error {
m.logger.Info("启动设备",
zap.String("device", deviceName))
var cmd *exec.Cmd
var output []byte
var err error
switch runtime.GOOS {
case "linux":
// Linux: ip link set up device
cmd = exec.Command("ip", "link", "set", "up", deviceName)
output, err = cmd.CombinedOutput()
case "windows":
// Windows: 用户态模式下 wireguard-go 会自动管理设备状态
// 这里不需要额外操作
m.logger.Debug("Windows 平台用户态模式无需手动启动设备")
return nil
case "darwin":
// macOS: ifconfig device up
cmd = exec.Command("ifconfig", deviceName, "up")
output, err = cmd.CombinedOutput()
default:
return fmt.Errorf("不支持的操作系统:%s", runtime.GOOS)
}
if err != nil {
return fmt.Errorf("启动设备失败 (%s): %w", string(output), err)
}
m.logger.Info("设备已启动",
zap.String("device", deviceName))
return nil
}
// cleanupDevice 清理残留设备(回滚用,跨平台实现)
func (m *WGManager) cleanupDevice(deviceName string) {
m.logger.Warn("清理残留设备",
zap.String("device", deviceName))
var cmd *exec.Cmd
var output []byte
switch runtime.GOOS {
case "linux":
// Linux: ip link delete device
cmd = exec.Command("ip", "link", "delete", deviceName)
output, _ = cmd.CombinedOutput()
case "windows":
// Windows: 无法通过命令行直接删除,需要用户手动操作
m.logger.Warn("Windows 平台需要通过 WireGuard 客户端删除设备",
zap.String("device", deviceName))
return
case "darwin":
// macOS: ifconfig device down delete
cmd = exec.Command("ifconfig", deviceName, "down")
_, _ = cmd.CombinedOutput()
// macOS 可能需要额外步骤,这里简化处理
default:
m.logger.Warn("不支持的操作系统,跳过清理",
zap.String("os", runtime.GOOS))
return
}
// 检查是否是"设备不存在"错误
if strings.Contains(string(output), "does not exist") ||
strings.Contains(string(output), "cannot find device") {
m.logger.Debug("设备不存在,无需清理",
zap.String("device", deviceName))
return
}
m.logger.Debug("设备清理完成",
zap.String("device", deviceName))
}