Skip to content

Commit 1215081

Browse files
colegagopherbot
authored andcommitted
http2: improve error when server sends HTTP/1
If for some reason the server sends an HTTP/1.1 response starting with "HTTP/1.1 " (9 bytes), http2 transport interprets that as a valid frame header for an expected payload of ~4MB, and fails with non-descriptive messages like "unexpected EOF". This could happen, for example, if ALPN is misconfigured on the server's load balancer. This change attempts to improve that feedback by noting in the error messages whenever the frame header was exactly the "HTTP/1.1 " bytes. Change-Id: I7bf9ed2ee7f299b939b9004386f5bfa30a4e9032 GitHub-Last-Rev: d6e410d GitHub-Pull-Request: #224 Reviewed-on: https://go-review.googlesource.com/c/net/+/623155 LUCI-TryBot-Result: Go LUCI <[email protected]> Reviewed-by: David Chase <[email protected]> Auto-Submit: Sean Liao <[email protected]> Reviewed-by: Damien Neil <[email protected]> Reviewed-by: Sean Liao <[email protected]>
1 parent 312450e commit 1215081

File tree

2 files changed

+53
-0
lines changed

2 files changed

+53
-0
lines changed

http2/frame.go

+11
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,11 @@ var fhBytes = sync.Pool{
225225
},
226226
}
227227

228+
func invalidHTTP1LookingFrameHeader() FrameHeader {
229+
fh, _ := readFrameHeader(make([]byte, frameHeaderLen), strings.NewReader("HTTP/1.1 "))
230+
return fh
231+
}
232+
228233
// ReadFrameHeader reads 9 bytes from r and returns a FrameHeader.
229234
// Most users should use Framer.ReadFrame instead.
230235
func ReadFrameHeader(r io.Reader) (FrameHeader, error) {
@@ -503,10 +508,16 @@ func (fr *Framer) ReadFrame() (Frame, error) {
503508
return nil, err
504509
}
505510
if fh.Length > fr.maxReadSize {
511+
if fh == invalidHTTP1LookingFrameHeader() {
512+
return nil, fmt.Errorf("http2: failed reading the frame payload: %w, note that the frame header looked like an HTTP/1.1 header", err)
513+
}
506514
return nil, ErrFrameTooLarge
507515
}
508516
payload := fr.getReadBuf(fh.Length)
509517
if _, err := io.ReadFull(fr.r, payload); err != nil {
518+
if fh == invalidHTTP1LookingFrameHeader() {
519+
return nil, fmt.Errorf("http2: failed reading the frame payload: %w, note that the frame header looked like an HTTP/1.1 header", err)
520+
}
510521
return nil, err
511522
}
512523
f, err := typeFrameParser(fh.Type)(fr.frameCache, fh, fr.countError, payload)

http2/transport_test.go

+42
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,48 @@ func TestTransport(t *testing.T) {
272272
}
273273
}
274274

275+
func TestTransportFailureErrorForHTTP1Response(t *testing.T) {
276+
const expectedHTTP1PayloadHint = "frame header looked like an HTTP/1.1 header"
277+
278+
ts := httptest.NewServer(http.NewServeMux())
279+
t.Cleanup(ts.Close)
280+
281+
for _, tc := range []struct {
282+
name string
283+
maxFrameSize uint32
284+
expectedErrorIs error
285+
}{
286+
{
287+
name: "with default max frame size",
288+
maxFrameSize: 0,
289+
},
290+
{
291+
name: "with enough frame size to start reading",
292+
maxFrameSize: invalidHTTP1LookingFrameHeader().Length + 1,
293+
},
294+
} {
295+
t.Run(tc.name, func(t *testing.T) {
296+
tr := &Transport{
297+
DialTLSContext: func(ctx context.Context, network, addr string, cfg *tls.Config) (net.Conn, error) {
298+
return net.Dial(network, addr)
299+
},
300+
MaxReadFrameSize: tc.maxFrameSize,
301+
AllowHTTP: true,
302+
}
303+
304+
req, err := http.NewRequest("GET", ts.URL, nil)
305+
if err != nil {
306+
t.Fatal(err)
307+
}
308+
309+
_, err = tr.RoundTrip(req)
310+
if !strings.Contains(err.Error(), expectedHTTP1PayloadHint) {
311+
t.Errorf("expected error to contain %q, got %v", expectedHTTP1PayloadHint, err)
312+
}
313+
})
314+
}
315+
}
316+
275317
func testTransportReusesConns(t *testing.T, useClient, wantSame bool, modReq func(*http.Request)) {
276318
ts := newTestServer(t, func(w http.ResponseWriter, r *http.Request) {
277319
io.WriteString(w, r.RemoteAddr)

0 commit comments

Comments
 (0)