-
-
Notifications
You must be signed in to change notification settings - Fork 2.2k
Description
Bug Description
The proxy's ALPN negotiation logic incorrectly excludes h2 from NextProtos when it detects an HTTP/1.1 request line, even when the client explicitly requests HTTP/2 upgrade via Upgrade: h2c headers. This prevents legitimate HTTP/2 protocol upgrades.
Affected Code
File: pkg/agent/proxy/proxy.go, lines ~917-927
nextProtos := []string{"http/1.1"} // default safe
isHTTP := util.IsHTTPReq(initialBuf)
isCONNECT := bytes.HasPrefix(initialBuf, []byte("CONNECT "))
// Allow H2 if:
// 1. It's not an HTTP/1.x request (could be gRPC/HTTP2 frames), OR
// 2. It's a CONNECT request (used by gRPC parser for tunneling, ALB requires H2)
if !isHTTP || isCONNECT {
nextProtos = []string{"h2", "http/1.1"}
} else {
// BUG: Never offers h2 for standard HTTP/1.1 requests
logger.Debug("NOT offering H2 (HTTP/1.x detected)", zap.Strings("nextProtos", nextProtos))
}Root Cause
The condition !isHTTP || isCONNECT means:
- If
isHTTPis true (HTTP/1.1 request detected) ANDisCONNECTis false, thennextProtosremains["http/1.1"]. - This blocks HTTP/2 negotiation even when:
- The client sends
Upgrade: h2cheader (explicit HTTP/2 upgrade request) - Both client and server fully support HTTP/2
- ALPN negotiation should offer
h2to enable protocol switch
- The client sends
Impact
1. HTTP/2 Upgrade Failures
Services using HTTP/1.1 → HTTP/2 upgrade mechanisms fail to negotiate HTTP/2:
- AWS Application Load Balancer (ALB)
- Some gRPC-Web implementations
- Cloudflare Workers
- Any service that uses
Upgrade: h2cfor protocol negotiation
2. Performance Degradation
- Forces HTTP/1.1 even when HTTP/2 is mutually supported
- Loss of HTTP/2 multiplexing, header compression, server push
- Increased latency and bandwidth usage
3. Silent Failures
- No error is thrown — the proxy silently downgrades to HTTP/1.1
- Developers may not realize their HTTP/2 services are running on HTTP/1.1
- Harder to debug than explicit failures
Reproduction
-
Deploy a service that sends HTTP/1.1 requests with
Upgrade: h2c:GET /api HTTP/1.1 Host: example.com Upgrade: h2c Connection: Upgrade, HTTP2-Settings HTTP2-Settings: AAMAAABkAARAAAAAAAIAAAAA -
Proxy the request through Keploy with TLS enabled
-
Check ALPN negotiation logs
Expected: ALPN offers ["h2", "http/1.1"] and successfully negotiates h2
Actual: ALPN only offers ["http/1.1"], preventing HTTP/2 upgrade
Suggested Fix
Option 1: Always Offer HTTP/2 (Recommended)
// Always offer H2 in ALPN for TLS connections (clients that don't support it will ignore)
nextProtos := []string{"h2", "http/1.1"}
if !isHTTP || isCONNECT {
logger.Debug("Offering H2 (prioritized for gRPC/CONNECT)", zap.Strings("nextProtos", nextProtos))
} else {
logger.Debug("Offering H2 (standard HTTP/1.1 with upgrade support)", zap.Strings("nextProtos", nextProtos))
}Rationale:
- ALPN is a negotiation — offering
h2doesn't force its use if the server doesn't support it - Modern best practice: always offer HTTP/2 in TLS handshakes
- The current "safe" default (
http/1.1only) is unsafe because it breaks upgrade scenarios
Option 2: Conservative Fix (Detect Upgrade Intent)
If backward compatibility is critical:
nextProtos := []string{"http/1.1"}
// Check for HTTP/2 upgrade intent in headers
if !isHTTP || isCONNECT || bytes.Contains(initialBuf, []byte("Upgrade: h2c")) {
nextProtos = []string{"h2", "http/1.1"}
logger.Debug("Offering H2 for ALPN", zap.Strings("nextProtos", nextProtos))
}Additional Context
- No test coverage exists for this scenario (checked with
grep -r "Upgrade.*h2c") - HTTP/2 upgrade is part of RFC 9113 (Section 3.2)
- ALPN (RFC 7301) recommends clients offer all supported protocols
Environment
- Keploy version: v3 (main branch)
- OS: All platforms (protocol-level bug)
- Go version: Any
Would you like me to submit a PR with the fix?