Files
Meshray-Manager/core/transport/relay.go
T
2026-06-30 15:14:37 +08:00

198 lines
5.2 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 transport
import (
"context"
"net"
"sync"
"go.uber.org/zap"
)
// OnDialTrigger 当从本地端口收到包但没有远端连接时触发
type OnDialTrigger func(peerKey string)
// Relay 数据转发器
// 负责从本地端口收包 → 查路由 → 通过 conn 发送
// 从 conn 收包 → 发到本地端口
type Relay struct {
plugin ProtocolPlugin
connMgr *ConnManager
localPorts map[uint32]net.PacketConn // route_id → local_port
lastAddr map[uint32]net.Addr // route_id → last wg source addr
portMu sync.RWMutex
logger *zap.Logger
OnDialTrigger OnDialTrigger // 拨号触发回调
}
// NewRelay 创建数据转发器
func NewRelay(plugin ProtocolPlugin, connMgr *ConnManager, logger *zap.Logger) *Relay {
return &Relay{
plugin: plugin,
connMgr: connMgr,
localPorts: make(map[uint32]net.PacketConn),
lastAddr: make(map[uint32]net.Addr),
logger: logger,
}
}
// RegisterLocalPort 注册本地端口(用于接收 WG 密文包)
func (r *Relay) RegisterLocalPort(routeID uint32, port net.PacketConn) {
r.portMu.Lock()
defer r.portMu.Unlock()
r.localPorts[routeID] = port
r.logger.Info("注册本地端口",
zap.Uint32("route_id", routeID),
zap.String("addr", port.LocalAddr().String()))
}
// UnregisterLocalPort 注销本地端口
func (r *Relay) UnregisterLocalPort(routeID uint32) {
r.portMu.Lock()
defer r.portMu.Unlock()
if port, ok := r.localPorts[routeID]; ok {
port.Close()
delete(r.localPorts, routeID)
delete(r.lastAddr, routeID)
r.logger.Info("注销本地端口", zap.Uint32("route_id", routeID))
}
}
// StartReadFromLocalPort 从本地端口读取 WG 密文包并转发(发送到远端)
func (r *Relay) StartReadFromLocalPort(ctx context.Context, routeID uint32, peerKey string) {
r.portMu.RLock()
port, ok := r.localPorts[routeID]
r.portMu.RUnlock()
if !ok {
r.logger.Warn("本地端口未注册", zap.Uint32("route_id", routeID))
return
}
go func() {
buf := make([]byte, 65535)
for {
select {
case <-ctx.Done():
return
default:
n, addr, err := port.ReadFrom(buf)
if err != nil {
// 检查是否是由于关闭引起的错误
select {
case <-ctx.Done():
return
default:
}
r.logger.Debug("读取本地端口失败",
zap.Uint32("route_id", routeID),
zap.Error(err))
return
}
// 记录 WG 的来源地址,以便后续把包发回去
r.portMu.Lock()
r.lastAddr[routeID] = addr
r.portMu.Unlock()
packet := buf[:n]
r.forwardOutgoing(ctx, packet, peerKey)
}
}
}()
r.logger.Info("启动本地端口读取协程",
zap.Uint32("route_id", routeID),
zap.String("peer_key", peerKey))
}
// StartReadFromRemoteConn 从远端连接读取数据并转发给本地监听端口(接收远端数据)
func (r *Relay) StartReadFromRemoteConn(ctx context.Context, peerKey string, conn net.Conn) {
if conn == nil {
return
}
go func() {
buf := make([]byte, 65535)
for {
select {
case <-ctx.Done():
return
default:
n, err := conn.Read(buf)
if err != nil {
r.logger.Debug("读取远端连接失败,停止读取协程",
zap.String("peer_key", peerKey),
zap.Error(err))
return
}
packet := buf[:n]
// 远端进来的包,需要根据 packet 里的索引转发给对应的 localPort
r.forwardIncoming(packet, peerKey)
}
}
}()
r.logger.Info("启动远端连接读取协程",
zap.String("peer_key", peerKey),
zap.String("addr", conn.RemoteAddr().String()))
}
// forwardOutgoing 处理发出去的包(Local -> Remote
func (r *Relay) forwardOutgoing(ctx context.Context, packet []byte, peerKey string) {
// 获取或触发建连
conn, ok := r.connMgr.Get(peerKey)
if !ok || conn == nil {
// 没有连接,触发拨号
if r.OnDialTrigger != nil {
r.OnDialTrigger(peerKey)
}
r.logger.Debug("尚未建立连接,包已丢弃,触发静默拨号", zap.String("peer_key", peerKey))
return
}
// 转发给远端
_, err := conn.Write(packet)
if err != nil {
r.logger.Debug("转发包到远端失败",
zap.String("peer_key", peerKey),
zap.Error(err))
}
}
// forwardIncoming 处理进来的包(Remote -> Local
func (r *Relay) forwardIncoming(packet []byte, _ string) {
// 1. 判断是否为控制包/数据包并提取 routeID
// 无论哪种 WG 包,前几位都是 routeID (receiver index)
routeID, err := r.plugin.ExtractRouteID(packet)
if err != nil {
r.logger.Debug("提取包内索引失败", zap.Error(err))
return
}
// 2. 这里的 routeID 是我们 RegisterLocalPort 时用的 ID
r.portMu.RLock()
port, ok := r.localPorts[routeID]
addr, addrOk := r.lastAddr[routeID]
r.portMu.RUnlock()
if !ok || port == nil {
r.logger.Debug("未找到转发目标的本地端口", zap.Uint32("route_id", routeID))
return
}
if !addrOk || addr == nil {
// 如果还没收到过 WG 的包,尝试发给 127.0.0.1:0 (通常不会成功,但作为 fallback)
// 实际上 WG 发送握手包后就会刷新 addr
addr = &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 0}
}
// 3. 转发给本地 WG
_, err = port.WriteTo(packet, addr)
if err != nil {
r.logger.Debug("转发给本地 WG 失败", zap.Uint32("route_id", routeID), zap.Error(err))
}
}