Add integration tests for multi-hop, gRPC trailers, and HTTP/1.1
Multi-hop integration tests (server package): - TestMultiHopProxyProtocol: full edge→origin deployment with two mc-proxy instances. Edge uses L4 passthrough with send_proxy_protocol, origin has proxy_protocol listener with L7 route. Verifies the real client IP (127.0.0.1) flows through PROXY protocol into the origin's X-Forwarded-For header on the h2c backend. - TestMultiHopFirewallBlocksRealIP: origin firewall blocks an IP from the PROXY header while allowing the TCP peer (edge proxy). Verifies the backend is never reached. L7 package integration tests: - TestL7LargeResponse: 1 MB response through the reverse proxy. - TestL7GRPCTrailers: HTTP/2 trailer propagation (Grpc-Status, Grpc-Message) through the reverse proxy, validating gRPC compatibility. - TestL7HTTP11Fallback: client negotiates HTTP/1.1 only (no h2 ALPN), verifies the proxy falls back to HTTP/1.1 serving and still forwards to the h2c backend successfully. Also updates PROGRESS.md to mark all five phases complete. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -361,3 +361,168 @@ func TestL7MultipleRequests(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestL7LargeResponse(t *testing.T) {
|
||||
certPath, keyPath := testCert(t, "large.test")
|
||||
|
||||
// Backend sends a 1 MB response.
|
||||
largeBody := make([]byte, 1<<20)
|
||||
for i := range largeBody {
|
||||
largeBody[i] = byte(i % 256)
|
||||
}
|
||||
backendAddr := startH2CBackend(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write(largeBody)
|
||||
}))
|
||||
|
||||
proxyLn, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
t.Fatalf("proxy listen: %v", err)
|
||||
}
|
||||
defer proxyLn.Close()
|
||||
|
||||
route := RouteConfig{
|
||||
Backend: backendAddr,
|
||||
TLSCert: certPath,
|
||||
TLSKey: keyPath,
|
||||
ConnectTimeout: 5 * time.Second,
|
||||
}
|
||||
|
||||
go func() {
|
||||
conn, err := proxyLn.Accept()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
logger := slog.New(slog.NewTextHandler(io.Discard, nil))
|
||||
Serve(context.Background(), conn, nil, route, netip.MustParseAddrPort("203.0.113.50:12345"), logger)
|
||||
}()
|
||||
|
||||
client := dialTLSToProxy(t, proxyLn.Addr().String(), "large.test")
|
||||
resp, err := client.Get("https://large.test/")
|
||||
if err != nil {
|
||||
t.Fatalf("GET: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
if len(body) != len(largeBody) {
|
||||
t.Fatalf("got %d bytes, want %d", len(body), len(largeBody))
|
||||
}
|
||||
}
|
||||
|
||||
func TestL7GRPCTrailers(t *testing.T) {
|
||||
certPath, keyPath := testCert(t, "trailers.test")
|
||||
|
||||
// Backend that sets HTTP trailers (used by gRPC for status).
|
||||
backendAddr := startH2CBackend(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Trailer", "Grpc-Status, Grpc-Message")
|
||||
w.Header().Set("Content-Type", "application/grpc")
|
||||
w.WriteHeader(200)
|
||||
// Flush to send headers.
|
||||
if f, ok := w.(http.Flusher); ok {
|
||||
f.Flush()
|
||||
}
|
||||
// Set trailers.
|
||||
w.Header().Set("Grpc-Status", "0")
|
||||
w.Header().Set("Grpc-Message", "OK")
|
||||
}))
|
||||
|
||||
proxyLn, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
t.Fatalf("proxy listen: %v", err)
|
||||
}
|
||||
defer proxyLn.Close()
|
||||
|
||||
route := RouteConfig{
|
||||
Backend: backendAddr,
|
||||
TLSCert: certPath,
|
||||
TLSKey: keyPath,
|
||||
ConnectTimeout: 5 * time.Second,
|
||||
}
|
||||
|
||||
go func() {
|
||||
conn, err := proxyLn.Accept()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
logger := slog.New(slog.NewTextHandler(io.Discard, nil))
|
||||
Serve(context.Background(), conn, nil, route, netip.MustParseAddrPort("203.0.113.50:12345"), logger)
|
||||
}()
|
||||
|
||||
client := dialTLSToProxy(t, proxyLn.Addr().String(), "trailers.test")
|
||||
req, _ := http.NewRequest("POST", "https://trailers.test/grpc.test.Service/Method", nil)
|
||||
req.Header.Set("Content-Type", "application/grpc")
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
t.Fatalf("POST: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// Read body to trigger trailer delivery.
|
||||
io.ReadAll(resp.Body)
|
||||
|
||||
// Verify trailers were forwarded through the proxy.
|
||||
grpcStatus := resp.Trailer.Get("Grpc-Status")
|
||||
if grpcStatus != "0" {
|
||||
t.Fatalf("Grpc-Status trailer = %q, want %q", grpcStatus, "0")
|
||||
}
|
||||
grpcMessage := resp.Trailer.Get("Grpc-Message")
|
||||
if grpcMessage != "OK" {
|
||||
t.Fatalf("Grpc-Message trailer = %q, want %q", grpcMessage, "OK")
|
||||
}
|
||||
}
|
||||
|
||||
func TestL7HTTP11Fallback(t *testing.T) {
|
||||
certPath, keyPath := testCert(t, "http11.test")
|
||||
|
||||
backendAddr := startH2CBackend(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintf(w, "proto=%s", r.Proto)
|
||||
}))
|
||||
|
||||
proxyLn, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
t.Fatalf("proxy listen: %v", err)
|
||||
}
|
||||
defer proxyLn.Close()
|
||||
|
||||
route := RouteConfig{
|
||||
Backend: backendAddr,
|
||||
TLSCert: certPath,
|
||||
TLSKey: keyPath,
|
||||
ConnectTimeout: 5 * time.Second,
|
||||
}
|
||||
|
||||
go func() {
|
||||
conn, err := proxyLn.Accept()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
logger := slog.New(slog.NewTextHandler(io.Discard, nil))
|
||||
Serve(context.Background(), conn, nil, route, netip.MustParseAddrPort("203.0.113.50:12345"), logger)
|
||||
}()
|
||||
|
||||
// Connect with HTTP/1.1 only (no h2 ALPN).
|
||||
tlsConf := &tls.Config{
|
||||
ServerName: "http11.test",
|
||||
InsecureSkipVerify: true,
|
||||
NextProtos: []string{"http/1.1"},
|
||||
}
|
||||
tr := &http.Transport{TLSClientConfig: tlsConf}
|
||||
client := &http.Client{Transport: tr}
|
||||
|
||||
resp, err := client.Get(fmt.Sprintf("https://%s/", proxyLn.Addr().String()))
|
||||
if err != nil {
|
||||
t.Fatalf("GET: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
t.Fatalf("status = %d, want 200", resp.StatusCode)
|
||||
}
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
// The backend sees HTTP/2 (proxied via h2c) regardless of client protocol.
|
||||
// Just verify we got a response — the protocol the backend sees depends
|
||||
// on the h2c transport.
|
||||
if len(body) == 0 {
|
||||
t.Fatal("empty response body")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user