TS-2729: Add HTTP/2 support to ATS
Project: http://git-wip-us.apache.org/repos/asf/trafficserver/repo Commit: http://git-wip-us.apache.org/repos/asf/trafficserver/commit/e4347ef8 Tree: http://git-wip-us.apache.org/repos/asf/trafficserver/tree/e4347ef8 Diff: http://git-wip-us.apache.org/repos/asf/trafficserver/diff/e4347ef8 Branch: refs/heads/master Commit: e4347ef80f2d4035eef7e3f5bd899d176e68cc23 Parents: 683a377 Author: Ryo Okubo <[email protected]> Authored: Mon Feb 23 12:03:54 2015 -0800 Committer: Bryan Call <[email protected]> Committed: Mon Feb 23 13:42:12 2015 -0800 ---------------------------------------------------------------------- lib/records/RecHttp.cc | 7 +- mgmt/RecordsConfig.cc | 8 + proxy/Main.cc | 2 + proxy/Makefile.am | 1 + proxy/hdrs/HdrToken.cc | 8 + proxy/hdrs/HdrToken.h | 6 + proxy/hdrs/MIME.cc | 10 + proxy/hdrs/MIME.h | 51 +- proxy/http/HttpClientSession.cc | 22 +- proxy/http/HttpClientSession.h | 3 + proxy/http/HttpTransact.cc | 41 ++ proxy/http/HttpTransact.h | 1 + proxy/http/Makefile.am | 3 +- proxy/http2/HPACK.cc | 156 +++-- proxy/http2/HPACK.h | 27 +- proxy/http2/HTTP2.cc | 401 ++++++++++-- proxy/http2/HTTP2.h | 127 +++- proxy/http2/Http2ClientSession.cc | 85 ++- proxy/http2/Http2ClientSession.h | 32 + proxy/http2/Http2ConnectionState.cc | 1015 ++++++++++++++++++++++++++++-- proxy/http2/Http2ConnectionState.h | 144 ++++- 21 files changed, 1928 insertions(+), 222 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/trafficserver/blob/e4347ef8/lib/records/RecHttp.cc ---------------------------------------------------------------------- diff --git a/lib/records/RecHttp.cc b/lib/records/RecHttp.cc index 5d263a2..6551788 100644 --- a/lib/records/RecHttp.cc +++ b/lib/records/RecHttp.cc @@ -596,8 +596,13 @@ ts_session_protocol_well_known_name_indices_init() SPDY_PROTOCOL_SET.markIn(TS_NPN_PROTOCOL_INDEX_SPDY_3_1); DEFAULT_TLS_SESSION_PROTOCOL_SET.markAllIn(); + // Don't enable HTTP/2 by default until it is stable. - DEFAULT_TLS_SESSION_PROTOCOL_SET.markOut(HTTP2_PROTOCOL_SET); + int http2_enabled = 0; + REC_ReadConfigInteger(http2_enabled, "proxy.config.http2.enabled"); + if (!http2_enabled) { + DEFAULT_TLS_SESSION_PROTOCOL_SET.markOut(HTTP2_PROTOCOL_SET); + } DEFAULT_NON_TLS_SESSION_PROTOCOL_SET = HTTP_PROTOCOL_SET; } http://git-wip-us.apache.org/repos/asf/trafficserver/blob/e4347ef8/mgmt/RecordsConfig.cc ---------------------------------------------------------------------- diff --git a/mgmt/RecordsConfig.cc b/mgmt/RecordsConfig.cc index aeebcf1..0b86350 100644 --- a/mgmt/RecordsConfig.cc +++ b/mgmt/RecordsConfig.cc @@ -1990,6 +1990,14 @@ static const RecordElement RecordsConfig[] = {RECT_CONFIG, "proxy.config.spdy.accept_no_activity_timeout", RECD_INT, "120", RECU_DYNAMIC, RR_NULL, RECC_STR, "^[0-9]+$", RECA_NULL} , + //############ + //# + //# HTTP/2 global configuration. + //# + //############ + {RECT_CONFIG, "proxy.config.http2.enabled", RECD_INT, "0", RECU_RESTART_TM, RR_NULL, RECC_INT, "[0-1]", RECA_NULL} + , + //# Add LOCAL Records Here {RECT_LOCAL, "proxy.local.incoming_ip_to_bind", RECD_STRING, NULL, RECU_NULL, RR_NULL, RECC_NULL, NULL, RECA_NULL} , http://git-wip-us.apache.org/repos/asf/trafficserver/blob/e4347ef8/proxy/Main.cc ---------------------------------------------------------------------- diff --git a/proxy/Main.cc b/proxy/Main.cc index 9d197e3..d2a0be6 100644 --- a/proxy/Main.cc +++ b/proxy/Main.cc @@ -80,6 +80,7 @@ extern "C" int plock(int); #include "MgmtUtils.h" #include "StatPages.h" #include "HTTP.h" +#include "HuffmanCodec.h" #include "Plugin.h" #include "DiagsConfig.h" #include "CoreUtils.h" @@ -1185,6 +1186,7 @@ init_http_header() url_init(); mime_init(); http_init(); + hpack_huffman_init(); } struct AutoStopCont: public Continuation http://git-wip-us.apache.org/repos/asf/trafficserver/blob/e4347ef8/proxy/Makefile.am ---------------------------------------------------------------------- diff --git a/proxy/Makefile.am b/proxy/Makefile.am index a225e5a..ce5041f 100644 --- a/proxy/Makefile.am +++ b/proxy/Makefile.am @@ -38,6 +38,7 @@ AM_CPPFLAGS = \ -I$(top_srcdir)/lib/records \ -I$(top_srcdir)/lib/ts \ -I$(srcdir)/http \ + -I$(srcdir)/http2 \ -I$(srcdir)/spdy \ -I$(srcdir)/logging \ -I$(srcdir)/http/remap \ http://git-wip-us.apache.org/repos/asf/trafficserver/blob/e4347ef8/proxy/hdrs/HdrToken.cc ---------------------------------------------------------------------- diff --git a/proxy/hdrs/HdrToken.cc b/proxy/hdrs/HdrToken.cc index 9f012e6..9b44ae3 100644 --- a/proxy/hdrs/HdrToken.cc +++ b/proxy/hdrs/HdrToken.cc @@ -148,6 +148,10 @@ static const char *_hdrtoken_strs[] = { "Sec-WebSocket-Key", "Sec-WebSocket-Version", + // HTTP/2 cleartext + MIME_UPGRADE_H2C_TOKEN, + "HTTP2-Settings", + // URL schemes "file", "ftp", @@ -481,6 +485,10 @@ static const char *_hdrtoken_commonly_tokenized_strs[] = { "Sec-WebSocket-Key", "Sec-WebSocket-Version", + // HTTP/2 cleartext + MIME_UPGRADE_H2C_TOKEN, + "HTTP2-Settings", + // URL schemes "file", "ftp", http://git-wip-us.apache.org/repos/asf/trafficserver/blob/e4347ef8/proxy/hdrs/HdrToken.h ---------------------------------------------------------------------- diff --git a/proxy/hdrs/HdrToken.h b/proxy/hdrs/HdrToken.h index 95dc95c..d273454 100644 --- a/proxy/hdrs/HdrToken.h +++ b/proxy/hdrs/HdrToken.h @@ -388,4 +388,10 @@ hdrtoken_wks_to_flags(const char *wks) /*------------------------------------------------------------------------- -------------------------------------------------------------------------*/ +// HTTP/2 Upgrade token +#define MIME_UPGRADE_H2C_TOKEN "h2c-14" + +/*------------------------------------------------------------------------- + -------------------------------------------------------------------------*/ + #endif /* __HDRTOKEN_H__ */ http://git-wip-us.apache.org/repos/asf/trafficserver/blob/e4347ef8/proxy/hdrs/MIME.cc ---------------------------------------------------------------------- diff --git a/proxy/hdrs/MIME.cc b/proxy/hdrs/MIME.cc index ca8de1a..85ddd3f 100644 --- a/proxy/hdrs/MIME.cc +++ b/proxy/hdrs/MIME.cc @@ -158,6 +158,7 @@ const char *MIME_FIELD_X_ID; const char *MIME_FIELD_X_FORWARDED_FOR; const char *MIME_FIELD_SEC_WEBSOCKET_KEY; const char *MIME_FIELD_SEC_WEBSOCKET_VERSION; +const char *MIME_FIELD_HTTP2_SETTINGS; const char *MIME_VALUE_BYTES; const char *MIME_VALUE_CHUNKED; @@ -182,6 +183,7 @@ const char *MIME_VALUE_PUBLIC; const char *MIME_VALUE_S_MAXAGE; const char *MIME_VALUE_NEED_REVALIDATE_ONCE; const char *MIME_VALUE_WEBSOCKET; +const char *MIME_VALUE_H2C; // Cache-control: extension "need-revalidate-once" is used internally by T.S. // to invalidate a document, and it is not returned/forwarded. @@ -271,6 +273,7 @@ int MIME_LEN_X_ID; int MIME_LEN_X_FORWARDED_FOR; int MIME_LEN_SEC_WEBSOCKET_KEY; int MIME_LEN_SEC_WEBSOCKET_VERSION; +int MIME_LEN_HTTP2_SETTINGS; int MIME_WKSIDX_ACCEPT; int MIME_WKSIDX_ACCEPT_CHARSET; @@ -348,6 +351,7 @@ int MIME_WKSIDX_X_ID; int MIME_WKSIDX_X_FORWARDED_FOR; int MIME_WKSIDX_SEC_WEBSOCKET_KEY; int MIME_WKSIDX_SEC_WEBSOCKET_VERSION; +int MIME_WKSIDX_HTTP2_SETTINGS; /*********************************************************************** * * @@ -722,6 +726,8 @@ mime_init() MIME_FIELD_SEC_WEBSOCKET_KEY = hdrtoken_string_to_wks("Sec-WebSocket-Key"); MIME_FIELD_SEC_WEBSOCKET_VERSION = hdrtoken_string_to_wks("Sec-WebSocket-Version"); + MIME_FIELD_HTTP2_SETTINGS = hdrtoken_string_to_wks("HTTP2-Settings"); + MIME_LEN_ACCEPT = hdrtoken_wks_to_length(MIME_FIELD_ACCEPT); MIME_LEN_ACCEPT_CHARSET = hdrtoken_wks_to_length(MIME_FIELD_ACCEPT_CHARSET); @@ -801,6 +807,8 @@ mime_init() MIME_LEN_SEC_WEBSOCKET_KEY = hdrtoken_wks_to_length(MIME_FIELD_SEC_WEBSOCKET_KEY); MIME_LEN_SEC_WEBSOCKET_VERSION = hdrtoken_wks_to_length(MIME_FIELD_SEC_WEBSOCKET_VERSION); + MIME_LEN_HTTP2_SETTINGS = hdrtoken_wks_to_length(MIME_FIELD_HTTP2_SETTINGS); + MIME_WKSIDX_ACCEPT = hdrtoken_wks_to_index(MIME_FIELD_ACCEPT); MIME_WKSIDX_ACCEPT_CHARSET = hdrtoken_wks_to_index(MIME_FIELD_ACCEPT_CHARSET); @@ -877,6 +885,7 @@ mime_init() MIME_WKSIDX_X_FORWARDED_FOR = hdrtoken_wks_to_index(MIME_FIELD_X_FORWARDED_FOR); MIME_WKSIDX_SEC_WEBSOCKET_KEY = hdrtoken_wks_to_index(MIME_FIELD_SEC_WEBSOCKET_KEY); MIME_WKSIDX_SEC_WEBSOCKET_VERSION = hdrtoken_wks_to_index(MIME_FIELD_SEC_WEBSOCKET_VERSION); + MIME_WKSIDX_HTTP2_SETTINGS = hdrtoken_wks_to_index(MIME_FIELD_HTTP2_SETTINGS); MIME_VALUE_BYTES = hdrtoken_string_to_wks("bytes"); MIME_VALUE_CHUNKED = hdrtoken_string_to_wks("chunked"); @@ -901,6 +910,7 @@ mime_init() MIME_VALUE_S_MAXAGE = hdrtoken_string_to_wks("s-maxage"); MIME_VALUE_NEED_REVALIDATE_ONCE = hdrtoken_string_to_wks("need-revalidate-once"); MIME_VALUE_WEBSOCKET = hdrtoken_string_to_wks("websocket"); + MIME_VALUE_H2C = hdrtoken_string_to_wks(MIME_UPGRADE_H2C_TOKEN); mime_init_date_format_table(); http://git-wip-us.apache.org/repos/asf/trafficserver/blob/e4347ef8/proxy/hdrs/MIME.h ---------------------------------------------------------------------- diff --git a/proxy/hdrs/MIME.h b/proxy/hdrs/MIME.h index 860af82..4b001b1 100644 --- a/proxy/hdrs/MIME.h +++ b/proxy/hdrs/MIME.h @@ -389,6 +389,7 @@ extern const char *MIME_FIELD_X_ID; extern const char *MIME_FIELD_X_FORWARDED_FOR; extern const char *MIME_FIELD_SEC_WEBSOCKET_KEY; extern const char *MIME_FIELD_SEC_WEBSOCKET_VERSION; +extern const char *MIME_FIELD_HTTP2_SETTINGS; extern const char *MIME_VALUE_BYTES; extern const char *MIME_VALUE_CHUNKED; @@ -413,6 +414,7 @@ extern const char *MIME_VALUE_PUBLIC; extern const char *MIME_VALUE_S_MAXAGE; extern const char *MIME_VALUE_NEED_REVALIDATE_ONCE; extern const char *MIME_VALUE_WEBSOCKET; +extern const char *MIME_VALUE_H2C; extern int MIME_LEN_ACCEPT; extern int MIME_LEN_ACCEPT_CHARSET; @@ -515,6 +517,8 @@ extern int MIME_LEN_NEED_REVALIDATE_ONCE; extern int MIME_LEN_SEC_WEBSOCKET_KEY; extern int MIME_LEN_SEC_WEBSOCKET_VERSION; +extern int MIME_LEN_HTTP2_SETTINGS; + extern int MIME_WKSIDX_ACCEPT; extern int MIME_WKSIDX_ACCEPT_CHARSET; extern int MIME_WKSIDX_ACCEPT_ENCODING; @@ -590,6 +594,7 @@ extern int MIME_WKSIDX_INT_DATA_INFO; extern int MIME_WKSIDX_X_ID; extern int MIME_WKSIDX_SEC_WEBSOCKET_KEY; extern int MIME_WKSIDX_SEC_WEBSOCKET_VERSION; +extern int MIME_WKSIDX_HTTP2_SETTINGS; /*********************************************************************** * * @@ -920,13 +925,13 @@ public: int parse(MIMEParser * parser, const char **start, const char *end, bool must_copy_strs, bool eof); - int value_get_index(const char *name, int name_length, const char *value, int value_length); - const char *value_get(const char *name, int name_length, int *value_length); - int32_t value_get_int(const char *name, int name_length); - uint32_t value_get_uint(const char *name, int name_length); - int64_t value_get_int64(const char *name, int name_length); - time_t value_get_date(const char *name, int name_length); - int value_get_comma_list(const char *name, int name_length, StrList * list); + int value_get_index(const char *name, int name_length, const char *value, int value_length) const; + const char *value_get(const char *name, int name_length, int *value_length) const; + int32_t value_get_int(const char *name, int name_length) const; + uint32_t value_get_uint(const char *name, int name_length) const; + int64_t value_get_int64(const char *name, int name_length) const; + time_t value_get_date(const char *name, int name_length) const; + int value_get_comma_list(const char *name, int name_length, StrList * list) const; void value_set(const char *name, int name_length, const char *value, int value_length); void value_set_int(const char *name, int name_length, int32_t value); @@ -948,7 +953,7 @@ public: void field_value_append(MIMEField * field, const char *value, int value_length, bool prepend_comma = false, const char separator = ','); time_t get_age(); - int64_t get_content_length(); + int64_t get_content_length() const; time_t get_date(); time_t get_expires(); time_t get_if_modified_since(); @@ -1214,9 +1219,9 @@ MIMEHdr::parse(MIMEParser * parser, const char **start, const char *end, bool mu /*------------------------------------------------------------------------- -------------------------------------------------------------------------*/ inline int -MIMEHdr::value_get_index(const char *name, int name_length, const char *value, int value_length) +MIMEHdr::value_get_index(const char *name, int name_length, const char *value, int value_length) const { - MIMEField *field = field_find(name, name_length); + const MIMEField *field = field_find(name, name_length); if (field) return field->value_get_index(value, value_length); else @@ -1227,10 +1232,10 @@ MIMEHdr::value_get_index(const char *name, int name_length, const char *value, i -------------------------------------------------------------------------*/ inline const char * -MIMEHdr::value_get(const char *name, int name_length, int *value_length_return) +MIMEHdr::value_get(const char *name, int name_length, int *value_length_return) const { // ink_assert(valid()); - MIMEField *field = field_find(name, name_length); + const MIMEField *field = field_find(name, name_length); if (field) return (mime_field_value_get(field, value_length_return)); @@ -1239,9 +1244,9 @@ MIMEHdr::value_get(const char *name, int name_length, int *value_length_return) } inline int32_t -MIMEHdr::value_get_int(const char *name, int name_length) +MIMEHdr::value_get_int(const char *name, int name_length) const { - MIMEField *field = field_find(name, name_length); + const MIMEField *field = field_find(name, name_length); if (field) return (mime_field_value_get_int(field)); @@ -1250,9 +1255,9 @@ MIMEHdr::value_get_int(const char *name, int name_length) } inline uint32_t -MIMEHdr::value_get_uint(const char *name, int name_length) +MIMEHdr::value_get_uint(const char *name, int name_length) const { - MIMEField *field = field_find(name, name_length); + const MIMEField *field = field_find(name, name_length); if (field) return (mime_field_value_get_uint(field)); @@ -1261,9 +1266,9 @@ MIMEHdr::value_get_uint(const char *name, int name_length) } inline int64_t -MIMEHdr::value_get_int64(const char *name, int name_length) +MIMEHdr::value_get_int64(const char *name, int name_length) const { - MIMEField *field = field_find(name, name_length); + const MIMEField *field = field_find(name, name_length); if (field) return (mime_field_value_get_int64(field)); @@ -1272,9 +1277,9 @@ MIMEHdr::value_get_int64(const char *name, int name_length) } inline time_t -MIMEHdr::value_get_date(const char *name, int name_length) +MIMEHdr::value_get_date(const char *name, int name_length) const { - MIMEField *field = field_find(name, name_length); + const MIMEField *field = field_find(name, name_length); if (field) return (mime_field_value_get_date(field)); @@ -1283,9 +1288,9 @@ MIMEHdr::value_get_date(const char *name, int name_length) } inline int -MIMEHdr::value_get_comma_list(const char *name, int name_length, StrList * list) +MIMEHdr::value_get_comma_list(const char *name, int name_length, StrList * list) const { - MIMEField *field = field_find(name, name_length); + const MIMEField *field = field_find(name, name_length); if (field) return (field->value_get_comma_list(list)); @@ -1420,7 +1425,7 @@ MIMEHdr::get_age() -------------------------------------------------------------------------*/ inline int64_t -MIMEHdr::get_content_length() +MIMEHdr::get_content_length() const { return (value_get_int64(MIME_FIELD_CONTENT_LENGTH, MIME_LEN_CONTENT_LENGTH)); } http://git-wip-us.apache.org/repos/asf/trafficserver/blob/e4347ef8/proxy/http/HttpClientSession.cc ---------------------------------------------------------------------- diff --git a/proxy/http/HttpClientSession.cc b/proxy/http/HttpClientSession.cc index 2ea5522..7bd19fc 100644 --- a/proxy/http/HttpClientSession.cc +++ b/proxy/http/HttpClientSession.cc @@ -37,6 +37,7 @@ #include "HttpDebugNames.h" #include "HttpServerSession.h" #include "Plugin.h" +#include "Http2ClientSession.h" #define DebugHttpSsn(fmt, ...) DebugSsn(this, "http_cs", fmt, __VA_ARGS__) @@ -61,9 +62,9 @@ ClassAllocator<HttpClientSession> httpClientSessionAllocator("httpClientSessionA HttpClientSession::HttpClientSession() : con_id(0), client_vc(NULL), magic(HTTP_CS_MAGIC_DEAD), transact_count(0), tcp_init_cwnd_set(false), - half_close(false), conn_decrease(false), bound_ss(NULL), - read_buffer(NULL), current_reader(NULL), read_state(HCS_INIT), - ka_vio(NULL), slave_ka_vio(NULL), + half_close(false), conn_decrease(false), upgrade_to_h2c(false), + bound_ss(NULL),read_buffer(NULL), current_reader(NULL), + read_state(HCS_INIT), ka_vio(NULL), slave_ka_vio(NULL), outbound_port(0), f_outbound_transparent(false), host_res_style(HOST_RES_IPV4), acl_record(NULL), m_active(false) @@ -75,7 +76,7 @@ HttpClientSession::destroy() { DebugHttpSsn("[%" PRId64 "] session destroy", con_id); - ink_release_assert(client_vc == NULL); + ink_release_assert(upgrade_to_h2c || !client_vc); ink_release_assert(bound_ss == NULL); ink_assert(read_buffer); @@ -296,9 +297,16 @@ HttpClientSession::do_io_close(int alerrno) client_vc->set_active_timeout(HRTIME_SECONDS(current_reader->t_state.txn_conf->keep_alive_no_activity_timeout_out)); } else { read_state = HCS_CLOSED; - client_vc->do_io_close(alerrno); - DebugHttpSsn("[%" PRId64 "] session closed", con_id); - client_vc = NULL; + if (upgrade_to_h2c) { + Http2ClientSession * h2_session = http2ClientSessionAllocator.alloc(); + h2_session->set_upgrade_context(¤t_reader->t_state.hdr_info.client_request); + h2_session->new_connection(client_vc, NULL, NULL, false /* backdoor */); + // TODO Consider about handling HTTP/1 hooks and stats + } else { + client_vc->do_io_close(alerrno); + DebugHttpSsn("[%" PRId64 "] session closed", con_id); + client_vc = NULL; + } HTTP_SUM_DYN_STAT(http_transactions_per_client_con, transact_count); HTTP_DECREMENT_DYN_STAT(http_current_client_connections_stat); conn_decrease = false; http://git-wip-us.apache.org/repos/asf/trafficserver/blob/e4347ef8/proxy/http/HttpClientSession.h ---------------------------------------------------------------------- diff --git a/proxy/http/HttpClientSession.h b/proxy/http/HttpClientSession.h index 24bd22a..6009e83 100644 --- a/proxy/http/HttpClientSession.h +++ b/proxy/http/HttpClientSession.h @@ -89,6 +89,8 @@ public: int get_transact_count() const { return transact_count; } + void set_h2c_upgrade_flag() { upgrade_to_h2c = true; } + private: HttpClientSession(HttpClientSession &); @@ -113,6 +115,7 @@ private: bool tcp_init_cwnd_set; bool half_close; bool conn_decrease; + bool upgrade_to_h2c; // Switching to HTTP/2 with upgrade mechanism HttpServerSession *bound_ss; http://git-wip-us.apache.org/repos/asf/trafficserver/blob/e4347ef8/proxy/http/HttpTransact.cc ---------------------------------------------------------------------- diff --git a/proxy/http/HttpTransact.cc b/proxy/http/HttpTransact.cc index 7cc6bec..a4b8e29 100644 --- a/proxy/http/HttpTransact.cc +++ b/proxy/http/HttpTransact.cc @@ -979,6 +979,29 @@ bool HttpTransact::handle_upgrade_request(State *s) { DebugTxn("http_trans_upgrade", "Unable to upgrade connection to websockets, invalid headers (RFC 6455)."); } } + + /* + draft-ietf-httpbis-http2-15 + + 3.2. Starting HTTP/2 for "http" URIs + + The client makes an HTTP/1.1 request + that includes an Upgrade header field identifying HTTP/2 with the + "h2c" token. The HTTP/1.1 request MUST include exactly one + HTTP2-Settings header field. + */ + if (s->upgrade_token_wks == MIME_VALUE_H2C) { + MIMEField *http2_settings = s->hdr_info.client_request.field_find(MIME_FIELD_HTTP2_SETTINGS, MIME_LEN_HTTP2_SETTINGS); + + // TODO Check whether h2c is enabled or not. + if (http2_settings) { + s->state_machine->ua_session->set_h2c_upgrade_flag(); + build_upgrade_response(s); + TRANSACT_RETURN_VAL(SM_ACTION_INTERNAL_CACHE_NOOP, NULL, true); + } else { + DebugTxn("http_trans_upgrade", "Unable to upgrade connection to h2c, invalid headers"); + } + } } else { DebugTxn("http_trans_upgrade", "Transaction requested upgrade for unknown protocol: %s", upgrade_hdr_val); } @@ -8169,6 +8192,24 @@ HttpTransact::build_redirect_response(State* s) s->arena.str_free(to_free); } +void +HttpTransact::build_upgrade_response(State* s) +{ + DebugTxn("http_upgrade", "[HttpTransact::build_upgrade_response]"); + + // 101 Switching Protocols + HTTPStatus status_code = HTTP_STATUS_SWITCHING_PROTOCOL; + const char *reason_phrase = http_hdr_reason_lookup(status_code); + build_response(s, &s->hdr_info.client_response, s->client_info.http_version, status_code, reason_phrase); + + ////////////////////////// + // set upgrade headers // + ////////////////////////// + HTTPHdr *h = &s->hdr_info.client_response; + h->value_set(MIME_FIELD_CONNECTION, MIME_LEN_CONNECTION, "Upgrade", strlen("Upgrade")); + h->value_set(MIME_FIELD_UPGRADE, MIME_LEN_UPGRADE, MIME_UPGRADE_H2C_TOKEN, strlen(MIME_UPGRADE_H2C_TOKEN)); +} + const char * HttpTransact::get_error_string(int erno) { http://git-wip-us.apache.org/repos/asf/trafficserver/blob/e4347ef8/proxy/http/HttpTransact.h ---------------------------------------------------------------------- diff --git a/proxy/http/HttpTransact.h b/proxy/http/HttpTransact.h index 4de29e8..4219677 100644 --- a/proxy/http/HttpTransact.h +++ b/proxy/http/HttpTransact.h @@ -1359,6 +1359,7 @@ public: static void build_error_response(State *s, HTTPStatus status_code, const char *reason_phrase_or_null, const char *error_body_type, const char *format, ...); static void build_redirect_response(State* s); + static void build_upgrade_response(State* s); static const char *get_error_string(int erno); // the stat functions http://git-wip-us.apache.org/repos/asf/trafficserver/blob/e4347ef8/proxy/http/Makefile.am ---------------------------------------------------------------------- diff --git a/proxy/http/Makefile.am b/proxy/http/Makefile.am index 8ff6985..258b1ee 100644 --- a/proxy/http/Makefile.am +++ b/proxy/http/Makefile.am @@ -32,7 +32,8 @@ AM_CPPFLAGS = \ -I$(top_srcdir)/proxy/shared \ -I$(top_srcdir)/proxy/http/remap \ -I$(top_srcdir)/proxy/logging \ - -I$(top_srcdir)/proxy/spdy + -I$(top_srcdir)/proxy/spdy \ + -I$(top_srcdir)/proxy/http2 noinst_HEADERS = HttpProxyServerMain.h noinst_LIBRARIES = libhttp.a http://git-wip-us.apache.org/repos/asf/trafficserver/blob/e4347ef8/proxy/http2/HPACK.cc ---------------------------------------------------------------------- diff --git a/proxy/http2/HPACK.cc b/proxy/http2/HPACK.cc index 2ef5083..f56a934 100644 --- a/proxy/http2/HPACK.cc +++ b/proxy/http2/HPACK.cc @@ -180,10 +180,10 @@ const static struct { }; int -Http2HeaderTable::get_header_from_indexing_tables(uint32_t index, MIMEFieldWrapper& field) const +Http2DynamicTable::get_header_from_indexing_tables(uint32_t index, MIMEFieldWrapper& field) const { // Index Address Space starts at 1, so index == 0 is invalid. - if (!index) return -1; + if (!index) return HPACK_ERROR_COMPRESSION_ERROR; if (index < TS_HPACK_STATIC_TABLE_ENTRY_NUM) { field.name_set(STATIC_TABLE[index].name, strlen(STATIC_TABLE[index].name)); @@ -201,7 +201,7 @@ Http2HeaderTable::get_header_from_indexing_tables(uint32_t index, MIMEFieldWrapp // 3.3.3. Index Address Space // Indices strictly greater than the sum of the lengths of both tables // MUST be treated as a decoding error. - return -1; + return HPACK_ERROR_COMPRESSION_ERROR; } return 0; @@ -212,9 +212,9 @@ Http2HeaderTable::get_header_from_indexing_tables(uint32_t index, MIMEFieldWrapp // are evicted from the end of the header table until the size of the // header table is less than or equal to the maximum size. void -Http2HeaderTable::set_header_table_size(uint32_t new_size) +Http2DynamicTable::set_dynamic_table_size(uint32_t new_size) { - uint32_t old_size = _settings_header_table_size; + uint32_t old_size = _settings_dynamic_table_size; while (old_size > new_size) { int last_name_len, last_value_len; MIMEField* last_field = _headers.last(); @@ -227,25 +227,25 @@ Http2HeaderTable::set_header_table_size(uint32_t new_size) _mhdr->field_delete(last_field, false); } - _settings_header_table_size = new_size; + _settings_dynamic_table_size = new_size; } void -Http2HeaderTable::add_header_field(const MIMEField * field) +Http2DynamicTable::add_header_field(const MIMEField * field) { int name_len, value_len; const char * name = field->name_get(&name_len); const char * value = field->value_get(&value_len); uint32_t header_size = ADDITIONAL_OCTETS + name_len + value_len; - if (header_size > _settings_header_table_size) { + if (header_size > _settings_dynamic_table_size) { // 5.3. It is not an error to attempt to add an entry that is larger than the maximum size; an // attempt to add an entry larger than the entire table causes the table to be emptied of all existing entries. _headers.clear(); _mhdr->fields_clear(); } else { _current_size += header_size; - while (_current_size > _settings_header_table_size) { + while (_current_size > _settings_dynamic_table_size) { int last_name_len, last_value_len; MIMEField* last_field = _headers.last(); @@ -310,9 +310,9 @@ encode_integer(uint8_t *buf_start, const uint8_t *buf_end, uint32_t value, uint8 uint8_t *p = buf_start; if (value < (static_cast<uint32_t>(1 << n) - 1)) { - *(p++) |= value; + *(p++) = value; } else { - *(p++) |= (1 << n) - 1; + *(p++) = (1 << n) - 1; value -= (1 << n) - 1; while (value >= 128) { if (p >= buf_end) { @@ -442,10 +442,16 @@ encode_literal_header_field(uint8_t *buf_start, const uint8_t *buf_end, const MI } *(p++) = flag; - // Name String + // Convert field name to lower case + Arena arena; int name_len; const char* name = header.name_get(&name_len); - len = encode_string(p, buf_end, name, name_len); + char* lower_name = arena.str_store(name, name_len); + for (int i=0; i<name_len; i++) + lower_name[i] = ParseRules::ink_tolower(lower_name[i]); + + // Name String + len = encode_string(p, buf_end, lower_name, name_len); if (len == -1) return -1; p += len; @@ -488,13 +494,13 @@ decode_integer(uint32_t& dst, const uint8_t *buf_start, const uint8_t *buf_end, if (dst == static_cast<uint32_t>(1 << n) - 1) { int m = 0; do { - if (++p >= buf_end) return -1; + if (++p >= buf_end) return HPACK_ERROR_COMPRESSION_ERROR; uint32_t added_value = *p & 0x7f; if ((UINT32_MAX >> m) < added_value) { // Excessively large integer encodings - in value or octet // length - MUST be treated as a decoding error. - return -1; + return HPACK_ERROR_COMPRESSION_ERROR; } dst += added_value << m; m += 7; @@ -507,7 +513,7 @@ decode_integer(uint32_t& dst, const uint8_t *buf_start, const uint8_t *buf_end, // 6.2 return content from String Data (Length octets) // with huffman decoding if it is encoded int64_t -decode_string(char **c_str, uint32_t& c_str_length, const uint8_t *buf_start, const uint8_t *buf_end) +decode_string(Arena& arena, char **str, uint32_t& str_length, const uint8_t *buf_start, const uint8_t *buf_end) { const uint8_t *p = buf_start; bool isHuffman = *p & 0x80; @@ -515,26 +521,26 @@ decode_string(char **c_str, uint32_t& c_str_length, const uint8_t *buf_start, co int64_t len = 0; len = decode_integer(encoded_string_len, p, buf_end, 7); - if (len == -1) return -1; + if (len == HPACK_ERROR_COMPRESSION_ERROR || encoded_string_len == 0) return HPACK_ERROR_COMPRESSION_ERROR; p += len; if (encoded_string_len > HEADER_FIELD_LIMIT_LENGTH || buf_start + encoded_string_len >= buf_end) { - return -1; + return HPACK_ERROR_COMPRESSION_ERROR; } if (isHuffman) { // Allocate temporary area twice the size of before decoded data - *c_str = static_cast<char*>(ats_malloc(encoded_string_len * 2)); + *str = arena.str_alloc(encoded_string_len * 2); - len = huffman_decode(*c_str, p, encoded_string_len); - if (len == -1) return -1; - c_str_length = len; + len = huffman_decode(*str, p, encoded_string_len); + if (len == HPACK_ERROR_COMPRESSION_ERROR) return HPACK_ERROR_COMPRESSION_ERROR; + str_length = len; } else { - *c_str = static_cast<char*>(ats_malloc(encoded_string_len)); + *str = arena.str_alloc(encoded_string_len); - memcpy(*c_str, reinterpret_cast<const char*>(p), encoded_string_len); + memcpy(*str, reinterpret_cast<const char*>(p), encoded_string_len); - c_str_length = encoded_string_len; + str_length = encoded_string_len; } return p + encoded_string_len - buf_start; @@ -542,15 +548,27 @@ decode_string(char **c_str, uint32_t& c_str_length, const uint8_t *buf_start, co // 7.1. Indexed Header Field Representation int64_t -decode_indexed_header_field(MIMEFieldWrapper& header, const uint8_t *buf_start, const uint8_t *buf_end, Http2HeaderTable& header_table) +decode_indexed_header_field(MIMEFieldWrapper& header, const uint8_t *buf_start, const uint8_t *buf_end, Http2DynamicTable& dynamic_table) { uint32_t index = 0; int64_t len = 0; + len = decode_integer(index, buf_start, buf_end, 7); - if (len == -1) return -1; + if (len == HPACK_ERROR_COMPRESSION_ERROR) return HPACK_ERROR_COMPRESSION_ERROR; - if (header_table.get_header_from_indexing_tables(index, header) == -1) { - return -1; + if (dynamic_table.get_header_from_indexing_tables(index, header) == HPACK_ERROR_COMPRESSION_ERROR) { + return HPACK_ERROR_COMPRESSION_ERROR; + } + + if (is_debug_tag_set("http2_hpack_decode")) { + int decoded_name_len; + const char *decoded_name = header.name_get(&decoded_name_len); + int decoded_value_len; + const char *decoded_value = header.value_get(&decoded_value_len); + + Arena arena; + Debug("http2_hpack_decode", "Decoded field: %s: %s\n", + arena.str_store(decoded_name, decoded_name_len), arena.str_store(decoded_value, decoded_value_len)); } return len; @@ -558,7 +576,7 @@ decode_indexed_header_field(MIMEFieldWrapper& header, const uint8_t *buf_start, // 7.2. Literal Header Field Representation int64_t -decode_literal_header_field(MIMEFieldWrapper& header, const uint8_t *buf_start, const uint8_t *buf_end, Http2HeaderTable& header_table) +decode_literal_header_field(MIMEFieldWrapper& header, const uint8_t *buf_start, const uint8_t *buf_end, Http2DynamicTable& dynamic_table) { const uint8_t *p = buf_start; bool isIncremental = false; @@ -579,40 +597,58 @@ decode_literal_header_field(MIMEFieldWrapper& header, const uint8_t *buf_start, len = decode_integer(index, p, buf_end, 4); } - if (len == -1) { - return -1; - } + if (len == HPACK_ERROR_COMPRESSION_ERROR) return HPACK_ERROR_COMPRESSION_ERROR; p += len; + Arena arena; + + // Decode header field name if (index) { - header_table.get_header_from_indexing_tables(index, header); + dynamic_table.get_header_from_indexing_tables(index, header); } else { - char *c_name = NULL; - uint32_t c_name_len = 0; - len = decode_string(&c_name, c_name_len, p, buf_end); - if (len == -1) { - ats_free(c_name); - return -1; + char* name_str = NULL; + uint32_t name_str_len = 0; + + len = decode_string(arena, &name_str, name_str_len, p, buf_end); + if (len == HPACK_ERROR_COMPRESSION_ERROR) return HPACK_ERROR_COMPRESSION_ERROR; + + // Check whether header field name is lower case + for (uint32_t i=0; i<name_str_len; i++) { + if (ParseRules::is_upalpha(name_str[i])) { + return -2; + } } + p += len; - header.name_set(c_name, c_name_len); - ats_free(c_name); - } - char *c_value = NULL; - uint32_t c_value_len = 0; - len = decode_string(&c_value, c_value_len, p, buf_end); - if (len == -1) { - ats_free(c_value); - return -1; + header.name_set(name_str, name_str_len); } + + // Decode header field value + char* value_str = NULL; + uint32_t value_str_len = 0; + + len = decode_string(arena, &value_str, value_str_len, p, buf_end); + if (len == HPACK_ERROR_COMPRESSION_ERROR) return HPACK_ERROR_COMPRESSION_ERROR; + p += len; - header.value_set(c_value, c_value_len); - ats_free(c_value); + header.value_set(value_str, value_str_len); + // Incremental Indexing adds header to header table as new entry if (isIncremental) { - header_table.add_header_field(header.field_get()); + dynamic_table.add_header_field(header.field_get()); + } + + // Print decoded header field + if (is_debug_tag_set("http2_hpack_decode")) { + int decoded_name_len; + const char *decoded_name = header.name_get(&decoded_name_len); + int decoded_value_len; + const char *decoded_value = header.value_get(&decoded_value_len); + + Debug("http2_hpack_decode", "Decoded field: %s: %s\n", + arena.str_store(decoded_name, decoded_name_len), arena.str_store(decoded_value, decoded_value_len)); } return p - buf_start; @@ -620,20 +656,16 @@ decode_literal_header_field(MIMEFieldWrapper& header, const uint8_t *buf_start, // 7.3. Header Table Size Update int64_t -update_header_table_size(const uint8_t *buf_start, const uint8_t *buf_end, Http2HeaderTable& header_table) +update_dynamic_table_size(const uint8_t *buf_start, const uint8_t *buf_end, Http2DynamicTable& dynamic_table) { - if (buf_start == buf_end) return -1; - - int64_t len = 0; + if (buf_start == buf_end) return HPACK_ERROR_COMPRESSION_ERROR; // Update header table size if its required. - if ((*buf_start & 0xe0) == 0x20) { - uint32_t size = 0; - len = decode_integer(size, buf_start, buf_end, 5); - if (len == -1) return -1; + uint32_t size = 0; + int64_t len = decode_integer(size, buf_start, buf_end, 5); + if (len == HPACK_ERROR_COMPRESSION_ERROR) return HPACK_ERROR_COMPRESSION_ERROR; - header_table.set_header_table_size(size); - } + dynamic_table.set_dynamic_table_size(size); return len; } http://git-wip-us.apache.org/repos/asf/trafficserver/blob/e4347ef8/proxy/http2/HPACK.h ---------------------------------------------------------------------- diff --git a/proxy/http2/HPACK.h b/proxy/http2/HPACK.h index 593b193..f0ff8d0 100644 --- a/proxy/http2/HPACK.h +++ b/proxy/http2/HPACK.h @@ -40,6 +40,11 @@ extern const unsigned HPACK_LEN_AUTHORITY; extern const unsigned HPACK_LEN_PATH; extern const unsigned HPACK_LEN_STATUS; +// It means that any header field can be compressed/decompressed by ATS +const static int HPACK_ERROR_COMPRESSION_ERROR = -1; +// It means that any header field is invalid in HTTP/2 spec +const static int HPACK_ERROR_HTTP2_PROTOCOL_ERROR = -2; + enum HpackFieldType { HPACK_FIELD_INDEX, // HPACK 7.1 Indexed Header Field Representation @@ -83,17 +88,17 @@ private: MIMEHdrImpl * _mh; }; -// 3.2 Header Table -class Http2HeaderTable +// 2.3.2. Dynamic Table +class Http2DynamicTable { public: - Http2HeaderTable() : _current_size(0), _settings_header_table_size(4096) { + Http2DynamicTable() : _current_size(0), _settings_dynamic_table_size(4096) { _mhdr = new MIMEHdr(); _mhdr->create(); } - ~Http2HeaderTable() { + ~Http2DynamicTable() { _headers.clear(); _mhdr->fields_clear(); delete _mhdr; @@ -101,7 +106,7 @@ public: void add_header_field(const MIMEField * field); int get_header_from_indexing_tables(uint32_t index, MIMEFieldWrapper& header_field) const; - void set_header_table_size(uint32_t new_size); + void set_dynamic_table_size(uint32_t new_size); private: @@ -114,7 +119,7 @@ private: } uint32_t _current_size; - uint32_t _settings_header_table_size; + uint32_t _settings_dynamic_table_size; MIMEHdr * _mhdr; Vec<MIMEField *> _headers; @@ -138,7 +143,7 @@ decode_integer(uint32_t& dst, const uint8_t *buf_start, const uint8_t *buf_end, int64_t encode_string(uint8_t *buf_start, const uint8_t *buf_end, const char* value, size_t value_len); int64_t -decode_string(char **c_str, uint32_t& c_str_length, const uint8_t *buf_start, const uint8_t *buf_end); +decode_string(Arena& arena, char **str, uint32_t& str_length, const uint8_t *buf_start, const uint8_t *buf_end); int64_t encode_indexed_header_field(uint8_t *buf_start, const uint8_t *buf_end, uint32_t index); @@ -147,11 +152,13 @@ encode_literal_header_field(uint8_t *buf_start, const uint8_t *buf_end, const MI int64_t encode_literal_header_field(uint8_t *buf_start, const uint8_t *buf_end, const MIMEFieldWrapper& header, HpackFieldType type); +// When these functions returns minus value, any error occurs +// TODO Separate error code and length of processed buffer int64_t -decode_indexed_header_field(MIMEFieldWrapper& header, const uint8_t *buf_start, const uint8_t *buf_end, Http2HeaderTable& header_table); +decode_indexed_header_field(MIMEFieldWrapper& header, const uint8_t *buf_start, const uint8_t *buf_end, Http2DynamicTable& dynamic_table); int64_t -decode_literal_header_field(MIMEFieldWrapper& header, const uint8_t *buf_start, const uint8_t *buf_end, Http2HeaderTable& header_table); +decode_literal_header_field(MIMEFieldWrapper& header, const uint8_t *buf_start, const uint8_t *buf_end, Http2DynamicTable& dynamic_table); int64_t -update_header_table_size(const uint8_t *buf_start, const uint8_t *buf_end, Http2HeaderTable& header_table); +update_dynamic_table_size(const uint8_t *buf_start, const uint8_t *buf_end, Http2DynamicTable& dynamic_table); #endif /* __HPACK_H__ */ http://git-wip-us.apache.org/repos/asf/trafficserver/blob/e4347ef8/proxy/http2/HTTP2.cc ---------------------------------------------------------------------- diff --git a/proxy/http2/HTTP2.cc b/proxy/http2/HTTP2.cc index 920c633..e11e8a6 100644 --- a/proxy/http2/HTTP2.cc +++ b/proxy/http2/HTTP2.cc @@ -27,6 +27,7 @@ #include "ink_assert.h" const char * const HTTP2_CONNECTION_PREFACE = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"; +static size_t HPACK_LEN_STATUS_VALUE_STR = 3; union byte_pointer { byte_pointer(void * p) : ptr(p) {} @@ -45,6 +46,13 @@ union byte_addressable_value }; static void +write_and_advance(byte_pointer& dst, const uint8_t* src, size_t length) +{ + memcpy(dst.u8, src, length); + dst.u8 += length; +} + +static void write_and_advance(byte_pointer& dst, uint32_t src) { byte_addressable_value<uint32_t> pval; @@ -54,8 +62,6 @@ write_and_advance(byte_pointer& dst, uint32_t src) dst.u8 += sizeof(pval.bytes); } -// Avoid a [-Werror,-Wunused-function] error until we need this overload ... -#if 0 static void write_and_advance(byte_pointer& dst, uint16_t src) { @@ -65,7 +71,6 @@ write_and_advance(byte_pointer& dst, uint16_t src) memcpy(dst.u8, pval.bytes, sizeof(pval.bytes)); dst.u8 += sizeof(pval.bytes); } -#endif static void write_and_advance(byte_pointer& dst, uint8_t src) @@ -102,8 +107,6 @@ http2_are_frame_flags_valid(uint8_t ftype, uint8_t fflags) HTTP2_FLAGS_GOAWAY_MASK, HTTP2_FLAGS_WINDOW_UPDATE_MASK, HTTP2_FLAGS_CONTINUATION_MASK, - HTTP2_FLAGS_ALTSVC_MASK, - HTTP2_FLAGS_BLOCKED_MASK, }; // The frame flags are valid for this frame if nothing outside the defined bits is set. @@ -117,7 +120,7 @@ http2_frame_header_is_valid(const Http2FrameHeader& hdr) return false; } - if (hdr.length > HTTP2_MAX_FRAME_PAYLOAD) { + if (hdr.length > HTTP2_MAX_FRAME_SIZE) { return false; } @@ -150,6 +153,16 @@ http2_settings_parameter_is_valid(const Http2SettingsParameter& param) return false; } + if (param.id == HTTP2_SETTINGS_ENABLE_PUSH && + param.value != 0 && param.value != 1) { + return false; + } + + if (param.id == HTTP2_SETTINGS_MAX_FRAME_SIZE && + (param.value < (1 << 14) || param.value > (1 << 24) -1)) { + return false; + } + return true; } @@ -213,6 +226,60 @@ http2_write_frame_header(const Http2FrameHeader& hdr, IOVec iov) return true; } +bool +http2_write_data(const uint8_t* src, size_t length, const IOVec& iov) +{ + byte_pointer ptr(iov.iov_base); + write_and_advance(ptr, src, length); + + return true; +} + +bool +http2_write_headers(const uint8_t* src, size_t length, const IOVec& iov) +{ + byte_pointer ptr(iov.iov_base); + write_and_advance(ptr, src, length); + + return true; +} + +bool +http2_write_rst_stream(uint32_t error_code, IOVec iov) +{ + byte_pointer ptr(iov.iov_base); + + write_and_advance(ptr, error_code); + + return true; +} + +bool +http2_write_settings(const Http2SettingsParameter& param, IOVec iov) +{ + byte_pointer ptr(iov.iov_base); + + if (unlikely(iov.iov_len < HTTP2_SETTINGS_PARAMETER_LEN)) { + return false; + } + + write_and_advance(ptr, param.id); + write_and_advance(ptr, param.value); + + return true; +} + +bool +http2_write_ping(const uint8_t * opaque_data, IOVec iov) +{ + if (iov.iov_len != HTTP2_PING_LEN) + return false; + + memcpy(iov.iov_base, opaque_data, HTTP2_PING_LEN); + + return true; +} + // 6.8. GOAWAY // // 0 1 2 3 @@ -240,6 +307,70 @@ http2_write_goaway(const Http2Goaway& goaway, IOVec iov) return true; } +bool +http2_write_window_update(const uint32_t new_size, const IOVec& iov) +{ + byte_pointer ptr(iov.iov_base); + write_and_advance(ptr, new_size); + + return true; +} + +bool +http2_parse_headers_parameter(IOVec iov, Http2HeadersParameter& params) +{ + byte_pointer ptr(iov.iov_base); + memcpy_and_advance(params.pad_length, ptr); + + return true; +} + + +// 6.3. PRIORITY +// +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// |E| Stream Dependency (31) | +// +-+-------------+-----------------------------------------------+ +// | Weight (8) | +// +-+-------------+ + +bool +http2_parse_priority_parameter(IOVec iov, Http2Priority& params) +{ + byte_pointer ptr(iov.iov_base); + byte_addressable_value<uint32_t> dependency; + + memcpy_and_advance(dependency.bytes, ptr); + memcpy_and_advance(params.weight, ptr); + + params.stream_dependency = ntohs(dependency.value); + + return true; +} + +// 6.4. RST_STREAM +// +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | Error Code (32) | +// +---------------------------------------------------------------+ + +bool +http2_parse_rst_stream(IOVec iov, Http2RstStream& rst_stream) +{ + byte_pointer ptr(iov.iov_base); + byte_addressable_value<uint32_t> ec; + + memcpy_and_advance(ec.bytes, ptr); + + rst_stream.error_code = ntohl(ec.value); + + return true; +} + // 6.5.1. SETTINGS Format // // 0 1 2 3 @@ -270,6 +401,56 @@ http2_parse_settings_parameter(IOVec iov, Http2SettingsParameter& param) return true; } + +// 6.8. GOAWAY +// +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// |R| Last-Stream-ID (31) | +// +-+-------------------------------------------------------------+ +// | Error Code (32) | +// +---------------------------------------------------------------+ +// | Additional Debug Data (*) | +// +---------------------------------------------------------------+ + +bool +http2_parse_goaway(IOVec iov, Http2Goaway& goaway) +{ + byte_pointer ptr(iov.iov_base); + byte_addressable_value<uint32_t> sid; + byte_addressable_value<uint32_t> ec; + + memcpy_and_advance(sid.bytes, ptr); + memcpy_and_advance(ec.bytes, ptr); + + goaway.last_streamid = ntohl(sid.value); + goaway.error_code = ntohl(ec.value); + return true; +} + + +// 6.9. WINDOW_UPDATE +// +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// |R| Window Size Increment (31) | +// +-+-------------------------------------------------------------+ + +bool +http2_parse_window_update(IOVec iov, uint32_t& size) +{ + byte_pointer ptr(iov.iov_base); + byte_addressable_value<uint32_t> s; + + memcpy_and_advance(s.bytes, ptr); + + size = ntohl(s.value); + + return true; +} + MIMEParseResult convert_from_2_to_1_1_header(HTTPHdr* headers) { @@ -348,38 +529,118 @@ convert_from_2_to_1_1_header(HTTPHdr* headers) headers->field_delete(HPACK_VALUE_STATUS, HPACK_LEN_STATUS); } + // Intermediaries SHOULD also remove other connection- + // specific header fields, such as Keep-Alive, Proxy-Connection, + // Transfer-Encoding and Upgrade, even if they are not nominated by + // Connection. + headers->field_delete(MIME_FIELD_CONNECTION, MIME_LEN_CONNECTION); + headers->field_delete(MIME_FIELD_KEEP_ALIVE, MIME_LEN_KEEP_ALIVE); + headers->field_delete(MIME_FIELD_PROXY_CONNECTION, MIME_LEN_PROXY_CONNECTION); + headers->field_delete(MIME_FIELD_TRANSFER_ENCODING, MIME_LEN_TRANSFER_ENCODING); + headers->field_delete(MIME_FIELD_UPGRADE, MIME_LEN_UPGRADE); + return PARSE_DONE; } +void +convert_headers_from_1_1_to_2(HTTPHdr* in) +{ + // Intermediaries SHOULD also remove other connection- + // specific header fields, such as Keep-Alive, Proxy-Connection, + // Transfer-Encoding and Upgrade, even if they are not nominated by + // Connection. + in->field_delete(MIME_FIELD_CONNECTION, MIME_LEN_CONNECTION); + in->field_delete(MIME_FIELD_KEEP_ALIVE, MIME_LEN_KEEP_ALIVE); + in->field_delete(MIME_FIELD_PROXY_CONNECTION, MIME_LEN_PROXY_CONNECTION); + in->field_delete(MIME_FIELD_TRANSFER_ENCODING, MIME_LEN_TRANSFER_ENCODING); + in->field_delete(MIME_FIELD_UPGRADE, MIME_LEN_UPGRADE); +} + +int64_t +http2_write_psuedo_headers(HTTPHdr* in, uint8_t* out, uint64_t out_len, Http2DynamicTable& /* dynamic_table */) +{ + uint8_t *p = out; + uint8_t *end = out + out_len; + int64_t len; + + ink_assert(http_hdr_type_get(in->m_http) != HTTP_TYPE_UNKNOWN); + + // TODO Check whether buffer size is enough + + // Set psuedo header + if (http_hdr_type_get(in->m_http) == HTTP_TYPE_RESPONSE) { + char status_str[HPACK_LEN_STATUS_VALUE_STR+1]; + snprintf(status_str, sizeof(status_str), "%d", in->status_get()); + + // Add 'Status:' dummy header field + MIMEField *status_field = mime_field_create(in->m_heap, in->m_http->m_fields_impl); + mime_field_name_value_set(in->m_heap, in->m_mime, status_field, -1, + HPACK_VALUE_STATUS, HPACK_LEN_STATUS, + status_str, HPACK_LEN_STATUS_VALUE_STR, true, HPACK_LEN_STATUS+HPACK_LEN_STATUS_VALUE_STR, 0); + mime_hdr_field_attach(in->m_mime, status_field, 1, NULL); + + // Encode psuedo headers by HPACK + MIMEFieldWrapper header(status_field, in->m_heap, in->m_http->m_fields_impl); + len = encode_literal_header_field(p, end, header, HPACK_FIELD_NEVERINDEX_LITERAL); + if (len == -1) return -1; + p += len; + + // Remove dummy header field + in->field_delete(HPACK_VALUE_STATUS, HPACK_LEN_STATUS); + } + + return p - out; +} + int64_t -convert_from_1_1_to_2_header(HTTPHdr* in, uint8_t* out, uint64_t out_len, Http2HeaderTable& /* header_table */) +http2_write_header_fragment(HTTPHdr* in, MIMEFieldIter& field_iter, uint8_t* out, uint64_t out_len, Http2DynamicTable& /* dynamic_table */, bool& cont) { uint8_t *p = out; uint8_t *end = out + out_len; int64_t len; ink_assert(http_hdr_type_get(in->m_http) != HTTP_TYPE_UNKNOWN); + ink_assert(in); // TODO Get a index value from the tables for the header field, and then choose a representation type. // TODO Each indexing types per field should be passed by a caller, HTTP/2 implementation. + // Get first header field which is required encoding MIMEField* field; - MIMEFieldIter field_iter; - for (field = in->iter_get_first(&field_iter); field != NULL; field = in->iter_get_next(&field_iter)) { + if (!field_iter.m_block) { + field = in->iter_get_first(&field_iter); + } else { + field = in->iter_get_next(&field_iter); + } + + // Set mime headers + cont = false; + while (field) { + MIMEFieldIter current_iter = field_iter; do { MIMEFieldWrapper header(field, in->m_heap, in->m_http->m_fields_impl); if ((len = encode_literal_header_field(p, end, header, HPACK_FIELD_INDEXED_LITERAL)) == -1) { - return -1; + if (!cont) { + // Parsing a part of headers is done + cont = true; + field_iter = current_iter; + return p - out; + } else { + // Parse error + return -1; + } } p += len; } while (field->has_dups() && (field = field->m_next_dup) != NULL); + field = in->iter_get_next(&field_iter); } + // Parsing all headers is done return p - out; } -MIMEParseResult -http2_parse_header_fragment(HTTPHdr * hdr, IOVec iov, Http2HeaderTable& header_table) +int64_t +http2_parse_header_fragment(HTTPHdr * hdr, IOVec iov, Http2DynamicTable& dynamic_table, bool cont) { uint8_t * buf_start = (uint8_t *)iov.iov_base; uint8_t * buf_end = (uint8_t *)iov.iov_base + iov.iov_len; @@ -391,10 +652,6 @@ http2_parse_header_fragment(HTTPHdr * hdr, IOVec iov, Http2HeaderTable& header_t do { int64_t read_bytes = 0; - if ((read_bytes = update_header_table_size(cursor, buf_end, header_table)) == -1) { - return PARSE_ERROR; - } - // decode a header field encoded by HPACK MIMEField *field = mime_field_create(heap, hh->m_fields_impl); MIMEFieldWrapper header(field, heap, hh->m_fields_impl); @@ -402,29 +659,91 @@ http2_parse_header_fragment(HTTPHdr * hdr, IOVec iov, Http2HeaderTable& header_t switch (ftype) { case HPACK_FIELD_INDEX: - if ((read_bytes = decode_indexed_header_field(header, cursor, buf_end, header_table)) == -1) { - return PARSE_ERROR; + read_bytes = decode_indexed_header_field(header, cursor, buf_end, dynamic_table); + if (read_bytes == HPACK_ERROR_COMPRESSION_ERROR) { + if (cont) { + // Parsing a part of headers is done + return cursor - buf_start; + } else { + // Parse error + return HPACK_ERROR_COMPRESSION_ERROR; + } } cursor += read_bytes; break; case HPACK_FIELD_INDEXED_LITERAL: case HPACK_FIELD_NOINDEX_LITERAL: case HPACK_FIELD_NEVERINDEX_LITERAL: - if ((read_bytes = decode_literal_header_field(header, cursor, buf_end, header_table)) == -1) { - return PARSE_ERROR; + read_bytes = decode_literal_header_field(header, cursor, buf_end, dynamic_table); + if (read_bytes == HPACK_ERROR_COMPRESSION_ERROR) { + if (cont) { + // Parsing a part of headers is done + return cursor - buf_start; + } else { + // Parse error + return HPACK_ERROR_COMPRESSION_ERROR; + } } cursor += read_bytes; break; case HPACK_FIELD_TABLESIZE_UPDATE: - // XXX not supported yet - return PARSE_ERROR; + read_bytes = update_dynamic_table_size(cursor, buf_end, dynamic_table); + if (read_bytes == HPACK_ERROR_COMPRESSION_ERROR) { + if (cont) { + // Parsing a part of headers is done + return cursor - buf_start; + } else { + // Parse error + return HPACK_ERROR_COMPRESSION_ERROR; + } + } + cursor += read_bytes; + continue; + } + + int name_len = 0; + const char * name = field->name_get(&name_len); + + // ':' started header name is only allowed for psuedo headers + if (hdr->fields_count() >= 4 && (name_len <= 0 || name[0] == ':')) { + // Decoded header field is invalid + return HPACK_ERROR_HTTP2_PROTOCOL_ERROR; + } + + // when The TE header field is received, it MUST NOT contain any + // value other than "trailers". + if (name_len == MIME_LEN_TE && + strncmp(name, MIME_FIELD_TE, name_len) == 0) { + int value_len = 0; + const char * value = field->value_get(&value_len); + if (!(value_len == strlen("trailers") && + strncmp(value, "trailers", value_len) == 0)) { + return HPACK_ERROR_HTTP2_PROTOCOL_ERROR; + } } // Store to HdrHeap mime_hdr_field_attach(hh->m_fields_impl, field, 1, NULL); + + // Check psuedo headers + if (hdr->fields_count() == 4) { + if (hdr->field_find(HPACK_VALUE_SCHEME, HPACK_LEN_SCHEME) == NULL || + hdr->field_find(HPACK_VALUE_METHOD, HPACK_LEN_METHOD) == NULL || + hdr->field_find(HPACK_VALUE_PATH, HPACK_LEN_PATH) == NULL || + hdr->field_find(HPACK_VALUE_AUTHORITY, HPACK_LEN_AUTHORITY) == NULL) { + // Decoded header field is invalid + return HPACK_ERROR_HTTP2_PROTOCOL_ERROR; + } + } } while (cursor < buf_end); - return PARSE_DONE; + // Psuedo headers is insufficient + if (hdr->fields_count() < 4 && !cont) { + return HPACK_ERROR_HTTP2_PROTOCOL_ERROR; + } + + // Parsing all headers is done + return cursor - buf_start; } #if TS_HAS_TESTS @@ -621,7 +940,7 @@ REGRESSION_TEST(HPACK_Encode)(RegressionTest * t, int, int *pstatus) box = REGRESSION_TEST_PASSED; uint8_t buf[BUFSIZE_FOR_REGRESSION_TEST]; - Http2HeaderTable header_table; + Http2DynamicTable dynamic_table; // FIXME Current encoder don't support indexing. for (unsigned int i=0; i<sizeof(encoded_field_test_case)/sizeof(encoded_field_test_case[0]); i++) { @@ -641,9 +960,17 @@ REGRESSION_TEST(HPACK_Encode)(RegressionTest * t, int, int *pstatus) } memset(buf, 0, BUFSIZE_FOR_REGRESSION_TEST); - int len = convert_from_1_1_to_2_header(headers, buf, BUFSIZE_FOR_REGRESSION_TEST, header_table); + convert_headers_from_1_1_to_2(headers); + uint64_t buf_len = BUFSIZE_FOR_REGRESSION_TEST; + int64_t len = http2_write_psuedo_headers(headers, buf, buf_len, dynamic_table); + buf_len -= len; - box.check(len == encoded_field_test_case[i].encoded_field_len, "encoded length was %d, expecting %d", + MIMEFieldIter field_iter; + field_iter.m_block = NULL; + bool cont = false; + len += http2_write_header_fragment(headers, field_iter, buf, buf_len, dynamic_table, cont); + + box.check(len == encoded_field_test_case[i].encoded_field_len, "encoded length was %ld, expecting %d", len, encoded_field_test_case[i].encoded_field_len); box.check(len > 0 && memcmp(buf, encoded_field_test_case[i].encoded_field, len) == 0, "encoded value was invalid"); } @@ -673,23 +1000,21 @@ REGRESSION_TEST(HPACK_DecodeString)(RegressionTest * t, int, int *pstatus) TestBox box(t, pstatus); box = REGRESSION_TEST_PASSED; + Arena arena; char* actual = NULL; - uint32_t actual_len; + uint32_t actual_len = 0; hpack_huffman_init(); for (unsigned int i=0; i<sizeof(string_test_case)/sizeof(string_test_case[0]); i++) { - int len = decode_string(&actual, actual_len, string_test_case[i].encoded_field, + int len = decode_string(arena, &actual, actual_len, string_test_case[i].encoded_field, string_test_case[i].encoded_field + string_test_case[i].encoded_field_len); box.check(len == string_test_case[i].encoded_field_len, "decoded length was %d, expecting %d", len, string_test_case[i].encoded_field_len); box.check(actual_len == string_test_case[i].raw_string_len, "length of decoded string was %d, expecting %d", actual_len, string_test_case[i].raw_string_len); - box.check(len > 0 && memcmp(actual, string_test_case[i].raw_string, actual_len) == 0, "decoded string was invalid"); - - ats_free(actual); - actual = NULL; + box.check(memcmp(actual, string_test_case[i].raw_string, actual_len) == 0, "decoded string was invalid"); } } @@ -698,7 +1023,7 @@ REGRESSION_TEST(HPACK_DecodeIndexedHeaderField)(RegressionTest * t, int, int *ps TestBox box(t, pstatus); box = REGRESSION_TEST_PASSED; - Http2HeaderTable header_table; + Http2DynamicTable dynamic_table; for (unsigned int i=0; i<sizeof(indexed_test_case)/sizeof(indexed_test_case[0]); i++) { ats_scoped_obj<HTTPHdr> headers(new HTTPHdr); @@ -707,7 +1032,7 @@ REGRESSION_TEST(HPACK_DecodeIndexedHeaderField)(RegressionTest * t, int, int *ps MIMEFieldWrapper header(field, headers->m_heap, headers->m_http->m_fields_impl); int len = decode_indexed_header_field(header, indexed_test_case[i].encoded_field, - indexed_test_case[i].encoded_field+indexed_test_case[i].encoded_field_len, header_table); + indexed_test_case[i].encoded_field+indexed_test_case[i].encoded_field_len, dynamic_table); box.check(len == indexed_test_case[i].encoded_field_len, "decoded length was %d, expecting %d", len, indexed_test_case[i].encoded_field_len); @@ -729,7 +1054,7 @@ REGRESSION_TEST(HPACK_DecodeLiteralHeaderField)(RegressionTest * t, int, int *ps TestBox box(t, pstatus); box = REGRESSION_TEST_PASSED; - Http2HeaderTable header_table; + Http2DynamicTable dynamic_table; for (unsigned int i=0; i<sizeof(literal_test_case)/sizeof(literal_test_case[0]); i++) { ats_scoped_obj<HTTPHdr> headers(new HTTPHdr); @@ -738,7 +1063,7 @@ REGRESSION_TEST(HPACK_DecodeLiteralHeaderField)(RegressionTest * t, int, int *ps MIMEFieldWrapper header(field, headers->m_heap, headers->m_http->m_fields_impl); int len = decode_literal_header_field(header, literal_test_case[i].encoded_field, - literal_test_case[i].encoded_field+literal_test_case[i].encoded_field_len, header_table); + literal_test_case[i].encoded_field+literal_test_case[i].encoded_field_len, dynamic_table); box.check(len == literal_test_case[i].encoded_field_len, "decoded length was %d, expecting %d", len, literal_test_case[i].encoded_field_len); @@ -760,13 +1085,13 @@ REGRESSION_TEST(HPACK_Decode)(RegressionTest * t, int, int *pstatus) TestBox box(t, pstatus); box = REGRESSION_TEST_PASSED; - Http2HeaderTable header_table; + Http2DynamicTable dynamic_table; for (unsigned int i=0; i<sizeof(encoded_field_test_case)/sizeof(encoded_field_test_case[0]); i++) { ats_scoped_obj<HTTPHdr> headers(new HTTPHdr); headers->create(HTTP_TYPE_REQUEST); - http2_parse_header_fragment(headers, make_iovec(encoded_field_test_case[i].encoded_field, encoded_field_test_case[i].encoded_field_len), header_table); + http2_parse_header_fragment(headers, make_iovec(encoded_field_test_case[i].encoded_field, encoded_field_test_case[i].encoded_field_len), dynamic_table, false); for (unsigned int j=0; j<sizeof(raw_field_test_case[i])/sizeof(raw_field_test_case[i][0]); j++) { const char* expected_name = raw_field_test_case[i][j].raw_name; http://git-wip-us.apache.org/repos/asf/trafficserver/blob/e4347ef8/proxy/http2/HTTP2.h ---------------------------------------------------------------------- diff --git a/proxy/http2/HTTP2.h b/proxy/http2/HTTP2.h index 6ae2b2a..c644acd 100644 --- a/proxy/http2/HTTP2.h +++ b/proxy/http2/HTTP2.h @@ -41,11 +41,25 @@ extern const char * const HTTP2_CONNECTION_PREFACE; const size_t HTTP2_CONNECTION_PREFACE_LEN = 24; const size_t HTTP2_FRAME_HEADER_LEN = 9; +const size_t HTTP2_DATA_PADLEN_LEN = 1; +const size_t HTTP2_HEADERS_PADLEN_LEN = 1; +const size_t HTTP2_PRIORITY_LEN = 5; +const size_t HTTP2_RST_STREAM_LEN = 4; +const size_t HTTP2_PING_LEN = 8; const size_t HTTP2_GOAWAY_LEN = 8; +const size_t HTTP2_WINDOW_UPDATE_LEN = 4; const size_t HTTP2_SETTINGS_PARAMETER_LEN = 6; -// 4.2. Frame Size. The absolute maximum size of a frame payload is 2^14-1 (16,383) octets. -const size_t HTTP2_MAX_FRAME_PAYLOAD = 16383; +// SETTINGS initial values +const uint32_t HTTP2_HEADER_TABLE_SIZE = 4096; +const uint32_t HTTP2_ENABLE_PUSH = 0; // Server Push is NOT supported +const uint32_t HTTP2_MAX_CONCURRENT_STREAMS = 100; +const uint32_t HTTP2_INITIAL_WINDOW_SIZE = 65535; +const uint32_t HTTP2_MAX_FRAME_SIZE = 16384; +const uint32_t HTTP2_MAX_HEADER_LIST_SIZE = UINT_MAX; + +// 6.9.1 The Flow Control Window +static const Http2WindowSize HTTP2_MAX_WINDOW_SIZE = 0x7FFFFFFF; enum Http2ErrorCode { @@ -67,6 +81,18 @@ enum Http2ErrorCode HTTP2_ERROR_MAX, }; +// 5.1. Stream States +enum Http2StreamState +{ + HTTP2_STREAM_STATE_IDLE, + HTTP2_STREAM_STATE_RESERVED_LOCAL, + HTTP2_STREAM_STATE_RESERVED_REMOTE, + HTTP2_STREAM_STATE_OPEN, + HTTP2_STREAM_STATE_HALF_CLOSED_LOCAL, + HTTP2_STREAM_STATE_HALF_CLOSED_REMOTE, + HTTP2_STREAM_STATE_CLOSED +}; + enum Http2FrameType { HTTP2_FRAME_TYPE_DATA = 0, @@ -79,8 +105,6 @@ enum Http2FrameType HTTP2_FRAME_TYPE_GOAWAY = 7, HTTP2_FRAME_TYPE_WINDOW_UPDATE = 8, HTTP2_FRAME_TYPE_CONTINUATION = 9, - HTTP2_FRAME_TYPE_ALTSVC = 10, - HTTP2_FRAME_TYPE_BLOCKED = 11, HTTP2_FRAME_TYPE_MAX, }; @@ -89,10 +113,7 @@ enum Http2FrameType enum Http2FrameFlagsData { HTTP2_FLAGS_DATA_END_STREAM = 0x01, - HTTP2_FLAGS_DATA_END_SEGMENT = 0x02, - HTTP2_FLAGS_DATA_PAD_LOW = 0x08, - HTTP2_FLAGS_DATA_PAD_HIGH = 0x10, - HTTP2_FLAGS_DATA_COMPRESSESD = 0x20, + HTTP2_FLAGS_DATA_PADDED = 0x08, HTTP2_FLAGS_DATA_MASK = 0x2B, }; @@ -101,9 +122,8 @@ enum Http2FrameFlagsData enum Http2FrameFlagsHeaders { HTTP2_FLAGS_HEADERS_END_STREAM = 0x01, - HTTP2_FLAGS_HEADERS_END_SEGMENT = 0x02, - HTTP2_FLAGS_HEADERS_PAD_LOW = 0x08, - HTTP2_FLAGS_HEADERS_PAD_HIGH = 0x10, + HTTP2_FLAGS_HEADERS_END_HEADERS = 0x04, + HTTP2_FLAGS_HEADERS_PADDED = 0x08, HTTP2_FLAGS_HEADERS_PRIORITY = 0x20, HTTP2_FLAGS_HEADERS_MASK = 0x2B, @@ -169,18 +189,6 @@ enum Http2FrameFlagsContinuation HTTP2_FLAGS_CONTINUATION_MASK = 0x1C, }; -// 6.11 Altsvc -enum Http2FrameFlagsAltsvc -{ - HTTP2_FLAGS_ALTSVC_MASK = 0x00 -}; - -// 6.12 Blocked -enum Http2FrameFlagsBlocked -{ - HTTP2_FLAGS_BLOCKED_MASK = 0x00 -}; - // 6.5.2 Defined SETTINGS Parameters enum Http2SettingsIdentifier { @@ -210,6 +218,20 @@ struct Http2SettingsParameter uint32_t value; }; +// 6.3 PRIORITY +struct Http2Priority +{ + uint32_t stream_dependency; + uint8_t weight; +}; + +// 6.2 HEADERS Format +struct Http2HeadersParameter +{ + uint8_t pad_length; + Http2Priority priority; +}; + // 6.8 GOAWAY Format struct Http2Goaway { @@ -221,11 +243,11 @@ struct Http2Goaway // just complicates memory management. }; -// 6.9.1 The Flow Control Window -static const Http2WindowSize HTTP2_MAX_WINDOW_SIZE = 0x7FFFFFFF; - -// 6.9.2 Initial Flow Control Window Size -static const Http2WindowSize HTTP2_INITIAL_WINDOW_SIZE = 0x0000FFFF; +// 6.4 RST_STREAM Format +struct Http2RstStream +{ + uint32_t error_code; +}; static inline bool http2_is_client_streamid(Http2StreamId streamid) { @@ -234,7 +256,7 @@ http2_is_client_streamid(Http2StreamId streamid) { static inline bool http2_is_server_streamid(Http2StreamId streamid) { - return (streamid & 0x1u) == 0x0u; + return (streamid & 0x1u) == 0x0u && streamid != 0x0u; } bool @@ -244,24 +266,63 @@ bool http2_write_frame_header(const Http2FrameHeader&, IOVec); bool +http2_write_data(const uint8_t*, size_t, const IOVec&); + +bool +http2_write_headers(const uint8_t*, size_t, const IOVec&); + +bool +http2_write_rst_stream(uint32_t, IOVec); + +bool +http2_write_settings(const Http2SettingsParameter&, IOVec); + +bool +http2_write_ping(const uint8_t *, IOVec); + +bool http2_write_goaway(const Http2Goaway&, IOVec); bool +http2_write_window_update(const uint32_t new_size, const IOVec&); + +bool http2_frame_header_is_valid(const Http2FrameHeader&); bool http2_settings_parameter_is_valid(const Http2SettingsParameter&); bool +http2_parse_headers_parameter(IOVec, Http2HeadersParameter&); + +bool +http2_parse_priority_parameter(IOVec, Http2Priority&); + +bool +http2_parse_rst_stream(IOVec, Http2RstStream&); + +bool http2_parse_settings_parameter(IOVec, Http2SettingsParameter&); -MIMEParseResult -http2_parse_header_fragment(HTTPHdr *, IOVec, Http2HeaderTable&); +bool +http2_parse_goaway(IOVec, Http2Goaway&); + +bool +http2_parse_window_update(IOVec, uint32_t&); + +int64_t +http2_parse_header_fragment(HTTPHdr *, IOVec, Http2DynamicTable&, bool); MIMEParseResult -convert_from_2_to_1_1_header(HTTPHdr * header); +convert_from_2_to_1_1_header(HTTPHdr *); + +void +convert_headers_from_1_1_to_2(HTTPHdr*); + +int64_t +http2_write_psuedo_headers(HTTPHdr*, uint8_t*, uint64_t, Http2DynamicTable&); int64_t -convert_from_1_1_to_2_header(HTTPHdr * in, uint8_t * out, uint64_t out_len, Http2HeaderTable& header_table); +http2_write_header_fragment(HTTPHdr*, MIMEFieldIter&, uint8_t*, uint64_t, Http2DynamicTable&, bool&); #endif /* __HTTP2_H__ */ http://git-wip-us.apache.org/repos/asf/trafficserver/blob/e4347ef8/proxy/http2/Http2ClientSession.cc ---------------------------------------------------------------------- diff --git a/proxy/http2/Http2ClientSession.cc b/proxy/http2/Http2ClientSession.cc index 3929e2e..bf763d5 100644 --- a/proxy/http2/Http2ClientSession.cc +++ b/proxy/http2/Http2ClientSession.cc @@ -59,7 +59,7 @@ send_connection_event(Continuation * cont, int event, void * edata) } Http2ClientSession::Http2ClientSession() - : con_id(0), client_vc(NULL), read_buffer(NULL), sm_reader(NULL), write_buffer(NULL), sm_writer(NULL) + : con_id(0), client_vc(NULL), read_buffer(NULL), sm_reader(NULL), write_buffer(NULL), sm_writer(NULL), upgrade_context(), is_sending_goaway(false) { } @@ -86,8 +86,14 @@ Http2ClientSession::start() HTTP2_SET_SESSION_HANDLER(&Http2ClientSession::state_read_connection_preface); read_vio = this->do_io_read(this, INT64_MAX, this->read_buffer); - this->do_io_write(this, INT64_MAX, this->sm_writer); + write_vio = this->do_io_write(this, INT64_MAX, this->sm_writer); + // 3.5 HTTP/2 Connection Preface. Upon establishment of a TCP connection and + // determination that HTTP/2 will be used by both peers, each endpoint MUST + // send a connection preface as a final confirmation ... + //this->write_buffer->write(HTTP2_CONNECTION_PREFACE, HTTP2_CONNECTION_PREFACE_LEN); + + this->connection_state.init(); send_connection_event(&this->connection_state, HTTP2_SESSION_EVENT_INIT, this); this->handleEvent(VC_EVENT_READ_READY, read_vio); } @@ -108,6 +114,7 @@ Http2ClientSession::new_connection(NetVConnection * new_vc, MIOBuffer * iobuf, I DebugHttp2Ssn("session born, netvc %p", this->client_vc); this->read_buffer = iobuf ? iobuf : new_MIOBuffer(HTTP2_HEADER_BUFFER_SIZE_INDEX); + this->read_buffer->water_mark = HTTP2_MAX_FRAME_SIZE; this->sm_reader = reader ? reader : this->read_buffer->alloc_reader(); this->write_buffer = new_MIOBuffer(HTTP2_HEADER_BUFFER_SIZE_INDEX); @@ -116,6 +123,43 @@ Http2ClientSession::new_connection(NetVConnection * new_vc, MIOBuffer * iobuf, I do_api_callout(TS_HTTP_SSN_START_HOOK); } +void +Http2ClientSession::set_upgrade_context(HTTPHdr * h) +{ + upgrade_context.req_header = new HTTPHdr(); + upgrade_context.req_header->copy(h); + + MIMEField *settings = upgrade_context.req_header->field_find(MIME_FIELD_HTTP2_SETTINGS, MIME_LEN_HTTP2_SETTINGS); + int svlen; + const char* sv = settings->value_get(&svlen); + + // Maybe size of data decoded by Base64URL is lower than size of encoded data. + unsigned char out_buf[svlen]; + if (sv && svlen > 0) { + size_t decoded_len; + ats_base64_decode(sv, svlen, out_buf, svlen, &decoded_len); + for (size_t nbytes=0; nbytes<decoded_len; nbytes+=HTTP2_SETTINGS_PARAMETER_LEN) { + Http2SettingsParameter param; + if (!http2_parse_settings_parameter(make_iovec(out_buf+nbytes, HTTP2_SETTINGS_PARAMETER_LEN), param) || + !http2_settings_parameter_is_valid(param)) { + // TODO ignore incoming invalid parameters and send suitable SETTINGS frame. + } + upgrade_context.client_settings.set((Http2SettingsIdentifier)param.id, param.value); + } + } + + // Such intermediaries SHOULD also remove other connection- + // specific header fields, such as Keep-Alive, Proxy-Connection, + // Transfer-Encoding and Upgrade, even if they are not nominated by + // Connection. + upgrade_context.req_header->field_delete(MIME_FIELD_CONNECTION, MIME_LEN_CONNECTION); + upgrade_context.req_header->field_delete(MIME_FIELD_KEEP_ALIVE, MIME_LEN_KEEP_ALIVE); + upgrade_context.req_header->field_delete(MIME_FIELD_PROXY_CONNECTION, MIME_LEN_PROXY_CONNECTION); + upgrade_context.req_header->field_delete(MIME_FIELD_TRANSFER_ENCODING, MIME_LEN_TRANSFER_ENCODING); + upgrade_context.req_header->field_delete(MIME_FIELD_UPGRADE, MIME_LEN_UPGRADE); + upgrade_context.req_header->field_delete(MIME_FIELD_HTTP2_SETTINGS, MIME_LEN_HTTP2_SETTINGS); +} + VIO * Http2ClientSession::do_io_read(Continuation * c, int64_t nbytes, MIOBuffer * buf) { @@ -169,6 +213,13 @@ Http2ClientSession::main_event_handler(int event, void * edata) case HTTP2_SESSION_EVENT_XMIT: { Http2Frame * frame = (Http2Frame *)edata; frame->xmit(this->write_buffer); + + // Mark to wait until sending GOAWAY is complete + if (frame->header().type == HTTP2_FRAME_TYPE_GOAWAY) { + is_sending_goaway = true; + } + + write_reenable(); return 0; } @@ -181,6 +232,17 @@ Http2ClientSession::main_event_handler(int event, void * edata) case VC_EVENT_WRITE_COMPLETE: case VC_EVENT_WRITE_READY: + // After sending GOAWAY, close the connection + if (is_sending_goaway && write_vio->ntodo() <= 0) { + this->do_io_close(); + } + return 0; + + case TS_FETCH_EVENT_EXT_HEAD_DONE: + case TS_FETCH_EVENT_EXT_BODY_READY: + case TS_FETCH_EVENT_EXT_BODY_DONE: + // Process responses from origin server + send_connection_event(&this->connection_state, event, edata); return 0; default: @@ -265,12 +327,23 @@ Http2ClientSession::state_start_frame_read(int event, void * edata) } // If we know up front that the payload is too long, nuke this connection. - if (this->current_hdr.length > HTTP2_MAX_FRAME_PAYLOAD) { - // XXX nuke it with HTTP2_ERROR_FRAME_SIZE_ERROR! + if (this->current_hdr.length > this->connection_state.client_settings.get(HTTP2_SETTINGS_MAX_FRAME_SIZE)) { + this->connection_state.send_goaway_frame(this->current_hdr.streamid, HTTP2_ERROR_FRAME_SIZE_ERROR); + return 0; } - if (!http2_is_client_streamid(this->current_hdr.streamid)) { - // XXX nuke it with HTTP2_ERROR_PROTOCOL_ERROR! + // Allow only stream id = 0 or streams started by client. + if (this->current_hdr.streamid != 0 && + !http2_is_client_streamid(this->current_hdr.streamid)) { + this->connection_state.send_goaway_frame(this->current_hdr.streamid, HTTP2_ERROR_PROTOCOL_ERROR); + return 0; + } + + // CONTINUATIONs MUST follow behind HEADERS which doesn't have END_HEADERS + if (this->connection_state.get_continued_id() != 0 && + this->current_hdr.type != HTTP2_FRAME_TYPE_CONTINUATION) { + this->connection_state.send_goaway_frame(this->current_hdr.streamid, HTTP2_ERROR_PROTOCOL_ERROR); + return 0; } HTTP2_SET_SESSION_HANDLER(&Http2ClientSession::state_complete_frame_read); http://git-wip-us.apache.org/repos/asf/trafficserver/blob/e4347ef8/proxy/http2/Http2ClientSession.h ---------------------------------------------------------------------- diff --git a/proxy/http2/Http2ClientSession.h b/proxy/http2/Http2ClientSession.h index 8648b43..e785dbb 100644 --- a/proxy/http2/Http2ClientSession.h +++ b/proxy/http2/Http2ClientSession.h @@ -41,6 +41,23 @@ static size_t const HTTP2_HEADER_BUFFER_SIZE_INDEX = CLIENT_CONNECTION_FIRST_READ_BUFFER_SIZE_INDEX; +// To support Upgrade: h2c +struct Http2UpgradeContext +{ + ~Http2UpgradeContext() { + if (req_header) { + req_header->clear(); + delete req_header; + } + } + + // Modified request header + HTTPHdr * req_header; + + // Decoded HTTP2-Settings Header Field + Http2ConnectionSettings client_settings; +}; + class Http2Frame { public: @@ -133,6 +150,13 @@ public: return this->con_id; } + sockaddr const* get_client_addr() { return client_vc->get_remote_addr(); } + + void write_reenable() { write_vio->reenable(); } + + void set_upgrade_context(HTTPHdr * h); + const Http2UpgradeContext& get_upgrade_context() const { return upgrade_context; } + private: Http2ClientSession(Http2ClientSession &); // noncopyable @@ -153,6 +177,14 @@ private: IOBufferReader * sm_writer; Http2FrameHeader current_hdr; Http2ConnectionState connection_state; + + // For Upgrade: h2c + Http2UpgradeContext upgrade_context; + + VIO * write_vio; + + // Mark whether ATS is sending GOAWAY + bool is_sending_goaway; }; extern ClassAllocator<Http2ClientSession> http2ClientSessionAllocator;
