Files
OmniSocketGo/cmd/internal/server/udp_relay.go
2026-03-27 23:03:00 +08:00

128 lines
3.0 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 server
import (
"fmt"
"log"
"net"
"sync"
"omnisocketgo/cmd/internal/transport"
)
// UDPRelay 负责在固定远端与多个客户端之间双向透明转发 KCP UDP datagram。
type UDPRelay struct {
conn net.PacketConn
remote *net.UDPAddr
mu sync.RWMutex
clients map[uint32]*net.UDPAddr
}
// NewUDPRelay 创建一个绑定到给定 PacketConn 的透明 UDP relay。
func NewUDPRelay(conn net.PacketConn, remote *net.UDPAddr) (*UDPRelay, error) {
if conn == nil {
return nil, fmt.Errorf("server: nil udp relay conn")
}
if remote == nil {
return nil, fmt.Errorf("server: nil udp relay remote")
}
return &UDPRelay{
conn: conn,
remote: cloneUDPAddr(remote),
clients: make(map[uint32]*net.UDPAddr),
}, nil
}
// Serve 持续双向转发原始 UDP datagram不解析业务消息。
func (r *UDPRelay) Serve() error {
buffer := make([]byte, 64*1024)
for {
n, addr, err := r.conn.ReadFrom(buffer)
if err != nil {
if isExpectedRelayServeExit(err) {
return nil
}
return fmt.Errorf("server: udp relay read packet: %w", err)
}
udpAddr, ok := addr.(*net.UDPAddr)
if !ok {
log.Printf("udp relay dropped packet from non-udp addr %T", addr)
continue
}
payload := append([]byte(nil), buffer[:n]...)
if sameUDPAddr(udpAddr, r.remote) {
if err := r.forwardRemotePacket(payload); err != nil {
log.Printf("udp relay failed forwarding remote packet from %s: %v", udpAddr, err)
}
continue
}
if err := r.forwardClientPacket(udpAddr, payload); err != nil {
log.Printf("udp relay failed forwarding client packet from %s: %v", udpAddr, err)
}
}
}
func (r *UDPRelay) forwardClientPacket(addr *net.UDPAddr, payload []byte) error {
convID, ok := transport.ParseKCPConversationID(payload)
if !ok {
return fmt.Errorf("missing kcp conversation id")
}
r.mu.Lock()
r.clients[convID] = cloneUDPAddr(addr)
r.mu.Unlock()
if _, err := r.conn.WriteTo(payload, r.remote); err != nil {
return fmt.Errorf("write conv %d to remote %s: %w", convID, r.remote, err)
}
return nil
}
func (r *UDPRelay) forwardRemotePacket(payload []byte) error {
convID, ok := transport.ParseKCPConversationID(payload)
if !ok {
return fmt.Errorf("missing kcp conversation id")
}
r.mu.RLock()
clientAddr := cloneUDPAddr(r.clients[convID])
r.mu.RUnlock()
if clientAddr == nil {
return fmt.Errorf("unknown client for conv %d", convID)
}
if _, err := r.conn.WriteTo(payload, clientAddr); err != nil {
return fmt.Errorf("write conv %d to client %s: %w", convID, clientAddr, err)
}
return nil
}
func cloneUDPAddr(addr *net.UDPAddr) *net.UDPAddr {
if addr == nil {
return nil
}
ipCopy := make([]byte, len(addr.IP))
copy(ipCopy, addr.IP)
return &net.UDPAddr{
IP: ipCopy,
Port: addr.Port,
Zone: addr.Zone,
}
}
func sameUDPAddr(left, right *net.UDPAddr) bool {
if left == nil || right == nil {
return left == right
}
if left.Port != right.Port || left.Zone != right.Zone {
return false
}
return left.IP.Equal(right.IP)
}