diff --git a/netstack2/http_handler.go b/netstack2/http_handler.go index 7ba2f63..354781e 100644 --- a/netstack2/http_handler.go +++ b/netstack2/http_handler.go @@ -139,6 +139,21 @@ type httpConnCtx struct { rule *SubnetRule } +// ConnectionState allows net/http.Server to populate Request.TLS when the +// underlying connection is TLS (e.g. *tls.Conn from tls.Server). Without this, +// the connection is not *tls.Conn and does not expose ConnectionState through +// the net.Conn interface field, so tlsState stays nil and the HTTPS redirect +// in handleRequest runs on every request. +func (c *httpConnCtx) ConnectionState() tls.ConnectionState { + type tlsConn interface { + ConnectionState() tls.ConnectionState + } + if tc, ok := c.Conn.(tlsConn); ok { + return tc.ConnectionState() + } + return tls.ConnectionState{} +} + // connCtxKey is the unexported context key used to store a *SubnetRule on the // per-connection context created by http.Server.ConnContext. type connCtxKey struct{} diff --git a/netstack2/http_handler_tls_test.go b/netstack2/http_handler_tls_test.go new file mode 100644 index 0000000..0f2ffdc --- /dev/null +++ b/netstack2/http_handler_tls_test.go @@ -0,0 +1,48 @@ +package netstack2 + +import ( + "crypto/tls" + "net" + "testing" +) + +// tlsConnStub is a minimal net.Conn that also exposes TLS state, matching +// *tls.Conn's ConnectionState used by net/http.Server. +type tlsConnStub struct { + net.Conn + state tls.ConnectionState +} + +func (t *tlsConnStub) ConnectionState() tls.ConnectionState { + return t.state +} + +func TestHTTPConnCtxForwardsConnectionState(t *testing.T) { + c1, c2 := net.Pipe() + defer c1.Close() + defer c2.Close() + + inner := &tlsConnStub{ + Conn: c1, + state: tls.ConnectionState{Version: tls.VersionTLS12, HandshakeComplete: true}, + } + wrapped := &httpConnCtx{Conn: inner, rule: nil} + + got := wrapped.ConnectionState() + if got.Version != tls.VersionTLS12 || !got.HandshakeComplete { + t.Fatalf("ConnectionState = %+v, want TLS 1.2 and HandshakeComplete", got) + } +} + +func TestHTTPConnCtxConnectionStatePlainTCP(t *testing.T) { + c1, c2 := net.Pipe() + defer c1.Close() + defer c2.Close() + + wrapped := &httpConnCtx{Conn: c1, rule: nil} + got := wrapped.ConnectionState() + if got.Version != 0 { + t.Fatalf("expected zero ConnectionState for plain conn, got %+v", got) + } + _ = c2 +}