# MeshRay 关键技术详解 ## 目录 1. [雪花算法 ID 生成](#1-雪花算法 id 生成) 2. [Conn.Bind 模型与 9 层传输](#2-connbind-模型与 9 层传输) 3. [Mesh 中继架构](#3-mesh-中继架构) 4. [ExternalService 服务市场](#4-externalservice-服务市场) 5. [MeshSeed 安全机制](#5-meshseed-安全机制) --- ## 1. 雪花算法 ID 生成 ### 算法结构 ``` 64 位整数: ┌─┬──────────────────────┬────────────┬─────────────┐ │0│ 41 bits 时间戳 │ 10 bits │ 12 bits │ │ │ (毫秒级,69 年) │ 节点 ID │ 序列号 │ └─┴──────────────────────┴────────────┴─────────────┘ ↑ ↑ ↑ 符号位 机器标识 单毫秒内自增 ``` ### Go 实现 ```go package snowflake import ( "sync" "time" ) type Snowflake struct { mu sync.Mutex lastStamp int64 // 上次生成 ID 的时间戳 sequence int64 // 当前毫秒内的序列号 machineID int64 // 机器 ID(0-1023) } func NewSnowflake(machineID int64) *Snowflake { return &Snowflake{ machineID: machineID, lastStamp: -1, } } func (s *Snowflake) Generate() int64 { s.mu.Lock() defer s.mu.Unlock() stamp := time.Now().UnixMilli() if stamp < s.lastStamp { panic("时钟回拨") } if stamp == s.lastStamp { // 同一毫秒内,序列号自增 s.sequence = (s.sequence + 1) & 0xFFF if s.sequence == 0 { // 序列号溢出,等待下一毫秒 for stamp <= s.lastStamp { stamp = time.Now().UnixMilli() } } } else { // 新毫秒,重置序列号 s.sequence = 0 } s.lastStamp = stamp // 组合 ID id := (stamp << 22) | (s.machineID << 12) | s.sequence return id } ``` ### 前端精度问题与解决方案 **问题现象**: ``javascript // ❌ JavaScript 无法精确表示 20 位数字 const backendID = 18446744073709551615; console.log(backendID); // 输出:18446744073709552000(精度丢失!) Number.MAX_SAFE_INTEGER = 9007199254740991 // 仅 16 位 ``` **解决方案:后端使用 string 传输** ``go // internal/model/network.go type Network struct { ID string `gorm:"primaryKey;type:varchar(36)" json:"id"` ID_UInt64 uint64 `gorm:"column:id_uint64;not null" json:"-"` } // BeforeCreate Hook:uint64 → string func (n *Network) BeforeCreate(tx *gorm.DB) error { n.ID = strconv.FormatUint(n.ID_UInt64, 10) return nil } // AfterFind Hook:string ← uint64 func (n *Network) AfterFind(tx *gorm.DB) error { n.ID_UInt64, _ = strconv.ParseUint(n.ID, 10, 64) return nil } ``` **前端使用**: ``typescript // 前端始终处理字符串 interface Network { id: string // "18446744073709551615" name: string } // 不需要任何特殊处理 const network = await api.getNetwork('18446744073709551615') console.log(network.id) // "18446744073709551615" ✅ ``` --- ## 2. Conn.Bind 模型与 9 层传输 ### Bind 接口定义 WireGuard 官方库提供的网络层抽象接口: ``` // golang.zx2c4.com/wireguard/conn/bind.go type Bind interface { Open(port uint16) (fns []ReceiveFunc, err error) Close() error SetMark(mark uint32) error Send(buffs [][]byte, ep Endpoint) error ParseEndpoint(addr string) (Endpoint, error) } ``` **接口契约说明**: | 方法 | 调用时机 | 返回值含义 | |------|---------|------------| | `Open` | WG 设备启动时 | `fns` 数组长度 = CPU 核心数(每核一个接收函数) | | `Close` | WG 设备停止时 | 必须释放所有资源(端口、goroutine) | | `SetMark` | Linux 特有 | 用于 iptables 路由策略 | | `Send` | WG 要发送密文包 | 返回 nil 表示已发出,非 nil 触发重试 | | `ParseEndpoint` | 添加 Peer 时 | 将字符串 "host:port" 转为 Endpoint 对象 | --- ### TransportFactory 接口定义 每种传输方式实现 `TransportFactory` 接口: ``` // core/connect/factory.go type TransportFactory interface { // Layer 返回所属传输层(用于优先级排序) Layer() Layer // Name 返回名称(用于日志和监控) Name() string // Dial 拨号建立连接(核心方法) Dial(ctx context.Context, config *DialConfig) (net.Conn, error) } // DialConfig 拨号配置(所有传输方式共享) type DialConfig struct { LocalAddr string // 本地地址 RemoteAddr string // 对端地址 PeerID string // 对端设备 ID Timeout time.Duration // 超时时间 STUNServers []string // STUN 服务器列表(P2P 需要) TURNConfig *TURNConfig // TURN 配置(TURN 需要) } ``` **接口契约说明**: | 方法 | 作用 | 实现要求 | |------|------|---------| | `Layer()` | 返回所属层级 | 决定在 9 层中的位置(LayerDirectUDP/LayerTURN_UDP 等) | | `Name()` | 人类可读的名称 | 用于日志输出:"Direct-UDP" / "TURN-TCP" / "WS" | | `Dial()` | 建立连接 | 返回 net.Conn 接口,封装底层协议细节 | --- ### CoreBind 实现 ``` // core/core_bind.go type CoreBind struct { conn.Bind routes map[RouteID]net.Conn // Route ID → 实际链路 mu sync.RWMutex } // Send 截获 WG 密文包,选择链路发送 func (cb *CoreBind) Send(buffs [][]byte, ep conn.Endpoint) error { cb.mu.RLock() defer cb.mu.RUnlock() packet := buffs[0] // 数据包分类 msgType := packet[0] if msgType == 1 || msgType == 2 || msgType == 3 { // 控制包(握手/Cookie):按已建链路透传 return cb.sendControlPacket(packet, ep) } if msgType == 4 { // 数据包:提取 Route ID 并查表 routeID := binary.LittleEndian.Uint32(packet[4:8]) link := cb.routes[routeID] if link == nil { return fmt.Errorf("no route for %d", routeID) } // 通过选定链路发送 _, err := link.Write(packet) return err } return nil } ``` ### 9 层传输工厂模式 每种传输方式实现 `TransportFactory` 接口: ``` // core/connect/factory.go type TransportFactory interface { Layer() Layer Name() string Dial(ctx context.Context, config *DialConfig) (net.Conn, error) } ``` #### 示例 1:Direct-UDP(STUN P2P) ``` // core/connect/p2p_factory.go type P2PFactory struct { stunServers []string logger *zap.Logger } func (f *P2PFactory) Layer() Layer { return LayerDirectUDP } func (f *P2PFactory) Name() string { return "Direct-UDP" } func (f *P2PFactory) Dial(ctx context.Context, config *DialConfig) (net.Conn, error) { // 1. STUN 打洞获取公网地址 publicAddr, err := stun.GetPublicAddr(f.stunServers...) // 2. 交换候选地址(通过 ctr 信令中转) localCandidate := Candidate{Addr: publicAddr, Priority: 100} remoteCandidate := f.exchangeCandidates(config.PeerID, localCandidate) // 3. 建立 P2P 连接 conn, err := net.DialUDP("udp", nil, &remoteCandidate.Addr) // 4. 包装为 P2PConn(带保活、NAT 刷新) return NewP2PConn(conn, f.logger), nil } ``` #### 示例 2:TURN-UDP ``` // core/connect/turn_factory.go type TURNFactory struct { serverConfig *TURNConfig logger *zap.Logger } func (f *TURNFactory) Dial(ctx context.Context, config *DialConfig) (net.Conn, error) { // 1. 连接 TURN 服务器 turnConn, err := turnclient.New(config.ServerAddr, config.Username, config.Password) // 2. 创建 Permission(允许对端 IP) turnConn.CreatePermission(config.RemoteAddr) // 3. 返回 TURNConn(封装了 Allocate/Send/Refresh) return NewTURNConn(turnConn, f.logger), nil } ``` #### 示例 3:FakeTCP(UDP 伪装 TCP) ``` // core/connect/fake_tcp_factory.go type FakeTCPFactory struct { logger *zap.Logger } func (f *FakeTCPFactory) Dial(ctx context.Context, config *DialConfig) (net.Conn, error) { // 1. 建立 TCP 连接 tcpConn, err := net.DialTCP("tcp", nil, config.RemoteAddr) // 2. 将 UDP 包封装为 TCP 流(带长度前缀) // [4 bytes length][UDP packet...] return NewFakeTCPConn(tcpConn, f.logger), nil } ``` ### 策略调度器 ``` // core/connect/strategy.go type StrategyScheduler struct { factories map[Layer]TransportFactory priority []Layer // 优先级顺序 } // TryAll 按优先级尝试所有层,直到成功 func (s *StrategyScheduler) TryAll(ctx context.Context, config *DialConfig) (net.Conn, error) { var lastErr error for _, layer := range s.priority { factory := s.factories[layer] if factory == nil { continue } conn, err := factory.Dial(ctx, config) if err == nil { // 成功,记录日志并返回 s.logger.Info("connected via " + factory.Name()) return conn, nil } lastErr = err s.logger.Debug(layer.String() + " failed: " + err.Error()) } return nil, fmt.Errorf("all layers failed: %w", lastErr) } ``` ### 传输状态机 ``` 初始状态 ──→ Layer 1 (Direct-UDP) ├─ 成功 → 保持(持续监控) ├─ 超时 (500ms) → Layer 2 (FakeTCP) └─ 丢包率 > 10% → Layer 2 Layer 2 (FakeTCP) ──→ Layer 3 (RealTCP) ├─ 成功 → 保持 └─ 失败 → Layer 4 ... 依次降级 ... Layer 8 (WS/WSS) ──→ 降级完成 → 启动恢复探测 │ ├─ 每 30 秒探测 Layer 1 ├─ 连续 2 次成功 → 切回 Layer 1 └─ 失败 → 继续等待下一轮探测 ``` **状态转换规则**: | 当前状态 | 触发条件 | 目标状态 | |----------|---------|---------| | Layer N | 握手成功 | 保持 Layer N | | Layer N | 单包超时 > 500ms | Layer N+1 | | Layer N | 丢包率 > 10% (10s 窗口) | Layer N+1 | | Layer N+1 | 建立成功 | 保持 Layer N+1 | | Layer 9 (WS) | 恢复探测成功×2 | Layer 1 | | Layer 9 (WS) | 恢复探测失败 | 保持 Layer 9 | ### 自动切换机制 ``` // 降级触发条件 type FallbackPolicy struct { PacketTimeout time.Duration // 单包超时:500ms LossRateWindow time.Duration // 丢包率窗口:10s LossRateThreshold float64 // 丢包率阈值:10% } // 恢复探测 func (s *StrategyScheduler) StartRecoveryProbe(currentLayer Layer) { ticker := time.NewTicker(30 * time.Second) go func() { for range ticker.C { // 每隔 30 秒向 Layer 1 发送探测包 success := s.probeLayer(LayerDirectUDP) if success { // 连续 2 次成功 → 直接切回 Layer 1 s.switchTo(LayerDirectUDP) break } } }() } ``` --- ## 3. Mesh 中继架构 ### 核心设计原则 **Mesh 中继不是 Core 的功能,而是 WG 设备层的静态路由拓扑配置。** ### 数据流向 ``` A (10.0.0.2) ──→ C (10.0.0.1) ──→ B (10.0.0.3) ↑ 中继节点 WG 视角: A 的 WG 配置:Peer C, AllowedIPs = 10.0.0.0/24 B 的 WG 配置:Peer C, AllowedIPs = 10.0.0.0/24 C 的 WG 配置:Peer A (AllowedIPs=10.0.0.2/32), Peer B (AllowedIPs=10.0.0.3/32) Core 视角: A.Core: Direct-UDP → C.Core B.Core: Direct-UDP → C.Core C.Core: 无感知,只是转发密文包 ``` ### ctr 配置中继节点 ``` // internal/ctr/wg.go func (c *Ctr) ConfigureRelay(relayDevice *model.Device, peers []*model.Device) error { wgConfig := wgtypes.Config{} // 为中继节点配置所有普通节点作为 Peer for _, peer := range peers { wgConfig.Peers = append(wgConfig.Peers, wgtypes.PeerConfig{ PublicKey: peer.PublicKey, AllowedIPs: []net.IPNet{ peer.VirtualIP, // 10.0.0.3/32 peer.LocalSubnet, // 192.168.1.0/24(可选) }, }) } // 调用 wgctrl 应用配置 return c.wgClient.ConfigureDevice(relayDevice.WGInterface, wgConfig) } ``` ### 系统要求(重要) **启用 Mesh 中继前,必须在操作系统层面开启 IP 转发**: #### Linux 系统 ``` # 1. 临时开启 IPv4 转发 sudo sysctl -w net.ipv4.ip_forward=1 # 2. 永久开启 echo "net.ipv4.ip_forward = 1" >> /etc/sysctl.conf sysctl -p # 3. 配置 iptables NAT 转发(必需!) sudo iptables -t nat -A POSTROUTING -o wg0 -j MASQUERADE sudo iptables -A FORWARD -i wg0 -j ACCEPT sudo iptables -A FORWARD -o wg0 -j ACCEPT # 4. 保存 iptables 规则 sudo apt install iptables-persistent sudo netfilter-persistent save ``` #### Docker 容器 ``` docker run \ --privileged \ # 必需:特权模式 --sysctl net.ipv4.ip_forward=1 \ # 必需:开启 IP 转发 --cap-add=NET_ADMIN \ # 推荐:网络管理权限 meshray-node:latest ``` --- ## 4. ExternalService 服务市场 ### 架构设计理念 **为什么需要三层架构?** | 问题 | 单层方案 | 三层方案 | |------|---------|----------| | **配置千差万别** | ❌ 需要 N 张表 | ✅ JSON 一张表 | | **如何防止乱填** | ❌ 手动校验每个字段 | ✅ Schema 自动验证 | | **前端如何知道填什么** | ❌ 硬编码表单 | ✅ 动态渲染 | | **后端类型安全** | ❌ map[string]interface{} | ✅ Go Struct | ### 三层架构详解 #### 第一层:JSON 存储 ``` type ExternalService struct { ID string `gorm:"primaryKey;type:varchar(36)" json:"id"` Category string `gorm:"type:varchar(32);index" json:"category"` ServiceType string `gorm:"type:varchar(64);index" json:"serviceType"` Name string `gorm:"type:varchar(64)" json:"name"` Tags string `gorm:"type:varchar(255)" json:"tags"` // JSON 数组 Config string `gorm:"type:text;not null" json:"config"` // ← JSON 字符串 Enabled bool `gorm:"default:true;index" json:"enabled"` } ``` **示例数据**: ``` { "category": "networking", "serviceType": "frp_server", "name": "我的 FRP 服务器", "config": "{\"server_addr\":\"frp.example.com\",\"token\":\"xxx\"}" } ``` #### 第二层:Schema 验证 **FRP 服务器配置 Schema**: ``` func (p *FRPServerProvider) ConfigSchema() string { return `{ "type": "object", "required": ["server_addr", "token"], "properties": { "server_addr": { "type": "string", "title": "FRP 服务器地址" }, "server_port": { "type": "integer", "default": 7000, "minimum": 1, "maximum": 65535 }, "token": { "type": "string", "format": "password", "minLength": 8 }, "protocol": { "type": "string", "enum": ["tcp", "kcp", "quic"], "default": "tcp" } } }` } ``` #### 第三层:Struct 类型转换 ``` type FRPConfig struct { ServerAddr string `json:"server_addr"` ServerPort int `json:"server_port,omitempty"` Token string `json:"token"` Protocol string `json:"protocol,omitempty"` } func (p *FRPServerProvider) ValidateConfig(configJSON string) error { var cfg FRPConfig json.Unmarshal([]byte(configJSON), &cfg) if cfg.ServerAddr == "" { return errors.New("服务器地址不能为空") } if cfg.Token == "" { return errors.New("认证令牌不能为空") } if len(cfg.Token) < 8 { return errors.New("认证令牌长度至少 8 位") } return nil } func (p *FRPServerProvider) BuildConfig(configJSON string) (*FRPConfig, error) { var cfg FRPConfig json.Unmarshal([]byte(configJSON), &cfg) // 设置默认值 if cfg.ServerPort == 0 { cfg.ServerPort = 7000 } if cfg.Protocol == "" { cfg.Protocol = "tcp" } return &cfg, nil } ``` ### 复杂场景支持 #### TURN 服务器多种认证方式 ``` type TURNConfig struct { ServerAddr string `json:"server_addr"` Realm string `json:"realm,omitempty"` // 认证方式(互斥) AuthType string `json:"auth_type"` // long_term | short_term | auth_secret LongTerm *LongTermAuth `json:"long_term,omitempty"` ShortTerm *ShortTermAuth `json:"short_term,omitempty"` } type LongTermAuth struct { Username string `json:"username"` Password string `json:"password"` } type ShortTermAuth struct { Username string `json:"username"` AuthSecret string `json:"auth_secret"` ExpiresIn int `json:"expires_in,omitempty"` } // GetCredentials 动态获取凭证 func (p *TURNServerProvider) GetCredentials(configJSON string) (Credentials, error) { var cfg TURNConfig json.Unmarshal([]byte(configJSON), &cfg) switch cfg.AuthType { case "long_term": return &LongTermCredentials{ Username: cfg.LongTerm.Username, Password: cfg.LongTerm.Password, }, nil case "short_term": // 动态生成短期凭证(HMAC-SHA1) return p.generateShortTermCredentials(cfg.ShortTerm) case "auth_secret": return p.generateAuthSecretCredentials(cfg.AuthSecret) default: return nil, errors.New("不支持的认证方式") } } ``` #### WS 隧道多种部署方式 ``` type WSTunnelConfig struct { // 服务器类型(仅用于 UI 分类) ServerType string `json:"server_type"` // coturn | nginx | standalone // 统一的 WebSocket 端点 EndpointType string `json:"endpoint_type"` // ws | wss Endpoint string `json:"endpoint"` // ws://example.com:80/ws // 是否关联 STUN(如果是 coturn) UseCoturnSTUN bool `json:"use_coturn_stun"` STUNEndpoint string `json:"stun_endpoint,omitempty"` } func (p *WSTunnelProvider) BuildTransportConfig(configJSON string) (*TransportConfig, error) { var cfg WSTunnelConfig json.Unmarshal([]byte(configJSON), &cfg) u, _ := url.Parse(cfg.Endpoint) return &TransportConfig{ Type: "ws_tunnel", Protocol: u.Scheme, Host: u.Host, Path: u.Path, // 如果是 coturn 且启用了 STUN,自动注入 STUN 配置 STUNServers: func() []string { if cfg.UseCoturnSTUN && cfg.STUNEndpoint != "" { return []string{cfg.STUNEndpoint} } return nil }(), }, nil } ``` --- ## 5. MeshSeed 安全机制 ### 双层安全设计 ``` ┌─────────────────────────────────────┐ │ 外层:AES-256-GCM 加密 │ │ - 保护机密性 │ │ - 防止偷看 DNS 记录 │ │ - 密钥派生:SHA256("meshray-ddns" + NetworkSecret) │ └─────────────────────────────────────┘ ↓ 包裹着 ┌─────────────────────────────────────┐ │ 内层:Ed25519 签名 │ │ - 保护完整性与来源 │ │ - 防止中间人篡改 │ │ - 公钥从 seed.IssuerNodeID 解析 │ └─────────────────────────────────────┘ ``` ### 加密流程 ``` // pkg/meshseed/crypto.go // 1. 派生 AES-256-GCM 密钥 func deriveKey(networkSecret string) []byte { hash := sha256.Sum256([]byte("meshray-ddns" + networkSecret)) return hash[:] } // 2. 加密 MeshSeed func Encrypt(seed *MeshSeed, networkSecret string) (string, error) { // 签名(如果还没有) if len(seed.Signature) == 0 { return "", fmt.Errorf("MeshSeed 未签名") } // 序列化 plaintext, _ := json.Marshal(seed) // 派生密钥 key := deriveKey(networkSecret) // AES-GCM 加密 block, _ := aes.NewCipher(key) gcm, _ := cipher.NewGCM(block) nonce := make([]byte, gcm.NonceSize()) rand.Read(nonce) ciphertext := gcm.Seal(nonce, nonce, plaintext, nil) // Base64URL 编码 return base64.URLEncoding.EncodeToString(ciphertext), nil } // 3. 解密 MeshSeed func Decrypt(encrypted string, networkSecret string) (*MeshSeed, error) { // Base64URL 解码 ciphertext, _ := base64.URLEncoding.DecodeString(encrypted) // 派生密钥 key := deriveKey(networkSecret) // AES-GCM 解密 block, _ := aes.NewCipher(key) gcm, _ := cipher.NewGCM(block) nonceSize := gcm.NonceSize() nonce, ciphertextBytes := ciphertext[:nonceSize], ciphertext[nonceSize:] plaintext, _ := gcm.Open(nil, nonce, ciphertextBytes, nil) // 反序列化 var seed MeshSeed json.Unmarshal(plaintext, &seed) return &seed, nil } ``` ### DDNS TXT 记录格式 ``` # DNS TXT 记录 _meshray.home.example.com. IN TXT "meshray-ddns:" # 数据结构 base64_url_encoded_data = nonce (12 字节) + ciphertext + tag (16 字节) ``` ### 完整使用流程 #### Publish 发布 MeshSeed ``` // internal/provider/sync/libdns_adapter.go func (a *LibdnsAdapter) Publish(configJSON string, seed *meshseed.MeshSeed) error { // 1. 解析配置 var cfg LibdnsConfig json.Unmarshal([]byte(configJSON), &cfg) // 2. 加密 MeshSeed encrypted, err := meshseed.Encrypt(seed, seed.NetworkSecret) if err != nil { return fmt.Errorf("加密失败:%w", err) } // 3. 构造 TXT 记录值 txtValue := "meshray-ddns:" + encrypted // 4. 写入 DNS TXT 记录 ctx := context.Background() _, err = a.appender.AppendRecords(ctx, cfg.Domain, []libdns.Record{ libdns.RR{ Type: "TXT", Name: cfg.TXTRecordName, Data: txtValue, TTL: time.Duration(cfg.TTL) * time.Second, }, }) return err } ``` #### Fetch 拉取 MeshSeed ``` func (a *LibdnsAdapter) Fetch(configJSON string, networkID uint64) (*meshseed.MeshSeed, error) { // 1. 解析配置 var cfg LibdnsConfig json.Unmarshal([]byte(configJSON), &cfg) // 2. 读取 DNS TXT 记录 ctx := context.Background() records, _ := a.getter.GetRecords(ctx, cfg.Domain) // 3. 查找匹配的记录 for _, record := range records { rr := record.RR() if rr.Type != "TXT" || rr.Name != cfg.TXTRecordName { continue } // 4. 提取加密数据 txtValue := rr.Data encrypted := strings.TrimPrefix(txtValue, "meshray-ddns:") // 5. 解密 MeshSeed(需要 NetworkSecret) seed, err := meshseed.Decrypt(encrypted, networkSecret) if err != nil { continue // 密钥不匹配或数据被篡改 } // 6. 验证 NetworkID if seed.NetworkID != networkID { continue } // 7. Ed25519 验签 if err := meshseed.Verify(seed, nil); err != nil { continue // 签名无效 } return seed, nil } return nil, fmt.Errorf("未找到 MeshSeed 记录") } ``` --- *最后更新:v2.1.0*