Commit a829e675 authored by Stefan Eissing's avatar Stefan Eissing
Browse files

merge of...

merge of 1752145,1753498,1753541,1754129,1754414,1754534,1755323,1756844,1757524,1757534,1757540 from trunk

mod_http2: backport of latest changes
 - intermediate responses
 - graceful shutdown of connections
 - ht debug draft update



git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/branches/2.4.x@1757542 13f79535-47bb-0310-9956-ffa450edef68
parent 06a1753d
Loading
Loading
Loading
Loading
+7 −0
Original line number Diff line number Diff line
@@ -2,6 +2,13 @@

Changes with Apache 2.4.24

  *) mod_http2: h2 status resource follows latest draft, see
     http://www.ietf.org/id/draft-benfield-http2-debug-state-01.txt
     [Stefan Eissing]
     
  *) mod_http2: handling graceful shutdown gracefully, e.g. handling existing
     streams to the end. [Stefan Eissing]
  
  *) core: CVE-2016-5387: Mitigate [f]cgi "httpoxy" issues.
     [Dominic Scheirlinck <dominic vendhq.com>, Yann Ylavic]

+3 −2
Original line number Diff line number Diff line
@@ -95,8 +95,6 @@ typedef enum {
    H2_SESSION_ST_IDLE,             /* nothing to write, expecting data inc */
    H2_SESSION_ST_BUSY,             /* read/write without stop */
    H2_SESSION_ST_WAIT,             /* waiting for tasks reporting back */
    H2_SESSION_ST_LOCAL_SHUTDOWN,   /* we announced GOAWAY */
    H2_SESSION_ST_REMOTE_SHUTDOWN,  /* client announced GOAWAY */
} h2_session_state;

typedef struct h2_session_props {
@@ -106,6 +104,7 @@ typedef struct h2_session_props {
    apr_uint32_t emitted_max;       /* the highest local stream id sent */
    apr_uint32_t error;             /* the last session error encountered */
    unsigned int accepting : 1;     /* if the session is accepting new streams */
    unsigned int shutdown : 1;      /* if the final GOAWAY has been sent */
} h2_session_props;


@@ -146,6 +145,8 @@ struct h2_response {
    apr_off_t   content_length;
    apr_table_t *headers;
    apr_table_t *trailers;
    struct h2_response *next;
    
    const char  *sos_filter;
};

+147 −47
Original line number Diff line number Diff line
@@ -15,6 +15,7 @@

#include <assert.h>

#include <apr_strings.h>
#include <httpd.h>
#include <http_core.h>
#include <http_log.h>
@@ -23,6 +24,7 @@

#include "h2_private.h"
#include "h2.h"
#include "h2_config.h"
#include "h2_conn_io.h"
#include "h2_ctx.h"
#include "h2_mplx.h"
@@ -31,6 +33,7 @@
#include "h2_stream.h"
#include "h2_request.h"
#include "h2_response.h"
#include "h2_stream.h"
#include "h2_session.h"
#include "h2_util.h"
#include "h2_version.h"
@@ -209,71 +212,168 @@ static apr_status_t bbout(apr_bucket_brigade *bb, const char *fmt, ...)
    return rv;
}

static apr_status_t h2_status_stream_filter(h2_stream *stream)
static void add_settings(apr_bucket_brigade *bb, h2_session *s, int last) 
{
    h2_session *session = stream->session;
    h2_mplx *mplx = session->mplx;
    conn_rec *c = session->c;
    h2_push_diary *diary;
    h2_mplx *m = s->mplx;
    
    bbout(bb, "  \"settings\": {\n");
    bbout(bb, "    \"SETTINGS_MAX_CONCURRENT_STREAMS\": %d,\n", m->max_streams); 
    bbout(bb, "    \"SETTINGS_MAX_FRAME_SIZE\": %d,\n", 16*1024); 
    bbout(bb, "    \"SETTINGS_INITIAL_WINDOW_SIZE\": %d,\n",
          h2_config_geti(s->config, H2_CONF_WIN_SIZE));
    bbout(bb, "    \"SETTINGS_ENABLE_PUSH\": %d\n", h2_session_push_enabled(s)); 
    bbout(bb, "  }%s\n", last? "" : ",");
}

static void add_peer_settings(apr_bucket_brigade *bb, h2_session *s, int last) 
{
    bbout(bb, "  \"peerSettings\": {\n");
    bbout(bb, "    \"SETTINGS_MAX_CONCURRENT_STREAMS\": %d,\n", 
        nghttp2_session_get_remote_settings(s->ngh2, NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS)); 
    bbout(bb, "    \"SETTINGS_MAX_FRAME_SIZE\": %d,\n", 
        nghttp2_session_get_remote_settings(s->ngh2, NGHTTP2_SETTINGS_MAX_FRAME_SIZE)); 
    bbout(bb, "    \"SETTINGS_INITIAL_WINDOW_SIZE\": %d,\n", 
        nghttp2_session_get_remote_settings(s->ngh2, NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE)); 
    bbout(bb, "    \"SETTINGS_ENABLE_PUSH\": %d,\n", 
        nghttp2_session_get_remote_settings(s->ngh2, NGHTTP2_SETTINGS_ENABLE_PUSH)); 
    bbout(bb, "    \"SETTINGS_HEADER_TABLE_SIZE\": %d,\n", 
        nghttp2_session_get_remote_settings(s->ngh2, NGHTTP2_SETTINGS_HEADER_TABLE_SIZE)); 
    bbout(bb, "    \"SETTINGS_MAX_HEADER_LIST_SIZE\": %d\n", 
        nghttp2_session_get_remote_settings(s->ngh2, NGHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE)); 
    bbout(bb, "  }%s\n", last? "" : ",");
}

typedef struct {
    apr_bucket_brigade *bb;
    apr_status_t status;
    h2_session *s;
    int idx;
} stream_ctx_t;

    if (!stream->response) {
        return APR_EINVAL;
static int add_stream(h2_stream *stream, void *ctx)
{
    stream_ctx_t *x = ctx;
    int32_t flowIn, flowOut;
    
    flowIn = nghttp2_session_get_stream_effective_local_window_size(x->s->ngh2, stream->id); 
    flowOut = nghttp2_session_get_stream_remote_window_size(x->s->ngh2, stream->id);
    bbout(x->bb, "%s\n    \"%d\": {\n", (x->idx? "," : ""), stream->id);
    bbout(x->bb, "    \"state\": \"%s\",\n", h2_stream_state_str(stream));
    bbout(x->bb, "    \"created\": %f,\n", ((double)stream->created)/APR_USEC_PER_SEC);
    bbout(x->bb, "    \"flowIn\": %d,\n", flowIn);
    bbout(x->bb, "    \"flowOut\": %d,\n", flowOut);
    bbout(x->bb, "    \"dataIn\": %"APR_UINT64_T_FMT",\n", stream->in_data_octets);  
    bbout(x->bb, "    \"dataOut\": %"APR_UINT64_T_FMT"\n", stream->out_data_octets);  
    bbout(x->bb, "    }");
    
    ++x->idx;
    return 1;
} 

    if (!stream->buffer) {
        stream->buffer = apr_brigade_create(stream->pool, c->bucket_alloc);
static void add_streams(apr_bucket_brigade *bb, h2_session *s, int last) 
{
    stream_ctx_t x;
    
    x.bb = bb;
    x.s = s;
    x.idx = 0;
    bbout(bb, "  \"streams\": {");
    h2_mplx_stream_do(s->mplx, add_stream, &x);
    bbout(bb, "\n  }%s\n", last? "" : ",");
}
    bb = stream->buffer;

    apr_table_unset(stream->response->headers, "Content-Length");
    stream->response->content_length = -1;
static void add_push(apr_bucket_brigade *bb, h2_session *s, 
                     h2_stream *stream, int last) 
{
    h2_push_diary *diary;
    apr_status_t status;
    
    bbout(bb, "{\n");
    bbout(bb, "  \"HTTP2\": \"on\",\n");
    bbout(bb, "  \"H2PUSH\": \"%s\",\n", h2_session_push_enabled(session)? "on" : "off");
    bbout(bb, "  \"mod_http2_version\": \"%s\",\n", MOD_HTTP2_VERSION);
    bbout(bb, "  \"session_id\": %ld,\n", (long)session->id);
    bbout(bb, "  \"streams_max\": %d,\n", (int)session->max_stream_count);
    bbout(bb, "  \"this_stream\": %d,\n", stream->id);
    bbout(bb, "  \"streams_open\": %d,\n", (int)h2_ihash_count(session->streams));
    bbout(bb, "  \"max_stream_started\": %d,\n", mplx->max_stream_started);
    bbout(bb, "  \"requests_received\": %d,\n", session->remote.emitted_count);
    bbout(bb, "  \"responses_submitted\": %d,\n", session->responses_submitted);
    bbout(bb, "  \"streams_reset\": %d, \n", session->streams_reset);
    bbout(bb, "  \"pushes_promised\": %d,\n", session->pushes_promised);
    bbout(bb, "  \"pushes_submitted\": %d,\n", session->pushes_submitted);
    bbout(bb, "  \"pushes_reset\": %d,\n", session->pushes_reset);
    
    diary = session->push_diary;
    bbout(bb, "    \"push\": {\n");
    diary = s->push_diary;
    if (diary) {
        const char *data;
        const char *base64_digest;
        apr_size_t len;
        
        status = h2_push_diary_digest_get(diary, stream->pool, 256, 
                                          stream->request->authority, &data, &len);
        status = h2_push_diary_digest_get(diary, bb->p, 256, 
                                          stream->request->authority, 
                                          &data, &len);
        if (status == APR_SUCCESS) {
            base64_digest = h2_util_base64url_encode(data, len, stream->pool);
            bbout(bb, "  \"cache_digest\": \"%s\",\n", base64_digest);
            base64_digest = h2_util_base64url_encode(data, len, bb->p);
            bbout(bb, "      \"cacheDigest\": \"%s\",\n", base64_digest);
        }
    }
    bbout(bb, "      \"promises\": %d,\n", s->pushes_promised);
    bbout(bb, "      \"submits\": %d,\n", s->pushes_submitted);
    bbout(bb, "      \"resets\": %d\n", s->pushes_reset);
    bbout(bb, "    }%s\n", last? "" : ",");
}

        /* try the reverse for testing purposes */
        status = h2_push_diary_digest_set(diary, stream->request->authority, data, len);
        if (status == APR_SUCCESS) {
            status = h2_push_diary_digest_get(diary, stream->pool, 256, 
                                              stream->request->authority, &data, &len);
            if (status == APR_SUCCESS) {
                base64_digest = h2_util_base64url_encode(data, len, stream->pool);
                bbout(bb, "  \"cache_digest^2\": \"%s\",\n", base64_digest);
static void add_in(apr_bucket_brigade *bb, h2_session *s, int last) 
{
    bbout(bb, "    \"in\": {\n");
    bbout(bb, "      \"requests\": %d,\n", s->remote.emitted_count);
    bbout(bb, "      \"resets\": %d, \n", s->streams_reset);
    bbout(bb, "      \"frames\": %ld,\n", (long)s->frames_received);
    bbout(bb, "      \"octets\": %"APR_UINT64_T_FMT"\n", s->io.bytes_read);
    bbout(bb, "    }%s\n", last? "" : ",");
}

static void add_out(apr_bucket_brigade *bb, h2_session *s, int last) 
{
    bbout(bb, "    \"out\": {\n");
    bbout(bb, "      \"responses\": %d,\n", s->responses_submitted);
    bbout(bb, "      \"frames\": %ld,\n", (long)s->frames_sent);
    bbout(bb, "      \"octets\": %"APR_UINT64_T_FMT"\n", s->io.bytes_written);
    bbout(bb, "    }%s\n", last? "" : ",");
}

static void add_stats(apr_bucket_brigade *bb, h2_session *s, 
                     h2_stream *stream, int last) 
{
    bbout(bb, "  \"stats\": {\n");
    add_in(bb, s, 0);
    add_out(bb, s, 0);
    add_push(bb, s, stream, 1);
    bbout(bb, "  }%s\n", last? "" : ",");
}

static apr_status_t h2_status_stream_filter(h2_stream *stream)
{
    h2_session *s = stream->session;
    conn_rec *c = s->c;
    apr_bucket_brigade *bb;
    int32_t connFlowIn, connFlowOut;
    
    if (!stream->response) {
        return APR_EINVAL;
    }
    bbout(bb, "  \"frames_received\": %ld,\n", (long)session->frames_received);
    bbout(bb, "  \"frames_sent\": %ld,\n", (long)session->frames_sent);
    bbout(bb, "  \"bytes_received\": %"APR_UINT64_T_FMT",\n", session->io.bytes_read);
    bbout(bb, "  \"bytes_sent\": %"APR_UINT64_T_FMT"\n", session->io.bytes_written);
    
    if (!stream->buffer) {
        stream->buffer = apr_brigade_create(stream->pool, c->bucket_alloc);
    }
    bb = stream->buffer;
    
    apr_table_unset(stream->response->headers, "Content-Length");
    stream->response->content_length = -1;
    
    connFlowIn = nghttp2_session_get_effective_local_window_size(s->ngh2); 
    connFlowOut = nghttp2_session_get_remote_window_size(s->ngh2);
    apr_table_setn(stream->response->headers, "conn-flow-in", 
                   apr_itoa(stream->pool, connFlowIn));
    apr_table_setn(stream->response->headers, "conn-flow-out", 
                   apr_itoa(stream->pool, connFlowOut));
     
    bbout(bb, "{\n");
    bbout(bb, "  \"version\": \"draft-01\",\n");
    add_settings(bb, s, 0);
    add_peer_settings(bb, s, 0);
    bbout(bb, "  \"connFlowIn\": %d,\n", connFlowIn);
    bbout(bb, "  \"connFlowOut\": %d,\n", connFlowOut);
    bbout(bb, "  \"sentGoAway\": %d,\n", s->local.shutdown);

    add_streams(bb, s, 0);
    
    add_stats(bb, s, stream, 1);
    bbout(bb, "}\n");
    
    return APR_SUCCESS;
+2 −1
Original line number Diff line number Diff line
@@ -471,7 +471,8 @@ static h2_response *create_response(h2_from_h1 *from_h1, request_rec *r)
                     (void *) headers, r->headers_out, NULL);
    }
    
    return h2_response_rcreate(from_h1->stream_id, r, headers, r->pool);
    return h2_response_rcreate(from_h1->stream_id, r, r->status, 
                               headers, r->pool);
}

apr_status_t h2_response_output_filter(ap_filter_t *f, apr_bucket_brigade *bb)
+67 −15
Original line number Diff line number Diff line
@@ -196,12 +196,17 @@ static int purge_stream(void *ctx, void *val)
{
    h2_mplx *m = ctx;
    h2_stream *stream = val;
    h2_task *task = h2_ihash_get(m->tasks, stream->id);
    h2_ihash_remove(m->spurge, stream->id);
    int stream_id = stream->id;
    h2_task *task = h2_ihash_get(m->tasks, stream_id);
    
    h2_ihash_remove(m->spurge, stream_id);
    h2_stream_destroy(stream);
    if (task) {
        task_destroy(m, task, 1);
    }
    /* FIXME: task_destroy() might in some twisted way place the
     * stream in the spurge hash again. Remove it last. */
    h2_ihash_remove(m->spurge, stream_id);
    return 0;
}

@@ -212,6 +217,7 @@ static void purge_streams(h2_mplx *m)
            /* repeat until empty */
        }
        h2_ihash_clear(m->spurge);
        AP_DEBUG_ASSERT(h2_ihash_empty(m->spurge));
    }
}

@@ -467,6 +473,33 @@ static int stream_done_iter(void *ctx, void *val)
    return 0;
}

typedef struct {
    h2_mplx_stream_cb *cb;
    void *ctx;
} stream_iter_ctx_t;

static int stream_iter_wrap(void *ctx, void *stream)
{
    stream_iter_ctx_t *x = ctx;
    return x->cb(stream, x->ctx);
}

apr_status_t h2_mplx_stream_do(h2_mplx *m, h2_mplx_stream_cb *cb, void *ctx)
{
    apr_status_t status;
    int acquired;
    
    if ((status = enter_mutex(m, &acquired)) == APR_SUCCESS) {
        stream_iter_ctx_t x;
        x.cb = cb;
        x.ctx = ctx;
        h2_ihash_iter(m->streams, stream_iter_wrap, &x);
        
        leave_mutex(m, acquired);
    }
    return status;
}

static int task_print(void *ctx, void *val)
{
    h2_mplx *m = ctx;
@@ -477,13 +510,13 @@ static int task_print(void *ctx, void *val)

        ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, m->c, /* NO APLOGNO */
                      "->03198: h2_stream(%s): %s %s %s -> %s %d"
                      "[orph=%d/started=%d/done=%d]", 
                      "[orph=%d/started=%d/done=%d/frozen=%d]", 
                      task->id, task->request->method, 
                      task->request->authority, task->request->path,
                      task->response? "http" : (task->rst_error? "reset" : "?"),
                      task->response? task->response->http_status : task->rst_error,
                      (stream? 0 : 1), task->worker_started, 
                      task->worker_done);
                      task->worker_done, task->frozen);
    }
    else if (task) {
        ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, m->c, /* NO APLOGNO */
@@ -566,8 +599,11 @@ apr_status_t h2_mplx_release_and_join(h2_mplx *m, apr_thread_cond_t *wait)
    
        if (!h2_ihash_empty(m->shold)) {
            ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, m->c,
                          "h2_mplx(%ld): 2. release_join with %d streams in hold", 
                          m->id, (int)h2_ihash_count(m->shold));
                          "h2_mplx(%ld): 2. release_join with %d streams in "
                          "hold, %d workers busy, %d tasks", 
                          m->id, (int)h2_ihash_count(m->shold),
                          m->workers_busy,  
                          (int)h2_ihash_count(m->tasks));
        }
        if (!h2_ihash_empty(m->spurge)) {
            ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, m->c,
@@ -608,6 +644,12 @@ apr_status_t h2_mplx_release_and_join(h2_mplx *m, apr_thread_cond_t *wait)
            }
        }
        
        if (!h2_ihash_empty(m->tasks) && APLOGctrace1(m->c)) {
            ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, m->c,
                          "h2_mplx(%ld): 3. release_join with %d tasks",
                          m->id, (int)h2_ihash_count(m->tasks));
            h2_ihash_iter(m->tasks, task_print, m);
        }
        AP_DEBUG_ASSERT(h2_ihash_empty(m->shold));
        if (!h2_ihash_empty(m->spurge)) {
            ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, m->c,
@@ -615,7 +657,6 @@ apr_status_t h2_mplx_release_and_join(h2_mplx *m, apr_thread_cond_t *wait)
                          m->id, (int)h2_ihash_count(m->spurge));
            purge_streams(m);
        }
        AP_DEBUG_ASSERT(h2_ihash_empty(m->spurge));
        
        if (!h2_ihash_empty(m->tasks)) {
            ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, m->c, APLOGNO(03056)
@@ -675,13 +716,15 @@ static apr_status_t out_open(h2_mplx *m, int stream_id, h2_response *response)
        return APR_ECONNABORTED;
    }
    
    ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, m->c,
                  "h2_mplx(%s): open response: %d, rst=%d",
    status = h2_task_add_response(task, response);
    ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, m->c,
                  "h2_mplx(%s): add response: %d, rst=%d",
                  task->id, response->http_status, response->rst_error);
    if (status != APR_SUCCESS) {
        return status;
    }
    
    h2_task_set_response(task, response);
    
    if (task->output.beam) {
    if (task->output.beam && !task->output.opened) {
        h2_beam_buffer_size_set(task->output.beam, m->stream_max_mem);
        h2_beam_timeout_set(task->output.beam, m->stream_timeout);
        h2_beam_on_consumed(task->output.beam, stream_output_consumed, task);
@@ -690,6 +733,7 @@ static apr_status_t out_open(h2_mplx *m, int stream_id, h2_response *response)
            h2_beam_on_file_beam(task->output.beam, can_beam_file, m);
        }
        h2_beam_mutex_set(task->output.beam, beam_enter, task->cond, m);
        task->output.opened = 1;
    }
    
    h2_ihash_add(m->sready, stream);
@@ -1375,13 +1419,21 @@ apr_status_t h2_mplx_dispatch_master_events(h2_mplx *m,
                              m->id, stream->id);
                task = h2_ihash_get(m->tasks, stream->id);
                if (task) {
                    task->submitted = 1;
                    task->response_sent = 1;
                    if (task->rst_error) {
                        h2_stream_rst(stream, task->rst_error);
                    }
                    else {
                        AP_DEBUG_ASSERT(task->response);
                        h2_stream_set_response(stream, task->response, task->output.beam);
                        status = h2_stream_add_response(stream, task->response, 
                                                        task->output.beam);
                        if (status != APR_SUCCESS) {
                            h2_stream_rst(stream, H2_ERR_INTERNAL_ERROR);
                        }
                        if (!h2_response_get_final(task->response)) {
                            /* the final response needs still to arrive */
                            task->response = NULL;
                        }
                    }
                }
                else {
@@ -1449,7 +1501,7 @@ apr_status_t h2_mplx_suspend_stream(h2_mplx *m, int stream_id)
            if (stream->started && (!task || task->worker_done)) {
                h2_ihash_add(m->sresume, stream);
            }
            else {
            else if (task->output.beam) {
                /* register callback so that we can resume on new output */
                h2_beam_on_produced(task->output.beam, output_produced, m);
            }
Loading