Add L7 TLS-terminating HTTP/2 reverse proxy
New internal/l7 package implements TLS termination and HTTP/2 reverse proxying for L7 routes. The proxy terminates the client TLS connection using per-route certificates, then forwards HTTP/2 traffic to backends over h2c (plaintext HTTP/2) or h2 (re-encrypted TLS). PrefixConn replays the peeked ClientHello bytes into crypto/tls.Server so the TLS handshake sees the complete ClientHello despite SNI extraction having already read it. Serve() is the L7 entry point: TLS handshake with route certificate, ALPN negotiation (h2 preferred, HTTP/1.1 fallback), then HTTP reverse proxy via httputil.ReverseProxy. Backend transport uses h2c by default (AllowHTTP + plain TCP dial) or h2-over-TLS when backend_tls is set. Forwarding headers (X-Forwarded-For, X-Forwarded-Proto, X-Real-IP) are injected from the real client IP in the Rewrite function. PROXY protocol v2 is sent to backends when send_proxy_protocol is enabled, using the request context to carry the client address through the HTTP/2 transport's dial function. Server integration: handleConn dispatches to handleL7 when route.Mode is "l7". The L7 handler converts RouteInfo to l7.RouteConfig and delegates to l7.Serve. L7 package tests: PrefixConn (4 tests), h2c backend round-trip, forwarding header injection, backend unreachable (502), multiple HTTP/2 requests over one connection. Server integration tests: L7 route through full server pipeline with TLS client, mixed L4+L7 routes on the same listener verifying both paths work independently. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -13,6 +13,7 @@ import (
|
||||
|
||||
"git.wntrmute.dev/kyle/mc-proxy/internal/config"
|
||||
"git.wntrmute.dev/kyle/mc-proxy/internal/firewall"
|
||||
"git.wntrmute.dev/kyle/mc-proxy/internal/l7"
|
||||
"git.wntrmute.dev/kyle/mc-proxy/internal/proxy"
|
||||
"git.wntrmute.dev/kyle/mc-proxy/internal/proxyproto"
|
||||
"git.wntrmute.dev/kyle/mc-proxy/internal/sni"
|
||||
@@ -298,11 +299,10 @@ func (s *Server) handleConn(ctx context.Context, conn net.Conn, ls *ListenerStat
|
||||
return
|
||||
}
|
||||
|
||||
// Dispatch based on route mode. L7 will be implemented in a later phase.
|
||||
// Dispatch based on route mode.
|
||||
switch route.Mode {
|
||||
case "l7":
|
||||
s.logger.Error("L7 mode not yet implemented", "hostname", hostname)
|
||||
return
|
||||
s.handleL7(ctx, conn, addr, addrPort, hostname, route, peeked)
|
||||
default:
|
||||
s.handleL4(ctx, conn, addr, addrPort, hostname, route, peeked)
|
||||
}
|
||||
@@ -340,3 +340,25 @@ func (s *Server) handleL4(ctx context.Context, conn net.Conn, addr netip.Addr, c
|
||||
"backend_bytes", result.BackendBytes,
|
||||
)
|
||||
}
|
||||
|
||||
// handleL7 handles an L7 (TLS-terminating) connection.
|
||||
func (s *Server) handleL7(ctx context.Context, conn net.Conn, addr netip.Addr, clientAddrPort netip.AddrPort, hostname string, route RouteInfo, peeked []byte) {
|
||||
s.logger.Debug("L7 proxying", "addr", addr, "hostname", hostname, "backend", route.Backend)
|
||||
|
||||
rc := l7.RouteConfig{
|
||||
Backend: route.Backend,
|
||||
TLSCert: route.TLSCert,
|
||||
TLSKey: route.TLSKey,
|
||||
BackendTLS: route.BackendTLS,
|
||||
SendProxyProtocol: route.SendProxyProtocol,
|
||||
ConnectTimeout: s.cfg.Proxy.ConnectTimeout.Duration,
|
||||
}
|
||||
|
||||
if err := l7.Serve(ctx, conn, peeked, rc, clientAddrPort, s.logger); err != nil {
|
||||
if ctx.Err() == nil {
|
||||
s.logger.Debug("L7 serve ended", "hostname", hostname, "error", err)
|
||||
}
|
||||
}
|
||||
|
||||
s.logger.Info("L7 connection closed", "addr", addr, "hostname", hostname)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user