aboutsummaryrefslogtreecommitdiffhomepage
path: root/tools/http2_interop
diff options
context:
space:
mode:
authorGravatar Carl Mastrangelo <notcarl@google.com>2015-11-20 17:43:15 -0800
committerGravatar Carl Mastrangelo <notcarl@google.com>2015-11-24 15:41:54 -0800
commit945836eade7d8f12f6eb84bc209da13ae7c89b38 (patch)
treed71142335c99eb5ccd124536c182320218e26a1b /tools/http2_interop
parent299bcd55216349fe45b17d30e9bab19a85073db8 (diff)
Handle goaway and http1 responses
Diffstat (limited to 'tools/http2_interop')
-rw-r--r--tools/http2_interop/frameheader.go13
-rw-r--r--tools/http2_interop/http1frame.go49
-rw-r--r--tools/http2_interop/http2interop.go178
-rw-r--r--tools/http2_interop/http2interop_test.go37
4 files changed, 200 insertions, 77 deletions
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")