You are viewing a plain text version of this content. The canonical link for it is here.
Posted to cvs@httpd.apache.org by ic...@apache.org on 2016/12/04 22:07:29 UTC

svn commit: r1772577 [1/2] - in /httpd/test/mod_h2/trunk: ./ conf/sites/ mh2fuzz/ test/ test/go/

Author: icing
Date: Sun Dec  4 22:07:28 2016
New Revision: 1772577

URL: http://svn.apache.org/viewvc?rev=1772577&view=rev
Log:
additional testing for frame sceanrios using golang's http2 framer

Added:
    httpd/test/mod_h2/trunk/test/go/h2fuzz.go
    httpd/test/mod_h2/trunk/test/go/in-footer-endless.fuzz
    httpd/test/mod_h2/trunk/test/go/in-footer.fuzz
    httpd/test/mod_h2/trunk/test/go/in-get.fuzz
    httpd/test/mod_h2/trunk/test/go/in-headers-endless.fuzz
    httpd/test/mod_h2/trunk/test/go/in-headers-many.fuzz
    httpd/test/mod_h2/trunk/test/go/test_fuzz.sh
Modified:
    httpd/test/mod_h2/trunk/conf/sites/test.example.org.conf
    httpd/test/mod_h2/trunk/configure.ac
    httpd/test/mod_h2/trunk/mh2fuzz/h2c_request.c
    httpd/test/mod_h2/trunk/mh2fuzz/h2c_request.h
    httpd/test/mod_h2/trunk/mh2fuzz/h2c_session.c
    httpd/test/mod_h2/trunk/mh2fuzz/mh2f_fuzzer.c
    httpd/test/mod_h2/trunk/test/go/get.go
    httpd/test/mod_h2/trunk/test/test.sh
    httpd/test/mod_h2/trunk/test/test_common.sh
    httpd/test/mod_h2/trunk/test/test_fuzz.sh

Modified: httpd/test/mod_h2/trunk/conf/sites/test.example.org.conf
URL: http://svn.apache.org/viewvc/httpd/test/mod_h2/trunk/conf/sites/test.example.org.conf?rev=1772577&r1=1772576&r2=1772577&view=diff
==============================================================================
--- httpd/test/mod_h2/trunk/conf/sites/test.example.org.conf (original)
+++ httpd/test/mod_h2/trunk/conf/sites/test.example.org.conf Sun Dec  4 22:07:28 2016
@@ -137,7 +137,6 @@
         Header add Link "</006/006.js>;rel=preload"
     </Location>
 
-
     <Location /hello.py>
         SSLOptions +StdEnvVars
     </Location>

Modified: httpd/test/mod_h2/trunk/configure.ac
URL: http://svn.apache.org/viewvc/httpd/test/mod_h2/trunk/configure.ac?rev=1772577&r1=1772576&r2=1772577&view=diff
==============================================================================
--- httpd/test/mod_h2/trunk/configure.ac (original)
+++ httpd/test/mod_h2/trunk/configure.ac Sun Dec  4 22:07:28 2016
@@ -52,7 +52,7 @@ AC_ARG_WITH([serverdir], [AS_HELP_STRING
     [Use serverdir directory for setup [default=gen/apache]])],
     [request_serverdir=$withval], [request_serverdir=$PWD/gen/apache])
 
-NGHTTP2_VERSION=1.15.0
+NGHTTP2_VERSION=1.16.0
 
 AC_ARG_WITH([nghttp2-version], [AS_HELP_STRING([--with-nghttp2-version],
     [Use nghttp2 of specified version [default=<ignore>]])],

Modified: httpd/test/mod_h2/trunk/mh2fuzz/h2c_request.c
URL: http://svn.apache.org/viewvc/httpd/test/mod_h2/trunk/mh2fuzz/h2c_request.c?rev=1772577&r1=1772576&r2=1772577&view=diff
==============================================================================
--- httpd/test/mod_h2/trunk/mh2fuzz/h2c_request.c (original)
+++ httpd/test/mod_h2/trunk/mh2fuzz/h2c_request.c Sun Dec  4 22:07:28 2016
@@ -45,7 +45,7 @@ apr_status_t h2c_request_create(h2c_requ
     return APR_SUCCESS;
 }
 
-void h2c_request_headers_set(h2c_request *r, const char *name, const char *value)
+void h2c_request_headers_add(h2c_request *r, const char *name, const char *value)
 {
     if (!strcmp(":method", name)) {
         r->method = value;
@@ -60,16 +60,16 @@ void h2c_request_headers_set(h2c_request
         r->scheme = value;
     }
     else {
-        apr_table_setn(r->headers, name, value);
+        apr_table_addn(r->headers, name, value);
     }
 }
 
-void h2c_request_headers_setn(h2c_request *r, apr_pool_t *p,
+void h2c_request_headers_addn(h2c_request *r, apr_pool_t *p,
                               const char *name, size_t namelen,
                               const char *value, size_t valuelen)
 {
-    h2c_request_headers_set(r, apr_pstrndup(p, name, namelen), 
-                           apr_pstrndup(p, value, valuelen));
+    h2c_request_headers_add(r, apr_pstrndup(p, name, namelen), 
+                            apr_pstrndup(p, value, valuelen));
 }
 
 apr_status_t h2c_request_ncreate(h2c_request **pr, apr_pool_t *p,
@@ -82,8 +82,8 @@ apr_status_t h2c_request_ncreate(h2c_req
     r->headers   = apr_table_make(p, 5);
     
     for (i = 0; i < nvlen; ++i) {
-        h2c_request_headers_setn(r, p, (char *)nva[i].name, nva[i].namelen, 
-                                (char *)nva[i].value, nva[i].valuelen);
+        h2c_request_headers_addn(r, p, (char *)nva[i].name, nva[i].namelen, 
+                                 (char *)nva[i].value, nva[i].valuelen);
     }
     
     *pr = r;

Modified: httpd/test/mod_h2/trunk/mh2fuzz/h2c_request.h
URL: http://svn.apache.org/viewvc/httpd/test/mod_h2/trunk/mh2fuzz/h2c_request.h?rev=1772577&r1=1772576&r2=1772577&view=diff
==============================================================================
--- httpd/test/mod_h2/trunk/mh2fuzz/h2c_request.h (original)
+++ httpd/test/mod_h2/trunk/mh2fuzz/h2c_request.h Sun Dec  4 22:07:28 2016
@@ -33,8 +33,8 @@ struct h2c_request {
 apr_status_t h2c_request_create(h2c_request **pr, apr_pool_t *p,
                                 const char *method, apr_uri_t *uri);
 
-void h2c_request_headers_set(h2c_request *r, const char *name, const char *val);
-void h2c_request_headers_setn(h2c_request *r, apr_pool_t *p,
+void h2c_request_headers_add(h2c_request *r, const char *name, const char *val);
+void h2c_request_headers_addn(h2c_request *r, apr_pool_t *p,
                               const char *name, size_t namelen,
                               const char *value, size_t valuelen);
 

Modified: httpd/test/mod_h2/trunk/mh2fuzz/h2c_session.c
URL: http://svn.apache.org/viewvc/httpd/test/mod_h2/trunk/mh2fuzz/h2c_session.c?rev=1772577&r1=1772576&r2=1772577&view=diff
==============================================================================
--- httpd/test/mod_h2/trunk/mh2fuzz/h2c_session.c (original)
+++ httpd/test/mod_h2/trunk/mh2fuzz/h2c_session.c Sun Dec  4 22:07:28 2016
@@ -449,7 +449,7 @@ static int on_header(nghttp2_session *ng
             ++s->open_streams;
             nghttp2_session_set_stream_user_data(ngh2, frame->push_promise.promised_stream_id, stream);
         }
-        h2c_request_headers_setn(stream->r, stream->p, 
+        h2c_request_headers_addn(stream->r, stream->p, 
                                  (const char *)name, namelen, 
                                  (const char *)value, valuelen);
     }
@@ -490,6 +490,7 @@ static apr_status_t on_conn_write(h2c_co
 static apr_status_t on_conn_connect(h2c_conn *c, h2c_conn_obs *obs)
 {
     h2c_session *s = obs->ctx;
+    nghttp2_option *options;
     nghttp2_session_callbacks *cbs;
     nghttp2_settings_entry settings[2];
     int add_conn_window;
@@ -504,7 +505,11 @@ static apr_status_t on_conn_connect(h2c_
     nghttp2_session_callbacks_set_before_frame_send_callback(cbs, before_frame_send);
     nghttp2_session_callbacks_set_send_callback(cbs, raw_send);
     
-    nghttp2_session_client_new(&s->ngh2, cbs, s);
+    nghttp2_option_new(&options);
+    nghttp2_option_set_max_deflate_dynamic_table_size(options, 100*1024*1024);
+    nghttp2_option_set_max_send_header_block_length(options,  100*1024*1024);
+    
+    nghttp2_session_client_new2(&s->ngh2, cbs, s, options);
     
     nghttp2_session_callbacks_del(cbs);
 
@@ -569,6 +574,11 @@ apr_status_t h2c_session_submit(h2c_sess
                                      r->method, r->authority, r->path);
     stream_id = nghttp2_submit_request(s->ngh2, NULL, ngh->nv, ngh->nvlen, 
                                        NULL, stream);
+    if (s->c->verbose > 1) {
+        fprintf(stderr, "%ld: submit %d headers -> STREAM[%d]\n", s->c->id, 
+            (int)ngh->nvlen, stream_id);
+    }
+    
     if (stream_id < 0) {
         return APR_EGENERAL;
     }
@@ -576,10 +586,6 @@ apr_status_t h2c_session_submit(h2c_sess
     s->max_stream_sent = H2_MAX(s->max_stream_sent, stream_id);
     stream->id = stream_id;
     
-    if (s->c->verbose > 1) {
-        fprintf(stderr, "%ld: submit STREAM[%d]\n", s->c->id, stream_id);
-    }
-    
     if (pid) {
         *pid =  stream_id;
     }

Modified: httpd/test/mod_h2/trunk/mh2fuzz/mh2f_fuzzer.c
URL: http://svn.apache.org/viewvc/httpd/test/mod_h2/trunk/mh2fuzz/mh2f_fuzzer.c?rev=1772577&r1=1772576&r2=1772577&view=diff
==============================================================================
--- httpd/test/mod_h2/trunk/mh2fuzz/mh2f_fuzzer.c (original)
+++ httpd/test/mod_h2/trunk/mh2fuzz/mh2f_fuzzer.c Sun Dec  4 22:07:28 2016
@@ -304,7 +304,7 @@ static void hh_submit(h2c_session *s, h2
         fprintf(stderr, "h2c_conn(%ld-%d): submit :authority as Host\n", 
                 s->c->id, stream->id);
     }
-    h2c_request_headers_set(r, "Host", r->authority);
+    h2c_request_headers_add(r, "Host", r->authority);
     r->authority = NULL;
 }
 
@@ -345,7 +345,7 @@ static void slh_submit(h2c_session *s, h
         slh[i] = 'A';
     }
     slh[hlen] = '\0';
-    h2c_request_headers_set(r, "super-long-header", slh);
+    h2c_request_headers_add(r, "super-long-header", slh);
 }
 
 static h2c_session_obs *slh_create(const char *name, apr_pool_t *p)
@@ -386,7 +386,7 @@ static void slp_submit(h2c_session *s, h
     }
     slh[0] = '/';
     slh[hlen] = '\0';
-    h2c_request_headers_set(r, ":path", slh);
+    h2c_request_headers_add(r, ":path", slh);
 }
 
 static h2c_session_obs *slp_create(const char *name, apr_pool_t *p)
@@ -407,13 +407,27 @@ static h2c_session_obs *slp_create(const
  ******************************************************************************/
  typedef struct fuzzer_smh {
     h2c_session_obs obs;
+    const char *name;
+    int count;
 } fuzzer_smh;
 
+static const char *STRING_100 = "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789";
+static const char *STRING_1000 = 
+    "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"
+    "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"
+    "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"
+    "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"
+    "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"
+    "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"
+    "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"
+    "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"
+    "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"
+    "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789";
+    
 static void smh_submit(h2c_session *s, h2c_stream *stream, h2c_request *r, 
                        h2c_session_obs *obs)
 {
     fuzzer_smh *f = obs->ctx;
-    apr_size_t hn = 128;
     char *name;
 
     if (s->c->verbose > 1) {
@@ -421,21 +435,29 @@ static void smh_submit(h2c_session *s, h
                 s->c->id, stream->id);
     }
     
-    for (int i = 0; i < hn; ++i) {
+    for (int i = 0; i < f->count; ++i) {
         name = apr_psprintf(stream->p, "smh-%d", i);
-        h2c_request_headers_set(r, name, "super many header fuzzing");
+        h2c_request_headers_add(r, STRING_100, STRING_100);
     }
 }
 
 static h2c_session_obs *smh_create(const char *name, apr_pool_t *p)
 {
-    fuzzer_ds *f;
+    fuzzer_smh *f;
     size_t prelen = sizeof("super-many-header")-1;
     
     f = apr_pcalloc(p, sizeof(*f));
     f->name             = apr_pstrdup(p, name);
     f->obs.ctx          = f;
     f->obs.before_submit = smh_submit;
+    f->count            = 123;
+    if (strlen(f->name) > prelen) {
+        const char *s = f->name + prelen;
+        int n = (int)apr_atoi64(s);
+        if (n > 0) {
+            f->count = n;
+        }
+    }
     
     return &f->obs;
 }
@@ -459,13 +481,12 @@ static void x100_submit(h2c_session *s,
                 s->c->id, stream->id);
     }
     
-    h2c_request_headers_set(r, "Expect", "100-continue");
+    h2c_request_headers_add(r, "Expect", "100-continue");
 }
 
 static h2c_session_obs *x100_create(const char *name, apr_pool_t *p)
 {
     fuzzer_ds *f;
-    size_t prelen = sizeof("super-many-header")-1;
     
     f = apr_pcalloc(p, sizeof(*f));
     f->name             = apr_pstrdup(p, name);

Modified: httpd/test/mod_h2/trunk/test/go/get.go
URL: http://svn.apache.org/viewvc/httpd/test/mod_h2/trunk/test/go/get.go?rev=1772577&r1=1772576&r2=1772577&view=diff
==============================================================================
--- httpd/test/mod_h2/trunk/test/go/get.go (original)
+++ httpd/test/mod_h2/trunk/test/go/get.go Sun Dec  4 22:07:28 2016
@@ -41,12 +41,7 @@ func main() {
         if err != nil {
             log.Fatal(err)
         } else {
-//            defer res.Body.Close()
-//            res.Write(os.Stdout)
             io.Copy(os.Stdout, res.Body)
-//            if err != nil {
-//                log.Fatal(err)
-//            }
         }
     }
 }

Added: httpd/test/mod_h2/trunk/test/go/h2fuzz.go
URL: http://svn.apache.org/viewvc/httpd/test/mod_h2/trunk/test/go/h2fuzz.go?rev=1772577&view=auto
==============================================================================
--- httpd/test/mod_h2/trunk/test/go/h2fuzz.go (added)
+++ httpd/test/mod_h2/trunk/test/go/h2fuzz.go Sun Dec  4 22:07:28 2016
@@ -0,0 +1,552 @@
+package main
+
+import (
+        "bufio"
+        "bytes"
+        "crypto/tls"
+        "errors"
+        "flag"
+        "fmt"
+        "io"
+        "log"
+        "net"
+        "os"
+        "time"
+        "strconv"
+        "strings"
+        
+        "golang.org/x/net/http2"
+        "golang.org/x/net/http2/hpack"
+        )
+
+// Flags
+var (
+     flagSettings  = flag.String("settings", "empty", "comma-separated list of KEY=value settings for the initial SETTINGS frame. The magic value 'empty' sends an empty initial settings frame, and the magic value 'omit' causes no initial settings frame to be sent.")
+     )
+
+type command struct {
+    run func(*h2fuzz, []string) error // required
+    
+    // complete optionally specifies tokens (case-insensitive) which are
+    // valid for this subcommand.
+    complete func() []string
+}
+
+var commands = map[string]command{
+    "ping":     {run: (*h2fuzz).cmdPing},
+    "settings": {run: (*h2fuzz).cmdSettings},
+    "quit":     {run: (*h2fuzz).cmdQuit},
+    "stream-get":  {run: (*h2fuzz).cmdStreamGet},
+    "stream-end":  {run: (*h2fuzz).cmdStreamEnd},
+    "headers-add": {run: (*h2fuzz).cmdHeadersAdd},
+    "headers-end": {run: (*h2fuzz).cmdHeadersEnd},
+    "data":        {run: (*h2fuzz).cmdData},
+    "headers-add-endless": {run: (*h2fuzz).cmdHeadersAddEndless},
+    "goaway":   {run: (*h2fuzz).cmdGoaway},
+    "wait":     {run: (*h2fuzz ).cmdWait},
+}
+
+func lookupCommand(name string) (c command, ok bool) {
+    name = strings.ToLower(name)
+    if c, ok = commands[name]; ok {
+        return c, ok
+    }
+    return c, c.run != nil
+}
+
+func usage() {
+    fmt.Fprintf(os.Stderr, "Usage: h2fuzz <hostname>\n\n")
+    flag.PrintDefaults()
+}
+
+// withPort adds ":443" if another port isn't already present.
+func withPort(host string) string {
+    if _, _, err := net.SplitHostPort(host); err != nil {
+        return net.JoinHostPort(host, "443")
+    }
+    return host
+}
+
+// withoutPort removes ":*" if present.
+func withoutPort(host string) string {
+    if h, _, err := net.SplitHostPort(host); err == nil {
+        return h
+    }
+    return host
+}
+
+func getFlags(args []string, flags map[string]bool) []string{
+    i := 0
+    for i < len(args) {
+        arg := args[i]
+        _, ok := flags[arg]
+        if ok {
+            flags[arg] = true
+            args = append(args[:i], args[i+1:]...)
+        } else {
+            i += 1
+        }
+    }
+    return args
+}
+
+// h2fuzz is the app's state.
+type h2fuzz struct {
+    host   string
+    tc     *tls.Conn
+    framer *http2.Framer
+    input  *bufio.Reader
+    output io.Writer
+    logHeaders bool
+    
+    // owned by the command loop:
+    streamID uint32
+    hbuf     bytes.Buffer
+    henc     *hpack.Encoder
+    eoh      bool  // end of headers
+    eos      bool  // end of stream
+    hcont    bool  // HEADER send, need CONTINUATION
+    
+    // owned by the readFrames loop:
+    peerSetting map[http2.SettingID]uint32
+    hdTableSize uint32
+    hdec        *hpack.Decoder
+    last_rst uint32
+}
+
+func main() {
+    flag.Usage = usage
+    flag.Parse()
+    if flag.NArg() != 1 {
+        usage()
+        os.Exit(2)
+    }
+    log.SetFlags(0)
+    
+    host := flag.Arg(0)
+    app := &h2fuzz{
+        host:        host,
+        peerSetting: make(map[http2.SettingID]uint32),
+        logHeaders : true,
+        hdTableSize: uint32(4 << 10),
+    }
+    app.henc = hpack.NewEncoder(&app.hbuf)
+    app.input = bufio.NewReader(os.Stdin)
+    app.output = os.Stdout
+    
+    if err := app.Main(); err != nil {
+        fmt.Fprintf(os.Stderr, "%v\n", err)
+        os.Exit(1)
+    }
+    fmt.Fprintf(os.Stdout, "\n")
+}
+
+func (app *h2fuzz) Main() error {
+    cfg := &tls.Config{
+    ServerName:         withoutPort(app.host),
+    NextProtos:         strings.Split("h2", ","),
+    InsecureSkipVerify: true,
+    }
+    
+    hostAndPort := withPort(app.host)
+    fmt.Fprintf(os.Stdout, "Connecting to %s ...", hostAndPort)
+    tc, err := tls.Dial("tcp", hostAndPort, cfg)
+    if err != nil {
+        return fmt.Errorf("Error dialing %s: %v", withPort(app.host), err)
+    }
+    fmt.Fprintf(os.Stdout, "Connected to %v", tc.RemoteAddr())
+    defer tc.Close()
+    
+    if err := tc.Handshake(); err != nil {
+        return fmt.Errorf("TLS handshake: %v", err)
+    }
+    state := tc.ConnectionState()
+    fmt.Fprintf(os.Stdout, "Negotiated protocol %q", state.NegotiatedProtocol)
+    if !state.NegotiatedProtocolIsMutual || state.NegotiatedProtocol == "" {
+        return fmt.Errorf("Could not negotiate protocol mutually")
+    }
+    
+    if _, err := io.WriteString(tc, http2.ClientPreface); err != nil {
+        return err
+    }
+    
+    app.framer = http2.NewFramer(tc, tc)
+    
+    errc := make(chan error, 2)
+    go func() { errc <- app.readFrames() }()
+    go func() { errc <- app.readConsole() }()
+    return <-errc
+}
+
+func (app *h2fuzz) logf(format string, args ...interface{}) {
+    fmt.Fprintf(app.output, format+"\r\n", args...)
+}
+
+var errExitApp = errors.New("internal sentinel error value to quit the console reading loop")
+
+func (app *h2fuzz) readConsole() error {
+    if s := *flagSettings; s != "omit" {
+        var args []string
+        if s != "empty" {
+            args = strings.Split(s, ",")
+        }
+        app.cmdSettings(args)
+    }
+    for {
+        bytes, _, err := app.input.ReadLine()
+        if err != nil {
+            return err
+        }
+        if len(bytes) == 0 {
+            break
+        }
+        line := string(bytes)
+        f := strings.Fields(line)
+        if len(f) == 0 {
+            continue
+        }
+        name, args := f[0], f[1:]
+        if f[0][0] == '#' {
+            continue
+        }
+        cmd, ok := lookupCommand(name)
+        if ok {
+            err = cmd.run(app, args)
+            if err != nil {
+                return err
+            }
+        } else {
+            return errors.New("unknown command: " + name)
+        }
+    }
+    return nil
+}
+
+func (a *h2fuzz) cmdQuit(args []string) error {
+    if len(args) > 0 {
+        a.logf("the QUIT command takes no argument")
+        return nil
+    }
+    return errExitApp
+}
+
+func (a *h2fuzz) cmdSettings(args []string) error {
+    if len(args) == 1 && strings.EqualFold(args[0], "ACK") {
+        return a.framer.WriteSettingsAck()
+    }
+    var settings []http2.Setting
+    for _, arg := range args {
+        if strings.EqualFold(arg, "ACK") {
+            a.logf("Error: ACK must be only argument with the SETTINGS command")
+            return nil
+        }
+        eq := strings.Index(arg, "=")
+        if eq == -1 {
+            a.logf("Error: invalid argument %q (expected SETTING_NAME=nnnn)", arg)
+            return nil
+        }
+        sid, ok := settingByName(arg[:eq])
+        if !ok {
+            a.logf("Error: unknown setting name %q", arg[:eq])
+            return nil
+        }
+        val, err := strconv.ParseUint(arg[eq+1:], 10, 32)
+        if err != nil {
+            a.logf("Error: invalid argument %q (expected SETTING_NAME=nnnn)", arg)
+            return nil
+        }
+        settings = append(settings, http2.Setting{
+        ID:  sid,
+        Val: uint32(val),
+        })
+    }
+    a.logf("Sending: %v", settings)
+    return a.framer.WriteSettings(settings...)
+}
+
+func settingByName(name string) (http2.SettingID, bool) {
+    for _, sid := range [...]http2.SettingID{
+        http2.SettingHeaderTableSize,
+        http2.SettingEnablePush,
+        http2.SettingMaxConcurrentStreams,
+        http2.SettingInitialWindowSize,
+        http2.SettingMaxFrameSize,
+        http2.SettingMaxHeaderListSize,
+    } {
+        if strings.EqualFold(sid.String(), name) {
+            return sid, true
+        }
+    }
+    return 0, false
+}
+
+func (app *h2fuzz) cmdPing(args []string) error {
+    if len(args) > 1 {
+        app.logf("invalid PING usage: only accepts 0 or 1 args")
+        return nil // nil means don't end the program
+    }
+    var data [8]byte
+    if len(args) == 1 {
+        copy(data[:], args[0])
+    } else {
+        copy(data[:], "h2fuzz_ping")
+    }
+    app.logf("send PING %v", data)
+    return app.framer.WritePing(false, data)
+}
+
+func (app *h2fuzz) readHeaders() error {
+    for {
+        bytes, _, err := app.input.ReadLine()
+        if err != nil {
+            return err
+        }
+        if len(bytes) == 0 {
+            break
+        }
+        line := string(bytes)
+        f := strings.SplitN(line, ": ", 2)
+        if len(f) != 2 {
+            break
+        }
+        app.writeHeader(f[0], f[1])
+    }
+    return nil;
+}
+
+func (app *h2fuzz) cmdStreamGet(args []string) error {
+    flags := map[string]bool {
+        "end": false,
+        "eos": false,
+    }
+    args = getFlags(args, flags)
+    if len(args) != 1 {
+        app.logf("Error: stream-get needs a path.")
+        return nil
+    }
+    app.startHeaders()
+    app.writeHeader(":authority", app.host)
+    app.writeHeader(":method", "GET")
+    app.writeHeader(":path", args[0])
+    app.writeHeader(":scheme", "https")
+    err := app.readHeaders()
+    if err != nil {
+        return err
+    }
+    
+    hbf := app.hbuf.Bytes()
+
+    if app.streamID == 0 {
+        app.streamID = 1
+    } else {
+        app.streamID += 2
+    }
+    app.eos = flags["eos"]
+    app.eoh = flags["eoh"]
+    app.hcont = !app.eoh
+    
+    app.logf("-->[HEADER stream=%d, method=GET, path=%s, eoh=%v, eos=%v]", 
+            app.streamID, args[0], app.eoh, app.eos)
+    return app.framer.WriteHeaders(http2.HeadersFrameParam{
+    StreamID:      app.streamID,
+    BlockFragment: hbf,
+    EndStream:     app.eos,
+    EndHeaders:    app.eoh,
+    })
+}
+
+func (app *h2fuzz) cmdHeadersAdd(args []string) error {
+    flags := map[string]bool {
+        "end": false,
+        "eos": false,
+    }
+    args = getFlags(args, flags)
+    if len(args) > 0 {
+        app.logf("Error: headers-add reads header from stdin.")
+        return nil
+    }
+    
+    if (app.eos && !app.hcont) {
+        app.logf("Error: headers-add stream already closed.")
+        return nil
+    }
+    hcont := app.hcont
+    app.eos = app.eoh || flags["eos"]
+    app.eoh = flags["end"]
+    app.hcont = !app.eoh
+    
+    app.startHeaders()
+    err := app.readHeaders()
+    if err != nil {
+        return err
+    }
+    
+    hbf := app.hbuf.Bytes()
+    if hcont {
+        app.logf("-->[CONTINUATION stream=%d, eoh=%v, eos=%v]", app.streamID, app.eoh, app.eos)
+        return app.framer.WriteContinuation(app.streamID, app.eoh, hbf)
+    } else {
+        app.logf("-->[HEADER stream=%d: eoh=%v, eos=%v]", app.streamID, app.eoh, app.eos)
+        return app.framer.WriteHeaders(http2.HeadersFrameParam{
+        StreamID:      app.streamID,
+        BlockFragment: hbf,
+        EndStream:     app.eos,
+        EndHeaders:    app.eoh,
+        })
+    }
+}
+
+func (app *h2fuzz) cmdHeadersEnd(args []string) error {
+    if app.hcont {
+        app.eoh = true
+        app.hcont = false
+        app.hbuf.Reset()
+        hbf := app.hbuf.Bytes()
+        app.logf("-->[CONTINUATION stream=%d, eoh=%v]", app.streamID, app.eoh)
+        return app.framer.WriteContinuation(app.streamID, app.eoh, hbf)
+    }
+    return nil
+}
+
+func (app *h2fuzz) cmdStreamEnd(args []string) error {
+    if app.hcont {
+        app.eos = true
+        return app.cmdHeadersEnd(args)
+    } else if !app.eos {
+        app.hbuf.Reset()
+        hbf := app.hbuf.Bytes()
+        app.eos = true
+        app.logf("-->[DATA stream=%d, eos=%v]", app.streamID, app.eos)
+        return app.framer.WriteData(app.streamID, true, hbf)
+    }
+    return nil
+}
+
+func (app *h2fuzz) cmdData(args []string) error {
+    if app.eos {
+        app.logf("Error: DATA after eos.")
+        return nil
+    }
+    if app.hcont {
+        var args []string
+        err := app.cmdHeadersEnd(args)
+        if err != nil {
+            return err
+        }
+    }
+    data := bytes.NewBuffer([]byte{})
+    data.Grow(1024)
+    app.logf("-->[DATA stream=%d, len=%d, eos=%v]", app.streamID, data.Len(), app.eos)
+    return app.framer.WriteData(app.streamID, false, data.Bytes())
+}
+
+func (app *h2fuzz) cmdHeadersAddEndless(args []string) error {
+    if len(args) > 0 {
+        app.logf("Error: get-headers-endless has no arguments.")
+        return nil
+    }
+    d, err := time.ParseDuration("1ms")
+    if err != nil {
+        return err
+    }
+    s := ""
+    for i := 0; i < 109; i++ {
+        s += "aaaaaaaaaa"
+    }
+    app.logHeaders = false
+    for app.last_rst != app.streamID {
+        app.hbuf.Reset()
+        for i := 0; i < 7; i++ {
+            app.writeHeader(s, s)
+        }
+        hbf := app.hbuf.Bytes()
+        if app.logHeaders {
+            app.logf("-->[CONTINUATION stream=%d, eoh=%v]", app.streamID, app.eoh)
+        }
+        err := app.framer.WriteContinuation(app.streamID, false, hbf)
+        if err != nil {
+            break
+        }
+        time.Sleep(d)
+    }
+    return err
+}
+
+func (app *h2fuzz) cmdGoaway(args []string) error {
+    app.logf("-->[GOAWAY")
+    return app.framer.WriteGoAway(0, 0, nil)
+}
+
+func (app *h2fuzz) cmdWait(args []string) error {
+    s := "1m"
+    if len(args) > 0 {
+        s = args[0]
+    }
+    d, err := time.ParseDuration(s)
+    if err == nil {
+        app.logf("Waiting %s", s)
+        time.Sleep(d)
+    }
+    return err
+}
+
+func (app *h2fuzz) readFrames() error {
+    for {
+        f, err := app.framer.ReadFrame()
+        if err == io.EOF {
+            return nil
+        }
+        if err != nil {
+            return fmt.Errorf("ReadFrame: %v", err)
+        }
+        app.logf("<--%v", f)
+        switch f := f.(type) {
+        case *http2.PingFrame:
+            app.logf("    Data = %q", f.Data)
+        case *http2.SettingsFrame:
+            f.ForeachSetting(func(s http2.Setting) error {
+                app.logf("    %v", s)
+                app.peerSetting[s.ID] = s.Val
+                return nil
+            })
+        case *http2.WindowUpdateFrame:
+            app.logf("    Window-Increment = %v", f.Increment)
+        case *http2.GoAwayFrame:
+            app.logf("    Last-Stream-ID = %d; Error-Code = %v (%d)", f.LastStreamID, f.ErrCode, f.ErrCode)
+        case *http2.DataFrame:
+            app.logf("    %q", f.Data())
+        case *http2.HeadersFrame:
+            if f.HasPriority() {
+                app.logf("    PRIORITY = %v", f.Priority)
+            }
+            if app.hdec == nil {
+                app.hdec = hpack.NewDecoder(app.hdTableSize, app.onNewHeaderField)
+            }
+            app.hdec.Write(f.HeaderBlockFragment())
+        case *http2.RSTStreamFrame:
+            app.last_rst = f.StreamID
+        }
+    }
+}
+
+// called from readLoop
+func (app *h2fuzz) onNewHeaderField(f hpack.HeaderField) {
+    if f.Sensitive {
+        app.logf("    %s = %q (SENSITIVE)", f.Name, f.Value)
+    }
+    app.logf("    %s = %q", f.Name, f.Value)
+}
+
+func (app *h2fuzz) writeHeader(name, value string) {
+    app.henc.WriteField(hpack.HeaderField{Name: name, Value: value})
+    if app.logHeaders {
+        app.logf("    %s = %s", name, value)
+    }
+}
+
+func (app *h2fuzz) startHeaders() {
+    app.hbuf.Reset()
+    if app.logHeaders {
+        app.logf("    #start headers")
+    }
+}

Added: httpd/test/mod_h2/trunk/test/go/in-footer-endless.fuzz
URL: http://svn.apache.org/viewvc/httpd/test/mod_h2/trunk/test/go/in-footer-endless.fuzz?rev=1772577&view=auto
==============================================================================
--- httpd/test/mod_h2/trunk/test/go/in-footer-endless.fuzz (added)
+++ httpd/test/mod_h2/trunk/test/go/in-footer-endless.fuzz Sun Dec  4 22:07:28 2016
@@ -0,0 +1,13 @@
+wait 1ms
+stream-get /
+trailer: x
+
+data
+headers-add
+x: y
+
+headers-add-endless
+stream-end
+wait 1ms
+goaway
+wait

Added: httpd/test/mod_h2/trunk/test/go/in-footer.fuzz
URL: http://svn.apache.org/viewvc/httpd/test/mod_h2/trunk/test/go/in-footer.fuzz?rev=1772577&view=auto
==============================================================================
--- httpd/test/mod_h2/trunk/test/go/in-footer.fuzz (added)
+++ httpd/test/mod_h2/trunk/test/go/in-footer.fuzz Sun Dec  4 22:07:28 2016
@@ -0,0 +1,11 @@
+wait 1ms
+stream-get / end
+trailer: x
+
+data
+headers-add end
+x: y
+
+wait 100ms
+goaway
+wait

Added: httpd/test/mod_h2/trunk/test/go/in-get.fuzz
URL: http://svn.apache.org/viewvc/httpd/test/mod_h2/trunk/test/go/in-get.fuzz?rev=1772577&view=auto
==============================================================================
--- httpd/test/mod_h2/trunk/test/go/in-get.fuzz (added)
+++ httpd/test/mod_h2/trunk/test/go/in-get.fuzz Sun Dec  4 22:07:28 2016
@@ -0,0 +1,14 @@
+wait 1ms
+stream-get / eos
+x: y
+y: 3
+z: something
+
+headers-add end
+x2: y
+y3: 3
+z4: something
+
+wait 100ms
+goaway
+wait

Added: httpd/test/mod_h2/trunk/test/go/in-headers-endless.fuzz
URL: http://svn.apache.org/viewvc/httpd/test/mod_h2/trunk/test/go/in-headers-endless.fuzz?rev=1772577&view=auto
==============================================================================
--- httpd/test/mod_h2/trunk/test/go/in-headers-endless.fuzz (added)
+++ httpd/test/mod_h2/trunk/test/go/in-headers-endless.fuzz Sun Dec  4 22:07:28 2016
@@ -0,0 +1,8 @@
+wait 1ms
+stream-get / eos
+
+headers-add-endless
+stream-end
+wait 1ms
+goaway
+wait