diff --git a/Makefile b/Makefile index f63b5ddb08..d69d48eaa4 100644 --- a/Makefile +++ b/Makefile @@ -35,8 +35,12 @@ DBG_HTTP_PARSER ?= 0 DBG_SS ?= 0 DBG_TLS ?= 0 DBG_APM ?= 0 +DBG_HTTP_FRAME ?= 0 +DBG_HTTP_STREAM ?= 0 TFW_CFLAGS += -DDBG_CFG=$(DBG_CFG) -DDBG_HTTP_PARSER=$(DBG_HTTP_PARSER) TFW_CFLAGS += -DDBG_SS=$(DBG_SS) -DDBG_TLS=$(DBG_TLS) -DDBG_APM=$(DBG_APM) +TFW_CFLAGS += -DDBG_HTTP_FRAME=$(DBG_HTTP_FRAME) +TFW_CFLAGS += -DDBG_HTTP_STREAM=$(DBG_HTTP_STREAM) PROC = $(shell cat /proc/cpuinfo) ARCH = $(shell uname -m) diff --git a/etc/tempesta_fw.conf b/etc/tempesta_fw.conf index 9c70bed8bd..6ddd135e06 100644 --- a/etc/tempesta_fw.conf +++ b/etc/tempesta_fw.conf @@ -418,7 +418,7 @@ # Tempesta FW listening address. # # Syntax: -# listen PORT | IPADDR[:PORT] [proto=http|https] +# listen PORT | IPADDR[:PORT] [proto=http|https[:h2|http/1.1]] # # IPADDR may be either an IPv4 or IPv6 address, no host names allowed. # IPv6 address must be enclosed in square brackets (e.g. "[::0]" but not "::0"). @@ -427,13 +427,20 @@ # If only IPADDR is given, then the default HTTP port 80 is used. # # It is allowed to specify the type of listening socket via the 'proto'. At -# the moment HTTP and HTTPS protos are supported. If no 'proto' option was -# given, then HTTP is supposed by the default. +# the moment HTTP and HTTPS protos are supported. For HTTPS proto (TLS actually) +# it is allowed the application level protocol (in context of ALPN extension of +# TLS) to be specified: HTTP/2 or HTTP/1.1 with RFC compliant names 'h2' and +# 'http/1.1' respectively. It is also possible to specify both protocols +# separated by comma; in this case, the protocols' order is significant for +# TLS ALPN: first specified protocol takes precedence over the last one in +# negotiation procedure. If no application level protocol is given, then +# HTTP/1.1 is default. If the entire 'proto' option is absent, then plain +# HTTP protocol (over TCP) is supposed by the default. # # Tempesta FW opens one socket for each 'listen' entry, so it may be repeated # to listen on multiple addresses/ports. For example: # listen 80; -# listen 443 proto=https; +# listen 443 proto=https:h2,http/1.1; # listen [::0]:80; # listen 127.0.0.1:8001; # listen [::1]:8001; diff --git a/tempesta_fw/connection.h b/tempesta_fw/connection.h index 1b58f86523..b4502f94e3 100644 --- a/tempesta_fw/connection.h +++ b/tempesta_fw/connection.h @@ -32,6 +32,7 @@ #include "http_parser.h" #include "sync_socket.h" +#include "http_frame.h" #include "tls.h" /* @@ -110,6 +111,7 @@ typedef struct { #define TFW_CONN_TYPE(c) ((c)->proto.type) #define TFW_CONN_PROTO(c) TFW_CONN_TYPE2IDX(TFW_CONN_TYPE(c)) +#define TFW_CONN_TLS(c) (TFW_CONN_TYPE(c) & TFW_FSM_HTTPS) /* * Queues in client and server connections provide support for correct @@ -211,7 +213,17 @@ typedef struct { TlsCtx tls; } TfwTlsConn; -#define tfw_tls_context(conn) (TlsCtx *)(&((TfwTlsConn *)conn)->tls) +#define tfw_tls_context(conn) ((TlsCtx *)(&((TfwTlsConn *)conn)->tls)) + +/** + * HTTP/2 connection. + */ +typedef struct { + TfwTlsConn tls_conn; + TfwH2Ctx h2; +} TfwH2Conn; + +#define tfw_h2_context(conn) ((TfwH2Ctx *)(&((TfwH2Conn *)conn)->h2)) /* Callbacks used by l5-l7 protocols to operate on connection level. */ typedef struct { diff --git a/tempesta_fw/http.c b/tempesta_fw/http.c index e56d1123df..81b56532a6 100644 --- a/tempesta_fw/http.c +++ b/tempesta_fw/http.c @@ -29,6 +29,7 @@ #include "http_limits.h" #include "http_tbl.h" #include "http_parser.h" +#include "http_frame.h" #include "client.h" #include "http_msg.h" #include "http_sess.h" @@ -3884,8 +3885,8 @@ tfw_http_resp_process(TfwConn *conn, const TfwFsmData *data) /** * @return status (application logic decision) of the message processing. */ -static int -__tfw_http_msg_process(void *conn, TfwFsmData *data) +int +tfw_http_msg_process_generic(void *conn, TfwFsmData *data) { TfwConn *c = (TfwConn *)conn; @@ -3930,7 +3931,9 @@ tfw_http_msg_process(void *conn, TfwFsmData *data) if (likely(r == T_OK || r == T_POSTPONE)) { data->skb->next = data->skb->prev = NULL; data->trail = !next ? trail : 0; - r = __tfw_http_msg_process(conn, data); + r = TFW_CONN_TLS((TfwConn *)conn) && TFW_CONN_H2(conn) + ? tfw_h2_frame_process(conn, data) + : tfw_http_msg_process_generic(conn, data); } else { kfree(data->skb); } diff --git a/tempesta_fw/http.h b/tempesta_fw/http.h index ff3cbc70b6..74642606d9 100644 --- a/tempesta_fw/http.h +++ b/tempesta_fw/http.h @@ -505,6 +505,7 @@ typedef void (*tfw_http_cache_cb_t)(TfwHttpMsg *); /* External HTTP functions. */ int tfw_http_msg_process(void *conn, TfwFsmData *data); +int tfw_http_msg_process_generic(void *conn, TfwFsmData *data); unsigned long tfw_http_req_key_calc(TfwHttpReq *req); void tfw_http_req_destruct(void *msg); void tfw_http_resp_fwd(TfwHttpResp *resp); diff --git a/tempesta_fw/http_frame.c b/tempesta_fw/http_frame.c new file mode 100644 index 0000000000..fd6ac1c77d --- /dev/null +++ b/tempesta_fw/http_frame.c @@ -0,0 +1,1581 @@ +/** + * Tempesta FW + * + * Copyright (C) 2019 Tempesta Technologies, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, + * or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#if DBG_HTTP_FRAME == 0 +#undef DEBUG +#endif +#include "lib/fsm.h" +#include "lib/str.h" +#include "procfs.h" +#include "http.h" +#include "http_frame.h" + +#define FRAME_PREFACE_CLI_MAGIC "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n" +#define FRAME_PREFACE_CLI_MAGIC_LEN 24 +#define FRAME_WND_UPDATE_SIZE 4 +#define FRAME_RST_STREAM_SIZE 4 +#define FRAME_PRIORITY_SIZE 5 +#define FRAME_SETTINGS_ENTRY_SIZE 6 +#define FRAME_PING_SIZE 8 +#define FRAME_GOAWAY_SIZE 8 +#define FRAME_STREAM_ID_MASK ((1U << 31) - 1) +#define FRAME_RESERVED_BIT_MASK (~FRAME_STREAM_ID_MASK) + +#define WND_INCREMENT_SIZE 4 +#define SETTINGS_KEY_SIZE 2 +#define SETTINGS_VAL_SIZE 4 +#define SREAM_ID_SIZE 4 +#define ERR_CODE_SIZE 4 + +#define MAX_WND_SIZE ((1U << 31) - 1) +#define DEF_WND_SIZE ((1U << 16) - 1) + +/** + * FSM states for HTTP/2 frames processing. + */ +typedef enum { + HTTP2_RECV_FRAME_HEADER, + HTTP2_RECV_CLI_START_SEQ, + HTTP2_RECV_FIRST_SETTINGS, + HTTP2_RECV_FRAME_PRIORITY, + HTTP2_RECV_FRAME_WND_UPDATE, + HTTP2_RECV_FRAME_PING, + HTTP2_RECV_FRAME_RST_STREAM, + HTTP2_RECV_FRAME_SETTINGS, + HTTP2_RECV_FRAME_GOAWAY, + HTTP2_RECV_FRAME_PADDED, + HTTP2_RECV_HEADER_PRI, + HTTP2_IGNORE_FRAME_DATA, + __HTTP2_RECV_FRAME_APP, + HTTP2_RECV_HEADER = __HTTP2_RECV_FRAME_APP, + HTTP2_RECV_CONT, + HTTP2_RECV_DATA +} TfwFrameState; + +/** + * IDs for SETTINGS parameters of HTTP/2 connection (RFC 7540 + * section 6.5.2). + */ +typedef enum { + HTTP2_SETTINGS_TABLE_SIZE = 0x01, + HTTP2_SETTINGS_ENABLE_PUSH, + HTTP2_SETTINGS_MAX_STREAMS, + HTTP2_SETTINGS_INIT_WND_SIZE, + HTTP2_SETTINGS_MAX_FRAME_SIZE, + HTTP2_SETTINGS_MAX_HDR_LIST_SIZE +} TfwSettingsId; + +#define __FRAME_FSM_EXIT() \ +do { \ + ctx->rlen = 0; \ + T_FSM_EXIT(); \ +} while (0) + +#define FRAME_FSM_EXIT(ret) \ +do { \ + r = ret; \ + __FRAME_FSM_EXIT(); \ +} while (0) + +#define FRAME_FSM_FINISH() \ + T_FSM_FINISH(r, ctx->state); \ + *read += p - buf; + +#define FRAME_FSM_MOVE(st) \ +do { \ + WARN_ON_ONCE(p - buf > len); \ + ctx->rlen = 0; \ + T_FSM_MOVE(st, \ + if (unlikely(p - buf >= len)) { \ + __fsm_const_state = st; \ + T_FSM_EXIT(); \ + }); \ +} while (0) + +#define FRAME_FSM_NEXT() \ +do { \ + WARN_ON_ONCE(p - buf > len); \ + ctx->rlen = 0; \ + if (unlikely(p - buf >= len)) { \ + __fsm_const_state = ctx->state; \ + T_FSM_EXIT(); \ + } \ + T_FSM_NEXT(); \ +} while (0) + +#define FRAME_FSM_READ_LAMBDA(to_read, lambda) \ +do { \ + WARN_ON_ONCE(ctx->rlen >= (to_read)); \ + n = min_t(int, (to_read) - ctx->rlen, buf + len - p); \ + lambda; \ + p += n; \ + ctx->rlen += n; \ + if (unlikely(ctx->rlen < (to_read))) \ + T_FSM_EXIT(); \ +} while (0) + +#define FRAME_FSM_READ_SRVC(to_read) \ + BUG_ON((to_read) > sizeof(ctx->rbuf)); \ + FRAME_FSM_READ_LAMBDA(to_read, { \ + memcpy_fast(ctx->rbuf + ctx->rlen, p, n); \ + }) + +#define FRAME_FSM_READ(to_read) \ + FRAME_FSM_READ_LAMBDA(to_read, { }) + +#define SET_TO_READ(ctx) \ +do { \ + (ctx)->to_read = (ctx)->hdr.length; \ + (ctx)->hdr.length = 0; \ +} while (0) + +#define SET_TO_READ_VERIFY(ctx, next_state) \ +do { \ + (ctx)->to_read = (ctx)->hdr.length; \ + if ((ctx)->hdr.length) { \ + (ctx)->state = next_state; \ + (ctx)->hdr.length = 0; \ + } else { \ + (ctx)->state = HTTP2_IGNORE_FRAME_DATA; \ + } \ +} while (0) + +#define APP_FRAME(ctx) \ + ((ctx)->state >= __HTTP2_RECV_FRAME_APP) + +#define STREAM_RECV_PROCESS(ctx, hdr) \ +({ \ + TfwStreamFsmRes res; \ + TfwH2Err err = HTTP2_ECODE_NO_ERROR; \ + BUG_ON(!(ctx)->cur_stream); \ + if ((res = tfw_h2_stream_fsm((ctx)->cur_stream, (hdr)->type, \ + (hdr)->flags, &err))) \ + { \ + T_DBG3("stream recv processed: result=%d, state=%d, id=%u," \ + " err=%d\n", res, (ctx)->cur_stream->state, \ + (ctx)->cur_stream->id, err); \ + SET_TO_READ_VERIFY((ctx), HTTP2_IGNORE_FRAME_DATA); \ + if (res == STREAM_FSM_RES_TERM_CONN) { \ + tfw_h2_conn_terminate((ctx), err); \ + return T_DROP; \ + } else if (res == STREAM_FSM_RES_TERM_STREAM) { \ + return tfw_h2_stream_terminate((ctx), \ + (hdr)->stream_id, \ + &(ctx)->cur_stream, \ + err); \ + } \ + return T_OK; \ + } \ +}) + +int +tfw_h2_init(void) +{ + return tfw_h2_stream_cache_create(); +} + +void +tfw_h2_cleanup(void) +{ + tfw_h2_stream_cache_destroy(); +} + +void +tfw_h2_context_init(TfwH2Ctx *ctx) +{ + TfwSettings *lset = &ctx->lsettings; + TfwSettings *rset = &ctx->rsettings; + + bzero_fast(ctx, sizeof(*ctx)); + + ctx->state = HTTP2_RECV_CLI_START_SEQ; + ctx->loc_wnd = MAX_WND_SIZE; + + lset->hdr_tbl_sz = rset->hdr_tbl_sz = 1 << 12; + lset->push = rset->push = 1; + lset->max_streams = rset->max_streams = 0xffffffff; + lset->max_frame_sz = rset->max_frame_sz = 1 << 14; + lset->max_lhdr_sz = rset->max_lhdr_sz = UINT_MAX; + /* + * We ignore client's window size until #498, so currently + * we set it to maximum allowed value. + */ + lset->wnd_sz = rset->wnd_sz = MAX_WND_SIZE; +} + +void +tfw_h2_context_clear(TfwH2Ctx *ctx) +{ + tfw_h2_streams_cleanup(&ctx->sched); +} + +static inline void +tfw_h2_unpack_frame_header(TfwFrameHdr *hdr, const unsigned char *buf) +{ + hdr->length = ntohl(*(int *)buf) >> 8; + hdr->type = buf[3]; + hdr->flags = buf[4]; + hdr->stream_id = ntohl(*(unsigned int *)&buf[5]) & FRAME_STREAM_ID_MASK; + + T_DBG3("%s: parsed, length=%d, stream_id=%u, type=%hhu, flags=0x%hhx\n", + __func__, hdr->length, hdr->stream_id, hdr->type, hdr->flags); +} + +static inline void +tfw_h2_pack_frame_header(unsigned char *p, const TfwFrameHdr *hdr) +{ + *(unsigned int *)p = htonl((unsigned int)(hdr->length << 8)); + p += 3; + *p++ = hdr->type; + *p++ = hdr->flags; + /* + * Stream id must occupy not more than 31 bit and reserved bit + * must be 0. + */ + WARN_ON_ONCE((unsigned int)(hdr->stream_id & FRAME_RESERVED_BIT_MASK)); + + *(unsigned int *)p = htonl(hdr->stream_id); +} + +static inline void +tfw_h2_unpack_priority(TfwFramePri *pri, const unsigned char *buf) +{ + pri->stream_id = ntohl(*(unsigned int *)buf) & FRAME_STREAM_ID_MASK; + pri->exclusive = (buf[0] & 0x80) > 0; + pri->weight = buf[4] + 1; +} + +/** + * Prepare and send HTTP/2 frame to the client; @hdr must contain + * the valid data to fill in the frame's header; @data may carry + * additional data as frame's payload. + * + * NOTE: Caller must leave first chunk of @data unoccupied - to + * provide the place for frame's header which will be packed and + * written in this procedure. + */ +static int +__tfw_h2_send_frame(TfwH2Ctx *ctx, TfwFrameHdr *hdr, TfwStr *data, bool close) +{ + int r; + TfwMsgIter it; + TfwMsg msg = {}; + unsigned char buf[FRAME_HEADER_SIZE]; + TfwStr *hdr_str = TFW_STR_CHUNK(data, 0); + TfwH2Conn *conn = container_of(ctx, TfwH2Conn, h2); + + BUG_ON(hdr_str->data); + hdr_str->data = buf; + hdr_str->len = FRAME_HEADER_SIZE; + + if (data != hdr_str) + data->len += FRAME_HEADER_SIZE; + + tfw_h2_pack_frame_header(buf, hdr); + + T_DBG2("Preparing HTTP/2 message with %lu bytes data\n", data->len); + + msg.len = data->len; + if ((r = tfw_msg_iter_setup(&it, &msg.skb_head, msg.len))) + goto err; + + if ((r = tfw_msg_write(&it, data))) + goto err; + + if (close) + msg.ss_flags |= SS_F_CONN_CLOSE; + + if ((r = tfw_connection_send((TfwConn *)conn, &msg))) + goto err; + /* + * For HTTP/2 we do not close client connection automatically in case + * of failed sending (unlike the HTTP/1.1 processing); thus, we should + * set Conn_Stop flag only if sending procedure was successful - to + * avoid hanged unclosed client connection. + */ + if (close) + TFW_CONN_TYPE((TfwConn *)conn) |= Conn_Stop; + + return 0; + +err: + ss_skb_queue_purge(&msg.skb_head); + return r; +} + +static inline int +tfw_h2_send_frame(TfwH2Ctx *ctx, TfwFrameHdr *hdr, TfwStr *data) +{ + return __tfw_h2_send_frame(ctx, hdr, data, false); +} + +static inline int +tfw_h2_send_frame_close(TfwH2Ctx *ctx, TfwFrameHdr *hdr, TfwStr *data) +{ + return __tfw_h2_send_frame(ctx, hdr, data, true); +} + +static inline int +tfw_h2_send_ping(TfwH2Ctx *ctx) +{ + TfwStr data = { + .chunks = (TfwStr []){ + {}, + { .data = ctx->rbuf, .len = ctx->rlen } + }, + .len = ctx->rlen, + .nchunks = 2 + }; + TfwFrameHdr hdr = { + .length = ctx->rlen, + .stream_id = 0, + .type = HTTP2_PING, + .flags = HTTP2_F_ACK + }; + + WARN_ON_ONCE(ctx->rlen != FRAME_PING_SIZE); + + return tfw_h2_send_frame(ctx, &hdr, &data); + +} + +static inline int +tfw_h2_send_wnd_update(TfwH2Ctx *ctx, unsigned int id, unsigned int wnd_incr) +{ + unsigned char incr_buf[WND_INCREMENT_SIZE]; + TfwStr data = { + .chunks = (TfwStr []){ + {}, + { .data = incr_buf, .len = WND_INCREMENT_SIZE } + }, + .len = WND_INCREMENT_SIZE, + .nchunks = 2 + }; + TfwFrameHdr hdr = { + .length = data.len, + .stream_id = id, + .type = HTTP2_WINDOW_UPDATE, + .flags = 0 + }; + + WARN_ON_ONCE((unsigned int)(wnd_incr & FRAME_RESERVED_BIT_MASK)); + + *(unsigned int *)incr_buf = htonl(wnd_incr); + + return tfw_h2_send_frame(ctx, &hdr, &data); +} + +static inline int +tfw_h2_send_settings_init(TfwH2Ctx *ctx) +{ + unsigned char key_buf[SETTINGS_KEY_SIZE]; + unsigned char val_buf[SETTINGS_VAL_SIZE]; + TfwStr data = { + .chunks = (TfwStr []){ + {}, + { .data = key_buf, .len = SETTINGS_KEY_SIZE }, + { .data = val_buf, .len = SETTINGS_VAL_SIZE } + }, + .len = SETTINGS_KEY_SIZE + SETTINGS_VAL_SIZE, + .nchunks = 3 + }; + TfwFrameHdr hdr = { + .length = data.len, + .stream_id = 0, + .type = HTTP2_SETTINGS, + .flags = 0 + }; + + BUILD_BUG_ON(SETTINGS_KEY_SIZE != sizeof(unsigned short) + || SETTINGS_VAL_SIZE != sizeof(unsigned int) + || SETTINGS_VAL_SIZE != sizeof(ctx->lsettings.wnd_sz)); + + *(unsigned short *)key_buf = htons(HTTP2_SETTINGS_INIT_WND_SIZE); + *(unsigned int *)val_buf = htonl(ctx->lsettings.wnd_sz); + + return tfw_h2_send_frame(ctx, &hdr, &data); +} + +static inline int +tfw_h2_send_settings_ack(TfwH2Ctx *ctx) +{ + TfwStr data = {}; + TfwFrameHdr hdr = { + .length = 0, + .stream_id = 0, + .type = HTTP2_SETTINGS, + .flags = HTTP2_F_ACK + }; + + return tfw_h2_send_frame(ctx, &hdr, &data); +} + +static inline int +tfw_h2_send_goaway(TfwH2Ctx *ctx, TfwH2Err err_code) +{ + unsigned char id_buf[SREAM_ID_SIZE]; + unsigned char err_buf[ERR_CODE_SIZE]; + TfwStr data = { + .chunks = (TfwStr []){ + {}, + { .data = id_buf, .len = SREAM_ID_SIZE }, + { .data = err_buf, .len = ERR_CODE_SIZE } + }, + .len = SREAM_ID_SIZE + ERR_CODE_SIZE, + .nchunks = 3 + }; + TfwFrameHdr hdr = { + .length = data.len, + .stream_id = 0, + .type = HTTP2_GOAWAY, + .flags = 0 + }; + + WARN_ON_ONCE((unsigned int)(ctx->lstream_id & FRAME_RESERVED_BIT_MASK)); + BUILD_BUG_ON(SREAM_ID_SIZE != sizeof(unsigned int) + || SREAM_ID_SIZE != sizeof(ctx->lstream_id) + || ERR_CODE_SIZE != sizeof(unsigned int) + || ERR_CODE_SIZE != sizeof(err_code)); + + *(unsigned int *)id_buf = htonl(ctx->lstream_id); + *(unsigned int *)err_buf = htonl(err_code); + + return tfw_h2_send_frame_close(ctx, &hdr, &data); +} + +static inline int +tfw_h2_send_rst_stream(TfwH2Ctx *ctx, unsigned int id, TfwH2Err err_code) +{ + unsigned char buf[ERR_CODE_SIZE]; + TfwStr data = { + .chunks = (TfwStr []){ + {}, + { .data = buf, .len = ERR_CODE_SIZE } + }, + .len = ERR_CODE_SIZE, + .nchunks = 2 + }; + TfwFrameHdr hdr = { + .length = data.len, + .stream_id = id, + .type = HTTP2_RST_STREAM, + .flags = 0 + }; + + *(unsigned int *)buf = htonl(err_code); + + return tfw_h2_send_frame(ctx, &hdr, &data); +} + +static inline int +tfw_h2_conn_terminate(TfwH2Ctx *ctx, TfwH2Err err_code) +{ + return tfw_h2_send_goaway(ctx, err_code); +} + +static inline int +tfw_h2_stream_terminate(TfwH2Ctx *ctx, unsigned int id, TfwStream **stream, + TfwH2Err err_code) +{ + if (stream && *stream) { + --ctx->streams_num; + tfw_h2_stop_stream(&ctx->sched, stream); + } + + return tfw_h2_send_rst_stream(ctx, id, err_code); +} + +static inline void +tfw_h2_check_closed_stream(TfwH2Ctx *ctx) +{ + BUG_ON(!ctx->cur_stream); + + T_DBG3("%s: stream->id=%u, stream->state=%d, stream=[%p], streams_num=" + "%lu\n", __func__, ctx->cur_stream->id, ctx->cur_stream->state, + ctx->cur_stream, ctx->streams_num); + + if (tfw_h2_stream_is_closed(ctx->cur_stream)) { + --ctx->streams_num; + tfw_h2_stop_stream(&ctx->sched, &ctx->cur_stream); + } +} + +#define VERIFY_FRAME_SIZE(ctx) \ +do { \ + if ((ctx)->hdr.length < 0) { \ + tfw_h2_conn_terminate(ctx, HTTP2_ECODE_SIZE); \ + return T_DROP; \ + } \ +} while (0) + +static inline int +tfw_h2_recv_priority(TfwH2Ctx *ctx) +{ + ctx->to_read = FRAME_PRIORITY_SIZE; + ctx->hdr.length -= ctx->to_read; + VERIFY_FRAME_SIZE(ctx); + ctx->state = HTTP2_RECV_HEADER_PRI; + return T_OK; +} + +static inline int +tfw_h2_recv_padded(TfwH2Ctx *ctx) +{ + ctx->to_read = 1; + ctx->hdr.length -= ctx->to_read; + VERIFY_FRAME_SIZE(ctx); + ctx->state = HTTP2_RECV_FRAME_PADDED; + return T_OK; +} + +static int +tfw_h2_headers_pri_process(TfwH2Ctx *ctx) +{ + TfwFramePri *pri = &ctx->priority; + TfwFrameHdr *hdr = &ctx->hdr; + + BUG_ON(!(hdr->flags & HTTP2_F_PRIORITY)); + + tfw_h2_unpack_priority(pri, ctx->rbuf); + + T_DBG3("%s: parsed, stream_id=%u, dep_stream_id=%u, weight=%hu," + " excl=%hhu\n", __func__, hdr->stream_id, pri->stream_id, + pri->weight, pri->exclusive); + + ctx->data_off += FRAME_PRIORITY_SIZE; + + SET_TO_READ_VERIFY(ctx, HTTP2_RECV_HEADER); + return T_OK; +} + +static TfwStream * +tfw_h2_stream_create(TfwH2Ctx *ctx, unsigned int id) +{ + TfwStream *stream, *dep = NULL; + TfwFramePri *pri = &ctx->priority; + bool excl = pri->exclusive; + + if (tfw_h2_find_stream_dep(&ctx->sched, pri->stream_id, &dep)) + return NULL; + + stream = tfw_h2_add_stream(&ctx->sched, id, pri->weight, + ctx->lsettings.wnd_sz); + if (!stream) + return NULL; + + tfw_h2_add_stream_dep(&ctx->sched, stream, dep, excl); + + ++ctx->streams_num; + + T_DBG3("%s: stream added, id=%u, stream=[%p] weight=%hu," + " streams_num=%lu, dep_stream_id=%u, dep_stream=[%p]," + " excl=%hhu\n", __func__, id, stream, stream->weight, + ctx->streams_num, pri->stream_id, dep, pri->exclusive); + + return stream; +} + +static int +tfw_h2_headers_process(TfwH2Ctx *ctx) +{ + TfwFrameHdr *hdr = &ctx->hdr; + + T_DBG3("%s: stream->id=%u, cur_stream=[%p]\n", __func__, + hdr->stream_id, ctx->cur_stream); + /* + * Stream cannot depend on itself (see RFC 7540 section 5.1.2 for + * details). + */ + if (ctx->priority.stream_id == hdr->stream_id) { + T_DBG("Invalid dependency: new stream with %u depends on" + " itself\n", hdr->stream_id); + + ctx->state = HTTP2_IGNORE_FRAME_DATA; + + return tfw_h2_stream_terminate(ctx, hdr->stream_id, + &ctx->cur_stream, + HTTP2_ECODE_PROTO); + } + + if (!ctx->cur_stream) { + ctx->cur_stream = tfw_h2_stream_create(ctx, hdr->stream_id); + if (!ctx->cur_stream) + return T_DROP; + ctx->lstream_id = hdr->stream_id; + } + /* + * Since the same received HEADERS frame can cause the stream to become + * 'open' (i.e. created) and right away become 'half-closed (remote)' + * (in case of both END_STREAM and END_HEADERS flags set in initial + * HEADERS frame), we should process its state here - when frame is + * fully received and new stream is created. + */ + STREAM_RECV_PROCESS(ctx, hdr); + + tfw_h2_check_closed_stream(ctx); + + return T_OK; +} + +static int +tfw_h2_wnd_update_process(TfwH2Ctx *ctx) +{ + unsigned int wnd_incr; + TfwFrameHdr *hdr = &ctx->hdr; + + wnd_incr = ntohl(*(unsigned int *)ctx->rbuf) & ((1U << 31) - 1); + if (!wnd_incr) { + if (ctx->cur_stream) + return tfw_h2_stream_terminate(ctx, hdr->stream_id, + &ctx->cur_stream, + HTTP2_ECODE_PROTO); + tfw_h2_conn_terminate(ctx, HTTP2_ECODE_PROTO); + return T_DROP; + } + /* + * TODO: apply new window size for entire connection or + * particular stream; ignore until #498. + */ + return T_OK; +} + +static inline int +tfw_h2_priority_process(TfwH2Ctx *ctx) +{ + TfwFrameHdr *hdr = &ctx->hdr; + TfwFramePri *pri = &ctx->priority; + + tfw_h2_unpack_priority(pri, ctx->rbuf); + + /* + * Stream cannot depend on itself (see RFC 7540 section 5.1.2 for + * details). + */ + if (pri->stream_id == hdr->stream_id) { + T_DBG("Invalid dependency: new stream with %u depends on" + " itself\n", hdr->stream_id); + + return tfw_h2_stream_terminate(ctx, hdr->stream_id, + &ctx->cur_stream, + HTTP2_ECODE_PROTO); + } + + T_DBG3("%s: parsed, stream_id=%u, dep_stream_id=%u, weight=%hu," + " excl=%hhu\n", __func__, hdr->stream_id, pri->stream_id, + pri->weight, pri->exclusive); + + tfw_h2_change_stream_dep(&ctx->sched, hdr->stream_id, pri->stream_id, + pri->weight, pri->exclusive); + return T_OK; +} + +static void +tfw_h2_rst_stream_process(TfwH2Ctx *ctx) +{ + BUG_ON(!ctx->cur_stream); + T_DBG3("%s: parsed, stream_id=%u, stream=[%p], err_code=%u\n", + __func__, ctx->hdr.stream_id, ctx->cur_stream, + ntohl(*(unsigned int *)ctx->rbuf)); + + --ctx->streams_num; + + tfw_h2_stop_stream(&ctx->sched, &ctx->cur_stream); +} + +static int +tfw_h2_apply_settings_entry(TfwSettings *dest, unsigned short id, + unsigned int val) +{ + switch (id) { + case HTTP2_SETTINGS_TABLE_SIZE: + dest->hdr_tbl_sz = val; + break; + + case HTTP2_SETTINGS_ENABLE_PUSH: + dest->push = val; + break; + + case HTTP2_SETTINGS_MAX_STREAMS: + dest->max_streams = val; + break; + + case HTTP2_SETTINGS_INIT_WND_SIZE: + dest->wnd_sz = val; + break; + + case HTTP2_SETTINGS_MAX_FRAME_SIZE: + dest->max_frame_sz = val; + break; + + case HTTP2_SETTINGS_MAX_HDR_LIST_SIZE: + dest->max_lhdr_sz = val; + break; + + default: + /* + * We should silently ignore unknown identifiers (see + * RFC 7540 section 6.5.2) + */ + return T_OK; + } + + /* + * TODO: apply settings entry. + */ + return T_OK; +} + +static void +tfw_h2_settings_ack_process(TfwH2Ctx *ctx) +{ + T_DBG3("%s: parsed, stream_id=%u, flags=%hhu\n", __func__, + ctx->hdr.stream_id, ctx->hdr.flags); + /* + * TODO: apply settings ACK. + */ +} + +static int +tfw_h2_settings_process(TfwH2Ctx *ctx) +{ + TfwFrameHdr *hdr = &ctx->hdr; + unsigned short id = ntohs(*(unsigned short *)&ctx->rbuf[0]); + unsigned int val = ntohl(*(unsigned int *)&ctx->rbuf[2]); + + T_DBG3("%s: entry parsed, id=%hu, val=%u\n", __func__, id, val); + + if (tfw_h2_apply_settings_entry(&ctx->rsettings, id, val)) + return T_BAD; + + ctx->to_read = hdr->length ? FRAME_SETTINGS_ENTRY_SIZE : 0; + hdr->length -= ctx->to_read; + + return T_OK; +} + +static int +tfw_h2_goaway_process(TfwH2Ctx *ctx) +{ + unsigned int last_id, err_code; + + last_id = ntohl(*(unsigned int *)ctx->rbuf) & FRAME_STREAM_ID_MASK; + err_code = ntohl(*(unsigned int *)&ctx->rbuf[4]); + + T_DBG3("%s: parsed, last_id=%u, err_code=%u\n", __func__, + last_id, err_code); + /* + * TODO: currently Tempesta FW does not initiate new streams in client + * connections, so for now we have nothing to do here, except + * continuation processing of existing streams until client will close + * TCP connection. But in context of #1194 (since Tempesta FW will be + * able to initiate new streams after PUSH_PROMISE implementation), we + * should close all streams initiated by our side with identifier + * higher than @last_id, and should not initiate new streams until + * connection will be closed (see RFC 7540 section 5.4.1 and section + * 6.8 for details). + */ + if (err_code) + T_LOG("HTTP/2 connection is closed by client with error code:" + " %u, ID of last processed stream: %u\n", err_code, + last_id); + SET_TO_READ(ctx); + return T_OK; +} + +static inline int +tfw_h2_first_settings_verify(TfwH2Ctx *ctx) +{ + int err_code = 0; + TfwFrameHdr *hdr = &ctx->hdr; + + BUG_ON(ctx->to_read); + + tfw_h2_unpack_frame_header(hdr, ctx->rbuf); + + if (hdr->type != HTTP2_SETTINGS + || (hdr->flags & HTTP2_F_ACK) + || hdr->stream_id) + { + err_code = HTTP2_ECODE_PROTO; + } + + if (hdr->length && (hdr->length % FRAME_SETTINGS_ENTRY_SIZE)) + err_code = HTTP2_ECODE_SIZE; + + if (err_code) { + tfw_h2_conn_terminate(ctx, err_code); + return T_DROP; + } + + ctx->to_read = hdr->length ? FRAME_SETTINGS_ENTRY_SIZE : 0; + hdr->length -= ctx->to_read; + + return T_OK; +} + +static inline int +tfw_h2_stream_id_verify(TfwH2Ctx *ctx) +{ + TfwFrameHdr *hdr = &ctx->hdr; + + if (ctx->cur_stream) + return T_OK; + /* + * If stream ID is not greater than last processed ID, + * there may be two reasons for that: + * 1. Stream has been created, processed, closed and + * removed by now; + * 2. Stream was never created and has been moved from + * idle to closed without processing (see RFC 7540 + * section 5.1.1 for details). + */ + if (ctx->lstream_id >= hdr->stream_id) { + T_DBG("Invalid ID of new stream: %u stream is" + " closed and removed, %u last initiated\n", + hdr->stream_id, ctx->lstream_id); + return T_DROP; + } + /* + * Streams initiated by client must use odd-numbered + * identifiers (see RFC 7540 section 5.1.1 for details). + */ + if (!(hdr->stream_id & 0x1)) { + T_DBG("Invalid ID of new stream: initiated by" + " server\n"); + return T_DROP; + } + + return T_OK; +} + +static inline int +tfw_h2_flow_control(TfwH2Ctx *ctx) +{ + TfwFrameHdr *hdr = &ctx->hdr; + TfwStream *stream = ctx->cur_stream; + TfwSettings *lset = &ctx->lsettings; + + BUG_ON(!stream); + if (hdr->length > stream->loc_wnd) + T_WARN("Stream flow control window exceeded: frame payload %d," + " current window %u\n", hdr->length, stream->loc_wnd); + + if(hdr->length > ctx->loc_wnd) + T_WARN("Connection flow control window exceeded: frame payload" + " %d, current window %u\n", hdr->length, ctx->loc_wnd); + + stream->loc_wnd -= hdr->length; + ctx->loc_wnd -= hdr->length; + + if (stream->loc_wnd <= lset->wnd_sz / 2 + && tfw_h2_send_wnd_update(ctx, stream->id, + lset->wnd_sz - stream->loc_wnd)) + { + return T_DROP; + } + + if (ctx->loc_wnd <= MAX_WND_SIZE / 2 + && tfw_h2_send_wnd_update(ctx, 0, MAX_WND_SIZE - ctx->loc_wnd)) + { + return T_DROP; + } + + return T_OK; +} + +static int +tfw_h2_frame_pad_process(TfwH2Ctx *ctx) +{ + TfwFrameHdr *hdr = &ctx->hdr; + + ++ctx->data_off; + ctx->padlen = ctx->rbuf[0]; + hdr->length -= ctx->padlen; + VERIFY_FRAME_SIZE(ctx); + + if (!hdr->length) { + ctx->state = HTTP2_IGNORE_FRAME_DATA; + ctx->to_read = 0; + return T_OK; + } + + switch (hdr->type) { + case HTTP2_DATA: + ctx->state = HTTP2_RECV_DATA; + break; + + case HTTP2_HEADERS: + if (hdr->flags & HTTP2_F_PRIORITY) + return tfw_h2_recv_priority(ctx); + ctx->state = HTTP2_RECV_HEADER; + break; + + default: + /* Only DATA and HEADERS frames can be padded. */ + BUG(); + } + + SET_TO_READ(ctx); + + return T_OK; +} + +/* + * Initial processing of received frames: verification and handling of + * frame header; also, stream states are processed here - during receiving + * of stream-related frames (CONTINUATION, DATA, RST_STREAM, PRIORITY, + * WINDOW_UPDATE). We do all that processing at the initial stage here, + * since we should drop invalid frames/streams/connections as soon as + * possible in order not to waste resources on their further processing. + * The only exception is received HEADERS frame which state are processed + * after full frame reception (see comments in @tfw_h2_headers_process() + * procedure). + */ +static int +tfw_h2_frame_type_process(TfwH2Ctx *ctx) +{ + TfwH2Err err_code = HTTP2_ECODE_SIZE; + TfwFrameHdr *hdr = &ctx->hdr; + + T_DBG3("%s: hdr->type=%hhu, ctx->state=%d\n", __func__, hdr->type, + ctx->state); + + switch (hdr->type) { + case HTTP2_DATA: + if (!hdr->stream_id) { + err_code = HTTP2_ECODE_PROTO; + goto conn_term; + } + + /* + * DATA frames are not allowed for idle streams (see RFC 7540 + * section 5.1 for details). + */ + if (hdr->stream_id > ctx->lstream_id) { + err_code = HTTP2_ECODE_PROTO; + goto conn_term; + } + + ctx->cur_stream = tfw_h2_find_stream(&ctx->sched, + hdr->stream_id); + /* + * If stream is removed, it had been closed before, so this is + * connection error (see RFC 7540 section 5.1). + */ + if (!ctx->cur_stream) { + err_code = HTTP2_ECODE_CLOSED; + goto conn_term; + } + + if (tfw_h2_flow_control(ctx)) + return T_DROP; + + STREAM_RECV_PROCESS(ctx, hdr); + + ctx->data_off = FRAME_HEADER_SIZE; + + if (hdr->flags & HTTP2_F_PADDED) + return tfw_h2_recv_padded(ctx); + + SET_TO_READ_VERIFY(ctx, HTTP2_RECV_DATA); + return T_OK; + + case HTTP2_HEADERS: + if (!hdr->stream_id) { + err_code = HTTP2_ECODE_PROTO; + goto conn_term; + } + /* + * TODO: in cases of sending RST_STREAM frame or END_STREAM + * flag - stream can be switched into the closed state - this + * is the race condition (when stream had been closed on server + * side, but the client does not aware about that yet), and we + * should silently discard such stream, i.e. continue process + * entire HTTP/2 connection but ignore HEADERS, CONTINUATION and + * DATA frames from this stream (not pass upstairs); to achieve + * such behavior (to avoid removing of such closed streams right + * away), we should store closed streams - for some predefined + * period of time or just limiting the amount of closed stored + * streams (see comments for @TfwStreamState enum at the + * beginning of http_stream.c). + */ + ctx->cur_stream = tfw_h2_find_stream(&ctx->sched, + hdr->stream_id); + /* + * Endpoints must not exceed the limit set by their peer for + * maximum number of concurrent streams (see RFC 7540 section + * 5.1.2 for details). + */ + if (!ctx->cur_stream + && ctx->lsettings.max_streams <= ctx->streams_num) + { + T_DBG("Max streams number exceeded: %lu\n", + ctx->streams_num); + SET_TO_READ_VERIFY(ctx, HTTP2_IGNORE_FRAME_DATA); + return tfw_h2_stream_terminate(ctx, hdr->stream_id, + NULL, + HTTP2_ECODE_REFUSED); + } + + if (tfw_h2_stream_id_verify(ctx)) { + err_code = HTTP2_ECODE_PROTO; + goto conn_term; + } + + ctx->data_off = FRAME_HEADER_SIZE; + + if (hdr->flags & HTTP2_F_PADDED) + return tfw_h2_recv_padded(ctx); + + if (hdr->flags & HTTP2_F_PRIORITY) + return tfw_h2_recv_priority(ctx); + + SET_TO_READ_VERIFY(ctx, HTTP2_RECV_HEADER); + return T_OK; + + case HTTP2_PRIORITY: + if (!hdr->stream_id) { + err_code = HTTP2_ECODE_PROTO; + goto conn_term; + } + + ctx->cur_stream = tfw_h2_find_stream(&ctx->sched, + hdr->stream_id); + if (hdr->length != FRAME_PRIORITY_SIZE) { + SET_TO_READ(ctx); + return tfw_h2_stream_terminate(ctx, hdr->stream_id, + &ctx->cur_stream, + HTTP2_ECODE_SIZE); + } + + if (ctx->cur_stream) + STREAM_RECV_PROCESS(ctx, hdr); + + ctx->state = HTTP2_RECV_FRAME_PRIORITY; + SET_TO_READ(ctx); + return T_OK; + + case HTTP2_WINDOW_UPDATE: + if (hdr->length != FRAME_WND_UPDATE_SIZE) + goto conn_term; + /* + * WINDOW_UPDATE frame not allowed for idle streams (see RFC + * 7540 section 5.1 for details). + */ + if (hdr->stream_id > ctx->lstream_id) { + err_code = HTTP2_ECODE_PROTO; + goto conn_term; + } + + if (hdr->stream_id) { + ctx->cur_stream = tfw_h2_find_stream(&ctx->sched, + hdr->stream_id); + if (!ctx->cur_stream) { + err_code = HTTP2_ECODE_CLOSED; + goto conn_term; + } + + STREAM_RECV_PROCESS(ctx, hdr); + } + + ctx->state = HTTP2_RECV_FRAME_WND_UPDATE; + SET_TO_READ(ctx); + return T_OK; + + case HTTP2_SETTINGS: + if (hdr->stream_id) { + err_code = HTTP2_ECODE_PROTO; + goto conn_term; + } + if ((hdr->length % FRAME_SETTINGS_ENTRY_SIZE) + || ((hdr->flags & HTTP2_F_ACK) + && hdr->length > 0)) + { + goto conn_term; + } + + if (hdr->flags & HTTP2_F_ACK) + tfw_h2_settings_ack_process(ctx); + + if (hdr->length) { + ctx->state = HTTP2_RECV_FRAME_SETTINGS; + ctx->to_read = FRAME_SETTINGS_ENTRY_SIZE; + hdr->length -= ctx->to_read; + } else { + /* + * SETTINGS frame does not have any payload in + * this case, so frame is fully received now. + */ + ctx->to_read = 0; + } + + return T_OK; + + case HTTP2_PUSH_PROMISE: + /* Client cannot push (RFC 7540 section 8.2). */ + err_code = HTTP2_ECODE_PROTO; + goto conn_term; + + case HTTP2_PING: + if (hdr->stream_id) { + err_code = HTTP2_ECODE_PROTO; + goto conn_term; + } + if (hdr->length != FRAME_PING_SIZE) + goto conn_term; + + ctx->state = HTTP2_RECV_FRAME_PING; + SET_TO_READ(ctx); + return T_OK; + + case HTTP2_RST_STREAM: + if (!hdr->stream_id) + { + err_code = HTTP2_ECODE_PROTO; + goto conn_term; + } + if (hdr->length != FRAME_RST_STREAM_SIZE) + goto conn_term; + /* + * RST_STREAM frames are not allowed for idle streams (see RFC + * 7540 section 5.1 and section 6.4 for details). + */ + if (hdr->stream_id > ctx->lstream_id) { + err_code = HTTP2_ECODE_PROTO; + goto conn_term; + } + + ctx->cur_stream = tfw_h2_find_stream(&ctx->sched, + hdr->stream_id); + if (!ctx->cur_stream) { + err_code = HTTP2_ECODE_CLOSED; + goto conn_term; + } + + STREAM_RECV_PROCESS(ctx, hdr); + + ctx->state = HTTP2_RECV_FRAME_RST_STREAM; + SET_TO_READ(ctx); + return T_OK; + + case HTTP2_GOAWAY: + if (hdr->stream_id) { + err_code = HTTP2_ECODE_PROTO; + goto conn_term; + } + if (hdr->length < FRAME_GOAWAY_SIZE) + goto conn_term; + + ctx->state = HTTP2_RECV_FRAME_GOAWAY; + ctx->to_read = FRAME_GOAWAY_SIZE; + hdr->length -= ctx->to_read; + return T_OK; + + case HTTP2_CONTINUATION: + if (!hdr->stream_id) { + err_code = HTTP2_ECODE_PROTO; + goto conn_term; + } + /* + * CONTINUATION frames are not allowed for idle streams (see + * RFC 7540 section 5.1 and section 6.4 for details). + */ + if (hdr->stream_id > ctx->lstream_id) { + err_code = HTTP2_ECODE_PROTO; + goto conn_term; + } + + ctx->cur_stream = tfw_h2_find_stream(&ctx->sched, + hdr->stream_id); + if (!ctx->cur_stream) { + err_code = HTTP2_ECODE_CLOSED; + goto conn_term; + } + + STREAM_RECV_PROCESS(ctx, hdr); + + ctx->data_off = FRAME_HEADER_SIZE; + + SET_TO_READ_VERIFY(ctx, HTTP2_RECV_CONT); + return T_OK; + + default: + /* + * Possible extension types of frames are not covered (yet) in + * this procedure. On current stage we just ignore such frames. + */ + T_DBG("HTTP/2: frame of unknown type '%u' received\n", + hdr->type); + ctx->state = HTTP2_IGNORE_FRAME_DATA; + SET_TO_READ(ctx); + return T_OK; + } + +conn_term: + BUG_ON(!err_code); + tfw_h2_conn_terminate(ctx, err_code); + return T_DROP; +} + +/** + * Main FSM for processing HTTP/2 frames. + */ +static int +tfw_h2_frame_recv(void *data, unsigned char *buf, size_t len, + unsigned int *read) +{ + int n, r = T_POSTPONE; + unsigned char *p = buf; + TfwH2Ctx *ctx = data; + T_FSM_INIT(ctx->state, "HTTP/2 Frame Receive"); + + T_FSM_START(ctx->state) { + + T_FSM_STATE(HTTP2_RECV_CLI_START_SEQ) { + FRAME_FSM_READ_LAMBDA(FRAME_PREFACE_CLI_MAGIC_LEN, { + if (memcmp_fast(FRAME_PREFACE_CLI_MAGIC + ctx->rlen, + p, n)) + { + T_DBG("Invalid client magic received," + " connection must be dropped\n"); + FRAME_FSM_EXIT(T_DROP); + } + }); + + if (tfw_h2_send_settings_init(ctx) + || tfw_h2_send_wnd_update(ctx, 0, + MAX_WND_SIZE - DEF_WND_SIZE)) + { + FRAME_FSM_EXIT(T_DROP); + } + + FRAME_FSM_MOVE(HTTP2_RECV_FIRST_SETTINGS); + } + + T_FSM_STATE(HTTP2_RECV_FIRST_SETTINGS) { + FRAME_FSM_READ_SRVC(FRAME_HEADER_SIZE); + + if (tfw_h2_first_settings_verify(ctx)) + FRAME_FSM_EXIT(T_DROP); + + if (ctx->to_read) + FRAME_FSM_MOVE(HTTP2_RECV_FRAME_SETTINGS); + + FRAME_FSM_EXIT(T_OK); + } + + T_FSM_STATE(HTTP2_RECV_FRAME_HEADER) { + FRAME_FSM_READ_SRVC(FRAME_HEADER_SIZE); + + tfw_h2_unpack_frame_header(&ctx->hdr, ctx->rbuf); + + if (tfw_h2_frame_type_process(ctx)) + FRAME_FSM_EXIT(T_DROP); + + if (ctx->to_read) + FRAME_FSM_NEXT(); + + FRAME_FSM_EXIT(T_OK); + } + + T_FSM_STATE(HTTP2_RECV_FRAME_PADDED) { + FRAME_FSM_READ_SRVC(ctx->to_read); + + if (tfw_h2_frame_pad_process(ctx)) + FRAME_FSM_EXIT(T_DROP); + + if (ctx->to_read) + FRAME_FSM_NEXT(); + + FRAME_FSM_EXIT(T_OK); + } + + T_FSM_STATE(HTTP2_RECV_FRAME_PRIORITY) { + FRAME_FSM_READ_SRVC(ctx->to_read); + + if (tfw_h2_priority_process(ctx)) + FRAME_FSM_EXIT(T_DROP); + + FRAME_FSM_EXIT(T_OK); + } + + T_FSM_STATE(HTTP2_RECV_FRAME_WND_UPDATE) { + FRAME_FSM_READ_SRVC(ctx->to_read); + + if (tfw_h2_wnd_update_process(ctx)) + FRAME_FSM_EXIT(T_DROP); + + FRAME_FSM_EXIT(T_OK); + } + + T_FSM_STATE(HTTP2_RECV_FRAME_PING) { + FRAME_FSM_READ_SRVC(ctx->to_read); + + if (!(ctx->hdr.flags & HTTP2_F_ACK) + && tfw_h2_send_ping(ctx)) + { + FRAME_FSM_EXIT(T_DROP); + } + + FRAME_FSM_EXIT(T_OK); + } + + T_FSM_STATE(HTTP2_RECV_FRAME_RST_STREAM) { + FRAME_FSM_READ_SRVC(ctx->to_read); + + tfw_h2_rst_stream_process(ctx); + + FRAME_FSM_EXIT(T_OK); + } + + T_FSM_STATE(HTTP2_RECV_FRAME_SETTINGS) { + FRAME_FSM_READ_SRVC(ctx->to_read); + + if (tfw_h2_settings_process(ctx)) + FRAME_FSM_EXIT(T_DROP); + + if (ctx->to_read) + FRAME_FSM_MOVE(HTTP2_RECV_FRAME_SETTINGS); + + if (tfw_h2_send_settings_ack(ctx)) + FRAME_FSM_EXIT(T_DROP); + + FRAME_FSM_EXIT(T_OK); + } + + T_FSM_STATE(HTTP2_RECV_FRAME_GOAWAY) { + FRAME_FSM_READ_SRVC(ctx->to_read); + + if (tfw_h2_goaway_process(ctx)) + FRAME_FSM_EXIT(T_DROP); + + if (ctx->to_read) + FRAME_FSM_MOVE(HTTP2_IGNORE_FRAME_DATA); + + FRAME_FSM_EXIT(T_OK); + } + + T_FSM_STATE(HTTP2_RECV_HEADER_PRI) { + FRAME_FSM_READ_SRVC(ctx->to_read); + + if (tfw_h2_headers_pri_process(ctx)) + FRAME_FSM_EXIT(T_DROP); + + if (ctx->to_read) + FRAME_FSM_NEXT(); + + FRAME_FSM_EXIT(T_OK); + } + + T_FSM_STATE(HTTP2_RECV_DATA) { + FRAME_FSM_READ(ctx->to_read); + + tfw_h2_check_closed_stream(ctx); + + FRAME_FSM_EXIT(T_OK); + } + + T_FSM_STATE(HTTP2_RECV_HEADER) { + FRAME_FSM_READ(ctx->to_read); + + if (tfw_h2_headers_process(ctx)) + FRAME_FSM_EXIT(T_DROP); + + FRAME_FSM_EXIT(T_OK); + } + + T_FSM_STATE(HTTP2_RECV_CONT) { + FRAME_FSM_READ(ctx->to_read); + + tfw_h2_check_closed_stream(ctx); + + FRAME_FSM_EXIT(T_OK); + } + + T_FSM_STATE(HTTP2_IGNORE_FRAME_DATA) { + FRAME_FSM_READ(ctx->to_read); + FRAME_FSM_EXIT(T_OK); + } + } + + FRAME_FSM_FINISH(); + + return r; +} + +/* + * Re-initialization of HTTP/2 framing context. Due to passing frames to + * upper level in per-skb granularity (not per-frame) and processing of + * padded frames - we need to pass upstairs postponed frames too (only + * app frames: HEADERS, DATA, CONTINUATION); thus, three situations can + * appear during framing context initialization: + * 1. On fully received service (non-app) frames and fully received app + * frames without padding - context must be reset; in this case the + * @ctx->state field will be set to HTTP2_RECV_FRAME_HEADER state (since + * its value is zero), and processing of the next frame will start from + * this state; + * 2. On fully received app frames with padding - context must not be + * reset and should be reinitialized to continue processing until all + * padding will be processed; + * 3. On postponed app frames (with or without padding) - context must + * not be reinitialized at all and should be further processed until + * the frame will be fully received. + */ +static inline void +tfw_h2_context_reinit(TfwH2Ctx *ctx, bool postponed) +{ + if (!APP_FRAME(ctx) || (!postponed && !ctx->padlen)) { + bzero_fast(ctx->__off, + sizeof(*ctx) - offsetof(TfwH2Ctx, __off)); + return; + } + if (!postponed && ctx->padlen) { + ctx->state = HTTP2_IGNORE_FRAME_DATA; + ctx->to_read = ctx->padlen; + ctx->padlen = 0; + } +} + +int +tfw_h2_frame_process(void *c, TfwFsmData *data) +{ + int r; + unsigned int unused, curr_tail; + TfwFsmData data_up = {}; + TfwH2Ctx *h2 = tfw_h2_context(c); + struct sk_buff *nskb = NULL, *skb = data->skb; + unsigned int parsed = 0, off = data->off, tail = data->trail; + + BUG_ON(off >= skb->len); + BUG_ON(tail >= skb->len); + +next_msg: + ss_skb_queue_tail(&h2->skb_head, skb); + r = ss_skb_process(skb, off, tail, tfw_h2_frame_recv, h2, &unused, + &parsed); + + curr_tail = off + parsed + tail < skb->len ? 0 : tail; + if (r >= T_POSTPONE && ss_skb_chop_head_tail(NULL, skb, off, curr_tail)) + { + r = T_DROP; + goto out; + } + + switch (r) { + default: + T_WARN("Unrecognized return code %d during HTTP/2 frame" + " receiving, drop frame\n", r); + case T_DROP: + T_DBG3("Drop invalid HTTP/2 frame\n"); + goto out; + case T_POSTPONE: + /* + * We don't collect all skbs for app frames and pass + * current skb to the upper level as soon as possible + * (after frame header is processed), including the + * postpone case. On the contrary, we accumulate all + * the skbs for the service frames, since for them we + * need not to pass any data upstairs; in this case + * all collected skbs are dropped at once when service + * frame fully received, processed and applied. + */ + if (!APP_FRAME(h2)) + return T_OK; + + break; + case T_OK: + T_DBG3("%s: parsed=%d skb->len=%u\n", __func__, + parsed, skb->len); + } + + /* + * For fully received frames possibly there are other frames + * in the current @skb, so create an skb sibling with next + * frame and process it on the next iteration. This situation + * is excluded for postponed frames, since for them the value + * of @parsed must be always equal to the length of skb currently + * processed. + */ + if (parsed < skb->len) { + nskb = ss_skb_split(skb, parsed); + if (unlikely(!nskb)) { + TFW_INC_STAT_BH(clnt.msgs_otherr); + r = T_DROP; + goto out; + } + } + + /* + * Before transferring the skb with app frame for further processing, + * certain service data should be separated from it (placed at the + * frame's beginning): frame header, optional pad length and optional + * priority data (the latter is for HEADERS frames only). Besides, + * DATA and HEADERS frames can contain some padding in the frame's + * tail, but we don't need to worry about that here since such padding + * is processed as service data, separately from app frame, and it + * will be just split into separate skb (above). + */ + if (APP_FRAME(h2)) { + while (unlikely(h2->skb_head->len <= h2->data_off)) { + struct sk_buff *skb = ss_skb_dequeue(&h2->skb_head); + h2->data_off -= skb->len; + kfree_skb(skb); + /* + * Special case when the frame is postponed just + * in the beginning of the app data, after all + * frame header fields processed. + */ + if (!h2->skb_head) { + WARN_ON_ONCE(h2->data_off); + return T_OK; + } + } + /* + * The skb should be last here, since we do not accumulate + * skbs until full frame will be received. + */ + WARN_ON_ONCE(h2->skb_head != h2->skb_head->next); + data_up.off = h2->data_off; + data_up.skb = h2->skb_head; + h2->data_off = 0; + h2->skb_head = NULL; + r = tfw_http_msg_process_generic(c, &data_up); + if (r == T_DROP) { + kfree_skb(nskb); + goto out; + } + } else { + ss_skb_queue_purge(&h2->skb_head); + } + + tfw_h2_context_reinit(h2, r == T_POSTPONE); + + if (nskb) { + skb = nskb; + nskb = NULL; + off = 0; + parsed = 0; + goto next_msg; + } + +out: + ss_skb_queue_purge(&h2->skb_head); + return r; +} diff --git a/tempesta_fw/http_frame.h b/tempesta_fw/http_frame.h new file mode 100644 index 0000000000..82dd0e0267 --- /dev/null +++ b/tempesta_fw/http_frame.h @@ -0,0 +1,166 @@ +/** + * Tempesta FW + * + * Copyright (C) 2019 Tempesta Technologies, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, + * or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +#ifndef __HTTP_FRAME__ +#define __HTTP_FRAME__ + +#include "gfsm.h" +#include "http_stream.h" + +#define FRAME_HEADER_SIZE 9 + +/** + * HTTP/2 frame types (RFC 7540 section 6). + */ +typedef enum { + HTTP2_DATA = 0, + HTTP2_HEADERS, + HTTP2_PRIORITY, + HTTP2_RST_STREAM, + HTTP2_SETTINGS, + HTTP2_PUSH_PROMISE, + HTTP2_PING, + HTTP2_GOAWAY, + HTTP2_WINDOW_UPDATE, + HTTP2_CONTINUATION +} TfwFrameType; + +/** + * HTTP/2 frame flags. Can be specified in frame's header and + * are specific to the particular frame types (RFC 7540 section + * 4.1 and section 6). + */ +typedef enum { + HTTP2_F_ACK = 0x01, + HTTP2_F_END_STREAM = 0x01, + HTTP2_F_END_HEADERS = 0x04, + HTTP2_F_PADDED = 0x08, + HTTP2_F_PRIORITY = 0x20 +} TfwFrameFlag; + +/** + * Unpacked header data of currently processed frame (RFC 7540 section + * 4.1). Reserved bit is not present here since it has no any semantic + * value for now and should be always ignored. + * + * @length - the frame's payload length; + * @stream_id - id of current stream (which frame is processed); + * @type - the type of frame being processed; + * @flags - frame's flags; + */ +typedef struct { + int length; + unsigned int stream_id; + unsigned char type; + unsigned char flags; +} TfwFrameHdr; + +/** + * Unpacked data from priority payload of frames (RFC 7540 section 6.2 + * and section 6.3). + * + * @stream_id - id for the stream that the current stream depends on; + * @weight - stream's priority weight; + * @exclusive - flag indicating exclusive stream dependency; + */ +typedef struct { + unsigned int stream_id; + unsigned short weight; + unsigned char exclusive; +} TfwFramePri; + +/** + * Representation of SETTINGS parameters for HTTP/2 connection (RFC 7540 + * section 6.5.2). + * + * @hdr_tbl_sz - maximum size of the endpoint's header compression + * table used to decode header blocks; + * @push - enable/disable indicator for server push; + * @max_streams - maximum number of streams that the endpoint will + * allow; + * @wnd_sz - endpoint's initial window size for stream-level + * flow control; + * @max_frame_sz - size of the largest frame payload the endpoint wish + * to receive; + * @max_lhdr_sz - maximum size of header list the endpoint prepared + * to accept; + */ +typedef struct { + unsigned int hdr_tbl_sz; + unsigned int push; + unsigned int max_streams; + unsigned int wnd_sz; + unsigned int max_frame_sz; + unsigned int max_lhdr_sz; +} TfwSettings; + +/** + * Context for HTTP/2 frames processing. + * + * @lsettings - local settings for HTTP/2 connection; + * @rsettings - settings for HTTP/2 connection received from the remote + * endpoint; + * @streams_num - number of the streams initiated by client; + * @sched - streams' priority scheduler; + * @lstream_id - ID of last stream initiated by client and processed on the + * server side; + * @loc_wnd - connection's current flow controlled window; + * @__off - offset to reinitialize processing context; + * @skb_head - collected list of processed skbs containing HTTP/2 frames; + * @cur_stream - found stream for the frame currently being processed; + * @priority - unpacked data from priority part of payload of processed + * HEADERS or PRIORITY frames; + * @hdr - unpacked data from header of currently processed frame; + * @state - current FSM state of HTTP/2 processing context; + * @to_read - indicates how much data of HTTP/2 frame should + * be read on next FSM @state; + * @rlen - length of accumulated data in @rbuf; + * @rbuf - buffer for data accumulation from frames headers and + * payloads (for service frames) during frames processing; + * @padlen - length of current frame's padding (if exists); + * @data_off - offset of app data in HEADERS, CONTINUATION and DATA + * frames (after all service payloads); + */ +typedef struct { + TfwSettings lsettings; + TfwSettings rsettings; + unsigned long streams_num; + TfwStreamSched sched; + unsigned int lstream_id; + unsigned int loc_wnd; + char __off[0]; + struct sk_buff *skb_head; + TfwStream *cur_stream; + TfwFramePri priority; + TfwFrameHdr hdr; + int state; + int to_read; + int rlen; + unsigned char rbuf[FRAME_HEADER_SIZE]; + unsigned char padlen; + unsigned char data_off; +} TfwH2Ctx; + +int tfw_h2_init(void); +void tfw_h2_cleanup(void); +void tfw_h2_context_init(TfwH2Ctx *ctx); +void tfw_h2_context_clear(TfwH2Ctx *ctx); +int tfw_h2_frame_process(void *c, TfwFsmData *data); + +#endif /* __HTTP_FRAME__ */ diff --git a/tempesta_fw/http_stream.c b/tempesta_fw/http_stream.c new file mode 100644 index 0000000000..0641550c25 --- /dev/null +++ b/tempesta_fw/http_stream.c @@ -0,0 +1,427 @@ +/** + * Tempesta FW + * + * Copyright (C) 2019 Tempesta Technologies, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, + * or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include + +#if DBG_HTTP_STREAM == 0 +#undef DEBUG +#endif +#include "http_frame.h" + +#define HTTP2_DEF_WEIGHT 16 + +/** + * States for HTTP/2 streams processing. + * + * NOTE: there is no exact matching between these states and states from + * RFC 7540 (section 5.1), since several intermediate states were added in + * current implementation to handle some edge states which are not mentioned + * explicitly in RFC (e.g. additional continuation states, and special kinds + * of closed state). Besides, there is no explicit 'idle' state here, since + * in current implementation idle stream is just a stream that has not been + * created yet. + * + * TODO: in HTTP2_STREAM_CLOSED state stream is removed from stream's storage, + * but for HTTP2_STREAM_LOC_CLOSED and HTTP2_STREAM_REM_CLOSED states stream + * must be present in memory for some period of time (see RFC 7540, section + * 5.1, the 'closed' paragraph). Since transitions to these states can occur + * only during response sending, thus some additional structure for temporary + * storage of closed streams (e.g. simple limited queue based on linked list - + * on top of existing storage structure) should be implemented in context of + * responses' sending functionality of #309. + */ +typedef enum { + HTTP2_STREAM_LOC_RESERVED, + HTTP2_STREAM_REM_RESERVED, + HTTP2_STREAM_OPENED, + HTTP2_STREAM_CONT, + HTTP2_STREAM_CONT_CLOSED, + HTTP2_STREAM_CONT_HC, + HTTP2_STREAM_CONT_HC_CLOSED, + HTTP2_STREAM_LOC_HALF_CLOSED, + HTTP2_STREAM_REM_HALF_CLOSED, + HTTP2_STREAM_LOC_CLOSED, + HTTP2_STREAM_REM_CLOSED, + HTTP2_STREAM_CLOSED +} TfwStreamState; + +static struct kmem_cache *stream_cache; + +int +tfw_h2_stream_cache_create(void) +{ + stream_cache = kmem_cache_create("tfw_stream_cache", sizeof(TfwStream), + 0, 0, NULL); + if (!stream_cache) + return -ENOMEM; + + return 0; +} + +void +tfw_h2_stream_cache_destroy(void) +{ + kmem_cache_destroy(stream_cache); +} + +/* + * Stream FSM processing during frames receipt (see RFC 7540 section + * 5.1 for details). + */ +TfwStreamFsmRes +tfw_h2_stream_fsm(TfwStream *stream, unsigned char type, unsigned char flags, + TfwH2Err *err) +{ + T_DBG3("enter %s: stream->state=%d, stream->id=%u, type=%hhu," + " flags=0x%hhx\n", __func__, stream->state, stream->id, + type, flags); + + switch (stream->state) { + case HTTP2_STREAM_LOC_RESERVED: + case HTTP2_STREAM_REM_RESERVED: + /* + * TODO: reserved states is not used for now, since client + * cannot push (RFC 7540 section 8.2), and Server Push on + * our side will be implemented in #1194. + */ + BUG(); + + case HTTP2_STREAM_OPENED: + /* + * In 'opened' state receiving of all frame types is allowed + * (in this implementation - except CONTINUATION frames, which + * are processed in special separate states). Receiving HEADERS + * frame with both END_HEADERS and END_STREAM flags (or DATA + * frame with END_STREAM flag) move stream into 'half-closed + * (remote)' state. + */ + if (type == HTTP2_HEADERS) { + if (flags & (HTTP2_F_END_STREAM | HTTP2_F_END_HEADERS)) { + stream->state = HTTP2_STREAM_REM_HALF_CLOSED; + } + /* + * If END_HEADERS flag is not received, move stream into + * the states of waiting CONTINUATION frame. + */ + else if (flags & HTTP2_F_END_STREAM) { + stream->state = HTTP2_STREAM_CONT_CLOSED; + } + else if (!(flags & HTTP2_F_END_HEADERS)) { + stream->state = HTTP2_STREAM_CONT; + } + } + else if (type == HTTP2_DATA && (flags & HTTP2_F_END_STREAM)) { + stream->state = HTTP2_STREAM_REM_HALF_CLOSED; + } + /* + * Received RST_STREAM frame immediately moves stream into the + * final 'closed' state. + */ + else if (type == HTTP2_RST_STREAM) { + stream->state = HTTP2_STREAM_CLOSED; + } + else if (type == HTTP2_CONTINUATION) { + /* + * CONTINUATION frames are allowed only in stream's + * state specially intended for continuation awaiting + * (RFC 7540 section 6.10). + */ + *err = HTTP2_ECODE_PROTO; + return STREAM_FSM_RES_TERM_CONN; + } + break; + + case HTTP2_STREAM_CONT: + /* + * Only CONTINUATION frames are allowed (after HEADERS or + * CONTINUATION frames) until frame with END_HEADERS flag will + * be received (see RFC 7540 section 6.2 for details). + */ + if (type != HTTP2_CONTINUATION) { + *err = HTTP2_ECODE_PROTO; + return STREAM_FSM_RES_TERM_CONN; + } + /* + * Once END_HEADERS flag is received, move stream into standard + * processing state (see RFC 7540 section 6.10 for details). + */ + if (flags & HTTP2_F_END_HEADERS) + stream->state = HTTP2_STREAM_OPENED; + break; + + case HTTP2_STREAM_CONT_CLOSED: + if (type != HTTP2_CONTINUATION) { + *err = HTTP2_ECODE_PROTO; + return STREAM_FSM_RES_TERM_CONN; + } + /* + * If END_HEADERS flag arrived in this state, this means that + * END_STREAM flag had been already received earlier, and we + * must move stream into half-closed (remote) processing state + * (see RFC 7540 section 6.2 for details). + */ + if (flags & HTTP2_F_END_HEADERS) + stream->state = HTTP2_STREAM_REM_HALF_CLOSED; + break; + + case HTTP2_STREAM_REM_CLOSED: + /* + * RST_STREAM and WINDOW_UPDATE frames must be ignored in this + * state. + */ + if (type == HTTP2_WINDOW_UPDATE + || type == HTTP2_RST_STREAM) + { + return STREAM_FSM_RES_IGNORE; + } + /* Fall through. */ + + case HTTP2_STREAM_REM_HALF_CLOSED: + /* + * The only allowed stream-related frames in 'half-closed + * (remote)' state are PRIORITY, RST_STREAM and WINDOW_UPDATE. + * If RST_STREAM frame is received in this state, the stream + * will be removed from stream's storage (i.e. moved into final + * 'closed' state). + */ + if (type == HTTP2_DATA + || type == HTTP2_HEADERS + || type == HTTP2_CONTINUATION) + { + *err = HTTP2_ECODE_CLOSED; + return STREAM_FSM_RES_TERM_STREAM; + } + + if (type == HTTP2_CONTINUATION) { + *err = HTTP2_ECODE_PROTO; + return STREAM_FSM_RES_TERM_CONN; + } + + if (type == HTTP2_RST_STREAM) + stream->state = HTTP2_STREAM_CLOSED; + break; + + case HTTP2_STREAM_CONT_HC: + if (type != HTTP2_CONTINUATION) { + *err = HTTP2_ECODE_PROTO; + return STREAM_FSM_RES_TERM_CONN; + } + /* + * If END_HEADERS flag is received in this state, move stream + * into 'half-closed (local)' state (see RFC 7540 section 5.1 + * and section 6.2 for details). + */ + if (flags & HTTP2_F_END_HEADERS) + stream->state = HTTP2_STREAM_LOC_HALF_CLOSED; + break; + + case HTTP2_STREAM_CONT_HC_CLOSED: + if (type != HTTP2_CONTINUATION) { + *err = HTTP2_ECODE_PROTO; + return STREAM_FSM_RES_TERM_CONN; + } + /* + * If END_HEADERS flag arrived in this state, this means that + * END_STREAM flag had been already received earlier - in + * half-closed (local) state, and now we must close the stream, + * i.e. move it to final 'closed' state. + */ + if (flags & HTTP2_F_END_HEADERS) + stream->state = HTTP2_STREAM_CLOSED; + break; + + case HTTP2_STREAM_LOC_HALF_CLOSED: + /* + * According to section 5.1 of RFC 7540 any frame type can be + * received and will be valid in 'half-closed (local)' state. + */ + if (type == HTTP2_HEADERS) + { + if (flags & (HTTP2_F_END_STREAM | HTTP2_F_END_HEADERS)) { + stream->state = HTTP2_STREAM_CLOSED; + } + else if (flags & HTTP2_F_END_STREAM) { + stream->state = HTTP2_STREAM_CONT_HC_CLOSED; + } + else if (!(flags & HTTP2_F_END_HEADERS)) { + stream->state = HTTP2_STREAM_CONT_HC; + } + } + else if ((type == HTTP2_DATA && (flags & HTTP2_F_END_STREAM)) + || type == HTTP2_RST_STREAM) + { + stream->state = HTTP2_STREAM_CLOSED; + } + else if (type == HTTP2_CONTINUATION) + { + *err = HTTP2_ECODE_PROTO; + return STREAM_FSM_RES_TERM_CONN; + } + break; + + case HTTP2_STREAM_CLOSED: + /* + * In moment when the final 'closed' state is achieved, stream + * actually must be removed from stream's storage (and from + * memory), thus the execution flow must not reach this point. + */ + default: + BUG(); + } + + T_DBG3("exit %s: stream->state=%d\n", __func__, stream->state); + + return STREAM_FSM_RES_OK; +} + +bool +tfw_h2_stream_is_closed(TfwStream *stream) +{ + return stream->state == HTTP2_STREAM_CLOSED; +} + +static inline void +tfw_h2_init_stream(TfwStream *stream, unsigned int id, unsigned short weight, + unsigned int wnd) +{ + RB_CLEAR_NODE(&stream->node); + stream->id = id; + stream->state = HTTP2_STREAM_OPENED; + stream->loc_wnd = wnd; + stream->weight = weight ? weight : HTTP2_DEF_WEIGHT; +} + +TfwStream * +tfw_h2_find_stream(TfwStreamSched *sched, unsigned int id) +{ + struct rb_node *node = sched->streams.rb_node; + + while (node) { + TfwStream *stream = rb_entry(node, TfwStream, node); + + if (id < stream->id) + node = node->rb_left; + else if (id > stream->id) + node = node->rb_right; + else + return stream; + } + + return NULL; +} + +TfwStream * +tfw_h2_add_stream(TfwStreamSched *sched, unsigned int id, unsigned short weight, + unsigned int wnd) +{ + TfwStream *new_stream; + struct rb_node **new = &sched->streams.rb_node; + struct rb_node *parent = NULL; + + while (*new) { + TfwStream *stream = rb_entry(*new, TfwStream, node); + + parent = *new; + if (id < stream->id) { + new = &parent->rb_left; + } else if (id > stream->id) { + new = &parent->rb_right; + } else { + WARN_ON_ONCE(1); + return NULL; + } + } + + new_stream = kmem_cache_alloc(stream_cache, GFP_ATOMIC | __GFP_ZERO); + if (unlikely(!new_stream)) + return NULL; + + tfw_h2_init_stream(new_stream, id, weight, wnd); + + rb_link_node(&new_stream->node, parent, new); + rb_insert_color(&new_stream->node, &sched->streams); + + return new_stream; +} + +static inline void +tfw_h2_remove_stream(TfwStreamSched *sched, TfwStream *stream) +{ + rb_erase(&stream->node, &sched->streams); +} + +void +tfw_h2_streams_cleanup(TfwStreamSched *sched) +{ + TfwStream *cur, *next; + + rbtree_postorder_for_each_entry_safe(cur, next, &sched->streams, node) + kmem_cache_free(stream_cache, cur); +} + +int +tfw_h2_find_stream_dep(TfwStreamSched *sched, unsigned int id, TfwStream **dep) +{ + /* + * TODO: implement dependency/priority logic (according to RFC 7540 + * section 5.3) in context of #1196. + */ + return 0; +} + +void +tfw_h2_add_stream_dep(TfwStreamSched *sched, TfwStream *stream, TfwStream *dep, + bool excl) +{ + /* + * TODO: implement dependency/priority logic (according to RFC 7540 + * section 5.3) in context of #1196. + */ +} + +void +tfw_h2_change_stream_dep(TfwStreamSched *sched, unsigned int stream_id, + unsigned int new_dep, unsigned short new_weight, + bool excl) +{ + /* + * TODO: implement dependency/priority logic (according to RFC 7540 + * section 5.3) in context of #1196. + */ +} + +static void +tfw_h2_remove_stream_dep(TfwStreamSched *sched, TfwStream *stream) +{ + /* + * TODO: implement dependency/priority logic (according to RFC 7540 + * section 5.3) in context of #1196. + */ +} + +void +tfw_h2_stop_stream(TfwStreamSched *sched, TfwStream **stream) +{ + tfw_h2_remove_stream_dep(sched, *stream); + tfw_h2_remove_stream(sched, *stream); + + kmem_cache_free(stream_cache, *stream); + *stream = NULL; +} diff --git a/tempesta_fw/http_stream.h b/tempesta_fw/http_stream.h new file mode 100644 index 0000000000..726d65faea --- /dev/null +++ b/tempesta_fw/http_stream.h @@ -0,0 +1,104 @@ +/** + * Tempesta FW + * + * Copyright (C) 2019 Tempesta Technologies, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, + * or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ +#ifndef __HTTP_STREAM__ +#define __HTTP_STREAM__ + +#include + +/** + * Final statuses of Stream FSM processing. + */ +typedef enum { + STREAM_FSM_RES_OK, + STREAM_FSM_RES_TERM_CONN, + STREAM_FSM_RES_TERM_STREAM, + STREAM_FSM_RES_IGNORE +} TfwStreamFsmRes; + +/** + * HTTP/2 error codes (RFC 7540 section 7). Used in RST_STREAM + * and GOAWAY frames to report the reasons of the stream or + * connection error. + */ +typedef enum { + HTTP2_ECODE_NO_ERROR = 0, + HTTP2_ECODE_PROTO, + HTTP2_ECODE_INTERNAL, + HTTP2_ECODE_FLOW, + HTTP2_ECODE_SETTINGS_TIMEOUT, + HTTP2_ECODE_CLOSED, + HTTP2_ECODE_SIZE, + HTTP2_ECODE_REFUSED, + HTTP2_ECODE_CANCEL, + HTTP2_ECODE_COMPRESSION, + HTTP2_ECODE_CONNECT, + HTTP2_ECODE_ENHANCE_YOUR_CALM, + HTTP2_ECODE_INADEQUATE_SECURITY, + HTTP2_ECODE_HTTP_1_1_REQUIRED +} TfwH2Err; + +/** + * Representation of HTTP/2 stream entity. + * + * @node - entry in per-connection storage of streams (red-black tree); + * @id - stream ID; + * @state - stream's current state; + * @loc_wnd - stream's current flow controlled window; + * @weight - stream's priority weight; + */ +typedef struct { + struct rb_node node; + unsigned int id; + int state; + unsigned int loc_wnd; + unsigned short weight; +} TfwStream; + +/** + * Scheduler for stream's processing distribution based on dependency/priority + * values. + * TODO: the structure is not completed yet and should be finished in context + * of #1196. + * + * @streams - root red-black tree entry for per-connection streams' storage; + */ +typedef struct { + struct rb_root streams; +} TfwStreamSched; + +int tfw_h2_stream_cache_create(void); +void tfw_h2_stream_cache_destroy(void); +TfwStreamFsmRes tfw_h2_stream_fsm(TfwStream *stream, unsigned char type, + unsigned char flags, TfwH2Err *err); +bool tfw_h2_stream_is_closed(TfwStream *stream); +TfwStream *tfw_h2_find_stream(TfwStreamSched *sched, unsigned int id); +TfwStream *tfw_h2_add_stream(TfwStreamSched *sched, unsigned int id, + unsigned short weight, unsigned int wnd); +void tfw_h2_streams_cleanup(TfwStreamSched *sched); +int tfw_h2_find_stream_dep(TfwStreamSched *sched, unsigned int id, + TfwStream **dep); +void tfw_h2_add_stream_dep(TfwStreamSched *sched, TfwStream *stream, + TfwStream *dep, bool excl); +void tfw_h2_change_stream_dep(TfwStreamSched *sched, unsigned int stream_id, + unsigned int new_dep, unsigned short new_weight, + bool excl); +void tfw_h2_stop_stream(TfwStreamSched *sched, TfwStream **stream); + +#endif /* __HTTP_STREAM__ */ diff --git a/tempesta_fw/sock_clnt.c b/tempesta_fw/sock_clnt.c index 47658d32dd..eada228d4b 100644 --- a/tempesta_fw/sock_clnt.c +++ b/tempesta_fw/sock_clnt.c @@ -38,14 +38,18 @@ */ static struct kmem_cache *tfw_cli_conn_cache; -static struct kmem_cache *tfw_tls_conn_cache; +static struct kmem_cache *tfw_h2_conn_cache; static int tfw_cli_cfg_ka_timeout = -1; static inline struct kmem_cache * tfw_cli_cache(int type) { + /* + * Currently any secure (TLS) connection is considered as HTTP/2 + * connection, since we don't have any business with plain TLS. + */ return type & TFW_FSM_HTTPS ? - tfw_tls_conn_cache : tfw_cli_conn_cache; + tfw_h2_conn_cache : tfw_cli_conn_cache; } static void @@ -193,7 +197,7 @@ tfw_sock_clnt_new(struct sock *sk) tfw_connection_link_peer(conn, (TfwPeer *)cli); ss_set_callbacks(sk); - if (TFW_CONN_TYPE(conn) & TFW_FSM_HTTPS) + if (TFW_CONN_TLS(conn)) /* * Probably, that's not beautiful to introduce an alternate * upcall beside GFSM and SS, but that's efficient and I didn't @@ -463,8 +467,7 @@ tfw_cfgop_listen(TfwCfgSpec *cs, TfwCfgEntry *ce) TfwAddr addr; const char *in_str = NULL; - r = tfw_cfg_check_val_n(ce, 1); - if (r) + if (tfw_cfg_check_val_n(ce, 1) || ce->attr_n > 1) goto parse_err; /* @@ -502,17 +505,15 @@ tfw_cfgop_listen(TfwCfgSpec *cs, TfwCfgEntry *ce) if (!strcasecmp(in_str, "http")) { return tfw_listen_sock_add(&addr, TFW_FSM_HTTP); } - else if (!strcasecmp(in_str, "https")) { + + if (!tfw_tls_cfg_alpn_protos(in_str)) { tfw_tls_cfg_require(); return tfw_listen_sock_add(&addr, TFW_FSM_HTTPS); } - else { - goto parse_err; - } parse_err: TFW_ERR_NL("Unable to parse 'listen' value: '%s'\n", - in_str ? in_str : "No value specified"); + in_str ? in_str : "Invalid directive format"); return -EINVAL; } @@ -544,6 +545,7 @@ static void tfw_cfgop_cleanup_sock_clnt(TfwCfgSpec *cs) { tfw_listen_sock_del_all(); + tfw_tls_free_alpn_protos(); } static int @@ -670,22 +672,22 @@ tfw_sock_clnt_init(void) TFW_FSM_HTTP | TFW_FSM_HTTPS)); BUG_ON(tfw_cli_conn_cache); - BUG_ON(tfw_tls_conn_cache); + BUG_ON(tfw_h2_conn_cache); tfw_cli_conn_cache = kmem_cache_create("tfw_cli_conn_cache", sizeof(TfwCliConn), 0, 0, NULL); - tfw_tls_conn_cache = kmem_cache_create("tfw_tls_conn_cache", - sizeof(TfwTlsConn), 0, 0, NULL); + tfw_h2_conn_cache = kmem_cache_create("tfw_h2_conn_cache", + sizeof(TfwH2Conn), 0, 0, NULL); - if (tfw_cli_conn_cache && tfw_tls_conn_cache) { + if (tfw_cli_conn_cache && tfw_h2_conn_cache) { tfw_mod_register(&tfw_sock_clnt_mod); return 0; } if (tfw_cli_conn_cache) kmem_cache_destroy(tfw_cli_conn_cache); - if (tfw_tls_conn_cache) - kmem_cache_destroy(tfw_tls_conn_cache); + if (tfw_h2_conn_cache) + kmem_cache_destroy(tfw_h2_conn_cache); return -ENOMEM; } @@ -694,6 +696,6 @@ void tfw_sock_clnt_exit(void) { tfw_mod_unregister(&tfw_sock_clnt_mod); - kmem_cache_destroy(tfw_tls_conn_cache); + kmem_cache_destroy(tfw_h2_conn_cache); kmem_cache_destroy(tfw_cli_conn_cache); } diff --git a/tempesta_fw/t/unit/test_http_sticky.c b/tempesta_fw/t/unit/test_http_sticky.c index ee9891163c..e124b93c59 100644 --- a/tempesta_fw/t/unit/test_http_sticky.c +++ b/tempesta_fw/t/unit/test_http_sticky.c @@ -48,6 +48,8 @@ #include "sock_srv.c" #include "client.c" #include "http_limits.c" +#include "http_stream.c" +#include "http_frame.c" #include "tls.c" /* rename original tfw_cli_conn_send(), a custom version will be used here */ diff --git a/tempesta_fw/tls.c b/tempesta_fw/tls.c index 4876781c5e..95c6137c04 100644 --- a/tempesta_fw/tls.c +++ b/tempesta_fw/tls.c @@ -24,6 +24,7 @@ #include "client.h" #include "msg.h" #include "procfs.h" +#include "http_frame.h" #include "tls.h" static struct { @@ -498,6 +499,7 @@ tfw_tls_conn_dtor(void *c) { struct sk_buff *skb; TlsCtx *tls = tfw_tls_context(c); + TfwH2Ctx *h2 = tfw_h2_context(c); if (tls) { while ((skb = ss_skb_dequeue(&tls->io_in.skb_list))) @@ -506,6 +508,7 @@ tfw_tls_conn_dtor(void *c) kfree_skb(skb); } + tfw_h2_context_clear(h2); ttls_ctx_clear(tls); tfw_cli_conn_release((TfwCliConn *)c); } @@ -515,6 +518,7 @@ tfw_tls_conn_init(TfwConn *c) { int r; TlsCtx *tls = tfw_tls_context(c); + TfwH2Ctx *h2 = tfw_h2_context(c); if ((r = ttls_ctx_init(tls, &tfw_tls.cfg))) { TFW_ERR("TLS (%pK) setup failed (%x)\n", tls, -r); @@ -524,6 +528,8 @@ tfw_tls_conn_init(TfwConn *c) if (tfw_conn_hook_call(TFW_FSM_HTTP, c, conn_init)) return -EINVAL; + tfw_h2_context_init(h2); + tfw_gfsm_state_init(&c->state, c, TFW_TLS_FSM_INIT); c->destructor = tfw_tls_conn_dtor; @@ -666,6 +672,92 @@ tfw_tls_cfg_require(void) tfw_tls_cgf |= TFW_TLS_CFG_F_REQUIRED; } +int +tfw_tls_cfg_alpn_protos(const char *cfg_str) +{ + ttls_alpn_proto *protos; + size_t len = strlen(cfg_str); + +#define CONF_HTTPS "https" +#define CONF_HTTP1 TTLS_ALPN_HTTP1 +#define CONF_HTTP2 TTLS_ALPN_HTTP2 +#define CONF_HTTPS_LEN (sizeof(CONF_HTTPS) - 1) +#define CONF_HTTP1_LEN (sizeof(CONF_HTTP1) - 1) +#define CONF_HTTP2_LEN (sizeof(CONF_HTTP2) - 1) +#define PROTO_INIT(order, proto) \ +do { \ + protos[order].name = TTLS_ALPN_##proto; \ + protos[order].len = sizeof(TTLS_ALPN_##proto) - 1; \ + protos[order].id = TTLS_ALPN_ID_##proto; \ +} while (0) + + if (strncasecmp(cfg_str, CONF_HTTPS, CONF_HTTPS_LEN)) + return -EINVAL; + + protos = kzalloc(TTLS_ALPN_PROTOS * sizeof(ttls_alpn_proto), GFP_ATOMIC); + if (unlikely(!protos)) + return -ENOMEM; + + /* Currently HTTP/1.1 protocol is default option. */ + PROTO_INIT(0, HTTP1); + tfw_tls.cfg.alpn_list = protos; + + if (len == CONF_HTTPS_LEN) + return 0; + + cfg_str += CONF_HTTPS_LEN; + if (cfg_str[0] != ':') + goto err; + + ++cfg_str; + len -= CONF_HTTPS_LEN + 1; + if (len >= CONF_HTTP2_LEN + && !strncasecmp(cfg_str, CONF_HTTP2, CONF_HTTP2_LEN)) + { + cfg_str += CONF_HTTP2_LEN; + len -= CONF_HTTP2_LEN; + if (!len) + return 0; + if (cfg_str[0] == ',' && !strcasecmp(++cfg_str, CONF_HTTP1)) { + PROTO_INIT(1, HTTP1); + return 0; + } + } + else if (len >= CONF_HTTP1_LEN + && !strncasecmp(cfg_str, CONF_HTTP1, CONF_HTTP1_LEN)) + { + PROTO_INIT(0, HTTP1); + cfg_str += CONF_HTTP1_LEN; + len -= CONF_HTTP1_LEN; + if (!len) + return 0; + if (cfg_str[0] == ',' && !strcasecmp(++cfg_str, CONF_HTTP2)) { + PROTO_INIT(1, HTTP2); + return 0; + } + } + +err: + tfw_tls.cfg.alpn_list = NULL; + kfree(protos); + + return -EINVAL; +#undef CONF_HTTPS +#undef CONF_HTTP1 +#undef CONF_HTTP2 +#undef CONF_LEN +#undef PROTO_INIT +} + +void +tfw_tls_free_alpn_protos(void) +{ + if (tfw_tls.cfg.alpn_list) { + kfree(tfw_tls.cfg.alpn_list); + tfw_tls.cfg.alpn_list = NULL; + } +} + static int tfw_tls_start(void) { @@ -854,16 +946,23 @@ tfw_tls_init(void) ttls_register_bio(tfw_tls_send); - r = tfw_gfsm_register_fsm(TFW_FSM_TLS, tfw_tls_msg_process); - if (r) { - tfw_tls_do_cleanup(); - return -EINVAL; - } + if ((r = tfw_h2_init())) + goto err_h2; + + if ((r = tfw_gfsm_register_fsm(TFW_FSM_TLS, tfw_tls_msg_process))) + goto err_fsm; tfw_connection_hooks_register(&tls_conn_hooks, TFW_FSM_TLS); tfw_mod_register(&tfw_tls_mod); return 0; + +err_fsm: + tfw_h2_cleanup(); +err_h2: + tfw_tls_do_cleanup(); + + return r; } void @@ -872,5 +971,6 @@ tfw_tls_exit(void) tfw_mod_unregister(&tfw_tls_mod); tfw_connection_hooks_unregister(TFW_FSM_TLS); tfw_gfsm_unregister_fsm(TFW_FSM_TLS); + tfw_h2_cleanup(); tfw_tls_do_cleanup(); } diff --git a/tempesta_fw/tls.h b/tempesta_fw/tls.h index efbcf62b04..243b522bfe 100644 --- a/tempesta_fw/tls.h +++ b/tempesta_fw/tls.h @@ -38,7 +38,12 @@ enum { TFW_TLS_FSM_DONE = TFW_GFSM_TLS_STATE(TFW_GFSM_STATE_LAST) }; +#define TFW_CONN_H2(c) \ + (tfw_tls_context(c)->alpn_chosen->id == TTLS_ALPN_ID_HTTP2) + void tfw_tls_cfg_require(void); +int tfw_tls_cfg_alpn_protos(const char *cfg_str); +void tfw_tls_free_alpn_protos(void); int tfw_tls_encrypt(struct sock *sk, struct sk_buff *skb, unsigned int limit); #endif /* __TFW_TLS_H__ */ diff --git a/tls/tls_cli.c b/tls/tls_cli.c index ccfc919829..61f6872df8 100644 --- a/tls/tls_cli.c +++ b/tls/tls_cli.c @@ -364,25 +364,24 @@ static void ssl_write_session_ticket_ext(ttls_context *ssl, static void ssl_write_alpn_ext(ttls_context *ssl, unsigned char *buf, size_t *olen) { + int i; unsigned char *p = buf; const unsigned char *end = ssl->out_msg + TLS_MAX_PAYLOAD_SIZE; size_t alpnlen = 0; - const char **cur; + const ttls_alpn_proto *cur; *olen = 0; - if (ssl->conf->alpn_list == NULL) - { - return; - } + BUG_ON(!ssl->conf->alpn_list); T_DBG3("client hello, adding alpn extension\n"); - for (cur = ssl->conf->alpn_list; *cur != NULL; cur++) - alpnlen += (unsigned char)(strlen(*cur) & 0xFF) + 1; + for (i = 0; i < TTLS_ALPN_PROTOS; ++i) { + cur = &ssl->conf->alpn_list[i]; + alpnlen += (unsigned char)(cur->len & 0xFF) + 1; + } - if (end < p || (size_t)(end - p) < 6 + alpnlen) - { + if (end < p || (size_t)(end - p) < 6 + alpnlen) { T_DBG("buffer too small\n"); return; } @@ -401,10 +400,10 @@ static void ssl_write_alpn_ext(ttls_context *ssl, /* Skip writing extension and list length for now */ p += 4; - for (cur = ssl->conf->alpn_list; *cur != NULL; cur++) - { - *p = (unsigned char)(strlen(*cur) & 0xFF); - memcpy(p + 1, *cur, *p); + for (i = 0; i < TTLS_ALPN_PROTOS; ++i) { + cur = &ssl->conf->alpn_list[i]; + *p = (unsigned char)(cur->len & 0xFF); + memcpy(p + 1, cur->name, *p); p += 1 + *p; } @@ -757,8 +756,9 @@ static int ssl_parse_supported_point_formats_ext(ttls_context *ssl, static int ssl_parse_alpn_ext(ttls_context *ssl, const unsigned char *buf, size_t len) { + int i; size_t list_len, name_len; - const char **p; + const ttls_alpn_proto *p; /* If we didn't send it, the server shouldn't send it */ if (ssl->conf->alpn_list == NULL) @@ -804,12 +804,10 @@ static int ssl_parse_alpn_ext(ttls_context *ssl, } /* Check that the server chosen protocol was in our list and save it */ - for (p = ssl->conf->alpn_list; *p != NULL; p++) - { - if (name_len == strlen(*p) && - memcmp(buf + 3, *p, name_len) == 0) - { - ssl->alpn_chosen = *p; + for (i = 0; i < TTLS_ALPN_PROTOS; ++i) { + p = &ssl->conf->alpn_list[i]; + if (ttls_alpn_ext_eq(p, buf + 3, name_len)) { + ssl->alpn_chosen = p; return 0; } } diff --git a/tls/tls_srv.c b/tls/tls_srv.c index 369e9a7466..7c9174b2ec 100644 --- a/tls/tls_srv.c +++ b/tls/tls_srv.c @@ -308,14 +308,13 @@ ttls_parse_session_ticket_ext(TlsCtx *tls, unsigned char *buf, size_t len) static int ttls_parse_alpn_ext(TlsCtx *tls, const unsigned char *buf, size_t len) { - size_t list_len, cur_len, ours_len; + int i; + size_t list_len, cur_len; const unsigned char *theirs, *start, *end; - const char **ours; + const ttls_alpn_proto *our, *alpn_list = tls->conf->alpn_list; - /* If ALPN not configured, just ignore the extension */ - /* TODO #309 #834 confugure on Tempesta side: "h2, http/1.1" */ - if (!tls->conf->alpn_list) - return 0; + /* If TLS processing is enabled, ALPN must be configured. */ + BUG_ON(!alpn_list); /* * opaque ProtocolName<1..2^8-1>; @@ -361,16 +360,13 @@ ttls_parse_alpn_ext(TlsCtx *tls, const unsigned char *buf, size_t len) } /* Use our order of preference. */ - for (ours = tls->conf->alpn_list; *ours; ours++) { - /* TODO store the string length to avoid strlen(). */ - ours_len = strlen(*ours); - WARN_ON_ONCE(ours_len > 32); + for (i = 0; i < TTLS_ALPN_PROTOS && alpn_list[i].name; ++i) { + our = &alpn_list[i]; + WARN_ON_ONCE(our->len > 32); for (theirs = start; theirs != end; theirs += cur_len) { cur_len = *theirs++; - if (cur_len == ours_len - && !memcmp_fast(theirs, *ours, cur_len)) - { - tls->alpn_chosen = *ours; + if (ttls_alpn_ext_eq(our, theirs, cur_len)) { + tls->alpn_chosen = our; return 0; } } @@ -1121,7 +1117,7 @@ ttls_write_alpn_ext(TlsCtx *tls, unsigned char *p, size_t *olen) */ *(unsigned short *)p = htons(TTLS_TLS_EXT_ALPN); - *olen = 7 + strlen(tls->alpn_chosen); + *olen = 7 + tls->alpn_chosen->len; p[2] = (unsigned char)(((*olen - 4) >> 8) & 0xFF); p[3] = (unsigned char)((*olen - 4) & 0xFF); @@ -1129,7 +1125,7 @@ ttls_write_alpn_ext(TlsCtx *tls, unsigned char *p, size_t *olen) p[5] = (unsigned char)((*olen - 6) & 0xFF); p[6] = (unsigned char)((*olen - 7) & 0xFF); - memcpy_fast(p + 7, tls->alpn_chosen, *olen - 7); + memcpy_fast(p + 7, tls->alpn_chosen->name, *olen - 7); } static int diff --git a/tls/ttls.c b/tls/ttls.c index 0c0f494fdb..902ab00de6 100644 --- a/tls/ttls.c +++ b/tls/ttls.c @@ -2079,33 +2079,10 @@ ttls_conf_sni(ttls_config *conf, conf->p_sni = p_sni; } -int -ttls_conf_alpn_protocols(ttls_config *conf, const char **protos) -{ - size_t cur_len, tot_len = 0; - const char **p; - - /* - * RFC 7301 3.1: "Empty strings MUST NOT be included and byte strings - * MUST NOT be truncated." - * We check lengths now rather than later. - */ - for (p = protos; *p; p++) { - cur_len = strlen(*p); - tot_len += cur_len; - - if (!cur_len || cur_len > 255 || tot_len > 65535) - return TTLS_ERR_BAD_INPUT_DATA; - } - conf->alpn_list = protos; - - return 0; -} - const char * ttls_get_alpn_protocol(const TlsCtx *tls) { - return tls->alpn_chosen; + return tls->alpn_chosen->name; } void @@ -2797,6 +2774,20 @@ ttls_get_key_exchange_md_tls1_2(TlsCtx *tls, unsigned char *output, return r; } +bool +ttls_alpn_ext_eq(const ttls_alpn_proto *proto, const unsigned char *buf, + size_t len) +{ + if (proto->len != len) + return false; + if (len == 8) + return *(unsigned long *)proto->name == *(unsigned long *)buf; + if (len == 2) + return *(unsigned short *)proto->name == *(unsigned short *)buf; + + return !memcmp_fast(proto->name, buf, len); +} + #if defined(DEBUG) && (DEBUG >= 3) unsigned long ttls_time_debug(void) diff --git a/tls/ttls.h b/tls/ttls.h index 28a5d9352f..14ce9e97ae 100644 --- a/tls/ttls.h +++ b/tls/ttls.h @@ -243,6 +243,25 @@ #define TTLS_TLS_EXT_SESSION_TICKET 35 #define TTLS_TLS_EXT_RENEGOTIATION_INFO 0xFF01 +/* + * Supported protocols for APLN extension. Currently only two + * protocols for ALPN are supported: HTTP/1.1 and HTTP/2. + * NOTE: according RFC 7301 3.1 the length of each protocol's name + * must be not greater than 255 and the total length of all names + * in the list must not exceed 65535. + */ +#define TTLS_ALPN_HTTP1 "http/1.1" +#define TTLS_ALPN_HTTP2 "h2" + +/* Number of protocols for negotiation in APLN extension. */ +#define TTLS_ALPN_PROTOS 2 + +/* The id numbers of supported protocols for APLN extension. */ +typedef enum { + TTLS_ALPN_ID_HTTP1, + TTLS_ALPN_ID_HTTP2 +} ttls_alpn_proto_id; + /* Dummy type used only for its size */ union ttls_premaster_secret { @@ -255,6 +274,7 @@ union ttls_premaster_secret #define TTLS_HS_RBUF_SZ TTLS_PREMASTER_SIZE /* Defined below */ +typedef struct ttls_alpn_proto ttls_alpn_proto; typedef struct ttls_context ttls_context; typedef struct ttls_config ttls_config; @@ -264,6 +284,19 @@ typedef struct ttls_handshake_params ttls_handshake_params; typedef struct ttls_sig_hash_set_t ttls_sig_hash_set_t; typedef struct ttls_key_cert ttls_key_cert; +/* + * ALPN protocol descriptor. + * + * @name - protocol name; + * @len - length of @name string; + * @id - protocol's internal number; + */ +struct ttls_alpn_proto { + const char *name; + unsigned int len; + int id; +}; + /* * This structure is used for storing current session data. * @@ -373,7 +406,7 @@ struct ttls_config ttls_mpi dhm_G; /*!< generator for DHM */ #endif - const char **alpn_list; /*!< ordered list of protocols */ + const ttls_alpn_proto *alpn_list; /*!< ordered list of protocols */ /* * Numerical settings (int then char) @@ -476,7 +509,7 @@ typedef struct ttls_context { spinlock_t lock; const ttls_config *conf; TlsHandshake *hs; - const char *alpn_chosen; + const ttls_alpn_proto *alpn_chosen; unsigned int state; unsigned char major; @@ -883,20 +916,6 @@ void ttls_conf_sni(ttls_config *conf, size_t), void *p_sni); -/** - * \brief Set the supported Application Layer Protocols. - * - * \param conf SSL configuration - * \param protos Pointer to a NULL-terminated list of supported protocols, - * in decreasing preference order. The pointer to the list is - * recorded by the library for later reference as required, so - * the lifetime of the table must be atleast as long as the - * lifetime of the SSL configuration structure. - * - * \return 0 on success, or TTLS_ERR_BAD_INPUT_DATA. - */ -int ttls_conf_alpn_protocols(ttls_config *conf, const char **protos); - /** * \brief Get the name of the negotiated Application Layer Protocol. * This function should be called after the handshake is @@ -961,6 +980,9 @@ void ttls_strerror(int errnum, char *buffer, size_t buflen); void ttls_aad2hdriv(TlsXfrm *xfrm, unsigned char *buf); +bool ttls_alpn_ext_eq(const ttls_alpn_proto *proto, const unsigned char *buf, + size_t len); + static inline unsigned char ttls_xfrm_taglen(const TlsXfrm *xfrm) {