mirror of
https://github.com/DeBrosOfficial/orama.git
synced 2026-06-16 23:14:13 +00:00
- add `orama-sni-router` binary to build process - introduce `cmd/sni-router` for TLS-level SNI routing - add documentation for stealth turn deployment architecture
144 lines
3.6 KiB
Go
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)
|
|
}
|
|
}
|
|
|