M1: single-node OpenTunnel-compatible server
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.
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
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])
|
||||
}
|
||||
Reference in New Issue
Block a user