orama/core/pkg/sniproxy/server_test.go
anonpenguin23 0379dc39f1 feat(core): implement sni-router for stealth turn
- add `orama-sni-router` binary to build process
- introduce `cmd/sni-router` for TLS-level SNI routing
- add documentation for stealth turn deployment architecture
2026-05-03 18:20:21 +03:00

144 lines
3.6 KiB
Go

package sniproxy
import (
"bufio"
"crypto/tls"
"errors"
"io"
"net"
"testing"
"time"
"go.uber.org/zap"
)
// startEchoBackend creates a TCP server that echoes the first 1024 bytes
// it reads, then closes. Returns the listener and a chan that receives
// the bytes the server saw.
func startEchoBackend(t *testing.T) (net.Listener, <-chan []byte) {
t.Helper()
ln, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatal(err)
}
got := make(chan []byte, 4)
go func() {
for {
conn, err := ln.Accept()
if err != nil {
return
}
go func(c net.Conn) {
defer c.Close()
_ = c.SetReadDeadline(time.Now().Add(2 * time.Second))
buf := make([]byte, 1024)
n, _ := c.Read(buf)
got <- append([]byte(nil), buf[:n]...)
}(conn)
}
}()
return ln, got
}
func TestServer_routes_TLS_to_correct_backend(t *testing.T) {
turnLn, turnGot := startEchoBackend(t)
defer turnLn.Close()
caddyLn, caddyGot := startEchoBackend(t)
defer caddyLn.Close()
router := NewRouter(Backend{Network: "tcp", Addr: caddyLn.Addr().String()})
router.Replace([]Route{
{Match: "turn.example.com", Backend: Backend{Network: "tcp", Addr: turnLn.Addr().String()}},
}, router.Fallback())
srv := NewServer(router, Config{}, zap.NewNop())
defer srv.Close()
frontLn, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatal(err)
}
defer frontLn.Close()
go func() { _ = srv.Serve(frontLn) }()
// Client A: SNI=turn.example.com -> goes to turnLn
dialAndStartTLS(t, frontLn.Addr().String(), "turn.example.com")
// Client B: SNI=other.example.com -> falls through to caddyLn
dialAndStartTLS(t, frontLn.Addr().String(), "other.example.com")
select {
case b := <-turnGot:
if len(b) == 0 {
t.Error("turn backend received empty bytes")
}
case <-time.After(3 * time.Second):
t.Error("turn backend did not receive bytes")
}
select {
case b := <-caddyGot:
if len(b) == 0 {
t.Error("caddy fallback received empty bytes")
}
case <-time.After(3 * time.Second):
t.Error("caddy fallback did not receive bytes")
}
}
// dialAndStartTLS opens a TLS handshake (which produces a ClientHello)
// against the given address with the given SNI. Returns immediately —
// the test only needs the proxy to forward the bytes; it doesn't
// require handshake completion.
func dialAndStartTLS(t *testing.T, addr, sni string) {
t.Helper()
conn, err := net.Dial("tcp", addr)
if err != nil {
t.Fatal(err)
}
go func() {
defer conn.Close()
_ = conn.SetDeadline(time.Now().Add(2 * time.Second))
c := tls.Client(conn, &tls.Config{ServerName: sni, InsecureSkipVerify: true})
_ = c.Handshake() // expected to fail (echo backend isn't TLS)
}()
}
func TestServer_no_backend_drops_connection(t *testing.T) {
router := NewRouter(Backend{}) // empty fallback, empty Addr -> dropped
srv := NewServer(router, Config{}, zap.NewNop())
defer srv.Close()
frontLn, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatal(err)
}
defer frontLn.Close()
go func() { _ = srv.Serve(frontLn) }()
conn, err := net.Dial("tcp", frontLn.Addr().String())
if err != nil {
t.Fatal(err)
}
defer conn.Close()
c := tls.Client(conn, &tls.Config{ServerName: "x.example.com", InsecureSkipVerify: true})
// Handshake should fail because connection is closed by proxy.
go func() { _ = c.Handshake() }()
// Reader should see EOF quickly.
_ = conn.SetDeadline(time.Now().Add(2 * time.Second))
br := bufio.NewReader(conn)
_, err = br.ReadByte()
if err == nil {
t.Error("expected connection drop")
}
if !errors.Is(err, io.EOF) {
// "use of closed network connection" is also fine.
t.Logf("acceptable read error: %v", err)
}
}