From 795ee8c20fc7ba36bae9651c64d4e0afa2d34be1 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Sun, 3 Apr 2016 22:58:25 +0900 Subject: [PATCH] altsvc: Receive ALTSVC frame --- lib/includes/nghttp2/nghttp2.h | 22 +++- lib/nghttp2_frame.c | 20 +++- lib/nghttp2_frame.h | 25 ++++- lib/nghttp2_option.c | 17 +++- lib/nghttp2_option.h | 7 +- lib/nghttp2_outbound_item.c | 2 + lib/nghttp2_session.c | 180 ++++++++++++++++++++++++++++++--- lib/nghttp2_session.h | 15 +++ lib/nghttp2_submit.c | 1 + src/nghttp.cc | 1 + 10 files changed, 272 insertions(+), 18 deletions(-) diff --git a/lib/includes/nghttp2/nghttp2.h b/lib/includes/nghttp2/nghttp2.h index acfe6ecb..99562beb 100644 --- a/lib/includes/nghttp2/nghttp2.h +++ b/lib/includes/nghttp2/nghttp2.h @@ -2377,6 +2377,26 @@ NGHTTP2_EXTERN void nghttp2_option_set_user_recv_extension_type(nghttp2_option *option, uint8_t type); +/** + * @ function + * + * Sets extension frame type the application is willing to receive + * using builtin handler. The |type| is the extension frame type to + * receive, and must be strictly greater than 0x9. Otherwise, this + * function does nothing. The application can call this function + * multiple times to set more than one frame type to receive. The + * application does not have to call this function if it just sends + * extension frames. + * + * If same frame type is passed to both + * `nghttp2_option_set_builtin_recv_extension_type()` and + * `nghttp2_option_set_user_recv_extension_type()`, the latter takes + * precedence. + */ +NGHTTP2_EXTERN void +nghttp2_option_set_builtin_recv_extension_type(nghttp2_option *option, + uint8_t type); + /** * @function * @@ -4123,7 +4143,7 @@ NGHTTP2_EXTERN int nghttp2_submit_extension(nghttp2_session *session, * The payload of ALTSVC frame. ALTSVC frame is one of extension * frame. If this frame is received, and * `nghttp2_option_set_user_recv_extension_type()` is not set, and - * `nghttp2_option_set_recv_extension_type()` is set for + * `nghttp2_option_set_builtin_recv_extension_type()` is set for * :enum:`NGHTTP2_ALTSVC`, ``nghttp2_extension.payload`` will point to * this struct. * diff --git a/lib/nghttp2_frame.c b/lib/nghttp2_frame.c index 879c07e6..b4c1d443 100644 --- a/lib/nghttp2_frame.c +++ b/lib/nghttp2_frame.c @@ -215,7 +215,6 @@ void nghttp2_frame_altsvc_free(nghttp2_extension *frame, nghttp2_mem *mem) { /* We use the same buffer for altsvc->origin and altsvc->field_value. */ nghttp2_mem_free(mem, altsvc->origin); - nghttp2_mem_free(mem, altsvc); } size_t nghttp2_frame_priority_len(uint8_t flags) { @@ -723,6 +722,25 @@ int nghttp2_frame_pack_altsvc(nghttp2_bufs *bufs, nghttp2_extension *frame) { return 0; } +void nghttp2_frame_unpack_altsvc_payload(nghttp2_extension *frame, + size_t origin_len, uint8_t *payload, + size_t payloadlen) { + nghttp2_ext_altsvc *altsvc; + uint8_t *p; + + altsvc = frame->payload; + p = payload; + + altsvc->origin = p; + + p += origin_len; + + altsvc->origin_len = origin_len; + + altsvc->field_value = p; + altsvc->field_value_len = (size_t)(payload + payloadlen - p); +} + nghttp2_settings_entry *nghttp2_frame_iv_copy(const nghttp2_settings_entry *iv, size_t niv, nghttp2_mem *mem) { nghttp2_settings_entry *iv_copy; diff --git a/lib/nghttp2_frame.h b/lib/nghttp2_frame.h index 69feb73b..c6111728 100644 --- a/lib/nghttp2_frame.h +++ b/lib/nghttp2_frame.h @@ -72,7 +72,7 @@ #define NGHTTP2_MAX_PADLEN 256 /* Union of extension frame payload */ -typedef union { int dummy; } nghttp2_ext_frame_payload; +typedef union { nghttp2_ext_altsvc altsvc; } nghttp2_ext_frame_payload; void nghttp2_frame_pack_frame_hd(uint8_t *buf, const nghttp2_frame_hd *hd); @@ -378,6 +378,16 @@ void nghttp2_frame_unpack_window_update_payload(nghttp2_window_update *frame, */ int nghttp2_frame_pack_altsvc(nghttp2_bufs *bufs, nghttp2_extension *ext); +/* + * Unpacks ALTSVC wire format into |frame|. The |payload| of + * |payloadlen| bytes contains frame payload. This function assumes + * that frame->payload points to the nghttp2_ext_altsvc object. + * + * This function always succeeds and returns 0. + */ +void nghttp2_frame_unpack_altsvc_payload(nghttp2_extension *frame, + size_t origin_len, uint8_t *payload, + size_t payloadlen); /* * Initializes HEADERS frame |frame| with given values. |frame| takes * ownership of |nva|, so caller must not free it. If |stream_id| is @@ -456,10 +466,23 @@ void nghttp2_frame_extension_init(nghttp2_extension *frame, uint8_t type, void nghttp2_frame_extension_free(nghttp2_extension *frame); +/* + * Initializes ALTSVC frame |frame| with given values. This function + * assumes that frame->payload points to nghttp2_ext_altsvc object. + * Also |origin| and |field_value| are allocated in single buffer, + * starting |origin|. On success, this function takes ownership of + * |origin|, so caller must not free it. + */ void nghttp2_frame_altsvc_init(nghttp2_extension *frame, int32_t stream_id, uint8_t *origin, size_t origin_len, uint8_t *field_value, size_t field_value_len); +/* + * Frees up resources under |frame|. This function does not free + * nghttp2_ext_altsvc object pointed by frame->payload. This function + * only frees origin pointed by nghttp2_ext_altsvc.origin. Therefore, + * other fields must be allocated in the same buffer with origin. + */ void nghttp2_frame_altsvc_free(nghttp2_extension *frame, nghttp2_mem *mem); /* diff --git a/lib/nghttp2_option.c b/lib/nghttp2_option.c index e3e8717b..65200942 100644 --- a/lib/nghttp2_option.c +++ b/lib/nghttp2_option.c @@ -63,6 +63,10 @@ void nghttp2_option_set_max_reserved_remote_streams(nghttp2_option *option, option->max_reserved_remote_streams = val; } +static void set_ext_type(uint8_t *ext_types, uint8_t type) { + ext_types[type / 8] = (uint8_t)(ext_types[type / 8] | (1 << (type & 0x7))); +} + void nghttp2_option_set_user_recv_extension_type(nghttp2_option *option, uint8_t type) { if (type < 10) { @@ -70,8 +74,17 @@ void nghttp2_option_set_user_recv_extension_type(nghttp2_option *option, } option->opt_set_mask |= NGHTTP2_OPT_USER_RECV_EXT_TYPES; - option->user_recv_ext_types[type / 8] = - (uint8_t)(option->user_recv_ext_types[type / 8] | (1 << (type & 0x7))); + set_ext_type(option->user_recv_ext_types, type); +} + +void nghttp2_option_set_builtin_recv_extension_type(nghttp2_option *option, + uint8_t type) { + if (type < 10) { + return; + } + + option->opt_set_mask |= NGHTTP2_OPT_BUILTIN_RECV_EXT_TYPES; + set_ext_type(option->builtin_recv_ext_types, type); } void nghttp2_option_set_no_auto_ping_ack(nghttp2_option *option, int val) { diff --git a/lib/nghttp2_option.h b/lib/nghttp2_option.h index f859b4eb..b7d69610 100644 --- a/lib/nghttp2_option.h +++ b/lib/nghttp2_option.h @@ -61,7 +61,8 @@ typedef enum { NGHTTP2_OPT_NO_HTTP_MESSAGING = 1 << 3, NGHTTP2_OPT_MAX_RESERVED_REMOTE_STREAMS = 1 << 4, NGHTTP2_OPT_USER_RECV_EXT_TYPES = 1 << 5, - NGHTTP2_OPT_NO_AUTO_PING_ACK = 1 << 6 + NGHTTP2_OPT_NO_AUTO_PING_ACK = 1 << 6, + NGHTTP2_OPT_BUILTIN_RECV_EXT_TYPES = 1 << 7 } nghttp2_option_flag; /** @@ -101,6 +102,10 @@ struct nghttp2_option { * NGHTTP2_OPT_USER_RECV_EXT_TYPES */ uint8_t user_recv_ext_types[32]; + /** + * NGHTTP2_OPT_BUILTIN_RECV_EXT_TYPES + */ + uint8_t builtin_recv_ext_types[32]; }; #endif /* NGHTTP2_OPTION_H */ diff --git a/lib/nghttp2_outbound_item.c b/lib/nghttp2_outbound_item.c index 1633cc36..9450ad7c 100644 --- a/lib/nghttp2_outbound_item.c +++ b/lib/nghttp2_outbound_item.c @@ -90,6 +90,8 @@ void nghttp2_outbound_item_free(nghttp2_outbound_item *item, nghttp2_mem *mem) { assert(0); break; } + + nghttp2_mem_free(mem, frame->ext.payload); } } } diff --git a/lib/nghttp2_session.c b/lib/nghttp2_session.c index dd63f6f0..4bede2e2 100644 --- a/lib/nghttp2_session.c +++ b/lib/nghttp2_session.c @@ -142,6 +142,10 @@ static int session_detect_idle_stream(nghttp2_session *session, return 0; } +static int check_ext_type_set(const uint8_t *ext_types, uint8_t type) { + return (ext_types[type / 8] & (1 << (type & 0x7))) > 0; +} + static int session_call_error_callback(nghttp2_session *session, const char *fmt, ...) { size_t bufsize; @@ -316,7 +320,18 @@ static void session_inbound_frame_reset(nghttp2_session *session) { break; default: /* extension frame */ - nghttp2_frame_extension_free(&iframe->frame.ext); + if (check_ext_type_set(session->user_recv_ext_types, + iframe->frame.hd.type)) { + nghttp2_frame_extension_free(&iframe->frame.ext); + } else if (check_ext_type_set(session->builtin_recv_ext_types, + iframe->frame.hd.type)) { + switch (iframe->frame.hd.type) { + case NGHTTP2_ALTSVC: + nghttp2_frame_altsvc_free(&iframe->frame.ext, mem); + break; + } + } + break; } @@ -332,6 +347,8 @@ static void session_inbound_frame_reset(nghttp2_session *session) { nghttp2_buf_free(&iframe->lbuf, mem); nghttp2_buf_wrap_init(&iframe->lbuf, NULL, 0); + iframe->raw_lbuf = NULL; + iframe->niv = 0; iframe->payloadleft = 0; iframe->padlen = 0; @@ -474,6 +491,12 @@ static int session_new(nghttp2_session **session_ptr, sizeof((*session_ptr)->user_recv_ext_types)); } + if (option->opt_set_mask & NGHTTP2_OPT_BUILTIN_RECV_EXT_TYPES) { + memcpy((*session_ptr)->builtin_recv_ext_types, + option->builtin_recv_ext_types, + sizeof((*session_ptr)->builtin_recv_ext_types)); + } + if ((option->opt_set_mask & NGHTTP2_OPT_NO_AUTO_PING_ACK) && option->no_auto_ping_ack) { (*session_ptr)->opt_flags |= NGHTTP2_OPTMASK_NO_AUTO_PING_ACK; @@ -4637,6 +4660,44 @@ static int session_process_window_update_frame(nghttp2_session *session) { return nghttp2_session_on_window_update_received(session, frame); } +int nghttp2_session_on_altsvc_received(nghttp2_session *session, + nghttp2_frame *frame) { + nghttp2_ext_altsvc *altsvc; + + altsvc = frame->ext.payload; + + if (session->server) { + return 0; + } + + if (frame->hd.stream_id == 0) { + if (altsvc->origin_len == 0) { + return 0; + } + } else if (altsvc->origin_len > 0) { + return 0; + } + + return session_call_on_frame_received(session, frame); +} + +static int session_process_altsvc_frame(nghttp2_session *session) { + nghttp2_inbound_frame *iframe = &session->iframe; + nghttp2_frame *frame = &iframe->frame; + + frame->ext.payload = &iframe->ext_frame_payload.altsvc; + + nghttp2_frame_unpack_altsvc_payload( + &frame->ext, nghttp2_get_uint16(iframe->sbuf.pos), iframe->lbuf.pos, + nghttp2_buf_len(&iframe->lbuf)); + + /* nghttp2_frame_unpack_altsvc_payload steals buffer from + iframe->lbuf */ + nghttp2_buf_wrap_init(&iframe->lbuf, NULL, 0); + + return nghttp2_session_on_altsvc_received(session, frame); +} + static int session_process_extension_frame(nghttp2_session *session) { int rv; nghttp2_inbound_frame *iframe = &session->iframe; @@ -5480,25 +5541,59 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session, const uint8_t *in, break; default: - DEBUGF(fprintf(stderr, "recv: unknown frame\n")); + DEBUGF(fprintf(stderr, "recv: extension frame\n")); - if (!session->callbacks.unpack_extension_callback || - (session->user_recv_ext_types[iframe->frame.hd.type / 8] & - (1 << (iframe->frame.hd.type & 0x7))) == 0) { - /* Silently ignore unknown frame type. */ + if (check_ext_type_set(session->user_recv_ext_types, + iframe->frame.hd.type)) { + if (!session->callbacks.unpack_extension_callback) { + /* Silently ignore unknown frame type. */ + busy = 1; + + iframe->state = NGHTTP2_IB_IGN_PAYLOAD; + + break; + } + + busy = 1; + + iframe->state = NGHTTP2_IB_READ_EXTENSION_PAYLOAD; + + break; + } else if (check_ext_type_set(session->builtin_recv_ext_types, + iframe->frame.hd.type)) { + switch (iframe->frame.hd.type) { + case NGHTTP2_ALTSVC: + DEBUGF(fprintf(stderr, "recv: ALTSVC\n")); + + iframe->frame.hd.flags = NGHTTP2_FLAG_NONE; + + if (iframe->payloadleft < 2) { + busy = 1; + iframe->state = NGHTTP2_IB_FRAME_SIZE_ERROR; + break; + } + + busy = 1; + + iframe->state = NGHTTP2_IB_READ_NBYTE; + inbound_frame_set_mark(iframe, 2); + + break; + default: + busy = 1; + + iframe->state = NGHTTP2_IB_IGN_PAYLOAD; + + break; + } + } else { busy = 1; iframe->state = NGHTTP2_IB_IGN_PAYLOAD; break; } - - busy = 1; - - iframe->state = NGHTTP2_IB_READ_EXTENSION_PAYLOAD; - - break; } if (!on_begin_frame_called) { @@ -5708,6 +5803,37 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session, const uint8_t *in, session_inbound_frame_reset(session); break; + case NGHTTP2_ALTSVC: { + size_t origin_len; + + origin_len = nghttp2_get_uint16(iframe->sbuf.pos); + + DEBUGF(fprintf(stderr, "recv: origin_len=%zu\n", origin_len)); + + if (2 + origin_len > iframe->payloadleft) { + busy = 1; + iframe->state = NGHTTP2_IB_FRAME_SIZE_ERROR; + break; + } + + if (iframe->frame.hd.length > 2) { + iframe->raw_lbuf = + nghttp2_mem_malloc(mem, iframe->frame.hd.length - 2); + + if (iframe->raw_lbuf == NULL) { + return NGHTTP2_ERR_NOMEM; + } + + nghttp2_buf_wrap_init(&iframe->lbuf, iframe->raw_lbuf, + iframe->frame.hd.length); + } + + busy = 1; + + iframe->state = NGHTTP2_IB_READ_ALTSVC_PAYLOAD; + + break; + } default: /* This is unknown frame */ session_inbound_frame_reset(session); @@ -6233,6 +6359,36 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session, const uint8_t *in, session_inbound_frame_reset(session); + break; + case NGHTTP2_IB_READ_ALTSVC_PAYLOAD: + DEBUGF(fprintf(stderr, "recv: [IB_READ_ALTSVC_PAYLOAD]\n")); + + readlen = inbound_frame_payload_readlen(iframe, in, last); + + if (readlen > 0) { + iframe->lbuf.last = nghttp2_cpymem(iframe->lbuf.last, in, readlen); + + iframe->payloadleft -= readlen; + in += readlen; + } + + DEBUGF(fprintf(stderr, "recv: readlen=%zu, payloadleft=%zu\n", readlen, + iframe->payloadleft)); + + if (iframe->payloadleft) { + assert(nghttp2_buf_avail(&iframe->lbuf) > 0); + + break; + } + + rv = session_process_altsvc_frame(session); + + if (nghttp2_is_fatal(rv)) { + return rv; + } + + session_inbound_frame_reset(session); + break; } diff --git a/lib/nghttp2_session.h b/lib/nghttp2_session.h index 3c3764de..1e391dcb 100644 --- a/lib/nghttp2_session.h +++ b/lib/nghttp2_session.h @@ -107,6 +107,7 @@ typedef enum { NGHTTP2_IB_READ_DATA, NGHTTP2_IB_IGN_DATA, NGHTTP2_IB_IGN_ALL, + NGHTTP2_IB_READ_ALTSVC_PAYLOAD, NGHTTP2_IB_READ_EXTENSION_PAYLOAD } nghttp2_inbound_state; @@ -313,6 +314,7 @@ struct nghttp2_session { bit is set, it indicates that incoming frame with that type is passed to user defined callbacks, otherwise they are ignored. */ uint8_t user_recv_ext_types[32]; + uint8_t builtin_recv_ext_types[32]; }; /* Struct used when updating initial window size of each active @@ -716,6 +718,19 @@ int nghttp2_session_on_goaway_received(nghttp2_session *session, int nghttp2_session_on_window_update_received(nghttp2_session *session, nghttp2_frame *frame); +/* + * Called when ALTSVC is recieved, assuming |frame| is properly + * initialized. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP2_ERR_CALLBACK_FAILURE + * The callback function failed. + */ +int nghttp2_session_on_altsvc_received(nghttp2_session *session, + nghttp2_frame *frame); + /* * Called when DATA is received, assuming |frame| is properly * initialized. diff --git a/lib/nghttp2_submit.c b/lib/nghttp2_submit.c index bdce2b24..4aa19470 100644 --- a/lib/nghttp2_submit.c +++ b/lib/nghttp2_submit.c @@ -485,6 +485,7 @@ int nghttp2_submit_altsvc(nghttp2_session *session, uint8_t flags _U_, rv = nghttp2_session_add_item(session, item); if (rv != 0) { nghttp2_frame_altsvc_free(&frame->ext, mem); + nghttp2_mem_free(mem, frame->ext.payload); nghttp2_mem_free(mem, item); return rv; diff --git a/src/nghttp.cc b/src/nghttp.cc index e3647b17..42d25d74 100644 --- a/src/nghttp.cc +++ b/src/nghttp.cc @@ -118,6 +118,7 @@ Config::Config() nghttp2_option_new(&http2_option); nghttp2_option_set_peer_max_concurrent_streams(http2_option, peer_max_concurrent_streams); + nghttp2_option_set_builtin_recv_extension_type(http2_option, NGHTTP2_ALTSVC); } Config::~Config() { nghttp2_option_del(http2_option); }