Core modules (M1-M6): - pkg/protocol: message format, encoding, NAT type enums - pkg/config: server/client config structs, env vars, validation - pkg/auth: CRC64 token, TOTP gen/verify, one-time relay tokens - pkg/nat: UDP/TCP STUN client and server - pkg/signal: WSS message dispatch, sync request/response - pkg/punch: UDP/TCP hole punching + priority chain - pkg/mux: stream multiplexer (7B frame: StreamID+Flags+Len) - pkg/tunnel: mux-based port forwarding with stats - pkg/relay: relay manager with TOTP auth + session bridging - internal/server: signaling server (login/heartbeat/report/coordinator) - internal/client: client (NAT detect/login/punch/relay/reconnect) - cmd/inp2ps + cmd/inp2pc: main entrypoints with graceful shutdown All tests pass: 16 tests across 5 packages Code: 3559 lines core + 861 lines tests = 19 source files
152 lines
3.7 KiB
Go
152 lines
3.7 KiB
Go
package server
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
"net/http"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/openp2p-cn/inp2p/pkg/config"
|
|
"github.com/openp2p-cn/inp2p/pkg/nat"
|
|
"github.com/openp2p-cn/inp2p/pkg/protocol"
|
|
"github.com/openp2p-cn/inp2p/pkg/signal"
|
|
"github.com/gorilla/websocket"
|
|
)
|
|
|
|
func TestLoginFlow(t *testing.T) {
|
|
// Start server
|
|
cfg := config.DefaultServerConfig()
|
|
cfg.WSPort = 29300
|
|
cfg.Token = 999
|
|
|
|
srv := New(cfg)
|
|
mux := http.NewServeMux()
|
|
mux.HandleFunc("/ws", srv.HandleWS)
|
|
go http.ListenAndServe(fmt.Sprintf(":%d", cfg.WSPort), mux)
|
|
time.Sleep(200 * time.Millisecond)
|
|
|
|
// Connect as client manually
|
|
ws, _, err := websocket.DefaultDialer.Dial(fmt.Sprintf("ws://127.0.0.1:%d/ws", cfg.WSPort), nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
conn := signal.NewConn(ws)
|
|
defer conn.Close()
|
|
|
|
// Start read loop in background
|
|
go conn.ReadLoop()
|
|
|
|
// Send login
|
|
loginReq := protocol.LoginReq{
|
|
Node: "testNode",
|
|
Token: 999,
|
|
Version: "test",
|
|
NATType: protocol.NATCone,
|
|
}
|
|
|
|
rspData, err := conn.Request(
|
|
protocol.MsgLogin, protocol.SubLoginReq, loginReq,
|
|
protocol.MsgLogin, protocol.SubLoginRsp,
|
|
5*time.Second,
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("login request failed: %v", err)
|
|
}
|
|
|
|
var rsp protocol.LoginRsp
|
|
protocol.DecodePayload(rspData, &rsp)
|
|
if rsp.Error != 0 {
|
|
t.Fatalf("login error: %d %s", rsp.Error, rsp.Detail)
|
|
}
|
|
log.Printf("Login OK: node=%s", rsp.Node)
|
|
|
|
// Verify node is registered
|
|
time.Sleep(100 * time.Millisecond)
|
|
nodes := srv.GetOnlineNodes()
|
|
if len(nodes) != 1 {
|
|
t.Fatalf("expected 1 node, got %d", len(nodes))
|
|
}
|
|
if nodes[0].Name != "testNode" {
|
|
t.Fatalf("expected testNode, got %s", nodes[0].Name)
|
|
}
|
|
|
|
srv.Stop()
|
|
}
|
|
|
|
func TestTwoClientsWithSTUN(t *testing.T) {
|
|
cfg := config.DefaultServerConfig()
|
|
cfg.WSPort = 29301
|
|
cfg.STUNUDP1 = 29382
|
|
cfg.STUNUDP2 = 29384
|
|
cfg.STUNTCP1 = 29380
|
|
cfg.STUNTCP2 = 29381
|
|
cfg.Token = 888
|
|
|
|
// STUN
|
|
stunQuit := make(chan struct{})
|
|
defer close(stunQuit)
|
|
go nat.ServeUDPSTUN(cfg.STUNUDP1, stunQuit)
|
|
go nat.ServeUDPSTUN(cfg.STUNUDP2, stunQuit)
|
|
go nat.ServeTCPSTUN(cfg.STUNTCP1, stunQuit)
|
|
go nat.ServeTCPSTUN(cfg.STUNTCP2, stunQuit)
|
|
|
|
srv := New(cfg)
|
|
srv.StartCleanup()
|
|
mux := http.NewServeMux()
|
|
mux.HandleFunc("/ws", srv.HandleWS)
|
|
go http.ListenAndServe(fmt.Sprintf(":%d", cfg.WSPort), mux)
|
|
time.Sleep(300 * time.Millisecond)
|
|
|
|
// NAT detect
|
|
natResult := nat.Detect("127.0.0.1", cfg.STUNUDP1, cfg.STUNUDP2, cfg.STUNTCP1, cfg.STUNTCP2)
|
|
log.Printf("NAT: type=%s publicIP=%s", natResult.Type, natResult.PublicIP)
|
|
|
|
// Client A
|
|
connectClient := func(name string, relay bool) *signal.Conn {
|
|
ws, _, err := websocket.DefaultDialer.Dial(fmt.Sprintf("ws://127.0.0.1:%d/ws", cfg.WSPort), nil)
|
|
if err != nil {
|
|
t.Fatalf("dial %s: %v", name, err)
|
|
}
|
|
conn := signal.NewConn(ws)
|
|
go conn.ReadLoop()
|
|
|
|
rspData, err := conn.Request(
|
|
protocol.MsgLogin, protocol.SubLoginReq,
|
|
protocol.LoginReq{Node: name, Token: 888, Version: "test", NATType: natResult.Type, RelayEnabled: relay},
|
|
protocol.MsgLogin, protocol.SubLoginRsp,
|
|
5*time.Second,
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("login %s: %v", name, err)
|
|
}
|
|
var rsp protocol.LoginRsp
|
|
protocol.DecodePayload(rspData, &rsp)
|
|
if rsp.Error != 0 {
|
|
t.Fatalf("login %s error: %s", name, rsp.Detail)
|
|
}
|
|
log.Printf("%s login ok", name)
|
|
return conn
|
|
}
|
|
|
|
connA := connectClient("nodeA", true)
|
|
defer connA.Close()
|
|
connB := connectClient("nodeB", false)
|
|
defer connB.Close()
|
|
|
|
time.Sleep(200 * time.Millisecond)
|
|
nodes := srv.GetOnlineNodes()
|
|
if len(nodes) != 2 {
|
|
t.Fatalf("expected 2 nodes, got %d", len(nodes))
|
|
}
|
|
|
|
// Test relay node discovery
|
|
relays := srv.GetRelayNodes("", "nodeB")
|
|
if len(relays) != 1 || relays[0].Name != "nodeA" {
|
|
t.Fatalf("expected nodeA as relay, got %v", relays)
|
|
}
|
|
log.Printf("Relay nodes: %v", relays[0].Name)
|
|
|
|
srv.Stop()
|
|
}
|