From 945836eade7d8f12f6eb84bc209da13ae7c89b38 Mon Sep 17 00:00:00 2001 From: Carl Mastrangelo Date: Fri, 20 Nov 2015 17:43:15 -0800 Subject: Handle goaway and http1 responses --- tools/http2_interop/frameheader.go | 13 ++- tools/http2_interop/http1frame.go | 49 +++++++++ tools/http2_interop/http2interop.go | 178 ++++++++++++++++++++----------- tools/http2_interop/http2interop_test.go | 37 ++++--- 4 files changed, 200 insertions(+), 77 deletions(-) create mode 100644 tools/http2_interop/http1frame.go (limited to 'tools') diff --git a/tools/http2_interop/frameheader.go b/tools/http2_interop/frameheader.go index 78fe4201f6..84f6fa5c55 100644 --- a/tools/http2_interop/frameheader.go +++ b/tools/http2_interop/frameheader.go @@ -58,7 +58,11 @@ func (fh *FrameHeader) MarshalBinary() ([]byte, error) { buf[0], buf[1], buf[2] = byte(fh.Length>>16), byte(fh.Length>>8), byte(fh.Length) buf[3] = byte(fh.Type) buf[4] = fh.Flags - binary.BigEndian.PutUint32(buf[5:], uint32(fh.StreamID)) + var res uint32 + if fh.Reserved { + res = 0x80000000 + } + binary.BigEndian.PutUint32(buf[5:], uint32(fh.StreamID)|res) return buf, nil } @@ -89,6 +93,8 @@ func (ft FrameType) String() string { return "WINDOW_UPDATE" case ContinuationFrameType: return "CONTINUATION" + case HTTP1FrameType: + return "HTTP/1.? (Bad)" default: return fmt.Sprintf("UNKNOWN(%d)", byte(ft)) } @@ -106,4 +112,9 @@ const ( GoAwayFrameType FrameType = 7 WindowUpdateFrameType FrameType = 8 ContinuationFrameType FrameType = 9 + + // HTTP1FrameType is not a real type, but rather a convenient way to check if the response + // is an http response. The type of a frame header is the 4th byte, which in an http1 + // response will be "HTTP/1.1 200 OK" or something like that. The character for "P" is 80. + HTTP1FrameType FrameType = 80 ) diff --git a/tools/http2_interop/http1frame.go b/tools/http2_interop/http1frame.go new file mode 100644 index 0000000000..68ab197b65 --- /dev/null +++ b/tools/http2_interop/http1frame.go @@ -0,0 +1,49 @@ +package http2interop + +import ( + "bytes" + "io" + "strings" +) + +// HTTP1Frame is not a real frame, but rather a way to represent an http1.x response. +type HTTP1Frame struct { + Header FrameHeader + Data []byte +} + +func (f *HTTP1Frame) GetHeader() *FrameHeader { + return &f.Header +} + +func (f *HTTP1Frame) ParsePayload(r io.Reader) error { + var buf bytes.Buffer + if _, err := io.Copy(&buf, r); err != nil { + return err + } + f.Data = buf.Bytes() + return nil +} + +func (f *HTTP1Frame) MarshalPayload() ([]byte, error) { + return []byte(string(f.Data)), nil +} + +func (f *HTTP1Frame) MarshalBinary() ([]byte, error) { + buf, err := f.Header.MarshalBinary() + if err != nil { + return nil, err + } + + buf = append(buf, f.Data...) + + return buf, nil +} + +func (f *HTTP1Frame) String() string { + s := string(f.Data) + parts := strings.SplitN(s, "\n", 2) + headerleft, _ := f.Header.MarshalBinary() + + return strings.TrimSpace(string(headerleft) + parts[0]) +} diff --git a/tools/http2_interop/http2interop.go b/tools/http2_interop/http2interop.go index bef8b0b656..abd7d4cf7d 100644 --- a/tools/http2_interop/http2interop.go +++ b/tools/http2_interop/http2interop.go @@ -49,11 +49,16 @@ func parseFrame(r io.Reader) (Frame, error) { f = &SettingsFrame{ Header: fh, } + case HTTP1FrameType: + f = &HTTP1Frame{ + Header: fh, + } default: f = &UnknownFrame{ Header: fh, } } + if err := f.ParsePayload(r); err != nil { return nil, err } @@ -73,13 +78,14 @@ func streamFrame(w io.Writer, f Frame) error { } func testClientShortSettings(ctx *HTTP2InteropCtx, length int) error { - c, err := connect(ctx) + conn, err := connect(ctx) if err != nil { return err } - defer c.Close() + defer conn.Close() + conn.SetDeadline(time.Now().Add(defaultTimeout)) - if _, err := c.Write([]byte(Preface)); err != nil { + if _, err := conn.Write([]byte(Preface)); err != nil { return err } @@ -90,30 +96,28 @@ func testClientShortSettings(ctx *HTTP2InteropCtx, length int) error { }, Data: make([]byte, length), } - if err := streamFrame(c, sf); err != nil { + if err := streamFrame(conn, sf); err != nil { ctx.T.Log("Unable to stream frame", sf) return err } - for { - if _, err := parseFrame(c); err != nil { - ctx.T.Log("Unable to parse frame") - return err - } + if _, err := expectGoAwaySoon(conn); err != nil { + return err } return nil } func testClientPrefaceWithStreamId(ctx *HTTP2InteropCtx) error { - c, err := connect(ctx) + conn, err := connect(ctx) if err != nil { return err } - defer c.Close() + defer conn.Close() + conn.SetDeadline(time.Now().Add(defaultTimeout)) // Good so far - if _, err := c.Write([]byte(Preface)); err != nil { + if _, err := conn.Write([]byte(Preface)); err != nil { return err } @@ -123,34 +127,25 @@ func testClientPrefaceWithStreamId(ctx *HTTP2InteropCtx) error { StreamID: 1, }, } - if err := streamFrame(c, sf); err != nil { + if err := streamFrame(conn, sf); err != nil { return err } - for { - if _, err := parseFrame(c); err != nil { - return err - } + if _, err := expectGoAwaySoon(conn); err != nil { + return err } - return nil } func testUnknownFrameType(ctx *HTTP2InteropCtx) error { - c, err := connect(ctx) + conn, err := connect(ctx) if err != nil { return err } - defer c.Close() - - if _, err := c.Write([]byte(Preface)); err != nil { - return err - } + defer conn.Close() + conn.SetDeadline(time.Now().Add(defaultTimeout)) - // Send some settings, which are part of the client preface - sf := &SettingsFrame{} - if err := streamFrame(c, sf); err != nil { - ctx.T.Log("Unable to stream frame", sf) + if err := http2Connect(conn, nil); err != nil { return err } @@ -161,7 +156,7 @@ func testUnknownFrameType(ctx *HTTP2InteropCtx) error { Type: ft, }, } - if err := streamFrame(c, fh); err != nil { + if err := streamFrame(conn, fh); err != nil { ctx.T.Log("Unable to stream frame", fh) return err } @@ -170,18 +165,19 @@ func testUnknownFrameType(ctx *HTTP2InteropCtx) error { pf := &PingFrame{ Data: []byte("01234567"), } - if err := streamFrame(c, pf); err != nil { - ctx.T.Log("Unable to stream frame", sf) + if err := streamFrame(conn, pf); err != nil { + ctx.T.Log("Unable to stream frame", pf) return err } for { - frame, err := parseFrame(c) + frame, err := parseFrame(conn) if err != nil { - ctx.T.Log("Unable to parse frame") + ctx.T.Log("Unable to parse frame", err) return err } if npf, ok := frame.(*PingFrame); !ok { + ctx.T.Log("Got frame", frame.GetHeader().Type) continue } else { if string(npf.Data) != string(pf.Data) || npf.Header.Flags&PING_ACK == 0 { @@ -195,21 +191,22 @@ func testUnknownFrameType(ctx *HTTP2InteropCtx) error { } func testShortPreface(ctx *HTTP2InteropCtx, prefacePrefix string) error { - c, err := connect(ctx) + conn, err := connect(ctx) if err != nil { return err } - defer c.Close() + defer conn.Close() + conn.SetDeadline(time.Now().Add(defaultTimeout)) - if _, err := c.Write([]byte(prefacePrefix)); err != nil { + if _, err := conn.Write([]byte(prefacePrefix)); err != nil { return err } - buf := make([]byte, 256) - for ; err == nil; _, err = c.Read(buf) { + if _, err := expectGoAwaySoon(conn); err != nil { + return err } - // TODO: maybe check for a GOAWAY? - return err + + return nil } func testTLSMaxVersion(ctx *HTTP2InteropCtx, version uint16) error { @@ -222,13 +219,18 @@ func testTLSMaxVersion(ctx *HTTP2InteropCtx, version uint16) error { defer conn.Close() conn.SetDeadline(time.Now().Add(defaultTimeout)) - buf := make([]byte, 256) - if n, err := conn.Read(buf); err != nil { - if n != 0 { - return fmt.Errorf("Expected no bytes to be read, but was %d", n) - } + if err := http2Connect(conn, nil); err != nil { + return err + } + + gf, err := expectGoAway(conn) + if err != nil { return err } + // TODO: make an enum out of this + if gf.Code != 0xC { + return fmt.Errorf("Expected an Inadequate security code: %v", gf) + } return nil } @@ -242,13 +244,18 @@ func testTLSApplicationProtocol(ctx *HTTP2InteropCtx) error { defer conn.Close() conn.SetDeadline(time.Now().Add(defaultTimeout)) - buf := make([]byte, 256) - if n, err := conn.Read(buf); err != nil { - if n != 0 { - return fmt.Errorf("Expected no bytes to be read, but was %d", n) - } + if err := http2Connect(conn, nil); err != nil { + return err + } + + gf, err := expectGoAway(conn) + if err != nil { return err } + // TODO: make an enum out of this + if gf.Code != 0xC { + return fmt.Errorf("Expected an Inadequate security code: %v", gf) + } return nil } @@ -279,16 +286,44 @@ func testTLSBadCipherSuites(ctx *HTTP2InteropCtx) error { return err } + gf, err := expectGoAway(conn) + if err != nil { + return err + } + // TODO: make an enum out of this + if gf.Code != 0xC { + return fmt.Errorf("Expected an Inadequate security code: %v", gf) + } + return nil +} + +func expectGoAway(conn net.Conn) (*GoAwayFrame, error) { + f, err := parseFrame(conn) + if err != nil { + return nil, err + } + if gf, ok := f.(*GoAwayFrame); !ok { + return nil, fmt.Errorf("Expected GoAway Frame %+v", f) + } else { + return gf, nil + } +} + +// expectGoAwaySoon checks that a GOAWAY frame eventually comes. Servers usually send +// the initial settings frames before any data has actually arrived. This function +// checks that a go away shows. +func expectGoAwaySoon(conn net.Conn) (*GoAwayFrame, error) { for { f, err := parseFrame(conn) if err != nil { - return err + return nil, err } - if gf, ok := f.(*GoAwayFrame); ok { - return fmt.Errorf("Got goaway frame %d", gf.Code) + if gf, ok := f.(*GoAwayFrame); !ok { + continue + } else { + return gf, nil } } - return nil } func http2Connect(c net.Conn, sf *SettingsFrame) error { @@ -304,8 +339,29 @@ func http2Connect(c net.Conn, sf *SettingsFrame) error { return nil } -func connect(ctx *HTTP2InteropCtx) (net.Conn, error) { - var conn net.Conn +// CapConn captures connection traffic if Log is non-nil +type CapConn struct { + net.Conn + Log func(args ...interface{}) +} + +func (c *CapConn) Write(data []byte) (int, error) { + if c.Log != nil { + c.Log(" SEND: ", data) + } + return c.Conn.Write(data) +} + +func (c *CapConn) Read(data []byte) (int, error) { + n, err := c.Conn.Read(data) + if c.Log != nil { + c.Log(" RECV: ", data[:n], err) + } + return n, err +} + +func connect(ctx *HTTP2InteropCtx) (*CapConn, error) { + var conn *CapConn var err error if !ctx.UseTLS { conn, err = connectWithoutTls(ctx) @@ -327,24 +383,22 @@ func buildTlsConfig(ctx *HTTP2InteropCtx) *tls.Config { NextProtos: []string{"h2"}, ServerName: ctx.authority, MinVersion: tls.VersionTLS12, - // TODO(carl-mastrangelo): remove this once all test certificates have been updated. - InsecureSkipVerify: true, } } -func connectWithoutTls(ctx *HTTP2InteropCtx) (net.Conn, error) { +func connectWithoutTls(ctx *HTTP2InteropCtx) (*CapConn, error) { conn, err := net.DialTimeout("tcp", ctx.serverSpec, defaultTimeout) if err != nil { return nil, err } - return conn, nil + return &CapConn{Conn: conn}, nil } -func connectWithTls(ctx *HTTP2InteropCtx, config *tls.Config) (*tls.Conn, error) { +func connectWithTls(ctx *HTTP2InteropCtx, config *tls.Config) (*CapConn, error) { conn, err := connectWithoutTls(ctx) if err != nil { return nil, err } - return tls.Client(conn, config), nil + return &CapConn{Conn: tls.Client(conn, config)}, nil } diff --git a/tools/http2_interop/http2interop_test.go b/tools/http2_interop/http2interop_test.go index e3d366f2f2..b35d085569 100644 --- a/tools/http2_interop/http2interop_test.go +++ b/tools/http2_interop/http2interop_test.go @@ -5,7 +5,6 @@ import ( "crypto/x509" "flag" "fmt" - "io" "io/ioutil" "os" "strconv" @@ -68,15 +67,25 @@ func (ctx *HTTP2InteropCtx) Close() error { return nil } +func TestClientShortSettings(t *testing.T) { + if *testCase != "framing" { + t.SkipNow() + } + ctx := InteropCtx(t) + for i := 1; i <= 5; i++ { + err := testClientShortSettings(ctx, i) + matchError(t, err, "EOF") + } +} + func TestShortPreface(t *testing.T) { if *testCase != "framing" { t.SkipNow() } ctx := InteropCtx(t) for i := 0; i < len(Preface)-1; i++ { - if err := testShortPreface(ctx, Preface[:i]+"X"); err != io.EOF { - t.Error("Expected an EOF but was", err) - } + err := testShortPreface(ctx, Preface[:i]+"X") + matchError(t, err, "EOF") } } @@ -90,13 +99,22 @@ func TestUnknownFrameType(t *testing.T) { } } +func TestClientPrefaceWithStreamId(t *testing.T) { + if *testCase != "framing" { + t.SkipNow() + } + ctx := InteropCtx(t) + err := testClientPrefaceWithStreamId(ctx) + matchError(t, err, "EOF") +} + func TestTLSApplicationProtocol(t *testing.T) { if *testCase != "tls" { t.SkipNow() } ctx := InteropCtx(t) err := testTLSApplicationProtocol(ctx) - matchError(t, err, "EOF") + matchError(t, err, "EOF", "broken pipe") } func TestTLSMaxVersion(t *testing.T) { @@ -119,15 +137,6 @@ func TestTLSBadCipherSuites(t *testing.T) { matchError(t, err, "EOF", "Got goaway frame") } -func TestClientPrefaceWithStreamId(t *testing.T) { - if *testCase != "framing" { - t.SkipNow() - } - ctx := InteropCtx(t) - err := testClientPrefaceWithStreamId(ctx) - matchError(t, err, "EOF") -} - func matchError(t *testing.T, err error, matches ...string) { if err == nil { t.Fatal("Expected an error") -- cgit v1.2.3