Handle goaway and http1 responses

pull/4196/head
Carl Mastrangelo 9 years ago
parent 299bcd5521
commit 945836eade
  1. 13
      tools/http2_interop/frameheader.go
  2. 49
      tools/http2_interop/http1frame.go
  3. 178
      tools/http2_interop/http2interop.go
  4. 37
      tools/http2_interop/http2interop_test.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
)

@ -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])
}

@ -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
}

@ -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")

Loading…
Cancel
Save