Merge pull request #3640 from carl-mastrangelo/master

Add initial interop tests
pull/3753/head
Craig Tiller 9 years ago
commit bf3c463cb4
  1. 9
      tools/http2_interop/README.md
  2. 6
      tools/http2_interop/doc.go
  3. 11
      tools/http2_interop/frame.go
  4. 109
      tools/http2_interop/frameheader.go
  5. 245
      tools/http2_interop/http2interop.go
  6. 50
      tools/http2_interop/http2interop_test.go
  7. 65
      tools/http2_interop/ping.go
  8. 109
      tools/http2_interop/settings.go
  9. 54
      tools/http2_interop/unknownframe.go

@ -0,0 +1,9 @@
HTTP/2 Interop Tests
====
This is a suite of tests that check a server to see if it plays nicely with other HTTP/2 clients. To run, just type:
`go test -spec :1234`
Where ":1234" is the ip:port of a running server.

@ -0,0 +1,6 @@
// http2interop project doc.go
/*
http2interop document
*/
package http2interop

@ -0,0 +1,11 @@
package http2interop
import (
"io"
)
type Frame interface {
GetHeader() *FrameHeader
ParsePayload(io.Reader) error
MarshalBinary() ([]byte, error)
}

@ -0,0 +1,109 @@
package http2interop
import (
"encoding/binary"
"fmt"
"io"
)
type FrameHeader struct {
Length int
Type FrameType
Flags byte
Reserved Reserved
StreamID
}
type Reserved bool
func (r Reserved) String() string {
if r {
return "R"
}
return ""
}
func (fh *FrameHeader) Parse(r io.Reader) error {
buf := make([]byte, 9)
if _, err := io.ReadFull(r, buf); err != nil {
return err
}
return fh.UnmarshalBinary(buf)
}
func (fh *FrameHeader) UnmarshalBinary(b []byte) error {
if len(b) != 9 {
return fmt.Errorf("Invalid frame header length %d", len(b))
}
*fh = FrameHeader{
Length: int(b[0])<<16 | int(b[1])<<8 | int(b[2]),
Type: FrameType(b[3]),
Flags: b[4],
Reserved: Reserved(b[5]>>7 == 1),
StreamID: StreamID(binary.BigEndian.Uint32(b[5:9]) & 0x7fffffff),
}
return nil
}
func (fh *FrameHeader) MarshalBinary() ([]byte, error) {
buf := make([]byte, 9, 9+fh.Length)
if fh.Length > 0xFFFFFF || fh.Length < 0 {
return nil, fmt.Errorf("Invalid frame header length: %d", fh.Length)
}
if fh.StreamID < 0 {
return nil, fmt.Errorf("Invalid Stream ID: %v", fh.StreamID)
}
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))
return buf, nil
}
type StreamID int32
type FrameType byte
func (ft FrameType) String() string {
switch ft {
case DataFrameType:
return "DATA"
case HeadersFrameType:
return "HEADERS"
case PriorityFrameType:
return "PRIORITY"
case ResetStreamFrameType:
return "RST_STREAM"
case SettingsFrameType:
return "SETTINGS"
case PushPromiseFrameType:
return "PUSH_PROMISE"
case PingFrameType:
return "PING"
case GoAwayFrameType:
return "GOAWAY"
case WindowUpdateFrameType:
return "WINDOW_UPDATE"
case ContinuationFrameType:
return "CONTINUATION"
default:
return fmt.Sprintf("UNKNOWN(%d)", byte(ft))
}
}
// Types
const (
DataFrameType FrameType = 0
HeadersFrameType FrameType = 1
PriorityFrameType FrameType = 2
ResetStreamFrameType FrameType = 3
SettingsFrameType FrameType = 4
PushPromiseFrameType FrameType = 5
PingFrameType FrameType = 6
GoAwayFrameType FrameType = 7
WindowUpdateFrameType FrameType = 8
ContinuationFrameType FrameType = 9
)

@ -0,0 +1,245 @@
package http2interop
import (
"crypto/tls"
"fmt"
"io"
"log"
)
const (
Preface = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"
)
func parseFrame(r io.Reader) (Frame, error) {
fh := FrameHeader{}
if err := fh.Parse(r); err != nil {
return nil, err
}
var f Frame
switch fh.Type {
case PingFrameType:
f = &PingFrame{
Header: fh,
}
case SettingsFrameType:
f = &SettingsFrame{
Header: fh,
}
default:
f = &UnknownFrame{
Header: fh,
}
}
if err := f.ParsePayload(r); err != nil {
return nil, err
}
return f, nil
}
func streamFrame(w io.Writer, f Frame) error {
raw, err := f.MarshalBinary()
if err != nil {
return err
}
if _, err := w.Write(raw); err != nil {
return err
}
return nil
}
func getHttp2Conn(addr string) (*tls.Conn, error) {
config := &tls.Config{
InsecureSkipVerify: true,
NextProtos: []string{"h2"},
}
conn, err := tls.Dial("tcp", addr, config)
if err != nil {
return nil, err
}
return conn, nil
}
func testClientShortSettings(addr string, length int) error {
c, err := getHttp2Conn(addr)
if err != nil {
return err
}
defer c.Close()
if _, err := c.Write([]byte(Preface)); err != nil {
return err
}
// Bad, settings, non multiple of 6
sf := &UnknownFrame{
Header: FrameHeader{
Type: SettingsFrameType,
},
Data: make([]byte, length),
}
if err := streamFrame(c, sf); err != nil {
return err
}
for {
frame, err := parseFrame(c)
if err != nil {
return err
}
log.Println(frame)
}
return nil
}
func testClientPrefaceWithStreamId(addr string) error {
c, err := getHttp2Conn(addr)
if err != nil {
return err
}
defer c.Close()
// Good so far
if _, err := c.Write([]byte(Preface)); err != nil {
return err
}
// Bad, settings do not have ids
sf := &SettingsFrame{
Header: FrameHeader{
StreamID: 1,
},
}
if err := streamFrame(c, sf); err != nil {
return err
}
for {
frame, err := parseFrame(c)
if err != nil {
return err
}
log.Println(frame)
}
return nil
}
func testUnknownFrameType(addr string) error {
c, err := getHttp2Conn(addr)
if err != nil {
return err
}
defer c.Close()
if _, err := c.Write([]byte(Preface)); err != nil {
return err
}
// Send some settings, which are part of the client preface
sf := &SettingsFrame{}
if err := streamFrame(c, sf); err != nil {
return err
}
// Write a bunch of invalid frame types.
for ft := ContinuationFrameType + 1; ft != 0; ft++ {
fh := &UnknownFrame{
Header: FrameHeader{
Type: ft,
},
}
if err := streamFrame(c, fh); err != nil {
return err
}
}
pf := &PingFrame{
Data: []byte("01234567"),
}
if err := streamFrame(c, pf); err != nil {
return err
}
for {
frame, err := parseFrame(c)
if err != nil {
return err
}
if npf, ok := frame.(*PingFrame); !ok {
continue
} else {
if string(npf.Data) != string(pf.Data) || npf.Header.Flags&PING_ACK == 0 {
return fmt.Errorf("Bad ping %+v", *npf)
}
return nil
}
}
return nil
}
func testShortPreface(addr string, prefacePrefix string) error {
c, err := getHttp2Conn(addr)
if err != nil {
return err
}
defer c.Close()
if _, err := c.Write([]byte(prefacePrefix)); err != nil {
return err
}
buf := make([]byte, 256)
for ; err == nil; _, err = c.Read(buf) {
}
// TODO: maybe check for a GOAWAY?
return err
}
func testTLSMaxVersion(addr string, version uint16) error {
config := &tls.Config{
InsecureSkipVerify: true,
NextProtos: []string{"h2"},
MaxVersion: version,
}
conn, err := tls.Dial("tcp", addr, config)
if err != nil {
return err
}
defer conn.Close()
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)
}
return err
}
return nil
}
func testTLSApplicationProtocol(addr string) error {
config := &tls.Config{
InsecureSkipVerify: true,
NextProtos: []string{"h2c"},
}
conn, err := tls.Dial("tcp", addr, config)
if err != nil {
return err
}
defer conn.Close()
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)
}
return err
}
return nil
}

@ -0,0 +1,50 @@
package http2interop
import (
"crypto/tls"
"flag"
"io"
"os"
"testing"
)
var (
serverSpec = flag.String("spec", ":50051", "The server spec to test")
)
func TestShortPreface(t *testing.T) {
for i := 0; i < len(Preface)-1; i++ {
if err := testShortPreface(*serverSpec, Preface[:i]+"X"); err != io.EOF {
t.Error("Expected an EOF but was", err)
}
}
}
func TestUnknownFrameType(t *testing.T) {
if err := testUnknownFrameType(*serverSpec); err != nil {
t.Fatal(err)
}
}
func TestTLSApplicationProtocol(t *testing.T) {
if err := testTLSApplicationProtocol(*serverSpec); err != io.EOF {
t.Fatal("Expected an EOF but was", err)
}
}
func TestTLSMaxVersion(t *testing.T) {
if err := testTLSMaxVersion(*serverSpec, tls.VersionTLS11); err != io.EOF {
t.Fatal("Expected an EOF but was", err)
}
}
func TestClientPrefaceWithStreamId(t *testing.T) {
if err := testClientPrefaceWithStreamId(*serverSpec); err != io.EOF {
t.Fatal("Expected an EOF but was", err)
}
}
func TestMain(m *testing.M) {
flag.Parse()
os.Exit(m.Run())
}

@ -0,0 +1,65 @@
package http2interop
import (
"fmt"
"io"
)
type PingFrame struct {
Header FrameHeader
Data []byte
}
const (
PING_ACK = 0x01
)
func (f *PingFrame) GetHeader() *FrameHeader {
return &f.Header
}
func (f *PingFrame) ParsePayload(r io.Reader) error {
raw := make([]byte, f.Header.Length)
if _, err := io.ReadFull(r, raw); err != nil {
return err
}
return f.UnmarshalPayload(raw)
}
func (f *PingFrame) UnmarshalPayload(raw []byte) error {
if f.Header.Length != len(raw) {
return fmt.Errorf("Invalid Payload length %d != %d", f.Header.Length, len(raw))
}
if f.Header.Length != 8 {
return fmt.Errorf("Invalid Payload length %d", f.Header.Length)
}
f.Data = []byte(string(raw))
return nil
}
func (f *PingFrame) MarshalPayload() ([]byte, error) {
if len(f.Data) != 8 {
return nil, fmt.Errorf("Invalid Payload length %d", len(f.Data))
}
return []byte(string(f.Data)), nil
}
func (f *PingFrame) MarshalBinary() ([]byte, error) {
payload, err := f.MarshalPayload()
if err != nil {
return nil, err
}
f.Header.Length = len(payload)
f.Header.Type = PingFrameType
header, err := f.Header.MarshalBinary()
if err != nil {
return nil, err
}
header = append(header, payload...)
return header, nil
}

@ -0,0 +1,109 @@
package http2interop
import (
"encoding/binary"
"fmt"
"io"
)
const (
SETTINGS_ACK = 1
)
type SettingsFrame struct {
Header FrameHeader
Params []SettingsParameter
}
type SettingsIdentifier uint16
const (
SettingsHeaderTableSize SettingsIdentifier = 1
SettingsEnablePush SettingsIdentifier = 2
SettingsMaxConcurrentStreams SettingsIdentifier = 3
SettingsInitialWindowSize SettingsIdentifier = 4
SettingsMaxFrameSize SettingsIdentifier = 5
SettingsMaxHeaderListSize SettingsIdentifier = 6
)
func (si SettingsIdentifier) String() string {
switch si {
case SettingsHeaderTableSize:
return "HEADER_TABLE_SIZE"
case SettingsEnablePush:
return "ENABLE_PUSH"
case SettingsMaxConcurrentStreams:
return "MAX_CONCURRENT_STREAMS"
case SettingsInitialWindowSize:
return "INITIAL_WINDOW_SIZE"
case SettingsMaxFrameSize:
return "MAX_FRAME_SIZE"
case SettingsMaxHeaderListSize:
return "MAX_HEADER_LIST_SIZE"
default:
return fmt.Sprintf("UNKNOWN(%d)", uint16(si))
}
}
type SettingsParameter struct {
Identifier SettingsIdentifier
Value uint32
}
func (f *SettingsFrame) GetHeader() *FrameHeader {
return &f.Header
}
func (f *SettingsFrame) ParsePayload(r io.Reader) error {
raw := make([]byte, f.Header.Length)
if _, err := io.ReadFull(r, raw); err != nil {
return err
}
return f.UnmarshalPayload(raw)
}
func (f *SettingsFrame) UnmarshalPayload(raw []byte) error {
if f.Header.Length != len(raw) {
return fmt.Errorf("Invalid Payload length %d != %d", f.Header.Length, len(raw))
}
if f.Header.Length%6 != 0 {
return fmt.Errorf("Invalid Payload length %d", f.Header.Length)
}
f.Params = make([]SettingsParameter, 0, f.Header.Length/6)
for i := 0; i < len(raw); i += 6 {
f.Params = append(f.Params, SettingsParameter{
Identifier: SettingsIdentifier(binary.BigEndian.Uint16(raw[i : i+2])),
Value: binary.BigEndian.Uint32(raw[i+2 : i+6]),
})
}
return nil
}
func (f *SettingsFrame) MarshalPayload() ([]byte, error) {
raw := make([]byte, 0, len(f.Params)*6)
for i, p := range f.Params {
binary.BigEndian.PutUint16(raw[i*6:i*6+2], uint16(p.Identifier))
binary.BigEndian.PutUint32(raw[i*6+2:i*6+6], p.Value)
}
return raw, nil
}
func (f *SettingsFrame) MarshalBinary() ([]byte, error) {
payload, err := f.MarshalPayload()
if err != nil {
return nil, err
}
f.Header.Length = len(payload)
f.Header.Type = SettingsFrameType
header, err := f.Header.MarshalBinary()
if err != nil {
return nil, err
}
header = append(header, payload...)
return header, nil
}

@ -0,0 +1,54 @@
package http2interop
import (
"fmt"
"io"
)
type UnknownFrame struct {
Header FrameHeader
Data []byte
}
func (f *UnknownFrame) GetHeader() *FrameHeader {
return &f.Header
}
func (f *UnknownFrame) ParsePayload(r io.Reader) error {
raw := make([]byte, f.Header.Length)
if _, err := io.ReadFull(r, raw); err != nil {
return err
}
return f.UnmarshalPayload(raw)
}
func (f *UnknownFrame) UnmarshalPayload(raw []byte) error {
if f.Header.Length != len(raw) {
return fmt.Errorf("Invalid Payload length %d != %d", f.Header.Length, len(raw))
}
f.Data = []byte(string(raw))
return nil
}
func (f *UnknownFrame) MarshalPayload() ([]byte, error) {
return []byte(string(f.Data)), nil
}
func (f *UnknownFrame) MarshalBinary() ([]byte, error) {
f.Header.Length = len(f.Data)
buf, err := f.Header.MarshalBinary()
if err != nil {
return nil, err
}
payload, err := f.MarshalPayload()
if err != nil {
return nil, err
}
buf = append(buf, payload...)
return buf, nil
}
Loading…
Cancel
Save