HTTP API per the recorded contract, bridge WebSocket, SNI relay with pass-through tunnel TLS, local CA issuance, JSON store. Verified end-to-end with the official opentunnel CLI.
98 lines
2.5 KiB
Go
98 lines
2.5 KiB
Go
package server
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/tls"
|
|
"net"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// TestReadClientHello captures a real ClientHello produced by crypto/tls and
|
|
// checks the parser and the byte-exact replay capture.
|
|
func TestReadClientHello(t *testing.T) {
|
|
client, serverConn := net.Pipe()
|
|
defer client.Close()
|
|
|
|
var written bytes.Buffer
|
|
go func() {
|
|
// The client writes its ClientHello and then blocks waiting for the
|
|
// server; we only need the write side.
|
|
tlsClient := tls.Client(captureConn{client, &written}, &tls.Config{
|
|
ServerName: "api.demo.opentunnel.xyz",
|
|
NextProtos: []string{"h2", "http/1.1"},
|
|
InsecureSkipVerify: true,
|
|
})
|
|
tlsClient.Handshake() // fails: nobody answers
|
|
}()
|
|
|
|
serverConn.SetReadDeadline(time.Now().Add(5 * time.Second))
|
|
raw, sni, alpn, err := readClientHello(serverConn)
|
|
if err != nil {
|
|
t.Fatalf("readClientHello: %v", err)
|
|
}
|
|
if sni != "api.demo.opentunnel.xyz" {
|
|
t.Errorf("sni = %q", sni)
|
|
}
|
|
if len(alpn) != 2 || alpn[0] != "h2" || alpn[1] != "http/1.1" {
|
|
t.Errorf("alpn = %v", alpn)
|
|
}
|
|
if !bytes.HasPrefix(written.Bytes(), raw) {
|
|
t.Errorf("captured %d bytes are not a prefix of the %d written", len(raw), written.Len())
|
|
}
|
|
}
|
|
|
|
// captureConn records everything written to it.
|
|
type captureConn struct {
|
|
net.Conn
|
|
w *bytes.Buffer
|
|
}
|
|
|
|
func (c captureConn) Write(p []byte) (int, error) {
|
|
c.w.Write(p)
|
|
return c.Conn.Write(p)
|
|
}
|
|
|
|
func TestReadClientHelloGarbage(t *testing.T) {
|
|
client, serverConn := net.Pipe()
|
|
go func() {
|
|
client.Write([]byte("not tls at all"))
|
|
client.Close()
|
|
}()
|
|
serverConn.SetReadDeadline(time.Now().Add(2 * time.Second))
|
|
if _, _, _, err := readClientHello(serverConn); err == nil {
|
|
t.Error("expected an error for non-TLS bytes")
|
|
}
|
|
}
|
|
|
|
func TestReadClientHelloTrickle(t *testing.T) {
|
|
// A ClientHello trickling in byte by byte still parses.
|
|
client, serverConn := net.Pipe()
|
|
var written bytes.Buffer
|
|
go func() {
|
|
tlsClient := tls.Client(captureConn{client, &written}, &tls.Config{
|
|
ServerName: "demo.opentunnel.xyz",
|
|
InsecureSkipVerify: true,
|
|
})
|
|
tlsClient.Handshake()
|
|
}()
|
|
serverConn.SetReadDeadline(time.Now().Add(5 * time.Second))
|
|
slow := &slowConn{Conn: serverConn} // one-byte reads, a trickling sender
|
|
raw, sni, _, err := readClientHello(slow)
|
|
if err != nil {
|
|
t.Fatalf("readClientHello: %v", err)
|
|
}
|
|
if sni != "demo.opentunnel.xyz" {
|
|
t.Errorf("sni = %q", sni)
|
|
}
|
|
if !bytes.HasPrefix(written.Bytes(), raw) {
|
|
t.Error("capture mismatch")
|
|
}
|
|
}
|
|
|
|
type slowConn struct{ net.Conn }
|
|
|
|
func (c *slowConn) Read(p []byte) (int, error) {
|
|
return c.Conn.Read(p[:1])
|
|
}
|