Files
OmniSocketGo/cmd/internal/server/udp_relay_test.go
2026-03-28 13:17:38 +08:00

292 lines
7.7 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 (
"net"
"strings"
"sync"
"testing"
"time"
kcp "github.com/xtaci/kcp-go/v5"
"omnisocketgo/cmd/internal/latencylog"
"omnisocketgo/cmd/internal/protocol"
"omnisocketgo/cmd/internal/transport"
)
// TestUDPRelayKCPForwardAndReturn 验证 KCP 通过 UDP relay 的完整双向转发路径:
// peer-b -> D(KCP hub) -> C(UDP relay) -> peer-a 以及反向。
func TestUDPRelayKCPForwardAndReturn(t *testing.T) {
// 启动 DKCP Hub
hub, hubAddr, hubCleanup := startKCPHubForRelay(t)
defer hubCleanup()
// 启动 CUDP Relayupstream 指向 D
relayAddr := startUDPRelay(t, hubAddr)
// peer-b 直连 DKCP
peerBConn := dialKCPPeer(t, hubAddr)
// peer-a 连 C通过 relay 间接连到 D
peerAConn := dialKCPPeer(t, relayAddr)
// 注册 peer-b
if err := peerBConn.Send(protocol.Message{
Type: protocol.MessageTypeRegister,
From: "peer-b",
To: protocol.ServerPeerID,
}); err != nil {
t.Fatalf("peerB register: %v", err)
}
// 注册 peer-a通过 relay
if err := peerAConn.Send(protocol.Message{
Type: protocol.MessageTypeRegister,
From: "peer-a",
To: protocol.ServerPeerID,
}); err != nil {
t.Fatalf("peerA register: %v", err)
}
waitForRelay(t, func() bool {
return hub.HasPeer("peer-a") && hub.HasPeer("peer-b")
}, "both peers to be registered")
// peer-b -> peer-a路径: B -> D -> C -> A
if err := peerBConn.Send(protocol.Message{
Type: protocol.MessageTypeText,
ID: 1,
From: "peer-b",
To: "peer-a",
Body: []byte("hello from peer-b"),
}); err != nil {
t.Fatalf("peerB send text: %v", err)
}
msg, err := peerAConn.Receive()
if err != nil {
t.Fatalf("peerA receive: %v", err)
}
if msg.Type != protocol.MessageTypeText {
t.Fatalf("message type = %s, want text", msg.Type)
}
if msg.From != "peer-b" {
t.Fatalf("message from = %s, want peer-b", msg.From)
}
if string(msg.Body) != "hello from peer-b" {
t.Fatalf("message body = %q, want %q", string(msg.Body), "hello from peer-b")
}
// peer-a -> peer-b路径: A -> C -> D -> B
if err := peerAConn.Send(protocol.Message{
Type: protocol.MessageTypeText,
ID: 2,
From: "peer-a",
To: "peer-b",
Body: []byte("reply from peer-单个 downstream peer 通过 relay 连到 KCP server”这条
链路是成立的转发逻辑本身没有明显的地址错误。cmd/internal/server/udp_relay.go 里就是原
样双向转发,下游来的包会记录 clientAddr 并写给上游,上游回来的包再写回这个 clientAddr。
关键代码在 cmd/internal/server/udp_relay.go:68 和 cmd/internal/server/udp_relay.go:89。
还有一个关键事实kcppeer 里那句 connected to ... as ... (KCP) 不能证明 peer-a 真的在
hub 注册成功a"),
}); err != nil {
t.Fatalf("peerA send text: %v", err)
}
msg2, err := peerBConn.Receive()
if err != nil {
t.Fatalf("peerB receive: %v", err)
}
if msg2.Type != protocol.MessageTypeText {
t.Fatalf("message type = %s, want text", msg2.Type)
}
if msg2.From != "peer-a" {
t.Fatalf("message from = %s, want peer-a", msg2.From)
}
if string(msg2.Body) != "reply from peer-a" {
t.Fatalf("message body = %q, want %q", string(msg2.Body), "reply from peer-a")
}
}
// TestUDPRelayKCPFileMessage 验证通过 relay 转发 KCP 文件消息。
func TestUDPRelayKCPFileMessage(t *testing.T) {
hub, hubAddr, hubCleanup := startKCPHubForRelay(t)
defer hubCleanup()
relayAddr := startUDPRelay(t, hubAddr)
peerBConn := dialKCPPeer(t, hubAddr)
peerAConn := dialKCPPeer(t, relayAddr)
_ = peerBConn.Send(protocol.Message{
Type: protocol.MessageTypeRegister,
From: "peer-b",
To: protocol.ServerPeerID,
})
_ = peerAConn.Send(protocol.Message{
Type: protocol.MessageTypeRegister,
From: "peer-a",
To: protocol.ServerPeerID,
})
waitForRelay(t, func() bool {
return hub.HasPeer("peer-a") && hub.HasPeer("peer-b")
}, "both peers to be registered")
if err := peerBConn.Send(protocol.Message{
Type: protocol.MessageTypeFile,
ID: 1,
From: "peer-b",
To: "peer-a",
FileName: "test.bin",
Body: []byte{0xDE, 0xAD, 0xBE, 0xEF},
}); err != nil {
t.Fatalf("peerB send file: %v", err)
}
msg, err := peerAConn.Receive()
if err != nil {
t.Fatalf("peerA receive: %v", err)
}
if msg.Type != protocol.MessageTypeFile {
t.Fatalf("message type = %s, want file", msg.Type)
}
if msg.FileName != "test.bin" {
t.Fatalf("file name = %q, want %q", msg.FileName, "test.bin")
}
if len(msg.Body) != 4 || msg.Body[0] != 0xDE {
t.Fatalf("file body mismatch: 单个 downstream peer 通过 relay 连到 KCP server”这条
链路是成立的转发逻辑本身没有明显的地址错误。cmd/internal/server/udp_relay.go 里就是原
样双向转发,下游来的包会记录 clientAddr 并写给上游,上游回来的包再写回这个 clientAddr。
关键代码在 cmd/internal/server/udp_relay.go:68 和 cmd/internal/server/udp_relay.go:89。
还有一个关键事实kcppeer 里那句 connected to ... as ... (KCP) 不能证明 peer-a 真的在
hub 注册成功got %v", msg.Body)
}
}
// startKCPHubForRelay 启动一个 KCP hub server返回 hub、监听地址和 cleanup 函数。
func startKCPHubForRelay(t *testing.T) (*KCPHub, string, func()) {
t.Helper()
hub := NewKCPHub()
listener, packetConn, err := transport.ListenKCPSessions("127.0.0.1:0", "", nil, latencylog.NodeRoleServer, "hub")
if err != nil {
t.Fatalf("ListenKCPSessions() error = %v", err)
}
var (
wg sync.WaitGroup
stop = make(chan struct{})
)
wg.Add(1)
go func() {
defer wg.Done()
for {
session, acceptErr := listener.AcceptKCP()
if acceptErr != nil {
select {
case <-stop:
return
default:
}
if strings.Contains(acceptErr.Error(), "closed") {
return
}
t.Errorf("AcceptKCP() error = %v", acceptErr)
return
}
wg.Add(1)
go func(sess *kcp.UDPSession) {
defer wg.Done()
if serveErr := hub.ServeSession(sess); serveErr != nil {
msg := serveErr.Error()
if !strings.Contains(msg, "closed") && !strings.Contains(msg, "broken pipe") {
t.Logf("hub.ServeSession() ended with %v", serveErr)
}
}
}(session)
}
}()
cleanup := func() {
close(stop)
_ = listener.Close()
_ = packetConn.Close()
wg.Wait()
}
return hub, listener.Addr().String(), cleanup
}
// dialKCPPeer 创建一条到指定地址的 KCP 连接,用于测试。
func dialKCPPeer(t *testing.T, serverAddr string) *transport.KCPConn {
t.Helper()
session, err := transport.DialKCPSession(serverAddr, "", "", nil, latencylog.NodeRolePeer, "test")
if err != nil {
t.Fatalf("DialKCPSession(%s) error = %v", serverAddr, err)
}
conn, err := transport.NewKCPConn(session)
if err != nil {
_ = session.Close()
t.Fatalf("NewKCPConn() error = %v", err)
}
t.Cleanup(func() {
_ = conn.Close()
})
return conn
}
// startUDPRelay 创建并启动一个 UDPRelay返回其监听地址字符串。
func startUDPRelay(t *testing.T, upstreamAddr string) string {
t.Helper()
addr, err := net.ResolveUDPAddr("udp", "127.0.0.1:0")
if err != nil {
t.Fatalf("ResolveUDPAddr() error = %v", err)
}
conn, err := net.ListenUDP("udp", addr)
if err != nil {
t.Fatalf("ListenUDP() error = %v", err)
}
relay, err := NewUDPRelay(conn, upstreamAddr)
if err != nil {
_ = conn.Close()
t.Fatalf("NewUDPRelay() error = %v", err)
}
go func() {
_ = relay.Serve()
}()
t.Cleanup(func() {
_ = relay.Close()
})
return conn.LocalAddr().String()
}
// waitForRelay 轮询等待条件满足,超时则 fail。
func waitForRelay(t *testing.T, condition func() bool, description string) {
t.Helper()
deadline := time.Now().Add(2 * time.Second)
for time.Now().Before(deadline) {
if condition() {
return
}
time.Sleep(10 * time.Millisecond)
}
t.Fatalf("timed out waiting for %s", description)
}