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
177 lines
4.0 KiB
Go
177 lines
4.0 KiB
Go
package tunnel
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"net"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestEndToEndForward(t *testing.T) {
|
|
// 1. Start a "target" TCP server (simulates SSH on the remote side)
|
|
targetLn, err := net.Listen("tcp", "127.0.0.1:0")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer targetLn.Close()
|
|
targetPort := targetLn.Addr().(*net.TCPAddr).Port
|
|
|
|
go func() {
|
|
for {
|
|
conn, err := targetLn.Accept()
|
|
if err != nil {
|
|
return
|
|
}
|
|
go func(c net.Conn) {
|
|
defer c.Close()
|
|
buf := make([]byte, 1024)
|
|
n, _ := c.Read(buf)
|
|
c.Write([]byte("ECHO:" + string(buf[:n])))
|
|
}(conn)
|
|
}
|
|
}()
|
|
|
|
// 2. Create a connected pair (simulates a P2P punch connection)
|
|
c1, c2 := net.Pipe()
|
|
|
|
// 3. Create tunnels on both sides
|
|
initiator := New("remote-node", c1, "test", 0, true)
|
|
responder := New("local-node", c2, "test", 0, false)
|
|
defer initiator.Close()
|
|
defer responder.Close()
|
|
|
|
// Responder accepts incoming mux streams and connects to local targets
|
|
go responder.AcceptAndConnect()
|
|
|
|
// 4. Initiator listens on a local port and forwards to remote target
|
|
localLn, err := net.Listen("tcp", "127.0.0.1:0")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
localPort := localLn.Addr().(*net.TCPAddr).Port
|
|
localLn.Close() // free the port so tunnel can use it
|
|
|
|
err = initiator.ListenAndForward("tcp", localPort, "127.0.0.1", targetPort)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
time.Sleep(50 * time.Millisecond)
|
|
|
|
// 5. Connect to the tunnel's local port
|
|
conn, err := net.Dial("tcp", fmt.Sprintf("127.0.0.1:%d", localPort))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer conn.Close()
|
|
|
|
// 6. Send data and verify echo
|
|
conn.Write([]byte("hello-tunnel"))
|
|
conn.SetReadDeadline(time.Now().Add(3 * time.Second))
|
|
buf := make([]byte, 1024)
|
|
n, err := conn.Read(buf)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
got := string(buf[:n])
|
|
want := "ECHO:hello-tunnel"
|
|
if got != want {
|
|
t.Errorf("got %q, want %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestMultipleConnections(t *testing.T) {
|
|
// Target server: echoes back with a prefix
|
|
targetLn, _ := net.Listen("tcp", "127.0.0.1:0")
|
|
defer targetLn.Close()
|
|
targetPort := targetLn.Addr().(*net.TCPAddr).Port
|
|
|
|
go func() {
|
|
for {
|
|
conn, err := targetLn.Accept()
|
|
if err != nil {
|
|
return
|
|
}
|
|
go func(c net.Conn) {
|
|
defer c.Close()
|
|
io.Copy(c, c) // pure echo
|
|
}(conn)
|
|
}
|
|
}()
|
|
|
|
c1, c2 := net.Pipe()
|
|
initiator := New("peer", c1, "test", 0, true)
|
|
responder := New("me", c2, "test", 0, false)
|
|
defer initiator.Close()
|
|
defer responder.Close()
|
|
|
|
go responder.AcceptAndConnect()
|
|
|
|
localLn, _ := net.Listen("tcp", "127.0.0.1:0")
|
|
localPort := localLn.Addr().(*net.TCPAddr).Port
|
|
localLn.Close()
|
|
|
|
initiator.ListenAndForward("tcp", localPort, "127.0.0.1", targetPort)
|
|
time.Sleep(50 * time.Millisecond)
|
|
|
|
// Open 5 concurrent connections through the tunnel
|
|
const N = 5
|
|
done := make(chan bool, N)
|
|
|
|
for i := 0; i < N; i++ {
|
|
go func(idx int) {
|
|
conn, err := net.Dial("tcp", fmt.Sprintf("127.0.0.1:%d", localPort))
|
|
if err != nil {
|
|
t.Errorf("conn %d: dial: %v", idx, err)
|
|
done <- false
|
|
return
|
|
}
|
|
defer conn.Close()
|
|
|
|
msg := fmt.Sprintf("msg-%d", idx)
|
|
conn.Write([]byte(msg))
|
|
conn.SetReadDeadline(time.Now().Add(3 * time.Second))
|
|
buf := make([]byte, 256)
|
|
n, err := conn.Read(buf)
|
|
if err != nil || string(buf[:n]) != msg {
|
|
t.Errorf("conn %d: got %q, want %q, err=%v", idx, buf[:n], msg, err)
|
|
done <- false
|
|
return
|
|
}
|
|
done <- true
|
|
}(i)
|
|
}
|
|
|
|
for i := 0; i < N; i++ {
|
|
if ok := <-done; !ok {
|
|
t.Errorf("connection %d failed", i)
|
|
}
|
|
}
|
|
|
|
stats := initiator.GetStats()
|
|
if stats.Connections != N {
|
|
t.Errorf("connections: got %d want %d", stats.Connections, N)
|
|
}
|
|
}
|
|
|
|
func TestTunnelStats(t *testing.T) {
|
|
c1, c2 := net.Pipe()
|
|
initiator := New("peer", c1, "test", 0, true)
|
|
responder := New("me", c2, "test", 0, false)
|
|
defer initiator.Close()
|
|
defer responder.Close()
|
|
|
|
if !initiator.IsAlive() || !responder.IsAlive() {
|
|
t.Error("tunnels should be alive")
|
|
}
|
|
|
|
initiator.Close()
|
|
time.Sleep(50 * time.Millisecond)
|
|
|
|
if initiator.IsAlive() {
|
|
t.Error("initiator should be dead after close")
|
|
}
|
|
}
|