From 3d77573c9b088b61d6e0c6410db0a964b8230b13 Mon Sep 17 00:00:00 2001 From: Alexander Ostapenko Date: Fri, 29 Mar 2019 03:52:23 +0700 Subject: [PATCH 01/17] HTTP/2 Framing layer implementation (#309). --- tempesta_fw/connection.h | 2 + tempesta_fw/http.c | 9 +- tempesta_fw/http.h | 1 + tempesta_fw/http_frame.c | 951 +++++++++++++++++++++++++++++++++++++++ tempesta_fw/http_frame.h | 116 +++++ tempesta_fw/http_types.h | 1 + tempesta_fw/tls.c | 37 ++ 7 files changed, 1114 insertions(+), 3 deletions(-) create mode 100644 tempesta_fw/http_frame.c create mode 100644 tempesta_fw/http_frame.h diff --git a/tempesta_fw/connection.h b/tempesta_fw/connection.h index 1b58f8652..3878571a6 100644 --- a/tempesta_fw/connection.h +++ b/tempesta_fw/connection.h @@ -209,9 +209,11 @@ enum { typedef struct { TfwCliConn cli_conn; TlsCtx tls; + TfwHttp2Ctx *h2; } TfwTlsConn; #define tfw_tls_context(conn) (TlsCtx *)(&((TfwTlsConn *)conn)->tls) +#define tfw_http2_context(conn) ((TfwHttp2Ctx *)((TfwTlsConn *)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 e56d1123d..d476b02c5 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_http2_context(conn) + ? tfw_http2_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 ff3cbc70b..74642606d 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 000000000..9d31cb791 --- /dev/null +++ b/tempesta_fw/http_frame.c @@ -0,0 +1,951 @@ +/** + * Tempesta FW + * + * Copyright (C) 2014 NatSys Lab. (info@natsys-lab.com). + * Copyright (C) 2015-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 "lib/fsm.h" +#include "lib/str.h" +#include "procfs.h" +#include "http.h" +#include "http_frame.h" + + +#define FRAME_CLI_MAGIC "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n" +#define FRAME_CLI_MAGIC_LEN 24 +#define FRAME_SRVC1_SIZE 4 +#define FRAME_PRIORITY_SIZE 5 +#define FRAME_STNGS_ENTRY_SIZE 6 +#define FRAME_SRVC2_SIZE 8 + +#define FRAME_STREAM_ID_MASK ((1U << 31) - 1) + +/* 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 error codes (RFC 7540 section 7). Used in RST_STREAM + * and GOAWAY frames to report the reasons of the stream or + * connection error. + */ +#define FRAME_ECODE_PROTO 0x1 +#define FRAME_ECODE_SIZE_ERROR 0x6 + +/* + * 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; + +#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) \ + 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 APP_FRAME(ctx) \ + ((ctx)->state >= __HTTP2_RECV_FRAME_APP) + +#define PAYLOAD(ctx) \ + ((ctx)->state != HTTP2_RECV_FRAME_HEADER) + +static inline void +tfw_http2_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; +} + +static inline void +tfw_http2_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 not occupy not more than 31 bit and reserved + * bit must be 0. + */ + WARN_ON_ONCE((unsigned int)(hdr->stream_id & ~FRAME_STREAM_ID_MASK)); + + *(unsigned int *)p = htonl(hdr->stream_id); +} + +static inline void +tfw_http2_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]; +} + +/** + * 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_http2_send_frame(TfwHttp2Ctx *ctx, TfwFrameHdr *hdr, TfwStr *data) +{ + int r; + TfwMsgIter it; + TfwMsg msg = {}; + unsigned char buf[FRAME_HEADER_SIZE]; + TfwStr *hdr_str = TFW_STR_CHUNK(data, 0); + + 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_http2_pack_frame_header(buf, hdr); + + TFW_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; + + return tfw_connection_send(ctx->conn, &msg);; + +err: + ss_skb_queue_purge(&msg.skb_head); + return r; +} + +static int +tfw_http2_send_ping(TfwHttp2Ctx *ctx) +{ + TfwStr data = { + .chunks = (TfwStr []){ + {}, + { .data = ctx->rbuf, .len = ctx->rlen } + }, + .len = ctx->rlen, + .nchunks = 2 + }; + TfwFrameHdr hdr = { + .length = FRAME_SRVC2_SIZE, + .stream_id = 0, + .type = HTTP2_PING, + .flags = HTTP2_F_ACK + }; + + return tfw_http2_send_frame(ctx, &hdr, &data); + +} + +static int +tfw_http2_send_settings_ack(TfwHttp2Ctx *ctx) +{ + TfwStr data = {}; + TfwFrameHdr hdr = { + .length = 0, + .stream_id = 0, + .type = HTTP2_SETTINGS, + .flags = HTTP2_F_ACK + }; + + return tfw_http2_send_frame(ctx, &hdr, &data); +} + +static int +tfw_http2_conn_terminate(TfwHttp2Ctx *ctx, int err_code) +{ + ctx->state = HTTP2_IGNORE_FRAME_DATA; + /* + * TODO: send appropriately filled GOAWAY frame, set + * Conn_Stop flag for connection, and close it (possibly + * via SS_F_CONN_CLOSE flag). + */ + return 0; +} + +#define VERIFY_FRAME_SIZE(ctx) \ +do { \ + if ((ctx)->hdr.length < 0) \ + return tfw_http2_conn_terminate(ctx, \ + FRAME_ECODE_SIZE_ERROR); \ +} while (0) + +static int +tfw_htt2_stream_verify(TfwHttp2Ctx *ctx) +{ + int stream = ctx->hdr.stream_id; + int err_code = 0; + + /* + * TODO: check of Stream existence and stream's current state; + * this function (and similar verification procedures) may have + * three results: + * 1. Continue normal frame receiving (if stream exists and is + * in appropriate state); + * 2. Set 'ctx->state = HTTP2_IGNORE_FRAME_DATA' to skip current + * frame's payload (if stream is in not appropriate state but + * we can continue operate with current connection); + * 3. Set 'ctx->state = HTTP2_IGNORE_ALL' to skip current and all + * incoming frames in future until connection will be closed + * (if we cannot operate with current connection and it must be + * closed); in this case @tfw_http2_connection_terminate() must + * be called (below). + */ + + if (stream == -1) + return tfw_http2_conn_terminate(ctx, err_code); + + return 0; +} + +static inline int +tfw_http2_recv_priority(TfwHttp2Ctx *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 int +tfw_http2_headers_pri_process(TfwHttp2Ctx *ctx) +{ + BUG_ON(!(ctx->hdr.flags & HTTP2_F_PRIORITY)); + + tfw_http2_unpack_priority(&ctx->priority, ctx->rbuf); + + if (tfw_htt2_stream_verify(ctx)) + return T_BAD; + if (ctx->state != HTTP2_IGNORE_FRAME_DATA) { + ctx->data_off += FRAME_PRIORITY_SIZE; + ctx->state = HTTP2_RECV_HEADER; + } + + SET_TO_READ(ctx); + return T_OK; +} + +static int +tfw_http2_headers_check(TfwHttp2Ctx *ctx) +{ + /* + * TODO: check END_HEADERS and other flags, stream and + * request/response verification etc. + */ + return T_OK; +} + +static int +tfw_http2_cont_check(TfwHttp2Ctx *ctx) +{ + /* + * TODO: check END_HEADERS flag. + */ + return T_OK; +} + +static int +tfw_http2_wnd_update_process(TfwHttp2Ctx *ctx) +{ + /* + * TODO: apply new window size for entire connection or + * particular stream; ignore until #498. + */ + return T_OK; +} + +static int +tfw_http2_rst_stream_process(TfwHttp2Ctx *ctx) +{ + unsigned int err_code = ntohl(*(unsigned int *)ctx->rbuf); + /* + * TODO: check @hdr->stream_id, check stream existence, + * and close the specified stream. + */ + if (err_code) + return T_BAD; + + return T_OK; +} +static int +tfw_http2_apply_settings_entry(int id, unsigned int val) +{ + /* + * TODO: apply settings entry. + */ + return T_OK; +} + +static int +tfw_http2_settings_process(TfwHttp2Ctx *ctx) +{ + TfwFrameHdr *hdr = &ctx->hdr; + int id = ntohl(*(int *)&ctx->rbuf[0]); + unsigned int val = ntohl(*(unsigned int *)&ctx->rbuf[2]); + + if (!tfw_http2_apply_settings_entry(id, val)) + return T_BAD; + + ctx->to_read = hdr->length ? FRAME_STNGS_ENTRY_SIZE : 0; + hdr->length -= ctx->to_read; + + return T_OK; +} + +static int +tfw_http2_goaway_process(TfwHttp2Ctx *ctx) +{ + unsigned int err_code = ntohl(*(unsigned int *)&ctx->rbuf[4]); + + ctx->lstream_id = ntohl(*(unsigned int *)ctx->rbuf) & FRAME_STREAM_ID_MASK; + SET_TO_READ(ctx); + /* + * TODO: close streams with @id greater than @ctx->lstream_id + */ + if (err_code) + return T_BAD; + + return T_OK; +} + +static inline int +tfw_http2_first_settings_verify(TfwHttp2Ctx *ctx) +{ + int err_code = 0; + TfwFrameHdr *hdr = &ctx->hdr; + + BUG_ON(ctx->to_read); + + if (ctx->rbuf[3] != HTTP2_SETTINGS + || (ctx->rbuf[4] & HTTP2_F_ACK) + || hdr->stream_id) + { + err_code = FRAME_ECODE_PROTO; + } + + if (hdr->length + && ((hdr->length % FRAME_STNGS_ENTRY_SIZE) + || (hdr->flags & HTTP2_F_ACK))) + { + err_code = FRAME_ECODE_SIZE_ERROR; + } + + if (err_code) + return tfw_http2_conn_terminate(ctx, err_code); + + ctx->to_read = hdr->length ? FRAME_STNGS_ENTRY_SIZE : 0; + hdr->length -= ctx->to_read; + + return T_OK; +} + +static int +tfw_http2_frame_pad_process(TfwHttp2Ctx *ctx) +{ + TfwFrameHdr *hdr = &ctx->hdr; + + ctx->padlen = ctx->rbuf[0]; + switch (hdr->type) { + case HTTP2_DATA: + ctx->state = HTTP2_RECV_DATA; + break; + + case HTTP2_HEADERS: + if (hdr->flags & HTTP2_F_PRIORITY) + return tfw_http2_recv_priority(ctx); + ctx->state = HTTP2_RECV_HEADER; + break; + + default: + /* Only DATA and HEADERS frames cab be padded. */ + BUG(); + } + + ++ctx->data_off; + ctx->to_read = hdr->length - ctx->padlen; + hdr->length = 0; + + return T_OK; +} + + +static int +tfw_http2_frame_type_process(TfwHttp2Ctx *ctx) +{ + int err_code = FRAME_ECODE_SIZE_ERROR; + TfwFrameHdr *hdr = &ctx->hdr; + + switch (hdr->type) { + case HTTP2_DATA: + BUG_ON(PAYLOAD(ctx)); + if (tfw_htt2_stream_verify(ctx)) + return T_BAD; + if (ctx->state == HTTP2_IGNORE_FRAME_DATA) { + SET_TO_READ(ctx); + return T_OK; + } + + ctx->data_off = FRAME_HEADER_SIZE; + + if (hdr->flags & HTTP2_F_PADDED) { + ctx->to_read = 1; + hdr->length -= ctx->to_read; + VERIFY_FRAME_SIZE(ctx); + ctx->state = HTTP2_RECV_FRAME_PADDED; + return T_OK; + } + + ctx->state = HTTP2_RECV_DATA; + SET_TO_READ(ctx); + return T_OK; + + case HTTP2_HEADERS: + BUG_ON(PAYLOAD(ctx)); + if (tfw_http2_headers_check(ctx)) + return T_BAD; + if (ctx->state == HTTP2_IGNORE_FRAME_DATA) { + SET_TO_READ(ctx); + return T_OK; + } + + ctx->data_off = FRAME_HEADER_SIZE; + + if (hdr->flags & HTTP2_F_PADDED) { + ctx->to_read = 1; + hdr->length -= ctx->to_read; + VERIFY_FRAME_SIZE(ctx); + ctx->state = HTTP2_RECV_FRAME_PADDED; + return T_OK; + } + + if (hdr->flags & HTTP2_F_PRIORITY) + return tfw_http2_recv_priority(ctx); + + if (tfw_htt2_stream_verify(ctx)) + return T_BAD; + if (ctx->state != HTTP2_IGNORE_FRAME_DATA) + ctx->state = HTTP2_RECV_HEADER; + + SET_TO_READ(ctx); + return T_OK; + + case HTTP2_PRIORITY: + /* + * TODO + */ + return T_BAD; + + case HTTP2_WINDOW_UPDATE: + if (!PAYLOAD(ctx)) { + if (ctx->hdr.length != FRAME_SRVC1_SIZE) + goto out_term; + + ctx->state = HTTP2_RECV_FRAME_SERVICE; + SET_TO_READ(ctx); + return T_OK; + } + + return tfw_http2_wnd_update_process(ctx); + + case HTTP2_SETTINGS: + BUG_ON(PAYLOAD(ctx)); + if (hdr->stream_id) { + err_code = FRAME_ECODE_PROTO; + goto out_term; + } + if ((hdr->length % FRAME_STNGS_ENTRY_SIZE) + || ((hdr->flags & HTTP2_F_ACK) + && hdr->length > 0)) + { + goto out_term; + } + + if (!(hdr->flags & HTTP2_F_ACK) && hdr->length) { + ctx->state = HTTP2_RECV_FRAME_SETTINGS; + ctx->to_read = FRAME_STNGS_ENTRY_SIZE; + hdr->length -= ctx->to_read; + } else { + ctx->state = HTTP2_RECV_FRAME_HEADER; + } + + return T_OK; + + case HTTP2_PUSH_PROMISE: + /* + * TODO + */ + return T_BAD; + + case HTTP2_PING: + if (!PAYLOAD(ctx)) { + if (ctx->hdr.stream_id) { + err_code = FRAME_ECODE_PROTO; + goto out_term; + } + if (ctx->hdr.length != FRAME_SRVC2_SIZE) + goto out_term; + + ctx->state = HTTP2_RECV_FRAME_SERVICE; + SET_TO_READ(ctx); + return T_OK; + } + if (!(hdr->flags & HTTP2_F_ACK)) + return tfw_http2_send_ping(ctx); + + return T_OK; + + case HTTP2_RST_STREAM: + if (!PAYLOAD(ctx)) { + if (ctx->hdr.length != FRAME_SRVC1_SIZE) + goto out_term; + + ctx->state = HTTP2_RECV_FRAME_SERVICE; + SET_TO_READ(ctx); + return T_OK; + } + + return tfw_http2_rst_stream_process(ctx); + case HTTP2_GOAWAY: + BUG_ON(PAYLOAD(ctx)); + if (ctx->hdr.stream_id) { + err_code = FRAME_ECODE_PROTO; + goto out_term; + } + if (ctx->hdr.length < FRAME_SRVC2_SIZE) + goto out_term; + + ctx->state = HTTP2_RECV_FRAME_GOAWAY; + ctx->to_read = FRAME_SRVC2_SIZE; + hdr->length -= ctx->to_read; + return T_OK; + + case HTTP2_CONTINUATION: + BUG_ON(PAYLOAD(ctx)); + if (tfw_http2_cont_check(ctx)) + return T_BAD; + if (ctx->state != HTTP2_IGNORE_FRAME_DATA) + ctx->state = HTTP2_RECV_CONT; + + ctx->data_off = FRAME_HEADER_SIZE; + + SET_TO_READ(ctx); + 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; + } + +out_term: + BUG_ON(!err_code); + return tfw_http2_conn_terminate(ctx, err_code); +} + +/** + * Main FSM for processing HTTP/2 frames. + */ +static int +tfw_http2_frame_recv(void *data, unsigned char *buf, size_t len, + unsigned int *read) +{ + int n, r = T_POSTPONE; + unsigned char *p = buf; + TfwHttp2Ctx *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_CLI_MAGIC_LEN, { + if (memcmp_fast(FRAME_CLI_MAGIC + ctx->rlen, p, n)) { + T_DBG("Invalid client magic received," + " connection must be dropped\n"); + 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_http2_first_settings_verify(ctx)) + FRAME_FSM_EXIT(T_DROP); + + if (ctx->to_read) + FRAME_FSM_MOVE(HTTP2_RECV_FRAME_SETTINGS); + + FRAME_FSM_MOVE(HTTP2_RECV_FRAME_HEADER); + } + + T_FSM_STATE(HTTP2_RECV_FRAME_HEADER) { + FRAME_FSM_READ_SRVC(FRAME_HEADER_SIZE); + + tfw_http2_unpack_frame_header(&ctx->hdr, ctx->rbuf); + + if (tfw_http2_frame_type_process(ctx)) + FRAME_FSM_EXIT(T_DROP); + + FRAME_FSM_NEXT(); + } + + T_FSM_STATE(HTTP2_RECV_FRAME_PADDED) { + BUG_ON(ctx->to_read > FRAME_HEADER_SIZE); + FRAME_FSM_READ_SRVC(ctx->to_read); + + if (tfw_http2_frame_pad_process(ctx)) + FRAME_FSM_EXIT(T_DROP); + + FRAME_FSM_NEXT(); + } + + T_FSM_STATE(HTTP2_RECV_FRAME_SERVICE) { + BUG_ON(ctx->to_read > FRAME_HEADER_SIZE); + FRAME_FSM_READ_SRVC(ctx->to_read); + + if (tfw_http2_frame_type_process(ctx)) + FRAME_FSM_EXIT(T_DROP); + + FRAME_FSM_EXIT(T_OK); + } + + T_FSM_STATE(HTTP2_RECV_FRAME_SETTINGS) { + BUG_ON(ctx->to_read > FRAME_HEADER_SIZE); + FRAME_FSM_READ_SRVC(ctx->to_read); + + if (tfw_http2_settings_process(ctx)) + FRAME_FSM_EXIT(T_DROP); + + if (ctx->to_read) + FRAME_FSM_MOVE(HTTP2_RECV_FRAME_SETTINGS); + + if (tfw_http2_send_settings_ack(ctx)) + FRAME_FSM_EXIT(T_DROP); + + FRAME_FSM_EXIT(T_OK); + } + + T_FSM_STATE(HTTP2_RECV_FRAME_GOAWAY) { + BUG_ON(ctx->to_read > FRAME_HEADER_SIZE); + FRAME_FSM_READ_SRVC(ctx->to_read); + + if (tfw_http2_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) { + BUG_ON(ctx->to_read > FRAME_HEADER_SIZE); + FRAME_FSM_READ_SRVC(ctx->to_read); + + if (tfw_http2_headers_pri_process(ctx)) + FRAME_FSM_EXIT(T_DROP); + + FRAME_FSM_NEXT(); + } + + T_FSM_STATE(HTTP2_RECV_DATA) { + FRAME_FSM_READ(ctx->to_read); + FRAME_FSM_EXIT(T_OK); + } + + T_FSM_STATE(HTTP2_RECV_HEADER) { + FRAME_FSM_READ(ctx->to_read); + + if (tfw_http2_headers_check(ctx)) + FRAME_FSM_EXIT(T_DROP); + + FRAME_FSM_EXIT(T_OK); + } + + T_FSM_STATE(HTTP2_RECV_CONT) { + FRAME_FSM_READ(ctx->to_read); + + if ((r = tfw_http2_cont_check(ctx))) + FRAME_FSM_EXIT(T_DROP); + + 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; +} + +/* + * 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 + * be appear during framing context initialization: + * 1. On fully received service (non-app) frames and fully received app + * frames without padding - context must be reset; + * 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_http2_context_init(TfwHttp2Ctx *ctx, bool postponed) +{ + if (!APP_FRAME(ctx) || (!postponed && !ctx->padlen)) { + bzero_fast(ctx->__off, + sizeof(*ctx) - offsetof(TfwHttp2Ctx, __off)); + return; + } + if (!postponed && ctx->padlen) { + ctx->state = HTTP2_IGNORE_FRAME_DATA; + ctx->to_read = ctx->padlen; + ctx->padlen = 0; + } +} + +int +tfw_http2_frame_process(void *c, TfwFsmData *data) +{ + int r; + unsigned int unused, curr_tail; + TfwFsmData data_up = {}; + TfwHttp2Ctx *h2 = tfw_http2_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_http2_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 containing 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 splitted 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_http2_context_init(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; +} +EXPORT_SYMBOL(tfw_http2_frame_process); diff --git a/tempesta_fw/http_frame.h b/tempesta_fw/http_frame.h new file mode 100644 index 000000000..aafb0240c --- /dev/null +++ b/tempesta_fw/http_frame.h @@ -0,0 +1,116 @@ +/** + * Tempesta FW + * + * Copyright (C) 2014 NatSys Lab. (info@natsys-lab.com). + * Copyright (C) 2015-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 "connection.h" + +#define FRAME_HEADER_SIZE 9 + +/** + * 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). + * + * @weight - stream's priority weight; + * @stream_id - id for the stream that the current stream depends on; + * @exclusive - flag indicating exclusive stream dependency; + */ +typedef struct { + int weight; + unsigned int stream_id; + unsigned char exclusive; +} TfwFramePri; + + +/** + * Context for HTTP/2 frames processing. + * + * @conn - pointer to corresponding connection instance; + * @__off - offset to reinitialize processing context; + * @to_read - indicates how much data of HTTP/2 frame should + * be read on next FSM @state; + * @lstream_id - highest id of stream processed by peer (GOAWAY frame); + * @skb_head - collected list of processed skbs containing HTTP/2 frames; + * @state - current FSM state of HTTP/2 processing context; + * @hdr - unpacked data from header of currently processed frame; + * @priority - unpacked data from priority part of payload of processed + * HEADERS or PRIORITY frames; + * @rbuf - buffer for data accumulation from frames headers and + * payloads (for service frames) during frames processing; + * @rlen - length of accumulated data in @rbuf; + * @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); + */ +struct tfw_http2_ctx_t { + TfwConn *conn; + char __off[0]; + int to_read; + unsigned int lstream_id; + struct sk_buff *skb_head; + TfwFrameState state; + TfwFrameHdr hdr; + TfwFramePri priority; + unsigned char rbuf[FRAME_HEADER_SIZE]; + unsigned char rlen; + unsigned char padlen; + unsigned char data_off; +}; + +/** + * 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_SERVICE, + 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; + +int tfw_http2_frame_process(void *c, TfwFsmData *data); + +#endif /* __HTTP_FRAME__ */ diff --git a/tempesta_fw/http_types.h b/tempesta_fw/http_types.h index 0a62215c7..54fd78647 100644 --- a/tempesta_fw/http_types.h +++ b/tempesta_fw/http_types.h @@ -25,5 +25,6 @@ typedef struct tfw_http_sess_t TfwHttpSess; typedef struct tfw_http_msg_t TfwHttpMsg; typedef struct tfw_http_req_t TfwHttpReq; typedef struct tfw_http_resp_t TfwHttpResp; +typedef struct tfw_http2_ctx_t TfwHttp2Ctx; #endif /* __TFW_HTTP_TYPES_H__ */ diff --git a/tempesta_fw/tls.c b/tempesta_fw/tls.c index 4876781c5..63372d9b3 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 { @@ -59,6 +60,34 @@ tfw_tls_chop_skb_rec(TlsCtx *tls, struct sk_buff *skb, TfwFsmData *data) return 0; } +static bool +tfw_tls_http2_mode(void *c) { + /* + * TODO: need to be implemented (from ALPN extension) + */ + return false; +} + +static inline int +tfw_tls_check_alloc_h2_context(void *c) +{ + TfwHttp2Ctx *h2; + TfwTlsConn *conn = c; + + if (likely(!tfw_tls_http2_mode(c) || conn->h2)) + return 0; + + h2 = ttls_calloc(1, sizeof(TfwHttp2Ctx)); + if (unlikely(!h2)) + return -ENOMEM; + + conn->h2 = h2; + h2->state = HTTP2_RECV_CLI_START_SEQ; + h2->conn = c; + + return 0; +} + static int tfw_tls_msg_process(void *conn, TfwFsmData *data) { @@ -68,6 +97,10 @@ tfw_tls_msg_process(void *conn, TfwFsmData *data) TlsCtx *tls = tfw_tls_context(c); TfwFsmData data_up = {}; + /* Enable HTTP/2 mode if this protocol has been negotiated in APLN. */ + if (tfw_tls_check_alloc_h2_context(conn)) + return T_DROP; + /* * @off is from TCP layer due to possible, but rare (usually malicious), * sequence numbers overlapping. We have to join the skb into a list @@ -498,6 +531,7 @@ tfw_tls_conn_dtor(void *c) { struct sk_buff *skb; TlsCtx *tls = tfw_tls_context(c); + TfwHttp2Ctx *h2 = tfw_http2_context(c); if (tls) { while ((skb = ss_skb_dequeue(&tls->io_in.skb_list))) @@ -506,6 +540,9 @@ tfw_tls_conn_dtor(void *c) kfree_skb(skb); } + if (h2) + ttls_free(h2); + ttls_ctx_clear(tls); tfw_cli_conn_release((TfwCliConn *)c); } From 10be35ae55ef1445a6299bfe455235422285b622 Mon Sep 17 00:00:00 2001 From: Alexander Ostapenko Date: Fri, 29 Mar 2019 04:01:07 +0700 Subject: [PATCH 02/17] Minor layout correction (#309). --- tempesta_fw/http_frame.h | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/tempesta_fw/http_frame.h b/tempesta_fw/http_frame.h index aafb0240c..5c8ab08fa 100644 --- a/tempesta_fw/http_frame.h +++ b/tempesta_fw/http_frame.h @@ -25,6 +25,25 @@ #define FRAME_HEADER_SIZE 9 +/** + * 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_SERVICE, + 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; + /** * Unpacked header data of currently processed frame (RFC 7540 section * 4.1). Reserved bit is not present here since it has no any semantic @@ -92,25 +111,6 @@ struct tfw_http2_ctx_t { unsigned char data_off; }; -/** - * 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_SERVICE, - 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; - int tfw_http2_frame_process(void *c, TfwFsmData *data); #endif /* __HTTP_FRAME__ */ From 7139f538e8db2a2b3e3d6cd12724dff15e27107e Mon Sep 17 00:00:00 2001 From: Alexander Ostapenko Date: Fri, 29 Mar 2019 16:17:58 +0700 Subject: [PATCH 03/17] Correction of HTTP/2 switching (#309). --- tempesta_fw/connection.h | 1 + tempesta_fw/http.c | 2 +- tempesta_fw/sock_clnt.c | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/tempesta_fw/connection.h b/tempesta_fw/connection.h index 3878571a6..d30fe214e 100644 --- a/tempesta_fw/connection.h +++ b/tempesta_fw/connection.h @@ -110,6 +110,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 diff --git a/tempesta_fw/http.c b/tempesta_fw/http.c index d476b02c5..4eae26506 100644 --- a/tempesta_fw/http.c +++ b/tempesta_fw/http.c @@ -3931,7 +3931,7 @@ 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_http2_context(conn) + r = TFW_CONN_TLS((TfwConn *)conn) && tfw_http2_context(conn) ? tfw_http2_frame_process(conn, data) : tfw_http_msg_process_generic(conn, data); } else { diff --git a/tempesta_fw/sock_clnt.c b/tempesta_fw/sock_clnt.c index 47658d32d..1d0de2fa9 100644 --- a/tempesta_fw/sock_clnt.c +++ b/tempesta_fw/sock_clnt.c @@ -193,7 +193,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 From 26cc7807c543ed2ada9b788566e2f8ef6b27faa5 Mon Sep 17 00:00:00 2001 From: Alexander Ostapenko Date: Fri, 29 Mar 2019 20:49:28 +0700 Subject: [PATCH 04/17] Minor correction in unit tests (#309). --- tempesta_fw/http_frame.c | 1 - tempesta_fw/t/unit/test_http_sticky.c | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/tempesta_fw/http_frame.c b/tempesta_fw/http_frame.c index 9d31cb791..21e036b31 100644 --- a/tempesta_fw/http_frame.c +++ b/tempesta_fw/http_frame.c @@ -948,4 +948,3 @@ tfw_http2_frame_process(void *c, TfwFsmData *data) ss_skb_queue_purge(&h2->skb_head); return r; } -EXPORT_SYMBOL(tfw_http2_frame_process); diff --git a/tempesta_fw/t/unit/test_http_sticky.c b/tempesta_fw/t/unit/test_http_sticky.c index ee9891163..bc5c9e6e5 100644 --- a/tempesta_fw/t/unit/test_http_sticky.c +++ b/tempesta_fw/t/unit/test_http_sticky.c @@ -48,6 +48,7 @@ #include "sock_srv.c" #include "client.c" #include "http_limits.c" +#include "http_frame.c" #include "tls.c" /* rename original tfw_cli_conn_send(), a custom version will be used here */ From fe57412b3eca031c53bfe68c1f675a9eb30d04d1 Mon Sep 17 00:00:00 2001 From: Alexander Ostapenko Date: Wed, 10 Apr 2019 23:51:51 +0700 Subject: [PATCH 05/17] HTTP/2 Starting/Switching implementation (#309). --- Makefile | 2 + tempesta_fw/http_frame.c | 154 ++++++++++++++++++++++++++++----------- tempesta_fw/http_frame.h | 12 +-- tempesta_fw/sock_clnt.c | 12 ++- tempesta_fw/tls.c | 100 ++++++++++++++++++++++--- tempesta_fw/tls.h | 2 + tls/tls_cli.c | 38 +++++----- tls/tls_srv.c | 28 +++---- tls/ttls.c | 39 ++++------ tls/ttls.h | 54 ++++++++++---- 10 files changed, 298 insertions(+), 143 deletions(-) diff --git a/Makefile b/Makefile index f63b5ddb0..d2fcf86f1 100644 --- a/Makefile +++ b/Makefile @@ -35,8 +35,10 @@ DBG_HTTP_PARSER ?= 0 DBG_SS ?= 0 DBG_TLS ?= 0 DBG_APM ?= 0 +DBG_HTTP_FRAME ?= 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) PROC = $(shell cat /proc/cpuinfo) ARCH = $(shell uname -m) diff --git a/tempesta_fw/http_frame.c b/tempesta_fw/http_frame.c index 21e036b31..1acd4b52b 100644 --- a/tempesta_fw/http_frame.c +++ b/tempesta_fw/http_frame.c @@ -19,6 +19,9 @@ * 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" @@ -134,7 +137,8 @@ do { \ } while (0) #define APP_FRAME(ctx) \ - ((ctx)->state >= __HTTP2_RECV_FRAME_APP) + (false)//!!! remove this temporary stub + /* ((ctx)->state >= __HTTP2_RECV_FRAME_APP) */ #define PAYLOAD(ctx) \ ((ctx)->state != HTTP2_RECV_FRAME_HEADER) @@ -146,6 +150,9 @@ tfw_http2_unpack_frame_header(TfwFrameHdr *hdr, const unsigned char *buf) 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 @@ -156,8 +163,8 @@ tfw_http2_pack_frame_header(unsigned char *p, const TfwFrameHdr *hdr) *p++ = hdr->type; *p++ = hdr->flags; /* - * Stream id must not occupy not more than 31 bit and reserved - * bit must be 0. + * Stream id must occupy not more than 31 bit and reserved bit + * must be 0. */ WARN_ON_ONCE((unsigned int)(hdr->stream_id & ~FRAME_STREAM_ID_MASK)); @@ -169,7 +176,7 @@ tfw_http2_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]; + pri->weight = buf[4] + 1; } /** @@ -215,7 +222,7 @@ tfw_http2_send_frame(TfwHttp2Ctx *ctx, TfwFrameHdr *hdr, TfwStr *data) return r; } -static int +static inline int tfw_http2_send_ping(TfwHttp2Ctx *ctx) { TfwStr data = { @@ -227,25 +234,27 @@ tfw_http2_send_ping(TfwHttp2Ctx *ctx) .nchunks = 2 }; TfwFrameHdr hdr = { - .length = FRAME_SRVC2_SIZE, + .length = ctx->rlen, .stream_id = 0, .type = HTTP2_PING, .flags = HTTP2_F_ACK }; + WARN_ON_ONCE(ctx->rlen != FRAME_SRVC2_SIZE); + return tfw_http2_send_frame(ctx, &hdr, &data); } -static int -tfw_http2_send_settings_ack(TfwHttp2Ctx *ctx) +static inline int +tfw_http2_send_settings(TfwHttp2Ctx *ctx, bool ack) { TfwStr data = {}; TfwFrameHdr hdr = { .length = 0, .stream_id = 0, .type = HTTP2_SETTINGS, - .flags = HTTP2_F_ACK + .flags = ack ? HTTP2_F_ACK : 0 }; return tfw_http2_send_frame(ctx, &hdr, &data); @@ -265,13 +274,13 @@ tfw_http2_conn_terminate(TfwHttp2Ctx *ctx, int err_code) #define VERIFY_FRAME_SIZE(ctx) \ do { \ - if ((ctx)->hdr.length < 0) \ + if ((ctx)->hdr.length <= 0) \ return tfw_http2_conn_terminate(ctx, \ FRAME_ECODE_SIZE_ERROR); \ } while (0) static int -tfw_htt2_stream_verify(TfwHttp2Ctx *ctx) +tfw_http2_stream_verify(TfwHttp2Ctx *ctx) { int stream = ctx->hdr.stream_id; int err_code = 0; @@ -315,7 +324,7 @@ tfw_http2_headers_pri_process(TfwHttp2Ctx *ctx) tfw_http2_unpack_priority(&ctx->priority, ctx->rbuf); - if (tfw_htt2_stream_verify(ctx)) + if (tfw_http2_stream_verify(ctx)) return T_BAD; if (ctx->state != HTTP2_IGNORE_FRAME_DATA) { ctx->data_off += FRAME_PRIORITY_SIZE; @@ -355,6 +364,21 @@ tfw_http2_wnd_update_process(TfwHttp2Ctx *ctx) return T_OK; } +static int +tfw_http2_priority_process(TfwHttp2Ctx *ctx) +{ + TfwFramePri *pri = &ctx->priority; + + tfw_http2_unpack_priority(pri, ctx->rbuf); + + T_DBG3("%s: parsed, stream_id=%u, weight=%hu, excl=%hhu\n", + __func__, pri->stream_id, pri->weight, pri->exclusive); + /* + * TODO: apply stream prioritization. + */ + return T_OK; +} + static int tfw_http2_rst_stream_process(TfwHttp2Ctx *ctx) { @@ -369,7 +393,7 @@ tfw_http2_rst_stream_process(TfwHttp2Ctx *ctx) return T_OK; } static int -tfw_http2_apply_settings_entry(int id, unsigned int val) +tfw_http2_apply_settings_entry(unsigned short id, unsigned int val) { /* * TODO: apply settings entry. @@ -377,14 +401,25 @@ tfw_http2_apply_settings_entry(int id, unsigned int val) return T_OK; } +static void +tfw_http2_settings_ack_process(TfwHttp2Ctx *ctx) +{ + /* + * TODO: apply settings ACK. + */ + return; +} + static int tfw_http2_settings_process(TfwHttp2Ctx *ctx) { TfwFrameHdr *hdr = &ctx->hdr; - int id = ntohl(*(int *)&ctx->rbuf[0]); + unsigned short id = ntohs(*(unsigned short *)&ctx->rbuf[0]); unsigned int val = ntohl(*(unsigned int *)&ctx->rbuf[2]); - if (!tfw_http2_apply_settings_entry(id, val)) + T_DBG3("%s: entry parsed, id=%hu, val=%u\n", __func__, id, val); + + if (tfw_http2_apply_settings_entry(id, val)) return T_BAD; ctx->to_read = hdr->length ? FRAME_STNGS_ENTRY_SIZE : 0; @@ -399,13 +434,16 @@ tfw_http2_goaway_process(TfwHttp2Ctx *ctx) unsigned int err_code = ntohl(*(unsigned int *)&ctx->rbuf[4]); ctx->lstream_id = ntohl(*(unsigned int *)ctx->rbuf) & FRAME_STREAM_ID_MASK; - SET_TO_READ(ctx); + + T_DBG3("%s: parsed, last_stream_id=%u, err_code=%u\n", __func__, + ctx->lstream_id, err_code); /* * TODO: close streams with @id greater than @ctx->lstream_id */ if (err_code) - return T_BAD; - + T_LOG("HTTP/2 connection is closed by client with error code:" + " %u\n", err_code); + SET_TO_READ(ctx); return T_OK; } @@ -417,19 +455,17 @@ tfw_http2_first_settings_verify(TfwHttp2Ctx *ctx) BUG_ON(ctx->to_read); - if (ctx->rbuf[3] != HTTP2_SETTINGS - || (ctx->rbuf[4] & HTTP2_F_ACK) + tfw_http2_unpack_frame_header(hdr, ctx->rbuf); + + if (hdr->type != HTTP2_SETTINGS + || (hdr->flags & HTTP2_F_ACK) || hdr->stream_id) { err_code = FRAME_ECODE_PROTO; } - if (hdr->length - && ((hdr->length % FRAME_STNGS_ENTRY_SIZE) - || (hdr->flags & HTTP2_F_ACK))) - { + if (hdr->length && (hdr->length % FRAME_STNGS_ENTRY_SIZE)) err_code = FRAME_ECODE_SIZE_ERROR; - } if (err_code) return tfw_http2_conn_terminate(ctx, err_code); @@ -445,7 +481,11 @@ tfw_http2_frame_pad_process(TfwHttp2Ctx *ctx) { TfwFrameHdr *hdr = &ctx->hdr; + ++ctx->data_off; ctx->padlen = ctx->rbuf[0]; + hdr->length -= ctx->padlen; + VERIFY_FRAME_SIZE(ctx); + switch (hdr->type) { case HTTP2_DATA: ctx->state = HTTP2_RECV_DATA; @@ -462,9 +502,7 @@ tfw_http2_frame_pad_process(TfwHttp2Ctx *ctx) BUG(); } - ++ctx->data_off; - ctx->to_read = hdr->length - ctx->padlen; - hdr->length = 0; + SET_TO_READ(ctx); return T_OK; } @@ -476,10 +514,16 @@ tfw_http2_frame_type_process(TfwHttp2Ctx *ctx) int err_code = FRAME_ECODE_SIZE_ERROR; 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: BUG_ON(PAYLOAD(ctx)); - if (tfw_htt2_stream_verify(ctx)) + if (!ctx->hdr.length) + goto out_term; + + if (tfw_http2_stream_verify(ctx)) return T_BAD; if (ctx->state == HTTP2_IGNORE_FRAME_DATA) { SET_TO_READ(ctx); @@ -502,6 +546,9 @@ tfw_http2_frame_type_process(TfwHttp2Ctx *ctx) case HTTP2_HEADERS: BUG_ON(PAYLOAD(ctx)); + if (!ctx->hdr.length) + goto out_term; + if (tfw_http2_headers_check(ctx)) return T_BAD; if (ctx->state == HTTP2_IGNORE_FRAME_DATA) { @@ -522,7 +569,7 @@ tfw_http2_frame_type_process(TfwHttp2Ctx *ctx) if (hdr->flags & HTTP2_F_PRIORITY) return tfw_http2_recv_priority(ctx); - if (tfw_htt2_stream_verify(ctx)) + if (tfw_http2_stream_verify(ctx)) return T_BAD; if (ctx->state != HTTP2_IGNORE_FRAME_DATA) ctx->state = HTTP2_RECV_HEADER; @@ -531,10 +578,20 @@ tfw_http2_frame_type_process(TfwHttp2Ctx *ctx) return T_OK; case HTTP2_PRIORITY: - /* - * TODO - */ - return T_BAD; + if (!PAYLOAD(ctx)) { + if (ctx->hdr.length != FRAME_PRIORITY_SIZE) + goto out_term; + + if (tfw_http2_stream_verify(ctx)) + return T_BAD; + if (ctx->state != HTTP2_IGNORE_FRAME_DATA) + ctx->state = HTTP2_RECV_FRAME_SERVICE; + + SET_TO_READ(ctx); + return T_OK; + } + + return tfw_http2_priority_process(ctx); case HTTP2_WINDOW_UPDATE: if (!PAYLOAD(ctx)) { @@ -561,21 +618,27 @@ tfw_http2_frame_type_process(TfwHttp2Ctx *ctx) goto out_term; } - if (!(hdr->flags & HTTP2_F_ACK) && hdr->length) { + if (hdr->flags & HTTP2_F_ACK) + tfw_http2_settings_ack_process(ctx); + + if (hdr->length) { ctx->state = HTTP2_RECV_FRAME_SETTINGS; ctx->to_read = FRAME_STNGS_ENTRY_SIZE; hdr->length -= ctx->to_read; } else { - ctx->state = HTTP2_RECV_FRAME_HEADER; + /* + * 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: - /* - * TODO - */ - return T_BAD; + /* Client cannot push (RFC 7540 section 8.2). */ + err_code = FRAME_ECODE_PROTO; + goto out_term; case HTTP2_PING: if (!PAYLOAD(ctx)) { @@ -672,6 +735,8 @@ tfw_http2_frame_recv(void *data, unsigned char *buf, size_t len, FRAME_FSM_EXIT(T_DROP); } }); + if (tfw_http2_send_settings(ctx, false)) + FRAME_FSM_EXIT(T_DROP); FRAME_FSM_MOVE(HTTP2_RECV_FIRST_SETTINGS); } @@ -685,7 +750,7 @@ tfw_http2_frame_recv(void *data, unsigned char *buf, size_t len, if (ctx->to_read) FRAME_FSM_MOVE(HTTP2_RECV_FRAME_SETTINGS); - FRAME_FSM_MOVE(HTTP2_RECV_FRAME_HEADER); + FRAME_FSM_EXIT(T_OK); } T_FSM_STATE(HTTP2_RECV_FRAME_HEADER) { @@ -696,7 +761,10 @@ tfw_http2_frame_recv(void *data, unsigned char *buf, size_t len, if (tfw_http2_frame_type_process(ctx)) FRAME_FSM_EXIT(T_DROP); - FRAME_FSM_NEXT(); + if (ctx->to_read) + FRAME_FSM_NEXT(); + + FRAME_FSM_EXIT(T_OK); } T_FSM_STATE(HTTP2_RECV_FRAME_PADDED) { @@ -729,7 +797,7 @@ tfw_http2_frame_recv(void *data, unsigned char *buf, size_t len, if (ctx->to_read) FRAME_FSM_MOVE(HTTP2_RECV_FRAME_SETTINGS); - if (tfw_http2_send_settings_ack(ctx)) + if (tfw_http2_send_settings(ctx, true)) FRAME_FSM_EXIT(T_DROP); FRAME_FSM_EXIT(T_OK); diff --git a/tempesta_fw/http_frame.h b/tempesta_fw/http_frame.h index 5c8ab08fa..c2f7af4f9 100644 --- a/tempesta_fw/http_frame.h +++ b/tempesta_fw/http_frame.h @@ -65,13 +65,13 @@ typedef struct { * Unpacked data from priority payload of frames (RFC 7540 section 6.2 * and section 6.3). * - * @weight - stream's priority weight; * @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 { - int weight; unsigned int stream_id; + unsigned short weight; unsigned char exclusive; } TfwFramePri; @@ -85,13 +85,13 @@ typedef struct { * be read on next FSM @state; * @lstream_id - highest id of stream processed by peer (GOAWAY frame); * @skb_head - collected list of processed skbs containing HTTP/2 frames; - * @state - current FSM state of HTTP/2 processing context; * @hdr - unpacked data from header of currently processed frame; + * @state - current FSM state of HTTP/2 processing context; * @priority - unpacked data from priority part of payload of processed * HEADERS or PRIORITY frames; + * @rlen - length of accumulated data in @rbuf; * @rbuf - buffer for data accumulation from frames headers and * payloads (for service frames) during frames processing; - * @rlen - length of accumulated data in @rbuf; * @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); @@ -102,11 +102,11 @@ struct tfw_http2_ctx_t { int to_read; unsigned int lstream_id; struct sk_buff *skb_head; - TfwFrameState state; TfwFrameHdr hdr; + TfwFrameState state; TfwFramePri priority; + int rlen; unsigned char rbuf[FRAME_HEADER_SIZE]; - unsigned char rlen; unsigned char padlen; unsigned char data_off; }; diff --git a/tempesta_fw/sock_clnt.c b/tempesta_fw/sock_clnt.c index 1d0de2fa9..87eeb63a7 100644 --- a/tempesta_fw/sock_clnt.c +++ b/tempesta_fw/sock_clnt.c @@ -463,8 +463,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 +501,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 +541,7 @@ static void tfw_cfgop_cleanup_sock_clnt(TfwCfgSpec *cs) { tfw_listen_sock_del_all(); + tfw_tls_free_alpn_protos(); } static int diff --git a/tempesta_fw/tls.c b/tempesta_fw/tls.c index 63372d9b3..f0b819878 100644 --- a/tempesta_fw/tls.c +++ b/tempesta_fw/tls.c @@ -60,24 +60,21 @@ tfw_tls_chop_skb_rec(TlsCtx *tls, struct sk_buff *skb, TfwFsmData *data) return 0; } -static bool -tfw_tls_http2_mode(void *c) { - /* - * TODO: need to be implemented (from ALPN extension) - */ - return false; -} - static inline int tfw_tls_check_alloc_h2_context(void *c) { TfwHttp2Ctx *h2; TfwTlsConn *conn = c; + TlsCtx *tls = &conn->tls; - if (likely(!tfw_tls_http2_mode(c) || conn->h2)) + if (likely(!tls->alpn_chosen + || tls->alpn_chosen->id != TTLS_ALPN_ID_HTTP2 + || conn->h2)) + { return 0; + } - h2 = ttls_calloc(1, sizeof(TfwHttp2Ctx)); + h2 = kzalloc(sizeof(TfwHttp2Ctx), GFP_ATOMIC); if (unlikely(!h2)) return -ENOMEM; @@ -541,7 +538,7 @@ tfw_tls_conn_dtor(void *c) } if (h2) - ttls_free(h2); + kfree(h2); ttls_ctx_clear(tls); tfw_cli_conn_release((TfwCliConn *)c); @@ -552,6 +549,9 @@ tfw_tls_conn_init(TfwConn *c) { int r; TlsCtx *tls = tfw_tls_context(c); + TfwTlsConn *conn = (TfwTlsConn *)c; + + conn->h2 = NULL; if ((r = ttls_ctx_init(tls, &tfw_tls.cfg))) { TFW_ERR("TLS (%pK) setup failed (%x)\n", tls, -r); @@ -703,6 +703,84 @@ 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); + int order = 0; + +#define CONF_HTTPS "https" +#define CONF_HTTP1 "http1" +#define CONF_HTTP2 "http2" +#define CONF_LEN 5 + if (strncasecmp(cfg_str, CONF_HTTPS, CONF_LEN)) + return -EINVAL; + + if (len == CONF_LEN) + goto found; + + cfg_str += CONF_LEN; + if (cfg_str[0] != ':') + return -EINVAL; + + ++cfg_str; + len -= CONF_LEN + 1; + if (len >= CONF_LEN && !strncasecmp(cfg_str, CONF_HTTP1, CONF_LEN)) + { + cfg_str += CONF_LEN; + len -= CONF_LEN; + if (!len || (cfg_str[0] == ',' + && !strcasecmp(++cfg_str, CONF_HTTP2))) + { + goto found; + } + } + else if (len >= CONF_LEN && + !strncasecmp(cfg_str, CONF_HTTP2, CONF_LEN)) + { + order = 1; + cfg_str += CONF_LEN; + len -= CONF_LEN; + if (!len || (cfg_str[0] == ',' + && !strcasecmp(++cfg_str, CONF_HTTP1))) + { + goto found; + } + } + + return -EINVAL; + +found: + protos = kzalloc(TTLS_ALPN_PROTOS * sizeof(ttls_alpn_proto), GFP_ATOMIC); + if (unlikely(!protos)) + return -ENOMEM; + + protos[order].name = TTLS_ALPN_HTTP1; + protos[order].len = sizeof(TTLS_ALPN_HTTP1) - 1; + protos[order].id = TTLS_ALPN_ID_HTTP1; + protos[!order].name = TTLS_ALPN_HTTP2; + protos[!order].len = sizeof(TTLS_ALPN_HTTP2) - 1; + protos[!order].id = TTLS_ALPN_ID_HTTP2; + + tfw_tls.cfg.alpn_list = protos; + + return 0; +#undef CONF_HTTPS +#undef CONF_HTTP1 +#undef CONF_HTTP2 +#undef CONF_LEN +} + +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) { diff --git a/tempesta_fw/tls.h b/tempesta_fw/tls.h index efbcf62b0..299fbe595 100644 --- a/tempesta_fw/tls.h +++ b/tempesta_fw/tls.h @@ -39,6 +39,8 @@ enum { }; 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 ccfc91982..61f6872df 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 369e9a746..24bc32012 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; - /* 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 processig is enabled, ALPN must be configured. */ + BUG_ON(!tls->conf->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; ++i) { + our = &tls->conf->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 0c0f494fd..902ab00de 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 28a5d9352..14ce9e97a 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) { From ed798308a7c11bdbc8ff5022c19d54e6a82c375b Mon Sep 17 00:00:00 2001 From: Alexander Ostapenko Date: Thu, 11 Apr 2019 00:23:34 +0700 Subject: [PATCH 06/17] Remove forgotten debug code (#309). --- tempesta_fw/http_frame.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tempesta_fw/http_frame.c b/tempesta_fw/http_frame.c index 1acd4b52b..9be27ab3b 100644 --- a/tempesta_fw/http_frame.c +++ b/tempesta_fw/http_frame.c @@ -137,8 +137,7 @@ do { \ } while (0) #define APP_FRAME(ctx) \ - (false)//!!! remove this temporary stub - /* ((ctx)->state >= __HTTP2_RECV_FRAME_APP) */ + ((ctx)->state >= __HTTP2_RECV_FRAME_APP) #define PAYLOAD(ctx) \ ((ctx)->state != HTTP2_RECV_FRAME_HEADER) From f6e8edc938d971748df77fd8ec5b10e278c3826f Mon Sep 17 00:00:00 2001 From: Alexander Ostapenko Date: Thu, 25 Apr 2019 19:05:39 +0700 Subject: [PATCH 07/17] Implementation of base functionality for HTTP/2 Stream Scheduler (#309). --- tempesta_fw/http_frame.c | 340 ++++++++++++++++++++------ tempesta_fw/http_frame.h | 43 +++- tempesta_fw/http_stream.c | 126 ++++++++++ tempesta_fw/http_stream.h | 81 ++++++ tempesta_fw/t/unit/test_http_sticky.c | 1 + tempesta_fw/tls.c | 7 +- 6 files changed, 517 insertions(+), 81 deletions(-) create mode 100644 tempesta_fw/http_stream.c create mode 100644 tempesta_fw/http_stream.h diff --git a/tempesta_fw/http_frame.c b/tempesta_fw/http_frame.c index 9be27ab3b..c53c5da2a 100644 --- a/tempesta_fw/http_frame.c +++ b/tempesta_fw/http_frame.c @@ -1,8 +1,7 @@ /** * Tempesta FW * - * Copyright (C) 2014 NatSys Lab. (info@natsys-lab.com). - * Copyright (C) 2015-2019 Tempesta Technologies, Inc. + * 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 @@ -29,18 +28,18 @@ #include "http_frame.h" -#define FRAME_CLI_MAGIC "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n" -#define FRAME_CLI_MAGIC_LEN 24 -#define FRAME_SRVC1_SIZE 4 -#define FRAME_PRIORITY_SIZE 5 -#define FRAME_STNGS_ENTRY_SIZE 6 -#define FRAME_SRVC2_SIZE 8 +#define FRAME_CLI_MAGIC "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n" +#define FRAME_CLI_MAGIC_LEN 24 +#define FRAME_SRVC1_SIZE 4 +#define FRAME_PRIORITY_SIZE 5 +#define FRAME_STNGS_ENTRY_SIZE 6 +#define FRAME_SRVC2_SIZE 8 -#define FRAME_STREAM_ID_MASK ((1U << 31) - 1) +#define FRAME_STREAM_ID_MASK ((1U << 31) - 1) /* HTTP/2 frame types (RFC 7540 section 6).*/ typedef enum { - HTTP2_DATA = 0, + HTTP2_DATA = 0, HTTP2_HEADERS, HTTP2_PRIORITY, HTTP2_RST_STREAM, @@ -57,8 +56,8 @@ typedef enum { * and GOAWAY frames to report the reasons of the stream or * connection error. */ -#define FRAME_ECODE_PROTO 0x1 -#define FRAME_ECODE_SIZE_ERROR 0x6 +#define FRAME_ECODE_PROTO 0x1 +#define FRAME_ECODE_SIZE_ERROR 0x6 /* * HTTP/2 frame flags. Can be specified in frame's header and @@ -66,13 +65,26 @@ typedef enum { * 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 + HTTP2_F_ACK = 0x01, + HTTP2_F_END_STREAM = 0x01, + HTTP2_F_END_HEADERS = 0x04, + HTTP2_F_PADDED = 0x08, + HTTP2_F_PRIORITY = 0x20 } TfwFrameFlag; +/* + * 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; \ @@ -205,7 +217,7 @@ tfw_http2_send_frame(TfwHttp2Ctx *ctx, TfwFrameHdr *hdr, TfwStr *data) tfw_http2_pack_frame_header(buf, hdr); - TFW_DBG2("Preparing HTTP/2 message with %lu bytes data\n", data->len); + 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))) @@ -282,7 +294,6 @@ static int tfw_http2_stream_verify(TfwHttp2Ctx *ctx) { int stream = ctx->hdr.stream_id; - int err_code = 0; /* * TODO: check of Stream existence and stream's current state; @@ -300,8 +311,8 @@ tfw_http2_stream_verify(TfwHttp2Ctx *ctx) * be called (below). */ - if (stream == -1) - return tfw_http2_conn_terminate(ctx, err_code); + if (!stream) + return tfw_http2_conn_terminate(ctx, FRAME_ECODE_PROTO); return 0; } @@ -319,9 +330,12 @@ tfw_http2_recv_priority(TfwHttp2Ctx *ctx) static int tfw_http2_headers_pri_process(TfwHttp2Ctx *ctx) { - BUG_ON(!(ctx->hdr.flags & HTTP2_F_PRIORITY)); + TfwFramePri *pri = &ctx->priority; + TfwFrameHdr *hdr = &ctx->hdr; - tfw_http2_unpack_priority(&ctx->priority, ctx->rbuf); + BUG_ON(!(hdr->flags & HTTP2_F_PRIORITY)); + + tfw_http2_unpack_priority(pri, ctx->rbuf); if (tfw_http2_stream_verify(ctx)) return T_BAD; @@ -330,18 +344,122 @@ tfw_http2_headers_pri_process(TfwHttp2Ctx *ctx) ctx->state = HTTP2_RECV_HEADER; } + 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); + SET_TO_READ(ctx); return T_OK; } +static TfwStream * +tfw_http2_stream_create(TfwHttp2Ctx *ctx, unsigned int id) +{ + TfwStream *stream, *dep = NULL; + TfwFramePri *pri = &ctx->priority; + bool excl = pri->exclusive; + + stream = kzalloc(sizeof(TfwStream), GFP_ATOMIC); + if (unlikely(!stream)) + return NULL; + + RB_CLEAR_NODE(&stream->node); + stream->id = id; + stream->state = HTTP2_STREAM_OPENED; + stream->weight = pri->weight; + + if (tfw_http2_find_stream_dep(&ctx->sched, pri->stream_id, &dep)) + goto out_free; + + if (tfw_http2_add_stream(&ctx->sched, stream)) + goto out_free; + + tfw_http2_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; + +out_free: + kfree(stream); + + return NULL; +} + +static inline void +tfw_http2_stream_delete(TfwHttp2Ctx *ctx, TfwStream *stream) +{ + +} + static int -tfw_http2_headers_check(TfwHttp2Ctx *ctx) +tfw_http2_headers_process(TfwHttp2Ctx *ctx) { + TfwFrameHdr *hdr = &ctx->hdr; + TfwStream *stream = tfw_http2_find_stream(&ctx->sched, hdr->stream_id); + + T_DBG3("%s: stream_id=%u, stream=[%p]\n", __func__, hdr->stream_id, + stream); + + if (!stream) { + /* + * Streams initiated by client must use odd-numbered identifiers + * (see RFC 7540 section 5.1.1). + */ + if (!(hdr->stream_id & 0x1)) { + T_DBG("Invalid ID of new stream: initiated by server\n"); + goto term; + } + + if (ctx->priority.stream_id == hdr->stream_id) { + T_DBG("Invalid dependency: new stream with %u depends" + " on itself\n", ctx->priority.stream_id); + goto term; + } + + if (ctx->lstream_id >= hdr->stream_id) { + T_DBG("Invalid ID of new stream: %u already in use, %u" + " last initiated\n", hdr->stream_id, + ctx->lstream_id); + goto term; + } + + if (ctx->lsettings.max_streams <= ctx->streams_num) { + T_DBG("Max streams number exceeded: %lu\n", ctx->streams_num); + goto term; + } + + if (!(stream = tfw_http2_stream_create(ctx, hdr->stream_id))) + return T_BAD; + + ctx->lstream_id = hdr->stream_id; + + return T_OK; + } + /* - * TODO: check END_HEADERS and other flags, stream and - * request/response verification etc. + * TODO: verification of stream's state is needed. + * NOTE: in some cases - if we haven't found the stream (since it had + * been closed and removed already) or if we have found the stream but + * it is in 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 in case of already removed streams, perhaps we should store + * closed streams - for some predefined period of time or just limiting + * the amount of closed stored streams. */ + return T_OK; + +term: + return tfw_http2_conn_terminate(ctx, FRAME_ECODE_PROTO); } static int @@ -363,37 +481,84 @@ tfw_http2_wnd_update_process(TfwHttp2Ctx *ctx) return T_OK; } -static int +static inline void tfw_http2_priority_process(TfwHttp2Ctx *ctx) { + TfwFrameHdr *hdr = &ctx->hdr; TfwFramePri *pri = &ctx->priority; tfw_http2_unpack_priority(pri, ctx->rbuf); - T_DBG3("%s: parsed, stream_id=%u, weight=%hu, excl=%hhu\n", - __func__, pri->stream_id, pri->weight, pri->exclusive); - /* - * TODO: apply stream prioritization. - */ - return T_OK; + 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_http2_change_stream_dep(&ctx->sched, hdr->stream_id, pri->stream_id, + pri->weight, pri->exclusive); } static int tfw_http2_rst_stream_process(TfwHttp2Ctx *ctx) { - unsigned int err_code = ntohl(*(unsigned int *)ctx->rbuf); + TfwFrameHdr *hdr = &ctx->hdr; + TfwStream *stream = tfw_http2_find_stream(&ctx->sched, hdr->stream_id); + /* - * TODO: check @hdr->stream_id, check stream existence, - * and close the specified stream. + * TODO: see note in @tfw_http2_headers_process(). */ - if (err_code) + if (!stream) return T_BAD; + T_DBG3("%s: parsed, stream_id=%u, stream=[%p], err_code=%u\n", __func__, + hdr->stream_id, stream, ntohl(*(unsigned int *)ctx->rbuf)); + + --ctx->streams_num; + + tfw_http2_remove_stream_dep(&ctx->sched, stream); + tfw_http2_remove_stream(&ctx->sched, stream); + + kfree(stream); + return T_OK; } + static int -tfw_http2_apply_settings_entry(unsigned short id, unsigned int val) +tfw_http2_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. */ @@ -403,10 +568,11 @@ tfw_http2_apply_settings_entry(unsigned short id, unsigned int val) static void tfw_http2_settings_ack_process(TfwHttp2Ctx *ctx) { + T_DBG3("%s: parsed, stream_id=%u, flags=%hhu\n", __func__, + ctx->hdr.stream_id, ctx->hdr.flags); /* * TODO: apply settings ACK. */ - return; } static int @@ -418,7 +584,7 @@ tfw_http2_settings_process(TfwHttp2Ctx *ctx) T_DBG3("%s: entry parsed, id=%hu, val=%u\n", __func__, id, val); - if (tfw_http2_apply_settings_entry(id, val)) + if (tfw_http2_apply_settings_entry(&ctx->rsettings, id, val)) return T_BAD; ctx->to_read = hdr->length ? FRAME_STNGS_ENTRY_SIZE : 0; @@ -431,17 +597,17 @@ static int tfw_http2_goaway_process(TfwHttp2Ctx *ctx) { unsigned int err_code = ntohl(*(unsigned int *)&ctx->rbuf[4]); + unsigned int last_id = ntohl(*(unsigned int *)ctx->rbuf) & FRAME_STREAM_ID_MASK; - ctx->lstream_id = ntohl(*(unsigned int *)ctx->rbuf) & FRAME_STREAM_ID_MASK; - - T_DBG3("%s: parsed, last_stream_id=%u, err_code=%u\n", __func__, - ctx->lstream_id, err_code); + T_DBG3("%s: parsed, last_id=%u, err_code=%u\n", __func__, + last_id, err_code); /* - * TODO: close streams with @id greater than @ctx->lstream_id + * TODO: close streams with @id greater than @last_id */ if (err_code) T_LOG("HTTP/2 connection is closed by client with error code:" - " %u\n", err_code); + " %u, ID of last processed stream: %u\n", err_code, + last_id); SET_TO_READ(ctx); return T_OK; } @@ -519,16 +685,14 @@ tfw_http2_frame_type_process(TfwHttp2Ctx *ctx) switch (hdr->type) { case HTTP2_DATA: BUG_ON(PAYLOAD(ctx)); - if (!ctx->hdr.length) + if (!hdr->stream_id) { + err_code = FRAME_ECODE_PROTO; goto out_term; - - if (tfw_http2_stream_verify(ctx)) - return T_BAD; - if (ctx->state == HTTP2_IGNORE_FRAME_DATA) { - SET_TO_READ(ctx); - return T_OK; } + if (!hdr->length) + goto out_term; + ctx->data_off = FRAME_HEADER_SIZE; if (hdr->flags & HTTP2_F_PADDED) { @@ -545,16 +709,14 @@ tfw_http2_frame_type_process(TfwHttp2Ctx *ctx) case HTTP2_HEADERS: BUG_ON(PAYLOAD(ctx)); - if (!ctx->hdr.length) + if (!hdr->stream_id) { + err_code = FRAME_ECODE_PROTO; goto out_term; - - if (tfw_http2_headers_check(ctx)) - return T_BAD; - if (ctx->state == HTTP2_IGNORE_FRAME_DATA) { - SET_TO_READ(ctx); - return T_OK; } + if (!hdr->length) + goto out_term; + ctx->data_off = FRAME_HEADER_SIZE; if (hdr->flags & HTTP2_F_PADDED) { @@ -568,17 +730,19 @@ tfw_http2_frame_type_process(TfwHttp2Ctx *ctx) if (hdr->flags & HTTP2_F_PRIORITY) return tfw_http2_recv_priority(ctx); - if (tfw_http2_stream_verify(ctx)) - return T_BAD; - if (ctx->state != HTTP2_IGNORE_FRAME_DATA) - ctx->state = HTTP2_RECV_HEADER; + ctx->state = HTTP2_RECV_HEADER; SET_TO_READ(ctx); return T_OK; case HTTP2_PRIORITY: if (!PAYLOAD(ctx)) { - if (ctx->hdr.length != FRAME_PRIORITY_SIZE) + if (!hdr->stream_id) { + err_code = FRAME_ECODE_PROTO; + goto out_term; + } + + if (hdr->length != FRAME_PRIORITY_SIZE) goto out_term; if (tfw_http2_stream_verify(ctx)) @@ -590,11 +754,12 @@ tfw_http2_frame_type_process(TfwHttp2Ctx *ctx) return T_OK; } - return tfw_http2_priority_process(ctx); + tfw_http2_priority_process(ctx); + return T_OK; case HTTP2_WINDOW_UPDATE: if (!PAYLOAD(ctx)) { - if (ctx->hdr.length != FRAME_SRVC1_SIZE) + if (hdr->length != FRAME_SRVC1_SIZE) goto out_term; ctx->state = HTTP2_RECV_FRAME_SERVICE; @@ -641,11 +806,11 @@ tfw_http2_frame_type_process(TfwHttp2Ctx *ctx) case HTTP2_PING: if (!PAYLOAD(ctx)) { - if (ctx->hdr.stream_id) { + if (hdr->stream_id) { err_code = FRAME_ECODE_PROTO; goto out_term; } - if (ctx->hdr.length != FRAME_SRVC2_SIZE) + if (hdr->length != FRAME_SRVC2_SIZE) goto out_term; ctx->state = HTTP2_RECV_FRAME_SERVICE; @@ -659,7 +824,12 @@ tfw_http2_frame_type_process(TfwHttp2Ctx *ctx) case HTTP2_RST_STREAM: if (!PAYLOAD(ctx)) { - if (ctx->hdr.length != FRAME_SRVC1_SIZE) + if (hdr->stream_id || ctx->lstream_id < hdr->stream_id) + { + err_code = FRAME_ECODE_PROTO; + goto out_term; + } + if (hdr->length != FRAME_SRVC1_SIZE) goto out_term; ctx->state = HTTP2_RECV_FRAME_SERVICE; @@ -667,14 +837,20 @@ tfw_http2_frame_type_process(TfwHttp2Ctx *ctx) return T_OK; } - return tfw_http2_rst_stream_process(ctx); + if (tfw_http2_rst_stream_process(ctx)) { + err_code = FRAME_ECODE_PROTO; + goto out_term; + } + + return T_OK; + case HTTP2_GOAWAY: BUG_ON(PAYLOAD(ctx)); - if (ctx->hdr.stream_id) { + if (hdr->stream_id) { err_code = FRAME_ECODE_PROTO; goto out_term; } - if (ctx->hdr.length < FRAME_SRVC2_SIZE) + if (hdr->length < FRAME_SRVC2_SIZE) goto out_term; ctx->state = HTTP2_RECV_FRAME_GOAWAY; @@ -833,7 +1009,7 @@ tfw_http2_frame_recv(void *data, unsigned char *buf, size_t len, T_FSM_STATE(HTTP2_RECV_HEADER) { FRAME_FSM_READ(ctx->to_read); - if (tfw_http2_headers_check(ctx)) + if (tfw_http2_headers_process(ctx)) FRAME_FSM_EXIT(T_DROP); FRAME_FSM_EXIT(T_OK); @@ -864,7 +1040,7 @@ tfw_http2_frame_recv(void *data, unsigned char *buf, size_t len, * 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 - * be appear during framing context initialization: + * appear during framing context initialization: * 1. On fully received service (non-app) frames and fully received app * frames without padding - context must be reset; * 2. On fully received app frames with padding - context must not be @@ -1015,3 +1191,17 @@ tfw_http2_frame_process(void *c, TfwFsmData *data) ss_skb_queue_purge(&h2->skb_head); return r; } + +void +tfw_http2_settings_init(TfwHttp2Ctx *ctx) +{ + TfwSettings *lset = &ctx->lsettings; + TfwSettings *rset = &ctx->rsettings; + + lset->hdr_tbl_sz = rset->hdr_tbl_sz = 1 << 12; + lset->push = rset->push = 1; + lset->max_streams = rset->max_streams = 0xffffffff; + lset->wnd_sz = rset->wnd_sz = (1 << 16) - 1; + lset->max_frame_sz = rset->max_frame_sz = 1 << 14; + lset->max_lhdr_sz = rset->max_lhdr_sz = UINT_MAX; +} diff --git a/tempesta_fw/http_frame.h b/tempesta_fw/http_frame.h index c2f7af4f9..b3c97c939 100644 --- a/tempesta_fw/http_frame.h +++ b/tempesta_fw/http_frame.h @@ -1,8 +1,7 @@ /** * Tempesta FW * - * Copyright (C) 2014 NatSys Lab. (info@natsys-lab.com). - * Copyright (C) 2015-2019 Tempesta Technologies, Inc. + * 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 @@ -22,6 +21,7 @@ #define __HTTP_FRAME__ #include "connection.h" +#include "http_stream.h" #define FRAME_HEADER_SIZE 9 @@ -75,15 +75,45 @@ typedef struct { 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. * * @conn - pointer to corresponding connection instance; + * @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; * @__off - offset to reinitialize processing context; * @to_read - indicates how much data of HTTP/2 frame should * be read on next FSM @state; - * @lstream_id - highest id of stream processed by peer (GOAWAY frame); * @skb_head - collected list of processed skbs containing HTTP/2 frames; * @hdr - unpacked data from header of currently processed frame; * @state - current FSM state of HTTP/2 processing context; @@ -98,9 +128,13 @@ typedef struct { */ struct tfw_http2_ctx_t { TfwConn *conn; + TfwSettings lsettings; + TfwSettings rsettings; + unsigned long streams_num; + TfwStreamSched sched; + unsigned int lstream_id; char __off[0]; int to_read; - unsigned int lstream_id; struct sk_buff *skb_head; TfwFrameHdr hdr; TfwFrameState state; @@ -112,5 +146,6 @@ struct tfw_http2_ctx_t { }; int tfw_http2_frame_process(void *c, TfwFsmData *data); +void tfw_http2_settings_init(TfwHttp2Ctx *ctx); #endif /* __HTTP_FRAME__ */ diff --git a/tempesta_fw/http_stream.c b/tempesta_fw/http_stream.c new file mode 100644 index 000000000..19d13b49c --- /dev/null +++ b/tempesta_fw/http_stream.c @@ -0,0 +1,126 @@ +/** + * 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 + +#include "http_stream.h" + +TfwStream * +tfw_http2_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; +} + +int +tfw_http2_add_stream(TfwStreamSched *sched, TfwStream *new_stream) +{ + unsigned int id = new_stream->id; + 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 -EEXIST; + } + } + + rb_link_node(&new_stream->node, parent, new); + rb_insert_color(&new_stream->node, &sched->streams); + + return 0; +} + +void +tfw_http2_remove_stream(TfwStreamSched *sched, TfwStream *stream) +{ + rb_erase(&stream->node, &sched->streams); +} + +void +tfw_http2_streams_cleanup(TfwStreamSched *sched) +{ + TfwStream *cur, *next; + + rbtree_postorder_for_each_entry_safe(cur, next, &sched->streams, node) { + kfree(cur); + } +} + +int +tfw_http2_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_http2_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_http2_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. + */ +} + +void +tfw_http2_remove_stream_dep(TfwStreamSched *sched, TfwStream *stream) +{ + /* + * TODO: implement dependency/priority logic (according to RFC 7540 + * section 5.3) in context of #1196. + */ +} diff --git a/tempesta_fw/http_stream.h b/tempesta_fw/http_stream.h new file mode 100644 index 000000000..03746ceb3 --- /dev/null +++ b/tempesta_fw/http_stream.h @@ -0,0 +1,81 @@ +/** + * 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 + +#define TFW_STREAMS_TBL_HBITS 10 + +/** + * States for HTTP/2 streams processing. + */ +typedef enum { + HTTP2_STREAM_IDLE, + HTTP2_STREAM_LOC_RESERVED, + HTTP2_STREAM_REM_RESERVED, + HTTP2_STREAM_OPENED, + HTTP2_STREAM_LOC_HALF_CLOSED, + HTTP2_STREAM_REM_HALF_CLOSED, + HTTP2_STREAM_CLOSED +} TfwStreamState; + +/** + * 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; + * @weight - stream's priority weight; + */ +typedef struct { + struct rb_node node; + unsigned int id; + TfwStreamState state; + 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; + + +TfwStream *tfw_http2_find_stream(TfwStreamSched *sched, unsigned int id); +int tfw_http2_add_stream(TfwStreamSched *sched, TfwStream *stream); +void tfw_http2_remove_stream(TfwStreamSched *sched, TfwStream *stream); +void tfw_http2_streams_cleanup(TfwStreamSched *sched); +int tfw_http2_find_stream_dep(TfwStreamSched *sched, unsigned int id, + TfwStream **dep); +void tfw_http2_add_stream_dep(TfwStreamSched *sched, TfwStream *stream, + TfwStream *dep, bool excl); +void tfw_http2_change_stream_dep(TfwStreamSched *sched, unsigned int stream_id, + unsigned int new_dep, unsigned short new_weight, + bool excl); +void tfw_http2_remove_stream_dep(TfwStreamSched *sched, TfwStream *stream); + +#endif /* __HTTP_STREAM__ */ diff --git a/tempesta_fw/t/unit/test_http_sticky.c b/tempesta_fw/t/unit/test_http_sticky.c index bc5c9e6e5..e124b93c5 100644 --- a/tempesta_fw/t/unit/test_http_sticky.c +++ b/tempesta_fw/t/unit/test_http_sticky.c @@ -48,6 +48,7 @@ #include "sock_srv.c" #include "client.c" #include "http_limits.c" +#include "http_stream.c" #include "http_frame.c" #include "tls.c" diff --git a/tempesta_fw/tls.c b/tempesta_fw/tls.c index f0b819878..e21ae4a6a 100644 --- a/tempesta_fw/tls.c +++ b/tempesta_fw/tls.c @@ -79,8 +79,9 @@ tfw_tls_check_alloc_h2_context(void *c) return -ENOMEM; conn->h2 = h2; - h2->state = HTTP2_RECV_CLI_START_SEQ; h2->conn = c; + h2->state = HTTP2_RECV_CLI_START_SEQ; + tfw_http2_settings_init(h2); return 0; } @@ -537,8 +538,10 @@ tfw_tls_conn_dtor(void *c) kfree_skb(skb); } - if (h2) + if (h2) { + tfw_http2_streams_cleanup(&h2->sched); kfree(h2); + } ttls_ctx_clear(tls); tfw_cli_conn_release((TfwCliConn *)c); From 79fcd17d19d7b31005b444fe00f8659295037cb3 Mon Sep 17 00:00:00 2001 From: Alexander Ostapenko Date: Sun, 28 Apr 2019 21:14:19 +0700 Subject: [PATCH 08/17] Corrections according review comments (#309). --- tempesta_fw/http_frame.c | 78 +++++++++++++++++++++++----------------- tempesta_fw/http_frame.h | 4 +-- tls/tls_srv.c | 2 +- 3 files changed, 49 insertions(+), 35 deletions(-) diff --git a/tempesta_fw/http_frame.c b/tempesta_fw/http_frame.c index c53c5da2a..bb078d121 100644 --- a/tempesta_fw/http_frame.c +++ b/tempesta_fw/http_frame.c @@ -30,10 +30,12 @@ #define FRAME_CLI_MAGIC "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n" #define FRAME_CLI_MAGIC_LEN 24 -#define FRAME_SRVC1_SIZE 4 +#define FRAME_WND_UPDATE_SIZE 4 +#define FRAME_RST_STREAM_SIZE 4 #define FRAME_PRIORITY_SIZE 5 -#define FRAME_STNGS_ENTRY_SIZE 6 -#define FRAME_SRVC2_SIZE 8 +#define FRAME_SETTINGS_ENTRY_SIZE 6 +#define FRAME_PING_SIZE 8 +#define FRAME_GOAWAY_SIZE 8 #define FRAME_STREAM_ID_MASK ((1U << 31) - 1) @@ -56,8 +58,22 @@ typedef enum { * and GOAWAY frames to report the reasons of the stream or * connection error. */ -#define FRAME_ECODE_PROTO 0x1 -#define FRAME_ECODE_SIZE_ERROR 0x6 +typedef enum { + FRAME_ECODE_NO_ERROR, + FRAME_ECODE_PROTO, + FRAME_ECODE_INTERNAL, + FRAME_ECODE_FLOW_CONTROL, + FRAME_ECODE_SETTINGS_TIMEOUT, + FRAME_ECODE_STREAM_CLOSED, + FRAME_ECODE_SIZE, + FRAME_ECODE_REFUSED_STREAM, + FRAME_ECODE_CANCEL, + FRAME_ECODE_COMPRESSION, + FRAME_ECODE_CONNECT, + FRAME_ECODE_ENHANCE_YOUR_CALM, + FRAME_ECODE_INADEQUATE_SECURITY, + FRAME_ECODE_HTTP_1_1_REQUIRED +} TfwFrameErr; /* * HTTP/2 frame flags. Can be specified in frame's header and @@ -135,6 +151,7 @@ do { \ } 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); \ }) @@ -251,7 +268,7 @@ tfw_http2_send_ping(TfwHttp2Ctx *ctx) .flags = HTTP2_F_ACK }; - WARN_ON_ONCE(ctx->rlen != FRAME_SRVC2_SIZE); + WARN_ON_ONCE(ctx->rlen != FRAME_PING_SIZE); return tfw_http2_send_frame(ctx, &hdr, &data); @@ -286,8 +303,7 @@ tfw_http2_conn_terminate(TfwHttp2Ctx *ctx, int err_code) #define VERIFY_FRAME_SIZE(ctx) \ do { \ if ((ctx)->hdr.length <= 0) \ - return tfw_http2_conn_terminate(ctx, \ - FRAME_ECODE_SIZE_ERROR); \ + return tfw_http2_conn_terminate(ctx, FRAME_ECODE_SIZE); \ } while (0) static int @@ -587,7 +603,7 @@ tfw_http2_settings_process(TfwHttp2Ctx *ctx) if (tfw_http2_apply_settings_entry(&ctx->rsettings, id, val)) return T_BAD; - ctx->to_read = hdr->length ? FRAME_STNGS_ENTRY_SIZE : 0; + ctx->to_read = hdr->length ? FRAME_SETTINGS_ENTRY_SIZE : 0; hdr->length -= ctx->to_read; return T_OK; @@ -629,13 +645,13 @@ tfw_http2_first_settings_verify(TfwHttp2Ctx *ctx) err_code = FRAME_ECODE_PROTO; } - if (hdr->length && (hdr->length % FRAME_STNGS_ENTRY_SIZE)) - err_code = FRAME_ECODE_SIZE_ERROR; + if (hdr->length && (hdr->length % FRAME_SETTINGS_ENTRY_SIZE)) + err_code = FRAME_ECODE_SIZE; if (err_code) return tfw_http2_conn_terminate(ctx, err_code); - ctx->to_read = hdr->length ? FRAME_STNGS_ENTRY_SIZE : 0; + ctx->to_read = hdr->length ? FRAME_SETTINGS_ENTRY_SIZE : 0; hdr->length -= ctx->to_read; return T_OK; @@ -663,7 +679,7 @@ tfw_http2_frame_pad_process(TfwHttp2Ctx *ctx) break; default: - /* Only DATA and HEADERS frames cab be padded. */ + /* Only DATA and HEADERS frames can be padded. */ BUG(); } @@ -676,7 +692,7 @@ tfw_http2_frame_pad_process(TfwHttp2Ctx *ctx) static int tfw_http2_frame_type_process(TfwHttp2Ctx *ctx) { - int err_code = FRAME_ECODE_SIZE_ERROR; + int err_code = FRAME_ECODE_SIZE; TfwFrameHdr *hdr = &ctx->hdr; T_DBG3("%s: hdr->type=%hhu, ctx->state=%d\n", __func__, hdr->type, @@ -759,7 +775,7 @@ tfw_http2_frame_type_process(TfwHttp2Ctx *ctx) case HTTP2_WINDOW_UPDATE: if (!PAYLOAD(ctx)) { - if (hdr->length != FRAME_SRVC1_SIZE) + if (hdr->length != FRAME_WND_UPDATE_SIZE) goto out_term; ctx->state = HTTP2_RECV_FRAME_SERVICE; @@ -775,7 +791,7 @@ tfw_http2_frame_type_process(TfwHttp2Ctx *ctx) err_code = FRAME_ECODE_PROTO; goto out_term; } - if ((hdr->length % FRAME_STNGS_ENTRY_SIZE) + if ((hdr->length % FRAME_SETTINGS_ENTRY_SIZE) || ((hdr->flags & HTTP2_F_ACK) && hdr->length > 0)) { @@ -787,7 +803,7 @@ tfw_http2_frame_type_process(TfwHttp2Ctx *ctx) if (hdr->length) { ctx->state = HTTP2_RECV_FRAME_SETTINGS; - ctx->to_read = FRAME_STNGS_ENTRY_SIZE; + ctx->to_read = FRAME_SETTINGS_ENTRY_SIZE; hdr->length -= ctx->to_read; } else { /* @@ -810,7 +826,7 @@ tfw_http2_frame_type_process(TfwHttp2Ctx *ctx) err_code = FRAME_ECODE_PROTO; goto out_term; } - if (hdr->length != FRAME_SRVC2_SIZE) + if (hdr->length != FRAME_PING_SIZE) goto out_term; ctx->state = HTTP2_RECV_FRAME_SERVICE; @@ -829,7 +845,7 @@ tfw_http2_frame_type_process(TfwHttp2Ctx *ctx) err_code = FRAME_ECODE_PROTO; goto out_term; } - if (hdr->length != FRAME_SRVC1_SIZE) + if (hdr->length != FRAME_RST_STREAM_SIZE) goto out_term; ctx->state = HTTP2_RECV_FRAME_SERVICE; @@ -850,11 +866,11 @@ tfw_http2_frame_type_process(TfwHttp2Ctx *ctx) err_code = FRAME_ECODE_PROTO; goto out_term; } - if (hdr->length < FRAME_SRVC2_SIZE) + if (hdr->length < FRAME_GOAWAY_SIZE) goto out_term; ctx->state = HTTP2_RECV_FRAME_GOAWAY; - ctx->to_read = FRAME_SRVC2_SIZE; + ctx->to_read = FRAME_GOAWAY_SIZE; hdr->length -= ctx->to_read; return T_OK; @@ -943,7 +959,6 @@ tfw_http2_frame_recv(void *data, unsigned char *buf, size_t len, } T_FSM_STATE(HTTP2_RECV_FRAME_PADDED) { - BUG_ON(ctx->to_read > FRAME_HEADER_SIZE); FRAME_FSM_READ_SRVC(ctx->to_read); if (tfw_http2_frame_pad_process(ctx)) @@ -953,7 +968,6 @@ tfw_http2_frame_recv(void *data, unsigned char *buf, size_t len, } T_FSM_STATE(HTTP2_RECV_FRAME_SERVICE) { - BUG_ON(ctx->to_read > FRAME_HEADER_SIZE); FRAME_FSM_READ_SRVC(ctx->to_read); if (tfw_http2_frame_type_process(ctx)) @@ -963,7 +977,6 @@ tfw_http2_frame_recv(void *data, unsigned char *buf, size_t len, } T_FSM_STATE(HTTP2_RECV_FRAME_SETTINGS) { - BUG_ON(ctx->to_read > FRAME_HEADER_SIZE); FRAME_FSM_READ_SRVC(ctx->to_read); if (tfw_http2_settings_process(ctx)) @@ -979,7 +992,6 @@ tfw_http2_frame_recv(void *data, unsigned char *buf, size_t len, } T_FSM_STATE(HTTP2_RECV_FRAME_GOAWAY) { - BUG_ON(ctx->to_read > FRAME_HEADER_SIZE); FRAME_FSM_READ_SRVC(ctx->to_read); if (tfw_http2_goaway_process(ctx)) @@ -992,7 +1004,6 @@ tfw_http2_frame_recv(void *data, unsigned char *buf, size_t len, } T_FSM_STATE(HTTP2_RECV_HEADER_PRI) { - BUG_ON(ctx->to_read > FRAME_HEADER_SIZE); FRAME_FSM_READ_SRVC(ctx->to_read); if (tfw_http2_headers_pri_process(ctx)) @@ -1036,13 +1047,16 @@ tfw_http2_frame_recv(void *data, unsigned char *buf, size_t len, } /* - * Initialization of HTTP/2 framing context. Due to passing frames to + * 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; + * 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; @@ -1051,7 +1065,7 @@ tfw_http2_frame_recv(void *data, unsigned char *buf, size_t len, * the frame will be fully received. */ static inline void -tfw_http2_context_init(TfwHttp2Ctx *ctx, bool postponed) +tfw_http2_context_reinit(TfwHttp2Ctx *ctx, bool postponed) { if (!APP_FRAME(ctx) || (!postponed && !ctx->padlen)) { bzero_fast(ctx->__off, @@ -1139,10 +1153,10 @@ tfw_http2_frame_process(void *c, TfwFsmData *data) * 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 containing some padding in the frame's + * 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 splitted into separate skb (above). + * will be just split into separate skb (above). */ if (APP_FRAME(h2)) { while (unlikely(h2->skb_head->len <= h2->data_off)) { @@ -1177,7 +1191,7 @@ tfw_http2_frame_process(void *c, TfwFsmData *data) ss_skb_queue_purge(&h2->skb_head); } - tfw_http2_context_init(h2, r == T_POSTPONE); + tfw_http2_context_reinit(h2, r == T_POSTPONE); if (nskb) { skb = nskb; diff --git a/tempesta_fw/http_frame.h b/tempesta_fw/http_frame.h index b3c97c939..914779ae5 100644 --- a/tempesta_fw/http_frame.h +++ b/tempesta_fw/http_frame.h @@ -116,9 +116,9 @@ typedef struct { * be read on next FSM @state; * @skb_head - collected list of processed skbs containing HTTP/2 frames; * @hdr - unpacked data from header of currently processed frame; - * @state - current FSM state of HTTP/2 processing context; * @priority - unpacked data from priority part of payload of processed * HEADERS or PRIORITY frames; + * @state - current FSM state of HTTP/2 processing context; * @rlen - length of accumulated data in @rbuf; * @rbuf - buffer for data accumulation from frames headers and * payloads (for service frames) during frames processing; @@ -137,8 +137,8 @@ struct tfw_http2_ctx_t { int to_read; struct sk_buff *skb_head; TfwFrameHdr hdr; - TfwFrameState state; TfwFramePri priority; + TfwFrameState state; int rlen; unsigned char rbuf[FRAME_HEADER_SIZE]; unsigned char padlen; diff --git a/tls/tls_srv.c b/tls/tls_srv.c index 24bc32012..18773fd42 100644 --- a/tls/tls_srv.c +++ b/tls/tls_srv.c @@ -313,7 +313,7 @@ ttls_parse_alpn_ext(TlsCtx *tls, const unsigned char *buf, size_t len) const unsigned char *theirs, *start, *end; const ttls_alpn_proto *our; - /* If TLS processig is enabled, ALPN must be configured. */ + /* If TLS processing is enabled, ALPN must be configured. */ BUG_ON(!tls->conf->alpn_list); /* From 05bd19403b4e2b6d37f890f602ef69401e948c4f Mon Sep 17 00:00:00 2001 From: Alexander Ostapenko Date: Mon, 29 Apr 2019 15:18:58 +0700 Subject: [PATCH 09/17] Add processing for zero length of application frames payload (#309). --- tempesta_fw/http_frame.c | 98 +++++++++++++++++++++++----------------- 1 file changed, 56 insertions(+), 42 deletions(-) diff --git a/tempesta_fw/http_frame.c b/tempesta_fw/http_frame.c index bb078d121..c04e4a207 100644 --- a/tempesta_fw/http_frame.c +++ b/tempesta_fw/http_frame.c @@ -165,6 +165,17 @@ do { \ (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) @@ -302,7 +313,7 @@ tfw_http2_conn_terminate(TfwHttp2Ctx *ctx, int err_code) #define VERIFY_FRAME_SIZE(ctx) \ do { \ - if ((ctx)->hdr.length <= 0) \ + if ((ctx)->hdr.length < 0) \ return tfw_http2_conn_terminate(ctx, FRAME_ECODE_SIZE); \ } while (0) @@ -343,6 +354,16 @@ tfw_http2_recv_priority(TfwHttp2Ctx *ctx) return T_OK; } +static inline int +tfw_http2_recv_padded(TfwHttp2Ctx *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_http2_headers_pri_process(TfwHttp2Ctx *ctx) { @@ -353,18 +374,20 @@ tfw_http2_headers_pri_process(TfwHttp2Ctx *ctx) tfw_http2_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); + if (tfw_http2_stream_verify(ctx)) return T_BAD; - if (ctx->state != HTTP2_IGNORE_FRAME_DATA) { - ctx->data_off += FRAME_PRIORITY_SIZE; - ctx->state = HTTP2_RECV_HEADER; + if (ctx->state == HTTP2_IGNORE_FRAME_DATA) { + SET_TO_READ(ctx); + return T_OK; } - 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(ctx); + SET_TO_READ_VERIFY(ctx, HTTP2_RECV_HEADER); return T_OK; } @@ -667,6 +690,12 @@ tfw_http2_frame_pad_process(TfwHttp2Ctx *ctx) 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; @@ -706,21 +735,12 @@ tfw_http2_frame_type_process(TfwHttp2Ctx *ctx) goto out_term; } - if (!hdr->length) - goto out_term; - ctx->data_off = FRAME_HEADER_SIZE; - if (hdr->flags & HTTP2_F_PADDED) { - ctx->to_read = 1; - hdr->length -= ctx->to_read; - VERIFY_FRAME_SIZE(ctx); - ctx->state = HTTP2_RECV_FRAME_PADDED; - return T_OK; - } + if (hdr->flags & HTTP2_F_PADDED) + return tfw_http2_recv_padded(ctx); - ctx->state = HTTP2_RECV_DATA; - SET_TO_READ(ctx); + SET_TO_READ_VERIFY(ctx, HTTP2_RECV_DATA); return T_OK; case HTTP2_HEADERS: @@ -730,25 +750,15 @@ tfw_http2_frame_type_process(TfwHttp2Ctx *ctx) goto out_term; } - if (!hdr->length) - goto out_term; - ctx->data_off = FRAME_HEADER_SIZE; - if (hdr->flags & HTTP2_F_PADDED) { - ctx->to_read = 1; - hdr->length -= ctx->to_read; - VERIFY_FRAME_SIZE(ctx); - ctx->state = HTTP2_RECV_FRAME_PADDED; - return T_OK; - } + if (hdr->flags & HTTP2_F_PADDED) + return tfw_http2_recv_padded(ctx); if (hdr->flags & HTTP2_F_PRIORITY) return tfw_http2_recv_priority(ctx); - ctx->state = HTTP2_RECV_HEADER; - - SET_TO_READ(ctx); + SET_TO_READ_VERIFY(ctx, HTTP2_RECV_HEADER); return T_OK; case HTTP2_PRIORITY: @@ -878,12 +888,14 @@ tfw_http2_frame_type_process(TfwHttp2Ctx *ctx) BUG_ON(PAYLOAD(ctx)); if (tfw_http2_cont_check(ctx)) return T_BAD; - if (ctx->state != HTTP2_IGNORE_FRAME_DATA) - ctx->state = HTTP2_RECV_CONT; + if (ctx->state == HTTP2_IGNORE_FRAME_DATA) { + SET_TO_READ(ctx); + return T_OK; + } ctx->data_off = FRAME_HEADER_SIZE; - SET_TO_READ(ctx); + SET_TO_READ_VERIFY(ctx, HTTP2_RECV_CONT); return T_OK; default: @@ -964,7 +976,10 @@ tfw_http2_frame_recv(void *data, unsigned char *buf, size_t len, if (tfw_http2_frame_pad_process(ctx)) FRAME_FSM_EXIT(T_DROP); - FRAME_FSM_NEXT(); + if (ctx->to_read) + FRAME_FSM_NEXT(); + + FRAME_FSM_EXIT(T_OK); } T_FSM_STATE(HTTP2_RECV_FRAME_SERVICE) { @@ -1009,7 +1024,10 @@ tfw_http2_frame_recv(void *data, unsigned char *buf, size_t len, if (tfw_http2_headers_pri_process(ctx)) FRAME_FSM_EXIT(T_DROP); - FRAME_FSM_NEXT(); + if (ctx->to_read) + FRAME_FSM_NEXT(); + + FRAME_FSM_EXIT(T_OK); } T_FSM_STATE(HTTP2_RECV_DATA) { @@ -1028,10 +1046,6 @@ tfw_http2_frame_recv(void *data, unsigned char *buf, size_t len, T_FSM_STATE(HTTP2_RECV_CONT) { FRAME_FSM_READ(ctx->to_read); - - if ((r = tfw_http2_cont_check(ctx))) - FRAME_FSM_EXIT(T_DROP); - FRAME_FSM_EXIT(T_OK); } From 23d53db7a3627c608a10d18874e2f7a73cd4afc6 Mon Sep 17 00:00:00 2001 From: Alexander Ostapenko Date: Mon, 29 Apr 2019 16:43:49 +0700 Subject: [PATCH 10/17] Corrections according review comments (#309). --- tempesta_fw/http_frame.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/tempesta_fw/http_frame.c b/tempesta_fw/http_frame.c index c04e4a207..4fa450bda 100644 --- a/tempesta_fw/http_frame.c +++ b/tempesta_fw/http_frame.c @@ -28,8 +28,8 @@ #include "http_frame.h" -#define FRAME_CLI_MAGIC "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n" -#define FRAME_CLI_MAGIC_LEN 24 +#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 @@ -931,8 +931,10 @@ tfw_http2_frame_recv(void *data, unsigned char *buf, size_t len, T_FSM_START(ctx->state) { T_FSM_STATE(HTTP2_RECV_CLI_START_SEQ) { - FRAME_FSM_READ_LAMBDA(FRAME_CLI_MAGIC_LEN, { - if (memcmp_fast(FRAME_CLI_MAGIC + ctx->rlen, p, n)) { + 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); From 5b93ee88f307e406d3511bdd9661a497a77344da Mon Sep 17 00:00:00 2001 From: Alexander Ostapenko Date: Wed, 8 May 2019 00:27:57 +0700 Subject: [PATCH 11/17] Implementation of HTTP/2 Stream FSM (#309). --- Makefile | 2 + tempesta_fw/http_frame.c | 668 ++++++++++++++++++++++++-------------- tempesta_fw/http_frame.h | 35 +- tempesta_fw/http_stream.c | 298 ++++++++++++++++- tempesta_fw/http_stream.h | 49 ++- 5 files changed, 780 insertions(+), 272 deletions(-) diff --git a/Makefile b/Makefile index d2fcf86f1..d69d48eaa 100644 --- a/Makefile +++ b/Makefile @@ -36,9 +36,11 @@ 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/tempesta_fw/http_frame.c b/tempesta_fw/http_frame.c index 4fa450bda..fcfc6e15f 100644 --- a/tempesta_fw/http_frame.c +++ b/tempesta_fw/http_frame.c @@ -27,7 +27,6 @@ #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 @@ -37,56 +36,10 @@ #define FRAME_PING_SIZE 8 #define FRAME_GOAWAY_SIZE 8 -#define FRAME_STREAM_ID_MASK ((1U << 31) - 1) - -/* 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; +#define SREAM_ID_SIZE 4 +#define ERR_CODE_SIZE 4 -/* - * 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 { - FRAME_ECODE_NO_ERROR, - FRAME_ECODE_PROTO, - FRAME_ECODE_INTERNAL, - FRAME_ECODE_FLOW_CONTROL, - FRAME_ECODE_SETTINGS_TIMEOUT, - FRAME_ECODE_STREAM_CLOSED, - FRAME_ECODE_SIZE, - FRAME_ECODE_REFUSED_STREAM, - FRAME_ECODE_CANCEL, - FRAME_ECODE_COMPRESSION, - FRAME_ECODE_CONNECT, - FRAME_ECODE_ENHANCE_YOUR_CALM, - FRAME_ECODE_INADEQUATE_SECURITY, - FRAME_ECODE_HTTP_1_1_REQUIRED -} TfwFrameErr; - -/* - * 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; +#define FRAME_STREAM_ID_MASK ((1U << 31) - 1) /* * IDs for SETTINGS parameters of HTTP/2 connection (RFC 7540 @@ -182,6 +135,31 @@ do { \ #define PAYLOAD(ctx) \ ((ctx)->state != HTTP2_RECV_FRAME_HEADER) +#define STREAM_RECV_PROCESS(ctx, hdr) \ +({ \ + TfwStreamFsmRes res; \ + TfwHttp2Err err = HTTP2_ECODE_NO_ERROR; \ + BUG_ON(!(ctx)->cur_stream); \ + if ((res = tfw_http2_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_http2_conn_terminate((ctx), err); \ + return T_DROP; \ + } else if (res == STREAM_FSM_RES_TERM_STREAM) { \ + return tfw_http2_stream_terminate((ctx), \ + (hdr)->stream_id, \ + &(ctx)->cur_stream, \ + err); \ + } \ + return T_OK; \ + } \ +}) + static inline void tfw_http2_unpack_frame_header(TfwFrameHdr *hdr, const unsigned char *buf) { @@ -228,7 +206,8 @@ tfw_http2_unpack_priority(TfwFramePri *pri, const unsigned char *buf) * written in this procedure. */ static int -tfw_http2_send_frame(TfwHttp2Ctx *ctx, TfwFrameHdr *hdr, TfwStr *data) +__tfw_http2_send_frame(TfwHttp2Ctx *ctx, TfwFrameHdr *hdr, TfwStr *data, + bool close) { int r; TfwMsgIter it; @@ -254,13 +233,39 @@ tfw_http2_send_frame(TfwHttp2Ctx *ctx, TfwFrameHdr *hdr, TfwStr *data) if ((r = tfw_msg_write(&it, data))) goto err; - return tfw_connection_send(ctx->conn, &msg);; + if (close) + msg.ss_flags |= SS_F_CONN_CLOSE; + + if ((r = tfw_connection_send(ctx->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(ctx->conn) |= Conn_Stop; + + return 0; err: ss_skb_queue_purge(&msg.skb_head); return r; } +static inline int +tfw_http2_send_frame(TfwHttp2Ctx *ctx, TfwFrameHdr *hdr, TfwStr *data) +{ + return __tfw_http2_send_frame(ctx, hdr, data, false); +} + +static inline int +tfw_http2_send_frame_close(TfwHttp2Ctx *ctx, TfwFrameHdr *hdr, TfwStr *data) +{ + return __tfw_http2_send_frame(ctx, hdr, data, true); +} + static inline int tfw_http2_send_ping(TfwHttp2Ctx *ctx) { @@ -299,51 +304,105 @@ tfw_http2_send_settings(TfwHttp2Ctx *ctx, bool ack) return tfw_http2_send_frame(ctx, &hdr, &data); } -static int -tfw_http2_conn_terminate(TfwHttp2Ctx *ctx, int err_code) +static inline int +tfw_http2_send_goaway(TfwHttp2Ctx *ctx, TfwHttp2Err err_code) { - ctx->state = HTTP2_IGNORE_FRAME_DATA; - /* - * TODO: send appropriately filled GOAWAY frame, set - * Conn_Stop flag for connection, and close it (possibly - * via SS_F_CONN_CLOSE flag). - */ - return 0; + 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_STREAM_ID_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_http2_send_frame_close(ctx, &hdr, &data); } -#define VERIFY_FRAME_SIZE(ctx) \ -do { \ - if ((ctx)->hdr.length < 0) \ - return tfw_http2_conn_terminate(ctx, FRAME_ECODE_SIZE); \ -} while (0) +static inline int +tfw_http2_send_rst_stream(TfwHttp2Ctx *ctx, unsigned int id, + TfwHttp2Err 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 + }; -static int -tfw_http2_stream_verify(TfwHttp2Ctx *ctx) + *(unsigned int *)buf = htonl(err_code); + + return tfw_http2_send_frame(ctx, &hdr, &data); +} + +static inline int +tfw_http2_conn_terminate(TfwHttp2Ctx *ctx, TfwHttp2Err err_code) { - int stream = ctx->hdr.stream_id; + return tfw_http2_send_goaway(ctx, err_code); +} - /* - * TODO: check of Stream existence and stream's current state; - * this function (and similar verification procedures) may have - * three results: - * 1. Continue normal frame receiving (if stream exists and is - * in appropriate state); - * 2. Set 'ctx->state = HTTP2_IGNORE_FRAME_DATA' to skip current - * frame's payload (if stream is in not appropriate state but - * we can continue operate with current connection); - * 3. Set 'ctx->state = HTTP2_IGNORE_ALL' to skip current and all - * incoming frames in future until connection will be closed - * (if we cannot operate with current connection and it must be - * closed); in this case @tfw_http2_connection_terminate() must - * be called (below). - */ +static inline int +tfw_http2_stream_terminate(TfwHttp2Ctx *ctx, unsigned int id, + TfwStream **stream, TfwHttp2Err err_code) +{ + if (stream && *stream) { + --ctx->streams_num; + tfw_http2_stop_stream(&ctx->sched, stream); + } - if (!stream) - return tfw_http2_conn_terminate(ctx, FRAME_ECODE_PROTO); + return tfw_http2_send_rst_stream(ctx, id, err_code); +} - return 0; +static inline void +tfw_http2_check_closed_stream(TfwHttp2Ctx *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_http2_stream_is_closed(ctx->cur_stream)) { + --ctx->streams_num; + tfw_http2_stop_stream(&ctx->sched, &ctx->cur_stream); + } } +#define VERIFY_FRAME_SIZE(ctx) \ +do { \ + if ((ctx)->hdr.length < 0) { \ + tfw_http2_conn_terminate(ctx, HTTP2_ECODE_SIZE); \ + return T_DROP; \ + } \ +} while (0) + static inline int tfw_http2_recv_priority(TfwHttp2Ctx *ctx) { @@ -378,13 +437,6 @@ tfw_http2_headers_pri_process(TfwHttp2Ctx *ctx) " excl=%hhu\n", __func__, hdr->stream_id, pri->stream_id, pri->weight, pri->exclusive); - if (tfw_http2_stream_verify(ctx)) - return T_BAD; - if (ctx->state == HTTP2_IGNORE_FRAME_DATA) { - SET_TO_READ(ctx); - return T_OK; - } - ctx->data_off += FRAME_PRIORITY_SIZE; SET_TO_READ_VERIFY(ctx, HTTP2_RECV_HEADER); @@ -398,20 +450,11 @@ tfw_http2_stream_create(TfwHttp2Ctx *ctx, unsigned int id) TfwFramePri *pri = &ctx->priority; bool excl = pri->exclusive; - stream = kzalloc(sizeof(TfwStream), GFP_ATOMIC); - if (unlikely(!stream)) - return NULL; - - RB_CLEAR_NODE(&stream->node); - stream->id = id; - stream->state = HTTP2_STREAM_OPENED; - stream->weight = pri->weight; - if (tfw_http2_find_stream_dep(&ctx->sched, pri->stream_id, &dep)) - goto out_free; + return NULL; - if (tfw_http2_add_stream(&ctx->sched, stream)) - goto out_free; + if (!(stream = tfw_http2_add_stream(&ctx->sched, id, pri->weight))) + return NULL; tfw_http2_add_stream_dep(&ctx->sched, stream, dep, excl); @@ -423,96 +466,65 @@ tfw_http2_stream_create(TfwHttp2Ctx *ctx, unsigned int id) ctx->streams_num, pri->stream_id, dep, pri->exclusive); return stream; - -out_free: - kfree(stream); - - return NULL; -} - -static inline void -tfw_http2_stream_delete(TfwHttp2Ctx *ctx, TfwStream *stream) -{ - } static int tfw_http2_headers_process(TfwHttp2Ctx *ctx) { TfwFrameHdr *hdr = &ctx->hdr; - TfwStream *stream = tfw_http2_find_stream(&ctx->sched, hdr->stream_id); - - T_DBG3("%s: stream_id=%u, stream=[%p]\n", __func__, hdr->stream_id, - stream); - if (!stream) { - /* - * Streams initiated by client must use odd-numbered identifiers - * (see RFC 7540 section 5.1.1). - */ - if (!(hdr->stream_id & 0x1)) { - T_DBG("Invalid ID of new stream: initiated by server\n"); - goto term; - } - - if (ctx->priority.stream_id == hdr->stream_id) { - T_DBG("Invalid dependency: new stream with %u depends" - " on itself\n", ctx->priority.stream_id); - goto term; - } - - if (ctx->lstream_id >= hdr->stream_id) { - T_DBG("Invalid ID of new stream: %u already in use, %u" - " last initiated\n", hdr->stream_id, - ctx->lstream_id); - goto term; - } + 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); - if (ctx->lsettings.max_streams <= ctx->streams_num) { - T_DBG("Max streams number exceeded: %lu\n", ctx->streams_num); - goto term; - } + ctx->state = HTTP2_IGNORE_FRAME_DATA; - if (!(stream = tfw_http2_stream_create(ctx, hdr->stream_id))) - return T_BAD; + return tfw_http2_stream_terminate(ctx, hdr->stream_id, + &ctx->cur_stream, + HTTP2_ECODE_PROTO); + } + if (!ctx->cur_stream) { + ctx->cur_stream = tfw_http2_stream_create(ctx, hdr->stream_id); + if (!ctx->cur_stream) + return T_DROP; ctx->lstream_id = hdr->stream_id; - - return T_OK; } - /* - * TODO: verification of stream's state is needed. - * NOTE: in some cases - if we haven't found the stream (since it had - * been closed and removed already) or if we have found the stream but - * it is in 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 in case of already removed streams, perhaps we should store - * closed streams - for some predefined period of time or just limiting - * the amount of closed stored streams. + * 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); - return T_OK; + tfw_http2_check_closed_stream(ctx); -term: - return tfw_http2_conn_terminate(ctx, FRAME_ECODE_PROTO); -} - -static int -tfw_http2_cont_check(TfwHttp2Ctx *ctx) -{ - /* - * TODO: check END_HEADERS flag. - */ return T_OK; } static int tfw_http2_wnd_update_process(TfwHttp2Ctx *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_http2_stream_terminate(ctx, hdr->stream_id, + &ctx->cur_stream, + HTTP2_ECODE_PROTO); + tfw_http2_conn_terminate(ctx, HTTP2_ECODE_PROTO); + return T_DROP; + } /* * TODO: apply new window size for entire connection or * particular stream; ignore until #498. @@ -520,7 +532,7 @@ tfw_http2_wnd_update_process(TfwHttp2Ctx *ctx) return T_OK; } -static inline void +static inline int tfw_http2_priority_process(TfwHttp2Ctx *ctx) { TfwFrameHdr *hdr = &ctx->hdr; @@ -528,37 +540,39 @@ tfw_http2_priority_process(TfwHttp2Ctx *ctx) tfw_http2_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_http2_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_http2_change_stream_dep(&ctx->sched, hdr->stream_id, pri->stream_id, pri->weight, pri->exclusive); + return T_OK; } -static int +static void tfw_http2_rst_stream_process(TfwHttp2Ctx *ctx) { - TfwFrameHdr *hdr = &ctx->hdr; - TfwStream *stream = tfw_http2_find_stream(&ctx->sched, hdr->stream_id); - - /* - * TODO: see note in @tfw_http2_headers_process(). - */ - if (!stream) - return T_BAD; - - T_DBG3("%s: parsed, stream_id=%u, stream=[%p], err_code=%u\n", __func__, - hdr->stream_id, stream, ntohl(*(unsigned int *)ctx->rbuf)); + 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_http2_remove_stream_dep(&ctx->sched, stream); - tfw_http2_remove_stream(&ctx->sched, stream); - - kfree(stream); - - return T_OK; + tfw_http2_stop_stream(&ctx->sched, &ctx->cur_stream); } static int @@ -635,13 +649,23 @@ tfw_http2_settings_process(TfwHttp2Ctx *ctx) static int tfw_http2_goaway_process(TfwHttp2Ctx *ctx) { - unsigned int err_code = ntohl(*(unsigned int *)&ctx->rbuf[4]); - unsigned int last_id = ntohl(*(unsigned int *)ctx->rbuf) & FRAME_STREAM_ID_MASK; + 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: close streams with @id greater than @last_id + * 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:" @@ -665,14 +689,16 @@ tfw_http2_first_settings_verify(TfwHttp2Ctx *ctx) || (hdr->flags & HTTP2_F_ACK) || hdr->stream_id) { - err_code = FRAME_ECODE_PROTO; + err_code = HTTP2_ECODE_PROTO; } if (hdr->length && (hdr->length % FRAME_SETTINGS_ENTRY_SIZE)) - err_code = FRAME_ECODE_SIZE; + err_code = HTTP2_ECODE_SIZE; - if (err_code) - return tfw_http2_conn_terminate(ctx, err_code); + if (err_code) { + tfw_http2_conn_terminate(ctx, err_code); + return T_DROP; + } ctx->to_read = hdr->length ? FRAME_SETTINGS_ENTRY_SIZE : 0; hdr->length -= ctx->to_read; @@ -717,11 +743,21 @@ tfw_http2_frame_pad_process(TfwHttp2Ctx *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_http2_headers_process() + * procedure). + */ static int tfw_http2_frame_type_process(TfwHttp2Ctx *ctx) { - int err_code = FRAME_ECODE_SIZE; + TfwHttp2Err err_code = HTTP2_ECODE_SIZE; TfwFrameHdr *hdr = &ctx->hdr; T_DBG3("%s: hdr->type=%hhu, ctx->state=%d\n", __func__, hdr->type, @@ -731,10 +767,32 @@ tfw_http2_frame_type_process(TfwHttp2Ctx *ctx) case HTTP2_DATA: BUG_ON(PAYLOAD(ctx)); if (!hdr->stream_id) { - err_code = FRAME_ECODE_PROTO; - goto out_term; + 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_http2_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; + } + + STREAM_RECV_PROCESS(ctx, hdr); + ctx->data_off = FRAME_HEADER_SIZE; if (hdr->flags & HTTP2_F_PADDED) @@ -746,8 +804,65 @@ tfw_http2_frame_type_process(TfwHttp2Ctx *ctx) case HTTP2_HEADERS: BUG_ON(PAYLOAD(ctx)); if (!hdr->stream_id) { - err_code = FRAME_ECODE_PROTO; - goto out_term; + 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_http2_find_stream(&ctx->sched, + hdr->stream_id); + if (!ctx->cur_stream) { + /* + * 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); + err_code = HTTP2_ECODE_PROTO; + goto conn_term; + } + /* + * 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"); + err_code = HTTP2_ECODE_PROTO; + goto conn_term; + } + /* + * Endpoints must not exceed the limit set by their peer + * (see RFC 7540 section 5.1.2 for details). + */ + if (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_http2_stream_terminate(ctx, + hdr->stream_id, + NULL, + HTTP2_ECODE_REFUSED); + } } ctx->data_off = FRAME_HEADER_SIZE; @@ -764,29 +879,53 @@ tfw_http2_frame_type_process(TfwHttp2Ctx *ctx) case HTTP2_PRIORITY: if (!PAYLOAD(ctx)) { if (!hdr->stream_id) { - err_code = FRAME_ECODE_PROTO; - goto out_term; + err_code = HTTP2_ECODE_PROTO; + goto conn_term; } - if (hdr->length != FRAME_PRIORITY_SIZE) - goto out_term; + ctx->cur_stream = tfw_http2_find_stream(&ctx->sched, + hdr->stream_id); + if (hdr->length != FRAME_PRIORITY_SIZE) { + SET_TO_READ(ctx); + return tfw_http2_stream_terminate(ctx, + hdr->stream_id, + &ctx->cur_stream, + HTTP2_ECODE_SIZE); + } - if (tfw_http2_stream_verify(ctx)) - return T_BAD; - if (ctx->state != HTTP2_IGNORE_FRAME_DATA) - ctx->state = HTTP2_RECV_FRAME_SERVICE; + if (ctx->cur_stream) + STREAM_RECV_PROCESS(ctx, hdr); + ctx->state = HTTP2_RECV_FRAME_SERVICE; SET_TO_READ(ctx); return T_OK; } - tfw_http2_priority_process(ctx); - return T_OK; + return tfw_http2_priority_process(ctx); case HTTP2_WINDOW_UPDATE: if (!PAYLOAD(ctx)) { if (hdr->length != FRAME_WND_UPDATE_SIZE) - goto out_term; + 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_http2_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_SERVICE; SET_TO_READ(ctx); @@ -798,14 +937,14 @@ tfw_http2_frame_type_process(TfwHttp2Ctx *ctx) case HTTP2_SETTINGS: BUG_ON(PAYLOAD(ctx)); if (hdr->stream_id) { - err_code = FRAME_ECODE_PROTO; - goto out_term; + err_code = HTTP2_ECODE_PROTO; + goto conn_term; } if ((hdr->length % FRAME_SETTINGS_ENTRY_SIZE) || ((hdr->flags & HTTP2_F_ACK) && hdr->length > 0)) { - goto out_term; + goto conn_term; } if (hdr->flags & HTTP2_F_ACK) @@ -827,17 +966,17 @@ tfw_http2_frame_type_process(TfwHttp2Ctx *ctx) case HTTP2_PUSH_PROMISE: /* Client cannot push (RFC 7540 section 8.2). */ - err_code = FRAME_ECODE_PROTO; - goto out_term; + err_code = HTTP2_ECODE_PROTO; + goto conn_term; case HTTP2_PING: if (!PAYLOAD(ctx)) { if (hdr->stream_id) { - err_code = FRAME_ECODE_PROTO; - goto out_term; + err_code = HTTP2_ECODE_PROTO; + goto conn_term; } if (hdr->length != FRAME_PING_SIZE) - goto out_term; + goto conn_term; ctx->state = HTTP2_RECV_FRAME_SERVICE; SET_TO_READ(ctx); @@ -850,34 +989,48 @@ tfw_http2_frame_type_process(TfwHttp2Ctx *ctx) case HTTP2_RST_STREAM: if (!PAYLOAD(ctx)) { - if (hdr->stream_id || ctx->lstream_id < hdr->stream_id) + if (!hdr->stream_id) { - err_code = FRAME_ECODE_PROTO; - goto out_term; + err_code = HTTP2_ECODE_PROTO; + goto conn_term; } if (hdr->length != FRAME_RST_STREAM_SIZE) - goto out_term; + 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_http2_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_SERVICE; SET_TO_READ(ctx); return T_OK; } - if (tfw_http2_rst_stream_process(ctx)) { - err_code = FRAME_ECODE_PROTO; - goto out_term; - } - + tfw_http2_rst_stream_process(ctx); return T_OK; case HTTP2_GOAWAY: BUG_ON(PAYLOAD(ctx)); if (hdr->stream_id) { - err_code = FRAME_ECODE_PROTO; - goto out_term; + err_code = HTTP2_ECODE_PROTO; + goto conn_term; } if (hdr->length < FRAME_GOAWAY_SIZE) - goto out_term; + goto conn_term; ctx->state = HTTP2_RECV_FRAME_GOAWAY; ctx->to_read = FRAME_GOAWAY_SIZE; @@ -886,13 +1039,28 @@ tfw_http2_frame_type_process(TfwHttp2Ctx *ctx) case HTTP2_CONTINUATION: BUG_ON(PAYLOAD(ctx)); - if (tfw_http2_cont_check(ctx)) - return T_BAD; - if (ctx->state == HTTP2_IGNORE_FRAME_DATA) { - SET_TO_READ(ctx); - return T_OK; + 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_http2_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); @@ -900,9 +1068,8 @@ tfw_http2_frame_type_process(TfwHttp2Ctx *ctx) default: /* - * Possible extension types of frames are not covered - * (yet) in this procedure. On current stage we just - * ignore such frames. + * 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); @@ -911,9 +1078,10 @@ tfw_http2_frame_type_process(TfwHttp2Ctx *ctx) return T_OK; } -out_term: +conn_term: BUG_ON(!err_code); - return tfw_http2_conn_terminate(ctx, err_code); + tfw_http2_conn_terminate(ctx, err_code); + return T_DROP; } /** @@ -1034,6 +1202,9 @@ tfw_http2_frame_recv(void *data, unsigned char *buf, size_t len, T_FSM_STATE(HTTP2_RECV_DATA) { FRAME_FSM_READ(ctx->to_read); + + tfw_http2_check_closed_stream(ctx); + FRAME_FSM_EXIT(T_OK); } @@ -1048,6 +1219,9 @@ tfw_http2_frame_recv(void *data, unsigned char *buf, size_t len, T_FSM_STATE(HTTP2_RECV_CONT) { FRAME_FSM_READ(ctx->to_read); + + tfw_http2_check_closed_stream(ctx); + FRAME_FSM_EXIT(T_OK); } diff --git a/tempesta_fw/http_frame.h b/tempesta_fw/http_frame.h index 914779ae5..0ec15008d 100644 --- a/tempesta_fw/http_frame.h +++ b/tempesta_fw/http_frame.h @@ -23,7 +23,7 @@ #include "connection.h" #include "http_stream.h" -#define FRAME_HEADER_SIZE 9 +#define FRAME_HEADER_SIZE 9 /** * FSM states for HTTP/2 frames processing. @@ -39,11 +39,40 @@ typedef enum { HTTP2_RECV_HEADER_PRI, HTTP2_IGNORE_FRAME_DATA, __HTTP2_RECV_FRAME_APP, - HTTP2_RECV_HEADER = __HTTP2_RECV_FRAME_APP, + HTTP2_RECV_HEADER = __HTTP2_RECV_FRAME_APP, HTTP2_RECV_CONT, HTTP2_RECV_DATA } TfwFrameState; +/** + * 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 @@ -115,6 +144,7 @@ typedef struct { * @to_read - indicates how much data of HTTP/2 frame should * be read on next FSM @state; * @skb_head - collected list of processed skbs containing HTTP/2 frames; + * @cur_stream - found stream for the frame currently being processed; * @hdr - unpacked data from header of currently processed frame; * @priority - unpacked data from priority part of payload of processed * HEADERS or PRIORITY frames; @@ -136,6 +166,7 @@ struct tfw_http2_ctx_t { char __off[0]; int to_read; struct sk_buff *skb_head; + TfwStream *cur_stream; TfwFrameHdr hdr; TfwFramePri priority; TfwFrameState state; diff --git a/tempesta_fw/http_stream.c b/tempesta_fw/http_stream.c index 19d13b49c..28565f8c1 100644 --- a/tempesta_fw/http_stream.c +++ b/tempesta_fw/http_stream.c @@ -20,7 +20,272 @@ #include -#include "http_stream.h" +#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; + +/* + * Stream FSM processing during frames receipt (see RFC 7540 section + * 5.1 for details). + */ +TfwStreamFsmRes +tfw_http2_stream_fsm(TfwStream *stream, unsigned char type, unsigned char flags, + TfwHttp2Err *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_http2_stream_is_closed(TfwStream *stream) +{ + return stream->state == HTTP2_STREAM_CLOSED; +} + +static inline void +tfw_http2_init_stream(TfwStream *stream, unsigned int id, unsigned short weight) +{ + RB_CLEAR_NODE(&stream->node); + stream->id = id; + stream->state = HTTP2_STREAM_OPENED; + stream->weight = weight ? weight : HTTP2_DEF_WEIGHT; +} TfwStream * tfw_http2_find_stream(TfwStreamSched *sched, unsigned int id) @@ -41,10 +306,11 @@ tfw_http2_find_stream(TfwStreamSched *sched, unsigned int id) return NULL; } -int -tfw_http2_add_stream(TfwStreamSched *sched, TfwStream *new_stream) +TfwStream * +tfw_http2_add_stream(TfwStreamSched *sched, unsigned int id, + unsigned short weight) { - unsigned int id = new_stream->id; + TfwStream *new_stream; struct rb_node **new = &sched->streams.rb_node; struct rb_node *parent = NULL; @@ -58,17 +324,23 @@ tfw_http2_add_stream(TfwStreamSched *sched, TfwStream *new_stream) new = &parent->rb_right; } else { WARN_ON_ONCE(1); - return -EEXIST; + return NULL; } } + new_stream = kzalloc(sizeof(TfwStream), GFP_ATOMIC); + if (unlikely(!new_stream)) + return NULL; + + tfw_http2_init_stream(new_stream, id, weight); + rb_link_node(&new_stream->node, parent, new); rb_insert_color(&new_stream->node, &sched->streams); - return 0; + return new_stream; } -void +static inline void tfw_http2_remove_stream(TfwStreamSched *sched, TfwStream *stream) { rb_erase(&stream->node, &sched->streams); @@ -116,7 +388,7 @@ tfw_http2_change_stream_dep(TfwStreamSched *sched, unsigned int stream_id, */ } -void +static void tfw_http2_remove_stream_dep(TfwStreamSched *sched, TfwStream *stream) { /* @@ -124,3 +396,13 @@ tfw_http2_remove_stream_dep(TfwStreamSched *sched, TfwStream *stream) * section 5.3) in context of #1196. */ } + +void +tfw_http2_stop_stream(TfwStreamSched *sched, TfwStream **stream) +{ + tfw_http2_remove_stream_dep(sched, *stream); + tfw_http2_remove_stream(sched, *stream); + + kfree(*stream); + *stream = NULL; +} diff --git a/tempesta_fw/http_stream.h b/tempesta_fw/http_stream.h index 03746ceb3..46f0854f0 100644 --- a/tempesta_fw/http_stream.h +++ b/tempesta_fw/http_stream.h @@ -22,20 +22,37 @@ #include -#define TFW_STREAMS_TBL_HBITS 10 +/** + * 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; /** - * States for HTTP/2 streams processing. + * 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_STREAM_IDLE, - HTTP2_STREAM_LOC_RESERVED, - HTTP2_STREAM_REM_RESERVED, - HTTP2_STREAM_OPENED, - HTTP2_STREAM_LOC_HALF_CLOSED, - HTTP2_STREAM_REM_HALF_CLOSED, - HTTP2_STREAM_CLOSED -} TfwStreamState; + HTTP2_ECODE_NO_ERROR = 0, + HTTP2_ECODE_PROTO, + HTTP2_ECODE_INTERNAL, + HTTP2_ECODE_FLOW_CONTROL, + 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 +} TfwHttp2Err; /** * Representation of HTTP/2 stream entity. @@ -48,7 +65,7 @@ typedef enum { typedef struct { struct rb_node node; unsigned int id; - TfwStreamState state; + int state; unsigned short weight; } TfwStream; @@ -64,10 +81,12 @@ typedef struct { struct rb_root streams; } TfwStreamSched; - +TfwStreamFsmRes tfw_http2_stream_fsm(TfwStream *stream, unsigned char type, + unsigned char flags, TfwHttp2Err *err); +bool tfw_http2_stream_is_closed(TfwStream *stream); TfwStream *tfw_http2_find_stream(TfwStreamSched *sched, unsigned int id); -int tfw_http2_add_stream(TfwStreamSched *sched, TfwStream *stream); -void tfw_http2_remove_stream(TfwStreamSched *sched, TfwStream *stream); +TfwStream *tfw_http2_add_stream(TfwStreamSched *sched, unsigned int id, + unsigned short weight); void tfw_http2_streams_cleanup(TfwStreamSched *sched); int tfw_http2_find_stream_dep(TfwStreamSched *sched, unsigned int id, TfwStream **dep); @@ -76,6 +95,6 @@ void tfw_http2_add_stream_dep(TfwStreamSched *sched, TfwStream *stream, void tfw_http2_change_stream_dep(TfwStreamSched *sched, unsigned int stream_id, unsigned int new_dep, unsigned short new_weight, bool excl); -void tfw_http2_remove_stream_dep(TfwStreamSched *sched, TfwStream *stream); +void tfw_http2_stop_stream(TfwStreamSched *sched, TfwStream **stream); #endif /* __HTTP_STREAM__ */ From 1e8be284bf5b72cab67114aa06ef7e6e5346e586 Mon Sep 17 00:00:00 2001 From: Alexander Ostapenko Date: Wed, 8 May 2019 02:09:10 +0700 Subject: [PATCH 12/17] Split header and payload processing for service frames (#309). --- tempesta_fw/http_frame.c | 200 +++++++++++++++++++-------------------- tempesta_fw/http_frame.h | 5 +- 2 files changed, 104 insertions(+), 101 deletions(-) diff --git a/tempesta_fw/http_frame.c b/tempesta_fw/http_frame.c index fcfc6e15f..ecbbcf227 100644 --- a/tempesta_fw/http_frame.c +++ b/tempesta_fw/http_frame.c @@ -132,9 +132,6 @@ do { \ #define APP_FRAME(ctx) \ ((ctx)->state >= __HTTP2_RECV_FRAME_APP) -#define PAYLOAD(ctx) \ - ((ctx)->state != HTTP2_RECV_FRAME_HEADER) - #define STREAM_RECV_PROCESS(ctx, hdr) \ ({ \ TfwStreamFsmRes res; \ @@ -765,7 +762,6 @@ tfw_http2_frame_type_process(TfwHttp2Ctx *ctx) switch (hdr->type) { case HTTP2_DATA: - BUG_ON(PAYLOAD(ctx)); if (!hdr->stream_id) { err_code = HTTP2_ECODE_PROTO; goto conn_term; @@ -802,7 +798,6 @@ tfw_http2_frame_type_process(TfwHttp2Ctx *ctx) return T_OK; case HTTP2_HEADERS: - BUG_ON(PAYLOAD(ctx)); if (!hdr->stream_id) { err_code = HTTP2_ECODE_PROTO; goto conn_term; @@ -877,65 +872,55 @@ tfw_http2_frame_type_process(TfwHttp2Ctx *ctx) return T_OK; case HTTP2_PRIORITY: - if (!PAYLOAD(ctx)) { - if (!hdr->stream_id) { - err_code = HTTP2_ECODE_PROTO; - goto conn_term; - } - - ctx->cur_stream = tfw_http2_find_stream(&ctx->sched, - hdr->stream_id); - if (hdr->length != FRAME_PRIORITY_SIZE) { - SET_TO_READ(ctx); - return tfw_http2_stream_terminate(ctx, - hdr->stream_id, - &ctx->cur_stream, - HTTP2_ECODE_SIZE); - } - - if (ctx->cur_stream) - STREAM_RECV_PROCESS(ctx, hdr); + if (!hdr->stream_id) { + err_code = HTTP2_ECODE_PROTO; + goto conn_term; + } - ctx->state = HTTP2_RECV_FRAME_SERVICE; + ctx->cur_stream = tfw_http2_find_stream(&ctx->sched, + hdr->stream_id); + if (hdr->length != FRAME_PRIORITY_SIZE) { SET_TO_READ(ctx); - return T_OK; + return tfw_http2_stream_terminate(ctx, hdr->stream_id, + &ctx->cur_stream, + HTTP2_ECODE_SIZE); } - return tfw_http2_priority_process(ctx); + if (ctx->cur_stream) + STREAM_RECV_PROCESS(ctx, hdr); - case HTTP2_WINDOW_UPDATE: - if (!PAYLOAD(ctx)) { - 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; - } + ctx->state = HTTP2_RECV_FRAME_PRIORITY; + SET_TO_READ(ctx); + return T_OK; - if (hdr->stream_id) { - ctx->cur_stream = tfw_http2_find_stream(&ctx->sched, - hdr->stream_id); - if (!ctx->cur_stream) { - err_code = HTTP2_ECODE_CLOSED; - goto conn_term; - } + 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; + } - STREAM_RECV_PROCESS(ctx, hdr); + if (hdr->stream_id) { + ctx->cur_stream = tfw_http2_find_stream(&ctx->sched, + hdr->stream_id); + if (!ctx->cur_stream) { + err_code = HTTP2_ECODE_CLOSED; + goto conn_term; } - ctx->state = HTTP2_RECV_FRAME_SERVICE; - SET_TO_READ(ctx); - return T_OK; + STREAM_RECV_PROCESS(ctx, hdr); } - return tfw_http2_wnd_update_process(ctx); + ctx->state = HTTP2_RECV_FRAME_WND_UPDATE; + SET_TO_READ(ctx); + return T_OK; case HTTP2_SETTINGS: - BUG_ON(PAYLOAD(ctx)); if (hdr->stream_id) { err_code = HTTP2_ECODE_PROTO; goto conn_term; @@ -970,61 +955,48 @@ tfw_http2_frame_type_process(TfwHttp2Ctx *ctx) goto conn_term; case HTTP2_PING: - if (!PAYLOAD(ctx)) { - 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_SERVICE; - SET_TO_READ(ctx); - return T_OK; + if (hdr->stream_id) { + err_code = HTTP2_ECODE_PROTO; + goto conn_term; } - if (!(hdr->flags & HTTP2_F_ACK)) - return tfw_http2_send_ping(ctx); + 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 (!PAYLOAD(ctx)) { - 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_http2_find_stream(&ctx->sched, - hdr->stream_id); - if (!ctx->cur_stream) { - err_code = HTTP2_ECODE_CLOSED; - goto conn_term; - } - - STREAM_RECV_PROCESS(ctx, hdr); + 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->state = HTTP2_RECV_FRAME_SERVICE; - SET_TO_READ(ctx); - return T_OK; + ctx->cur_stream = tfw_http2_find_stream(&ctx->sched, + hdr->stream_id); + if (!ctx->cur_stream) { + err_code = HTTP2_ECODE_CLOSED; + goto conn_term; } - tfw_http2_rst_stream_process(ctx); + STREAM_RECV_PROCESS(ctx, hdr); + + ctx->state = HTTP2_RECV_FRAME_RST_STREAM; + SET_TO_READ(ctx); return T_OK; case HTTP2_GOAWAY: - BUG_ON(PAYLOAD(ctx)); if (hdr->stream_id) { err_code = HTTP2_ECODE_PROTO; goto conn_term; @@ -1038,14 +1010,13 @@ tfw_http2_frame_type_process(TfwHttp2Ctx *ctx) return T_OK; case HTTP2_CONTINUATION: - BUG_ON(PAYLOAD(ctx)); 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). + * 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; @@ -1152,15 +1123,44 @@ tfw_http2_frame_recv(void *data, unsigned char *buf, size_t len, FRAME_FSM_EXIT(T_OK); } - T_FSM_STATE(HTTP2_RECV_FRAME_SERVICE) { + T_FSM_STATE(HTTP2_RECV_FRAME_PRIORITY) { FRAME_FSM_READ_SRVC(ctx->to_read); - if (tfw_http2_frame_type_process(ctx)) + if (tfw_http2_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_http2_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_http2_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_http2_rst_stream_process(ctx); + + FRAME_FSM_EXIT(T_OK); + } + T_FSM_STATE(HTTP2_RECV_FRAME_SETTINGS) { FRAME_FSM_READ_SRVC(ctx->to_read); diff --git a/tempesta_fw/http_frame.h b/tempesta_fw/http_frame.h index 0ec15008d..14bc5b639 100644 --- a/tempesta_fw/http_frame.h +++ b/tempesta_fw/http_frame.h @@ -32,7 +32,10 @@ typedef enum { HTTP2_RECV_FRAME_HEADER, HTTP2_RECV_CLI_START_SEQ, HTTP2_RECV_FIRST_SETTINGS, - HTTP2_RECV_FRAME_SERVICE, + 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, From 1d962dd15aaa7be9ced0ab7ec82d681a4e7363a1 Mon Sep 17 00:00:00 2001 From: Alexander Ostapenko Date: Mon, 13 May 2019 20:34:24 +0700 Subject: [PATCH 13/17] Add support for maximum window of Flow Control (#309). --- tempesta_fw/http_frame.c | 160 +++++++++++++++++++++++++++++++++++--- tempesta_fw/http_frame.h | 38 +++------ tempesta_fw/http_stream.c | 8 +- tempesta_fw/http_stream.h | 6 +- tempesta_fw/tls.c | 3 +- 5 files changed, 168 insertions(+), 47 deletions(-) diff --git a/tempesta_fw/http_frame.c b/tempesta_fw/http_frame.c index ecbbcf227..2dc48f771 100644 --- a/tempesta_fw/http_frame.c +++ b/tempesta_fw/http_frame.c @@ -35,13 +35,41 @@ #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 FRAME_STREAM_ID_MASK ((1U << 31) - 1) +#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). */ @@ -180,7 +208,7 @@ tfw_http2_pack_frame_header(unsigned char *p, const TfwFrameHdr *hdr) * Stream id must occupy not more than 31 bit and reserved bit * must be 0. */ - WARN_ON_ONCE((unsigned int)(hdr->stream_id & ~FRAME_STREAM_ID_MASK)); + WARN_ON_ONCE((unsigned int)(hdr->stream_id & FRAME_RESERVED_BIT_MASK)); *(unsigned int *)p = htonl(hdr->stream_id); } @@ -288,14 +316,72 @@ tfw_http2_send_ping(TfwHttp2Ctx *ctx) } static inline int -tfw_http2_send_settings(TfwHttp2Ctx *ctx, bool ack) +tfw_http2_send_wnd_update(TfwHttp2Ctx *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_http2_send_frame(ctx, &hdr, &data); +} + +static inline int +tfw_http2_send_settings_init(TfwHttp2Ctx *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_http2_send_frame(ctx, &hdr, &data); +} + +static inline int +tfw_http2_send_settings_ack(TfwHttp2Ctx *ctx) { TfwStr data = {}; TfwFrameHdr hdr = { .length = 0, .stream_id = 0, .type = HTTP2_SETTINGS, - .flags = ack ? HTTP2_F_ACK : 0 + .flags = HTTP2_F_ACK }; return tfw_http2_send_frame(ctx, &hdr, &data); @@ -322,7 +408,7 @@ tfw_http2_send_goaway(TfwHttp2Ctx *ctx, TfwHttp2Err err_code) .flags = 0 }; - WARN_ON_ONCE((unsigned int)(ctx->lstream_id & ~FRAME_STREAM_ID_MASK)); + 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) @@ -450,7 +536,9 @@ tfw_http2_stream_create(TfwHttp2Ctx *ctx, unsigned int id) if (tfw_http2_find_stream_dep(&ctx->sched, pri->stream_id, &dep)) return NULL; - if (!(stream = tfw_http2_add_stream(&ctx->sched, id, pri->weight))) + stream = tfw_http2_add_stream(&ctx->sched, id, pri->weight, + ctx->lsettings.wnd_sz); + if (!stream) return NULL; tfw_http2_add_stream_dep(&ctx->sched, stream, dep, excl); @@ -703,6 +791,41 @@ tfw_http2_first_settings_verify(TfwHttp2Ctx *ctx) return T_OK; } +static inline int +tfw_http2_flow_control(TfwHttp2Ctx *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_http2_send_wnd_update(ctx, stream->id, + lset->wnd_sz - stream->loc_wnd)) + { + return T_DROP; + } + + if (ctx->loc_wnd <= MAX_WND_SIZE / 2 + && tfw_http2_send_wnd_update(ctx, 0, MAX_WND_SIZE - ctx->loc_wnd)) + { + return T_DROP; + } + + return T_OK; +} + static int tfw_http2_frame_pad_process(TfwHttp2Ctx *ctx) { @@ -787,6 +910,9 @@ tfw_http2_frame_type_process(TfwHttp2Ctx *ctx) goto conn_term; } + if (tfw_http2_flow_control(ctx)) + return T_DROP; + STREAM_RECV_PROCESS(ctx, hdr); ctx->data_off = FRAME_HEADER_SIZE; @@ -1079,8 +1205,13 @@ tfw_http2_frame_recv(void *data, unsigned char *buf, size_t len, FRAME_FSM_EXIT(T_DROP); } }); - if (tfw_http2_send_settings(ctx, false)) + + if (tfw_http2_send_settings_init(ctx) + || tfw_http2_send_wnd_update(ctx, 0, + MAX_WND_SIZE - DEF_WND_SIZE)) + { FRAME_FSM_EXIT(T_DROP); + } FRAME_FSM_MOVE(HTTP2_RECV_FIRST_SETTINGS); } @@ -1170,7 +1301,7 @@ tfw_http2_frame_recv(void *data, unsigned char *buf, size_t len, if (ctx->to_read) FRAME_FSM_MOVE(HTTP2_RECV_FRAME_SETTINGS); - if (tfw_http2_send_settings(ctx, true)) + if (tfw_http2_send_settings_ack(ctx)) FRAME_FSM_EXIT(T_DROP); FRAME_FSM_EXIT(T_OK); @@ -1397,7 +1528,7 @@ tfw_http2_frame_process(void *c, TfwFsmData *data) } void -tfw_http2_settings_init(TfwHttp2Ctx *ctx) +tfw_http2_init(TfwHttp2Ctx *ctx) { TfwSettings *lset = &ctx->lsettings; TfwSettings *rset = &ctx->rsettings; @@ -1405,7 +1536,14 @@ tfw_http2_settings_init(TfwHttp2Ctx *ctx) lset->hdr_tbl_sz = rset->hdr_tbl_sz = 1 << 12; lset->push = rset->push = 1; lset->max_streams = rset->max_streams = 0xffffffff; - lset->wnd_sz = rset->wnd_sz = (1 << 16) - 1; 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; + + ctx->state = HTTP2_RECV_CLI_START_SEQ; + ctx->loc_wnd = MAX_WND_SIZE; } diff --git a/tempesta_fw/http_frame.h b/tempesta_fw/http_frame.h index 14bc5b639..c031cafad 100644 --- a/tempesta_fw/http_frame.h +++ b/tempesta_fw/http_frame.h @@ -25,28 +25,6 @@ #define FRAME_HEADER_SIZE 9 -/** - * 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; - /** * HTTP/2 frame types (RFC 7540 section 6). */ @@ -143,15 +121,16 @@ typedef struct { * @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; - * @to_read - indicates how much data of HTTP/2 frame should - * be read on next FSM @state; * @skb_head - collected list of processed skbs containing HTTP/2 frames; * @cur_stream - found stream for the frame currently being processed; - * @hdr - unpacked data from header of currently processed frame; * @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; @@ -166,13 +145,14 @@ struct tfw_http2_ctx_t { unsigned long streams_num; TfwStreamSched sched; unsigned int lstream_id; + unsigned int loc_wnd; char __off[0]; - int to_read; struct sk_buff *skb_head; TfwStream *cur_stream; - TfwFrameHdr hdr; TfwFramePri priority; - TfwFrameState state; + TfwFrameHdr hdr; + int state; + int to_read; int rlen; unsigned char rbuf[FRAME_HEADER_SIZE]; unsigned char padlen; @@ -180,6 +160,6 @@ struct tfw_http2_ctx_t { }; int tfw_http2_frame_process(void *c, TfwFsmData *data); -void tfw_http2_settings_init(TfwHttp2Ctx *ctx); +void tfw_http2_init(TfwHttp2Ctx *ctx); #endif /* __HTTP_FRAME__ */ diff --git a/tempesta_fw/http_stream.c b/tempesta_fw/http_stream.c index 28565f8c1..6c3246598 100644 --- a/tempesta_fw/http_stream.c +++ b/tempesta_fw/http_stream.c @@ -279,11 +279,13 @@ tfw_http2_stream_is_closed(TfwStream *stream) } static inline void -tfw_http2_init_stream(TfwStream *stream, unsigned int id, unsigned short weight) +tfw_http2_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; } @@ -308,7 +310,7 @@ tfw_http2_find_stream(TfwStreamSched *sched, unsigned int id) TfwStream * tfw_http2_add_stream(TfwStreamSched *sched, unsigned int id, - unsigned short weight) + unsigned short weight, unsigned int wnd) { TfwStream *new_stream; struct rb_node **new = &sched->streams.rb_node; @@ -332,7 +334,7 @@ tfw_http2_add_stream(TfwStreamSched *sched, unsigned int id, if (unlikely(!new_stream)) return NULL; - tfw_http2_init_stream(new_stream, id, weight); + tfw_http2_init_stream(new_stream, id, weight, wnd); rb_link_node(&new_stream->node, parent, new); rb_insert_color(&new_stream->node, &sched->streams); diff --git a/tempesta_fw/http_stream.h b/tempesta_fw/http_stream.h index 46f0854f0..43ebb33e9 100644 --- a/tempesta_fw/http_stream.h +++ b/tempesta_fw/http_stream.h @@ -41,7 +41,7 @@ typedef enum { HTTP2_ECODE_NO_ERROR = 0, HTTP2_ECODE_PROTO, HTTP2_ECODE_INTERNAL, - HTTP2_ECODE_FLOW_CONTROL, + HTTP2_ECODE_FLOW, HTTP2_ECODE_SETTINGS_TIMEOUT, HTTP2_ECODE_CLOSED, HTTP2_ECODE_SIZE, @@ -60,12 +60,14 @@ typedef enum { * @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; @@ -86,7 +88,7 @@ TfwStreamFsmRes tfw_http2_stream_fsm(TfwStream *stream, unsigned char type, bool tfw_http2_stream_is_closed(TfwStream *stream); TfwStream *tfw_http2_find_stream(TfwStreamSched *sched, unsigned int id); TfwStream *tfw_http2_add_stream(TfwStreamSched *sched, unsigned int id, - unsigned short weight); + unsigned short weight, unsigned int wnd); void tfw_http2_streams_cleanup(TfwStreamSched *sched); int tfw_http2_find_stream_dep(TfwStreamSched *sched, unsigned int id, TfwStream **dep); diff --git a/tempesta_fw/tls.c b/tempesta_fw/tls.c index e21ae4a6a..bb6df8d01 100644 --- a/tempesta_fw/tls.c +++ b/tempesta_fw/tls.c @@ -80,8 +80,7 @@ tfw_tls_check_alloc_h2_context(void *c) conn->h2 = h2; h2->conn = c; - h2->state = HTTP2_RECV_CLI_START_SEQ; - tfw_http2_settings_init(h2); + tfw_http2_init(h2); return 0; } From b22c1f7032a20fdb1d6299e5bbf59d57f624f3b4 Mon Sep 17 00:00:00 2001 From: Alexander Ostapenko Date: Wed, 15 May 2019 21:37:40 +0700 Subject: [PATCH 14/17] Corrections according review comments (#309). 1. "http2 -> h2" renaming for functions and structures; 2. SLAB usage for allocations. --- tempesta_fw/connection.h | 4 +- tempesta_fw/http.c | 4 +- tempesta_fw/http_frame.c | 414 ++++++++++++++++++++++---------------- tempesta_fw/http_frame.h | 9 +- tempesta_fw/http_stream.c | 73 ++++--- tempesta_fw/http_stream.h | 34 ++-- tempesta_fw/http_types.h | 2 +- tempesta_fw/tls.c | 40 ++-- 8 files changed, 331 insertions(+), 249 deletions(-) diff --git a/tempesta_fw/connection.h b/tempesta_fw/connection.h index d30fe214e..60590050a 100644 --- a/tempesta_fw/connection.h +++ b/tempesta_fw/connection.h @@ -210,11 +210,11 @@ enum { typedef struct { TfwCliConn cli_conn; TlsCtx tls; - TfwHttp2Ctx *h2; + TfwH2Ctx *h2; } TfwTlsConn; #define tfw_tls_context(conn) (TlsCtx *)(&((TfwTlsConn *)conn)->tls) -#define tfw_http2_context(conn) ((TfwHttp2Ctx *)((TfwTlsConn *)conn)->h2) +#define tfw_h2_context(conn) ((TfwH2Ctx *)((TfwTlsConn *)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 4eae26506..f620b4b0c 100644 --- a/tempesta_fw/http.c +++ b/tempesta_fw/http.c @@ -3931,8 +3931,8 @@ 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_CONN_TLS((TfwConn *)conn) && tfw_http2_context(conn) - ? tfw_http2_frame_process(conn, data) + r = TFW_CONN_TLS((TfwConn *)conn) && tfw_h2_context(conn) + ? tfw_h2_frame_process(conn, data) : tfw_http_msg_process_generic(conn, data); } else { kfree(data->skb); diff --git a/tempesta_fw/http_frame.c b/tempesta_fw/http_frame.c index 2dc48f771..5f750902f 100644 --- a/tempesta_fw/http_frame.c +++ b/tempesta_fw/http_frame.c @@ -163,9 +163,9 @@ do { \ #define STREAM_RECV_PROCESS(ctx, hdr) \ ({ \ TfwStreamFsmRes res; \ - TfwHttp2Err err = HTTP2_ECODE_NO_ERROR; \ + TfwH2Err err = HTTP2_ECODE_NO_ERROR; \ BUG_ON(!(ctx)->cur_stream); \ - if ((res = tfw_http2_stream_fsm((ctx)->cur_stream, (hdr)->type, \ + 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," \ @@ -173,10 +173,10 @@ do { \ (ctx)->cur_stream->id, err); \ SET_TO_READ_VERIFY((ctx), HTTP2_IGNORE_FRAME_DATA); \ if (res == STREAM_FSM_RES_TERM_CONN) { \ - tfw_http2_conn_terminate((ctx), err); \ + tfw_h2_conn_terminate((ctx), err); \ return T_DROP; \ } else if (res == STREAM_FSM_RES_TERM_STREAM) { \ - return tfw_http2_stream_terminate((ctx), \ + return tfw_h2_stream_terminate((ctx), \ (hdr)->stream_id, \ &(ctx)->cur_stream, \ err); \ @@ -185,8 +185,80 @@ do { \ } \ }) +static struct kmem_cache *h2_ctx_cache; + +int +tfw_h2_init(void) +{ + int r = 0; + + h2_ctx_cache = kmem_cache_create("tfw_h2_ctx_cache", sizeof(TfwH2Ctx), + 0, 0, NULL); + if (!h2_ctx_cache) + return -ENOMEM; + + if ((r = tfw_h2_stream_cache_create())) { + kmem_cache_destroy(h2_ctx_cache); + return r; + } + + return 0; +} + +void +tfw_h2_cleanup(void) +{ + tfw_h2_stream_cache_destroy(); + kmem_cache_destroy(h2_ctx_cache); +} + +int +tfw_h2_context_create(TfwTlsConn *conn) +{ + TfwH2Ctx *ctx; + TfwSettings *lset; + TfwSettings *rset; + + ctx = kmem_cache_alloc(h2_ctx_cache, GFP_ATOMIC | __GFP_ZERO); + if (unlikely(!ctx)) + return -ENOMEM; + + conn->h2 = ctx; + ctx->conn = (TfwConn *)conn; + ctx->state = HTTP2_RECV_CLI_START_SEQ; + ctx->loc_wnd = MAX_WND_SIZE; + + lset = &ctx->lsettings; + rset = &ctx->rsettings; + + 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; + + return 0; +} + +void +tfw_h2_context_free(TfwTlsConn *conn) +{ + TfwH2Ctx *ctx = conn->h2; + + if (!ctx) + return; + + tfw_h2_streams_cleanup(&ctx->sched); + kmem_cache_free(h2_ctx_cache, ctx); +} + static inline void -tfw_http2_unpack_frame_header(TfwFrameHdr *hdr, const unsigned char *buf) +tfw_h2_unpack_frame_header(TfwFrameHdr *hdr, const unsigned char *buf) { hdr->length = ntohl(*(int *)buf) >> 8; hdr->type = buf[3]; @@ -198,7 +270,7 @@ tfw_http2_unpack_frame_header(TfwFrameHdr *hdr, const unsigned char *buf) } static inline void -tfw_http2_pack_frame_header(unsigned char *p, const TfwFrameHdr *hdr) +tfw_h2_pack_frame_header(unsigned char *p, const TfwFrameHdr *hdr) { *(unsigned int *)p = htonl((unsigned int)(hdr->length << 8)); p += 3; @@ -214,7 +286,7 @@ tfw_http2_pack_frame_header(unsigned char *p, const TfwFrameHdr *hdr) } static inline void -tfw_http2_unpack_priority(TfwFramePri *pri, const unsigned char *buf) +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; @@ -231,8 +303,7 @@ tfw_http2_unpack_priority(TfwFramePri *pri, const unsigned char *buf) * written in this procedure. */ static int -__tfw_http2_send_frame(TfwHttp2Ctx *ctx, TfwFrameHdr *hdr, TfwStr *data, - bool close) +__tfw_h2_send_frame(TfwH2Ctx *ctx, TfwFrameHdr *hdr, TfwStr *data, bool close) { int r; TfwMsgIter it; @@ -247,7 +318,7 @@ __tfw_http2_send_frame(TfwHttp2Ctx *ctx, TfwFrameHdr *hdr, TfwStr *data, if (data != hdr_str) data->len += FRAME_HEADER_SIZE; - tfw_http2_pack_frame_header(buf, hdr); + tfw_h2_pack_frame_header(buf, hdr); T_DBG2("Preparing HTTP/2 message with %lu bytes data\n", data->len); @@ -280,19 +351,19 @@ __tfw_http2_send_frame(TfwHttp2Ctx *ctx, TfwFrameHdr *hdr, TfwStr *data, } static inline int -tfw_http2_send_frame(TfwHttp2Ctx *ctx, TfwFrameHdr *hdr, TfwStr *data) +tfw_h2_send_frame(TfwH2Ctx *ctx, TfwFrameHdr *hdr, TfwStr *data) { - return __tfw_http2_send_frame(ctx, hdr, data, false); + return __tfw_h2_send_frame(ctx, hdr, data, false); } static inline int -tfw_http2_send_frame_close(TfwHttp2Ctx *ctx, TfwFrameHdr *hdr, TfwStr *data) +tfw_h2_send_frame_close(TfwH2Ctx *ctx, TfwFrameHdr *hdr, TfwStr *data) { - return __tfw_http2_send_frame(ctx, hdr, data, true); + return __tfw_h2_send_frame(ctx, hdr, data, true); } static inline int -tfw_http2_send_ping(TfwHttp2Ctx *ctx) +tfw_h2_send_ping(TfwH2Ctx *ctx) { TfwStr data = { .chunks = (TfwStr []){ @@ -311,13 +382,12 @@ tfw_http2_send_ping(TfwHttp2Ctx *ctx) WARN_ON_ONCE(ctx->rlen != FRAME_PING_SIZE); - return tfw_http2_send_frame(ctx, &hdr, &data); + return tfw_h2_send_frame(ctx, &hdr, &data); } static inline int -tfw_http2_send_wnd_update(TfwHttp2Ctx *ctx, unsigned int id, - unsigned int wnd_incr) +tfw_h2_send_wnd_update(TfwH2Ctx *ctx, unsigned int id, unsigned int wnd_incr) { unsigned char incr_buf[WND_INCREMENT_SIZE]; TfwStr data = { @@ -339,11 +409,11 @@ tfw_http2_send_wnd_update(TfwHttp2Ctx *ctx, unsigned int id, *(unsigned int *)incr_buf = htonl(wnd_incr); - return tfw_http2_send_frame(ctx, &hdr, &data); + return tfw_h2_send_frame(ctx, &hdr, &data); } static inline int -tfw_http2_send_settings_init(TfwHttp2Ctx *ctx) +tfw_h2_send_settings_init(TfwH2Ctx *ctx) { unsigned char key_buf[SETTINGS_KEY_SIZE]; unsigned char val_buf[SETTINGS_VAL_SIZE]; @@ -370,11 +440,11 @@ tfw_http2_send_settings_init(TfwHttp2Ctx *ctx) *(unsigned short *)key_buf = htons(HTTP2_SETTINGS_INIT_WND_SIZE); *(unsigned int *)val_buf = htonl(ctx->lsettings.wnd_sz); - return tfw_http2_send_frame(ctx, &hdr, &data); + return tfw_h2_send_frame(ctx, &hdr, &data); } static inline int -tfw_http2_send_settings_ack(TfwHttp2Ctx *ctx) +tfw_h2_send_settings_ack(TfwH2Ctx *ctx) { TfwStr data = {}; TfwFrameHdr hdr = { @@ -384,11 +454,11 @@ tfw_http2_send_settings_ack(TfwHttp2Ctx *ctx) .flags = HTTP2_F_ACK }; - return tfw_http2_send_frame(ctx, &hdr, &data); + return tfw_h2_send_frame(ctx, &hdr, &data); } static inline int -tfw_http2_send_goaway(TfwHttp2Ctx *ctx, TfwHttp2Err err_code) +tfw_h2_send_goaway(TfwH2Ctx *ctx, TfwH2Err err_code) { unsigned char id_buf[SREAM_ID_SIZE]; unsigned char err_buf[ERR_CODE_SIZE]; @@ -417,12 +487,11 @@ tfw_http2_send_goaway(TfwHttp2Ctx *ctx, TfwHttp2Err err_code) *(unsigned int *)id_buf = htonl(ctx->lstream_id); *(unsigned int *)err_buf = htonl(err_code); - return tfw_http2_send_frame_close(ctx, &hdr, &data); + return tfw_h2_send_frame_close(ctx, &hdr, &data); } static inline int -tfw_http2_send_rst_stream(TfwHttp2Ctx *ctx, unsigned int id, - TfwHttp2Err err_code) +tfw_h2_send_rst_stream(TfwH2Ctx *ctx, unsigned int id, TfwH2Err err_code) { unsigned char buf[ERR_CODE_SIZE]; TfwStr data = { @@ -442,29 +511,29 @@ tfw_http2_send_rst_stream(TfwHttp2Ctx *ctx, unsigned int id, *(unsigned int *)buf = htonl(err_code); - return tfw_http2_send_frame(ctx, &hdr, &data); + return tfw_h2_send_frame(ctx, &hdr, &data); } static inline int -tfw_http2_conn_terminate(TfwHttp2Ctx *ctx, TfwHttp2Err err_code) +tfw_h2_conn_terminate(TfwH2Ctx *ctx, TfwH2Err err_code) { - return tfw_http2_send_goaway(ctx, err_code); + return tfw_h2_send_goaway(ctx, err_code); } static inline int -tfw_http2_stream_terminate(TfwHttp2Ctx *ctx, unsigned int id, - TfwStream **stream, TfwHttp2Err err_code) +tfw_h2_stream_terminate(TfwH2Ctx *ctx, unsigned int id, TfwStream **stream, + TfwH2Err err_code) { if (stream && *stream) { --ctx->streams_num; - tfw_http2_stop_stream(&ctx->sched, stream); + tfw_h2_stop_stream(&ctx->sched, stream); } - return tfw_http2_send_rst_stream(ctx, id, err_code); + return tfw_h2_send_rst_stream(ctx, id, err_code); } static inline void -tfw_http2_check_closed_stream(TfwHttp2Ctx *ctx) +tfw_h2_check_closed_stream(TfwH2Ctx *ctx) { BUG_ON(!ctx->cur_stream); @@ -472,22 +541,22 @@ tfw_http2_check_closed_stream(TfwHttp2Ctx *ctx) "%lu\n", __func__, ctx->cur_stream->id, ctx->cur_stream->state, ctx->cur_stream, ctx->streams_num); - if (tfw_http2_stream_is_closed(ctx->cur_stream)) { + if (tfw_h2_stream_is_closed(ctx->cur_stream)) { --ctx->streams_num; - tfw_http2_stop_stream(&ctx->sched, &ctx->cur_stream); + tfw_h2_stop_stream(&ctx->sched, &ctx->cur_stream); } } #define VERIFY_FRAME_SIZE(ctx) \ do { \ if ((ctx)->hdr.length < 0) { \ - tfw_http2_conn_terminate(ctx, HTTP2_ECODE_SIZE); \ + tfw_h2_conn_terminate(ctx, HTTP2_ECODE_SIZE); \ return T_DROP; \ } \ } while (0) static inline int -tfw_http2_recv_priority(TfwHttp2Ctx *ctx) +tfw_h2_recv_priority(TfwH2Ctx *ctx) { ctx->to_read = FRAME_PRIORITY_SIZE; ctx->hdr.length -= ctx->to_read; @@ -497,7 +566,7 @@ tfw_http2_recv_priority(TfwHttp2Ctx *ctx) } static inline int -tfw_http2_recv_padded(TfwHttp2Ctx *ctx) +tfw_h2_recv_padded(TfwH2Ctx *ctx) { ctx->to_read = 1; ctx->hdr.length -= ctx->to_read; @@ -507,14 +576,14 @@ tfw_http2_recv_padded(TfwHttp2Ctx *ctx) } static int -tfw_http2_headers_pri_process(TfwHttp2Ctx *ctx) +tfw_h2_headers_pri_process(TfwH2Ctx *ctx) { TfwFramePri *pri = &ctx->priority; TfwFrameHdr *hdr = &ctx->hdr; BUG_ON(!(hdr->flags & HTTP2_F_PRIORITY)); - tfw_http2_unpack_priority(pri, ctx->rbuf); + 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, @@ -527,21 +596,21 @@ tfw_http2_headers_pri_process(TfwHttp2Ctx *ctx) } static TfwStream * -tfw_http2_stream_create(TfwHttp2Ctx *ctx, unsigned int id) +tfw_h2_stream_create(TfwH2Ctx *ctx, unsigned int id) { TfwStream *stream, *dep = NULL; TfwFramePri *pri = &ctx->priority; bool excl = pri->exclusive; - if (tfw_http2_find_stream_dep(&ctx->sched, pri->stream_id, &dep)) + if (tfw_h2_find_stream_dep(&ctx->sched, pri->stream_id, &dep)) return NULL; - stream = tfw_http2_add_stream(&ctx->sched, id, pri->weight, + stream = tfw_h2_add_stream(&ctx->sched, id, pri->weight, ctx->lsettings.wnd_sz); if (!stream) return NULL; - tfw_http2_add_stream_dep(&ctx->sched, stream, dep, excl); + tfw_h2_add_stream_dep(&ctx->sched, stream, dep, excl); ++ctx->streams_num; @@ -554,7 +623,7 @@ tfw_http2_stream_create(TfwHttp2Ctx *ctx, unsigned int id) } static int -tfw_http2_headers_process(TfwHttp2Ctx *ctx) +tfw_h2_headers_process(TfwH2Ctx *ctx) { TfwFrameHdr *hdr = &ctx->hdr; @@ -570,13 +639,13 @@ tfw_http2_headers_process(TfwHttp2Ctx *ctx) ctx->state = HTTP2_IGNORE_FRAME_DATA; - return tfw_http2_stream_terminate(ctx, hdr->stream_id, + return tfw_h2_stream_terminate(ctx, hdr->stream_id, &ctx->cur_stream, HTTP2_ECODE_PROTO); } if (!ctx->cur_stream) { - ctx->cur_stream = tfw_http2_stream_create(ctx, hdr->stream_id); + ctx->cur_stream = tfw_h2_stream_create(ctx, hdr->stream_id); if (!ctx->cur_stream) return T_DROP; ctx->lstream_id = hdr->stream_id; @@ -590,13 +659,13 @@ tfw_http2_headers_process(TfwHttp2Ctx *ctx) */ STREAM_RECV_PROCESS(ctx, hdr); - tfw_http2_check_closed_stream(ctx); + tfw_h2_check_closed_stream(ctx); return T_OK; } static int -tfw_http2_wnd_update_process(TfwHttp2Ctx *ctx) +tfw_h2_wnd_update_process(TfwH2Ctx *ctx) { unsigned int wnd_incr; TfwFrameHdr *hdr = &ctx->hdr; @@ -604,10 +673,10 @@ tfw_http2_wnd_update_process(TfwHttp2Ctx *ctx) wnd_incr = ntohl(*(unsigned int *)ctx->rbuf) & ((1U << 31) - 1); if (!wnd_incr) { if (ctx->cur_stream) - return tfw_http2_stream_terminate(ctx, hdr->stream_id, + return tfw_h2_stream_terminate(ctx, hdr->stream_id, &ctx->cur_stream, HTTP2_ECODE_PROTO); - tfw_http2_conn_terminate(ctx, HTTP2_ECODE_PROTO); + tfw_h2_conn_terminate(ctx, HTTP2_ECODE_PROTO); return T_DROP; } /* @@ -618,12 +687,12 @@ tfw_http2_wnd_update_process(TfwHttp2Ctx *ctx) } static inline int -tfw_http2_priority_process(TfwHttp2Ctx *ctx) +tfw_h2_priority_process(TfwH2Ctx *ctx) { TfwFrameHdr *hdr = &ctx->hdr; TfwFramePri *pri = &ctx->priority; - tfw_http2_unpack_priority(pri, ctx->rbuf); + tfw_h2_unpack_priority(pri, ctx->rbuf); /* * Stream cannot depend on itself (see RFC 7540 section 5.1.2 for @@ -633,7 +702,7 @@ tfw_http2_priority_process(TfwHttp2Ctx *ctx) T_DBG("Invalid dependency: new stream with %u depends on" " itself\n", hdr->stream_id); - return tfw_http2_stream_terminate(ctx, hdr->stream_id, + return tfw_h2_stream_terminate(ctx, hdr->stream_id, &ctx->cur_stream, HTTP2_ECODE_PROTO); } @@ -642,13 +711,13 @@ tfw_http2_priority_process(TfwHttp2Ctx *ctx) " excl=%hhu\n", __func__, hdr->stream_id, pri->stream_id, pri->weight, pri->exclusive); - tfw_http2_change_stream_dep(&ctx->sched, hdr->stream_id, pri->stream_id, + tfw_h2_change_stream_dep(&ctx->sched, hdr->stream_id, pri->stream_id, pri->weight, pri->exclusive); return T_OK; } static void -tfw_http2_rst_stream_process(TfwHttp2Ctx *ctx) +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", @@ -657,12 +726,12 @@ tfw_http2_rst_stream_process(TfwHttp2Ctx *ctx) --ctx->streams_num; - tfw_http2_stop_stream(&ctx->sched, &ctx->cur_stream); + tfw_h2_stop_stream(&ctx->sched, &ctx->cur_stream); } static int -tfw_http2_apply_settings_entry(TfwSettings *dest, unsigned short id, - unsigned int val) +tfw_h2_apply_settings_entry(TfwSettings *dest, unsigned short id, + unsigned int val) { switch (id) { case HTTP2_SETTINGS_TABLE_SIZE: @@ -704,7 +773,7 @@ tfw_http2_apply_settings_entry(TfwSettings *dest, unsigned short id, } static void -tfw_http2_settings_ack_process(TfwHttp2Ctx *ctx) +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); @@ -714,7 +783,7 @@ tfw_http2_settings_ack_process(TfwHttp2Ctx *ctx) } static int -tfw_http2_settings_process(TfwHttp2Ctx *ctx) +tfw_h2_settings_process(TfwH2Ctx *ctx) { TfwFrameHdr *hdr = &ctx->hdr; unsigned short id = ntohs(*(unsigned short *)&ctx->rbuf[0]); @@ -722,7 +791,7 @@ tfw_http2_settings_process(TfwHttp2Ctx *ctx) T_DBG3("%s: entry parsed, id=%hu, val=%u\n", __func__, id, val); - if (tfw_http2_apply_settings_entry(&ctx->rsettings, 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; @@ -732,7 +801,7 @@ tfw_http2_settings_process(TfwHttp2Ctx *ctx) } static int -tfw_http2_goaway_process(TfwHttp2Ctx *ctx) +tfw_h2_goaway_process(TfwH2Ctx *ctx) { unsigned int last_id, err_code; @@ -761,14 +830,14 @@ tfw_http2_goaway_process(TfwHttp2Ctx *ctx) } static inline int -tfw_http2_first_settings_verify(TfwHttp2Ctx *ctx) +tfw_h2_first_settings_verify(TfwH2Ctx *ctx) { int err_code = 0; TfwFrameHdr *hdr = &ctx->hdr; BUG_ON(ctx->to_read); - tfw_http2_unpack_frame_header(hdr, ctx->rbuf); + tfw_h2_unpack_frame_header(hdr, ctx->rbuf); if (hdr->type != HTTP2_SETTINGS || (hdr->flags & HTTP2_F_ACK) @@ -781,7 +850,7 @@ tfw_http2_first_settings_verify(TfwHttp2Ctx *ctx) err_code = HTTP2_ECODE_SIZE; if (err_code) { - tfw_http2_conn_terminate(ctx, err_code); + tfw_h2_conn_terminate(ctx, err_code); return T_DROP; } @@ -792,7 +861,42 @@ tfw_http2_first_settings_verify(TfwHttp2Ctx *ctx) } static inline int -tfw_http2_flow_control(TfwHttp2Ctx *ctx) +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; @@ -811,14 +915,14 @@ tfw_http2_flow_control(TfwHttp2Ctx *ctx) ctx->loc_wnd -= hdr->length; if (stream->loc_wnd <= lset->wnd_sz / 2 - && tfw_http2_send_wnd_update(ctx, stream->id, + && 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_http2_send_wnd_update(ctx, 0, MAX_WND_SIZE - ctx->loc_wnd)) + && tfw_h2_send_wnd_update(ctx, 0, MAX_WND_SIZE - ctx->loc_wnd)) { return T_DROP; } @@ -827,7 +931,7 @@ tfw_http2_flow_control(TfwHttp2Ctx *ctx) } static int -tfw_http2_frame_pad_process(TfwHttp2Ctx *ctx) +tfw_h2_frame_pad_process(TfwH2Ctx *ctx) { TfwFrameHdr *hdr = &ctx->hdr; @@ -849,7 +953,7 @@ tfw_http2_frame_pad_process(TfwHttp2Ctx *ctx) case HTTP2_HEADERS: if (hdr->flags & HTTP2_F_PRIORITY) - return tfw_http2_recv_priority(ctx); + return tfw_h2_recv_priority(ctx); ctx->state = HTTP2_RECV_HEADER; break; @@ -871,13 +975,13 @@ tfw_http2_frame_pad_process(TfwHttp2Ctx *ctx) * 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_http2_headers_process() + * after full frame reception (see comments in @tfw_h2_headers_process() * procedure). */ static int -tfw_http2_frame_type_process(TfwHttp2Ctx *ctx) +tfw_h2_frame_type_process(TfwH2Ctx *ctx) { - TfwHttp2Err err_code = HTTP2_ECODE_SIZE; + TfwH2Err err_code = HTTP2_ECODE_SIZE; TfwFrameHdr *hdr = &ctx->hdr; T_DBG3("%s: hdr->type=%hhu, ctx->state=%d\n", __func__, hdr->type, @@ -899,7 +1003,7 @@ tfw_http2_frame_type_process(TfwHttp2Ctx *ctx) goto conn_term; } - ctx->cur_stream = tfw_http2_find_stream(&ctx->sched, + ctx->cur_stream = tfw_h2_find_stream(&ctx->sched, hdr->stream_id); /* * If stream is removed, it had been closed before, so this is @@ -910,7 +1014,7 @@ tfw_http2_frame_type_process(TfwHttp2Ctx *ctx) goto conn_term; } - if (tfw_http2_flow_control(ctx)) + if (tfw_h2_flow_control(ctx)) return T_DROP; STREAM_RECV_PROCESS(ctx, hdr); @@ -918,7 +1022,7 @@ tfw_http2_frame_type_process(TfwHttp2Ctx *ctx) ctx->data_off = FRAME_HEADER_SIZE; if (hdr->flags & HTTP2_F_PADDED) - return tfw_http2_recv_padded(ctx); + return tfw_h2_recv_padded(ctx); SET_TO_READ_VERIFY(ctx, HTTP2_RECV_DATA); return T_OK; @@ -942,57 +1046,36 @@ tfw_http2_frame_type_process(TfwHttp2Ctx *ctx) * streams (see comments for @TfwStreamState enum at the * beginning of http_stream.c). */ - ctx->cur_stream = tfw_http2_find_stream(&ctx->sched, + ctx->cur_stream = tfw_h2_find_stream(&ctx->sched, hdr->stream_id); - if (!ctx->cur_stream) { - /* - * 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); - err_code = HTTP2_ECODE_PROTO; - goto conn_term; - } - /* - * 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"); - err_code = HTTP2_ECODE_PROTO; - goto conn_term; - } - /* - * Endpoints must not exceed the limit set by their peer - * (see RFC 7540 section 5.1.2 for details). - */ - if (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_http2_stream_terminate(ctx, - hdr->stream_id, - NULL, - HTTP2_ECODE_REFUSED); - } + /* + * 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_http2_recv_padded(ctx); + return tfw_h2_recv_padded(ctx); if (hdr->flags & HTTP2_F_PRIORITY) - return tfw_http2_recv_priority(ctx); + return tfw_h2_recv_priority(ctx); SET_TO_READ_VERIFY(ctx, HTTP2_RECV_HEADER); return T_OK; @@ -1003,11 +1086,11 @@ tfw_http2_frame_type_process(TfwHttp2Ctx *ctx) goto conn_term; } - ctx->cur_stream = tfw_http2_find_stream(&ctx->sched, + ctx->cur_stream = tfw_h2_find_stream(&ctx->sched, hdr->stream_id); if (hdr->length != FRAME_PRIORITY_SIZE) { SET_TO_READ(ctx); - return tfw_http2_stream_terminate(ctx, hdr->stream_id, + return tfw_h2_stream_terminate(ctx, hdr->stream_id, &ctx->cur_stream, HTTP2_ECODE_SIZE); } @@ -1032,7 +1115,7 @@ tfw_http2_frame_type_process(TfwHttp2Ctx *ctx) } if (hdr->stream_id) { - ctx->cur_stream = tfw_http2_find_stream(&ctx->sched, + ctx->cur_stream = tfw_h2_find_stream(&ctx->sched, hdr->stream_id); if (!ctx->cur_stream) { err_code = HTTP2_ECODE_CLOSED; @@ -1059,7 +1142,7 @@ tfw_http2_frame_type_process(TfwHttp2Ctx *ctx) } if (hdr->flags & HTTP2_F_ACK) - tfw_http2_settings_ack_process(ctx); + tfw_h2_settings_ack_process(ctx); if (hdr->length) { ctx->state = HTTP2_RECV_FRAME_SETTINGS; @@ -1109,7 +1192,7 @@ tfw_http2_frame_type_process(TfwHttp2Ctx *ctx) goto conn_term; } - ctx->cur_stream = tfw_http2_find_stream(&ctx->sched, + ctx->cur_stream = tfw_h2_find_stream(&ctx->sched, hdr->stream_id); if (!ctx->cur_stream) { err_code = HTTP2_ECODE_CLOSED; @@ -1149,7 +1232,7 @@ tfw_http2_frame_type_process(TfwHttp2Ctx *ctx) goto conn_term; } - ctx->cur_stream = tfw_http2_find_stream(&ctx->sched, + ctx->cur_stream = tfw_h2_find_stream(&ctx->sched, hdr->stream_id); if (!ctx->cur_stream) { err_code = HTTP2_ECODE_CLOSED; @@ -1177,7 +1260,7 @@ tfw_http2_frame_type_process(TfwHttp2Ctx *ctx) conn_term: BUG_ON(!err_code); - tfw_http2_conn_terminate(ctx, err_code); + tfw_h2_conn_terminate(ctx, err_code); return T_DROP; } @@ -1185,12 +1268,12 @@ tfw_http2_frame_type_process(TfwHttp2Ctx *ctx) * Main FSM for processing HTTP/2 frames. */ static int -tfw_http2_frame_recv(void *data, unsigned char *buf, size_t len, - unsigned int *read) +tfw_h2_frame_recv(void *data, unsigned char *buf, size_t len, + unsigned int *read) { int n, r = T_POSTPONE; unsigned char *p = buf; - TfwHttp2Ctx *ctx = data; + TfwH2Ctx *ctx = data; T_FSM_INIT(ctx->state, "HTTP/2 Frame Receive"); T_FSM_START(ctx->state) { @@ -1206,8 +1289,8 @@ tfw_http2_frame_recv(void *data, unsigned char *buf, size_t len, } }); - if (tfw_http2_send_settings_init(ctx) - || tfw_http2_send_wnd_update(ctx, 0, + 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); @@ -1219,7 +1302,7 @@ tfw_http2_frame_recv(void *data, unsigned char *buf, size_t len, T_FSM_STATE(HTTP2_RECV_FIRST_SETTINGS) { FRAME_FSM_READ_SRVC(FRAME_HEADER_SIZE); - if (tfw_http2_first_settings_verify(ctx)) + if (tfw_h2_first_settings_verify(ctx)) FRAME_FSM_EXIT(T_DROP); if (ctx->to_read) @@ -1231,9 +1314,9 @@ tfw_http2_frame_recv(void *data, unsigned char *buf, size_t len, T_FSM_STATE(HTTP2_RECV_FRAME_HEADER) { FRAME_FSM_READ_SRVC(FRAME_HEADER_SIZE); - tfw_http2_unpack_frame_header(&ctx->hdr, ctx->rbuf); + tfw_h2_unpack_frame_header(&ctx->hdr, ctx->rbuf); - if (tfw_http2_frame_type_process(ctx)) + if (tfw_h2_frame_type_process(ctx)) FRAME_FSM_EXIT(T_DROP); if (ctx->to_read) @@ -1245,7 +1328,7 @@ tfw_http2_frame_recv(void *data, unsigned char *buf, size_t len, T_FSM_STATE(HTTP2_RECV_FRAME_PADDED) { FRAME_FSM_READ_SRVC(ctx->to_read); - if (tfw_http2_frame_pad_process(ctx)) + if (tfw_h2_frame_pad_process(ctx)) FRAME_FSM_EXIT(T_DROP); if (ctx->to_read) @@ -1257,7 +1340,7 @@ tfw_http2_frame_recv(void *data, unsigned char *buf, size_t len, T_FSM_STATE(HTTP2_RECV_FRAME_PRIORITY) { FRAME_FSM_READ_SRVC(ctx->to_read); - if (tfw_http2_priority_process(ctx)) + if (tfw_h2_priority_process(ctx)) FRAME_FSM_EXIT(T_DROP); FRAME_FSM_EXIT(T_OK); @@ -1266,7 +1349,7 @@ tfw_http2_frame_recv(void *data, unsigned char *buf, size_t len, T_FSM_STATE(HTTP2_RECV_FRAME_WND_UPDATE) { FRAME_FSM_READ_SRVC(ctx->to_read); - if (tfw_http2_wnd_update_process(ctx)) + if (tfw_h2_wnd_update_process(ctx)) FRAME_FSM_EXIT(T_DROP); FRAME_FSM_EXIT(T_OK); @@ -1276,7 +1359,7 @@ tfw_http2_frame_recv(void *data, unsigned char *buf, size_t len, FRAME_FSM_READ_SRVC(ctx->to_read); if (!(ctx->hdr.flags & HTTP2_F_ACK) - && tfw_http2_send_ping(ctx)) + && tfw_h2_send_ping(ctx)) { FRAME_FSM_EXIT(T_DROP); } @@ -1287,7 +1370,7 @@ tfw_http2_frame_recv(void *data, unsigned char *buf, size_t len, T_FSM_STATE(HTTP2_RECV_FRAME_RST_STREAM) { FRAME_FSM_READ_SRVC(ctx->to_read); - tfw_http2_rst_stream_process(ctx); + tfw_h2_rst_stream_process(ctx); FRAME_FSM_EXIT(T_OK); } @@ -1295,13 +1378,13 @@ tfw_http2_frame_recv(void *data, unsigned char *buf, size_t len, T_FSM_STATE(HTTP2_RECV_FRAME_SETTINGS) { FRAME_FSM_READ_SRVC(ctx->to_read); - if (tfw_http2_settings_process(ctx)) + if (tfw_h2_settings_process(ctx)) FRAME_FSM_EXIT(T_DROP); if (ctx->to_read) FRAME_FSM_MOVE(HTTP2_RECV_FRAME_SETTINGS); - if (tfw_http2_send_settings_ack(ctx)) + if (tfw_h2_send_settings_ack(ctx)) FRAME_FSM_EXIT(T_DROP); FRAME_FSM_EXIT(T_OK); @@ -1310,7 +1393,7 @@ tfw_http2_frame_recv(void *data, unsigned char *buf, size_t len, T_FSM_STATE(HTTP2_RECV_FRAME_GOAWAY) { FRAME_FSM_READ_SRVC(ctx->to_read); - if (tfw_http2_goaway_process(ctx)) + if (tfw_h2_goaway_process(ctx)) FRAME_FSM_EXIT(T_DROP); if (ctx->to_read) @@ -1322,7 +1405,7 @@ tfw_http2_frame_recv(void *data, unsigned char *buf, size_t len, T_FSM_STATE(HTTP2_RECV_HEADER_PRI) { FRAME_FSM_READ_SRVC(ctx->to_read); - if (tfw_http2_headers_pri_process(ctx)) + if (tfw_h2_headers_pri_process(ctx)) FRAME_FSM_EXIT(T_DROP); if (ctx->to_read) @@ -1334,7 +1417,7 @@ tfw_http2_frame_recv(void *data, unsigned char *buf, size_t len, T_FSM_STATE(HTTP2_RECV_DATA) { FRAME_FSM_READ(ctx->to_read); - tfw_http2_check_closed_stream(ctx); + tfw_h2_check_closed_stream(ctx); FRAME_FSM_EXIT(T_OK); } @@ -1342,7 +1425,7 @@ tfw_http2_frame_recv(void *data, unsigned char *buf, size_t len, T_FSM_STATE(HTTP2_RECV_HEADER) { FRAME_FSM_READ(ctx->to_read); - if (tfw_http2_headers_process(ctx)) + if (tfw_h2_headers_process(ctx)) FRAME_FSM_EXIT(T_DROP); FRAME_FSM_EXIT(T_OK); @@ -1351,7 +1434,7 @@ tfw_http2_frame_recv(void *data, unsigned char *buf, size_t len, T_FSM_STATE(HTTP2_RECV_CONT) { FRAME_FSM_READ(ctx->to_read); - tfw_http2_check_closed_stream(ctx); + tfw_h2_check_closed_stream(ctx); FRAME_FSM_EXIT(T_OK); } @@ -1386,11 +1469,11 @@ tfw_http2_frame_recv(void *data, unsigned char *buf, size_t len, * the frame will be fully received. */ static inline void -tfw_http2_context_reinit(TfwHttp2Ctx *ctx, bool postponed) +tfw_h2_context_reinit(TfwH2Ctx *ctx, bool postponed) { if (!APP_FRAME(ctx) || (!postponed && !ctx->padlen)) { bzero_fast(ctx->__off, - sizeof(*ctx) - offsetof(TfwHttp2Ctx, __off)); + sizeof(*ctx) - offsetof(TfwH2Ctx, __off)); return; } if (!postponed && ctx->padlen) { @@ -1401,12 +1484,12 @@ tfw_http2_context_reinit(TfwHttp2Ctx *ctx, bool postponed) } int -tfw_http2_frame_process(void *c, TfwFsmData *data) +tfw_h2_frame_process(void *c, TfwFsmData *data) { int r; unsigned int unused, curr_tail; TfwFsmData data_up = {}; - TfwHttp2Ctx *h2 = tfw_http2_context(c); + TfwH2Ctx *h2 = tfw_h2_context(c); struct sk_buff *nskb = NULL, *skb = data->skb; unsigned int parsed = 0, off = data->off, tail = data->trail; @@ -1415,7 +1498,7 @@ tfw_http2_frame_process(void *c, TfwFsmData *data) next_msg: ss_skb_queue_tail(&h2->skb_head, skb); - r = ss_skb_process(skb, off, tail, tfw_http2_frame_recv, h2, &unused, + r = ss_skb_process(skb, off, tail, tfw_h2_frame_recv, h2, &unused, &parsed); curr_tail = off + parsed + tail < skb->len ? 0 : tail; @@ -1512,7 +1595,7 @@ tfw_http2_frame_process(void *c, TfwFsmData *data) ss_skb_queue_purge(&h2->skb_head); } - tfw_http2_context_reinit(h2, r == T_POSTPONE); + tfw_h2_context_reinit(h2, r == T_POSTPONE); if (nskb) { skb = nskb; @@ -1526,24 +1609,3 @@ tfw_http2_frame_process(void *c, TfwFsmData *data) ss_skb_queue_purge(&h2->skb_head); return r; } - -void -tfw_http2_init(TfwHttp2Ctx *ctx) -{ - TfwSettings *lset = &ctx->lsettings; - TfwSettings *rset = &ctx->rsettings; - - 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; - - ctx->state = HTTP2_RECV_CLI_START_SEQ; - ctx->loc_wnd = MAX_WND_SIZE; -} diff --git a/tempesta_fw/http_frame.h b/tempesta_fw/http_frame.h index c031cafad..cd5d5d5c8 100644 --- a/tempesta_fw/http_frame.h +++ b/tempesta_fw/http_frame.h @@ -138,7 +138,7 @@ typedef struct { * @data_off - offset of app data in HEADERS, CONTINUATION and DATA * frames (after all service payloads); */ -struct tfw_http2_ctx_t { +struct tfw_h2_ctx_t { TfwConn *conn; TfwSettings lsettings; TfwSettings rsettings; @@ -159,7 +159,10 @@ struct tfw_http2_ctx_t { unsigned char data_off; }; -int tfw_http2_frame_process(void *c, TfwFsmData *data); -void tfw_http2_init(TfwHttp2Ctx *ctx); +int tfw_h2_init(void); +void tfw_h2_cleanup(void); +int tfw_h2_context_create(TfwTlsConn *conn); +void tfw_h2_context_free(TfwTlsConn *conn); +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 index 6c3246598..0641550c2 100644 --- a/tempesta_fw/http_stream.c +++ b/tempesta_fw/http_stream.c @@ -62,13 +62,32 @@ typedef enum { 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_http2_stream_fsm(TfwStream *stream, unsigned char type, unsigned char flags, - TfwHttp2Err *err) +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, @@ -246,7 +265,7 @@ tfw_http2_stream_fsm(TfwStream *stream, unsigned char type, unsigned char flags, } } else if ((type == HTTP2_DATA && (flags & HTTP2_F_END_STREAM)) - || type == HTTP2_RST_STREAM) + || type == HTTP2_RST_STREAM) { stream->state = HTTP2_STREAM_CLOSED; } @@ -273,14 +292,14 @@ tfw_http2_stream_fsm(TfwStream *stream, unsigned char type, unsigned char flags, } bool -tfw_http2_stream_is_closed(TfwStream *stream) +tfw_h2_stream_is_closed(TfwStream *stream) { return stream->state == HTTP2_STREAM_CLOSED; } static inline void -tfw_http2_init_stream(TfwStream *stream, unsigned int id, unsigned short weight, - unsigned int wnd) +tfw_h2_init_stream(TfwStream *stream, unsigned int id, unsigned short weight, + unsigned int wnd) { RB_CLEAR_NODE(&stream->node); stream->id = id; @@ -290,7 +309,7 @@ tfw_http2_init_stream(TfwStream *stream, unsigned int id, unsigned short weight, } TfwStream * -tfw_http2_find_stream(TfwStreamSched *sched, unsigned int id) +tfw_h2_find_stream(TfwStreamSched *sched, unsigned int id) { struct rb_node *node = sched->streams.rb_node; @@ -309,8 +328,8 @@ tfw_http2_find_stream(TfwStreamSched *sched, unsigned int id) } TfwStream * -tfw_http2_add_stream(TfwStreamSched *sched, unsigned int id, - unsigned short weight, unsigned int wnd) +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; @@ -330,11 +349,11 @@ tfw_http2_add_stream(TfwStreamSched *sched, unsigned int id, } } - new_stream = kzalloc(sizeof(TfwStream), GFP_ATOMIC); + new_stream = kmem_cache_alloc(stream_cache, GFP_ATOMIC | __GFP_ZERO); if (unlikely(!new_stream)) return NULL; - tfw_http2_init_stream(new_stream, id, weight, wnd); + 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); @@ -343,24 +362,22 @@ tfw_http2_add_stream(TfwStreamSched *sched, unsigned int id, } static inline void -tfw_http2_remove_stream(TfwStreamSched *sched, TfwStream *stream) +tfw_h2_remove_stream(TfwStreamSched *sched, TfwStream *stream) { rb_erase(&stream->node, &sched->streams); } void -tfw_http2_streams_cleanup(TfwStreamSched *sched) +tfw_h2_streams_cleanup(TfwStreamSched *sched) { TfwStream *cur, *next; - rbtree_postorder_for_each_entry_safe(cur, next, &sched->streams, node) { - kfree(cur); - } + rbtree_postorder_for_each_entry_safe(cur, next, &sched->streams, node) + kmem_cache_free(stream_cache, cur); } int -tfw_http2_find_stream_dep(TfwStreamSched *sched, unsigned int id, - TfwStream **dep) +tfw_h2_find_stream_dep(TfwStreamSched *sched, unsigned int id, TfwStream **dep) { /* * TODO: implement dependency/priority logic (according to RFC 7540 @@ -370,8 +387,8 @@ tfw_http2_find_stream_dep(TfwStreamSched *sched, unsigned int id, } void -tfw_http2_add_stream_dep(TfwStreamSched *sched, TfwStream *stream, - TfwStream *dep, bool excl) +tfw_h2_add_stream_dep(TfwStreamSched *sched, TfwStream *stream, TfwStream *dep, + bool excl) { /* * TODO: implement dependency/priority logic (according to RFC 7540 @@ -380,9 +397,9 @@ tfw_http2_add_stream_dep(TfwStreamSched *sched, TfwStream *stream, } void -tfw_http2_change_stream_dep(TfwStreamSched *sched, unsigned int stream_id, - unsigned int new_dep, unsigned short new_weight, - bool excl) +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 @@ -391,7 +408,7 @@ tfw_http2_change_stream_dep(TfwStreamSched *sched, unsigned int stream_id, } static void -tfw_http2_remove_stream_dep(TfwStreamSched *sched, TfwStream *stream) +tfw_h2_remove_stream_dep(TfwStreamSched *sched, TfwStream *stream) { /* * TODO: implement dependency/priority logic (according to RFC 7540 @@ -400,11 +417,11 @@ tfw_http2_remove_stream_dep(TfwStreamSched *sched, TfwStream *stream) } void -tfw_http2_stop_stream(TfwStreamSched *sched, TfwStream **stream) +tfw_h2_stop_stream(TfwStreamSched *sched, TfwStream **stream) { - tfw_http2_remove_stream_dep(sched, *stream); - tfw_http2_remove_stream(sched, *stream); + tfw_h2_remove_stream_dep(sched, *stream); + tfw_h2_remove_stream(sched, *stream); - kfree(*stream); + kmem_cache_free(stream_cache, *stream); *stream = NULL; } diff --git a/tempesta_fw/http_stream.h b/tempesta_fw/http_stream.h index 43ebb33e9..726d65fae 100644 --- a/tempesta_fw/http_stream.h +++ b/tempesta_fw/http_stream.h @@ -52,7 +52,7 @@ typedef enum { HTTP2_ECODE_ENHANCE_YOUR_CALM, HTTP2_ECODE_INADEQUATE_SECURITY, HTTP2_ECODE_HTTP_1_1_REQUIRED -} TfwHttp2Err; +} TfwH2Err; /** * Representation of HTTP/2 stream entity. @@ -83,20 +83,22 @@ typedef struct { struct rb_root streams; } TfwStreamSched; -TfwStreamFsmRes tfw_http2_stream_fsm(TfwStream *stream, unsigned char type, - unsigned char flags, TfwHttp2Err *err); -bool tfw_http2_stream_is_closed(TfwStream *stream); -TfwStream *tfw_http2_find_stream(TfwStreamSched *sched, unsigned int id); -TfwStream *tfw_http2_add_stream(TfwStreamSched *sched, unsigned int id, - unsigned short weight, unsigned int wnd); -void tfw_http2_streams_cleanup(TfwStreamSched *sched); -int tfw_http2_find_stream_dep(TfwStreamSched *sched, unsigned int id, - TfwStream **dep); -void tfw_http2_add_stream_dep(TfwStreamSched *sched, TfwStream *stream, - TfwStream *dep, bool excl); -void tfw_http2_change_stream_dep(TfwStreamSched *sched, unsigned int stream_id, - unsigned int new_dep, unsigned short new_weight, - bool excl); -void tfw_http2_stop_stream(TfwStreamSched *sched, TfwStream **stream); +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/http_types.h b/tempesta_fw/http_types.h index 54fd78647..9e7380e27 100644 --- a/tempesta_fw/http_types.h +++ b/tempesta_fw/http_types.h @@ -25,6 +25,6 @@ typedef struct tfw_http_sess_t TfwHttpSess; typedef struct tfw_http_msg_t TfwHttpMsg; typedef struct tfw_http_req_t TfwHttpReq; typedef struct tfw_http_resp_t TfwHttpResp; -typedef struct tfw_http2_ctx_t TfwHttp2Ctx; +typedef struct tfw_h2_ctx_t TfwH2Ctx; #endif /* __TFW_HTTP_TYPES_H__ */ diff --git a/tempesta_fw/tls.c b/tempesta_fw/tls.c index bb6df8d01..0c9b43729 100644 --- a/tempesta_fw/tls.c +++ b/tempesta_fw/tls.c @@ -61,9 +61,9 @@ tfw_tls_chop_skb_rec(TlsCtx *tls, struct sk_buff *skb, TfwFsmData *data) } static inline int -tfw_tls_check_alloc_h2_context(void *c) +tfw_tls_check_create_h2_context(void *c) { - TfwHttp2Ctx *h2; + int r; TfwTlsConn *conn = c; TlsCtx *tls = &conn->tls; @@ -74,13 +74,8 @@ tfw_tls_check_alloc_h2_context(void *c) return 0; } - h2 = kzalloc(sizeof(TfwHttp2Ctx), GFP_ATOMIC); - if (unlikely(!h2)) - return -ENOMEM; - - conn->h2 = h2; - h2->conn = c; - tfw_http2_init(h2); + if ((r = tfw_h2_context_create(conn))) + return r; return 0; } @@ -95,7 +90,7 @@ tfw_tls_msg_process(void *conn, TfwFsmData *data) TfwFsmData data_up = {}; /* Enable HTTP/2 mode if this protocol has been negotiated in APLN. */ - if (tfw_tls_check_alloc_h2_context(conn)) + if (tfw_tls_check_create_h2_context(conn)) return T_DROP; /* @@ -528,7 +523,6 @@ tfw_tls_conn_dtor(void *c) { struct sk_buff *skb; TlsCtx *tls = tfw_tls_context(c); - TfwHttp2Ctx *h2 = tfw_http2_context(c); if (tls) { while ((skb = ss_skb_dequeue(&tls->io_in.skb_list))) @@ -537,11 +531,7 @@ tfw_tls_conn_dtor(void *c) kfree_skb(skb); } - if (h2) { - tfw_http2_streams_cleanup(&h2->sched); - kfree(h2); - } - + tfw_h2_context_free((TfwTlsConn *)c); ttls_ctx_clear(tls); tfw_cli_conn_release((TfwCliConn *)c); } @@ -971,16 +961,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 @@ -989,5 +986,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(); } From 5515e82444c46d2745ab3b6daecb3f0d472973fa Mon Sep 17 00:00:00 2001 From: Alexander Ostapenko Date: Thu, 16 May 2019 20:44:18 +0700 Subject: [PATCH 15/17] Corrections according review comments (#309). 1. Make 'TfwH2Conn' (with HTTP/2 context) inherited from 'TfwTlsConn'. 2. Avoid default enabling of HTTP/2 protocol. --- tempesta_fw/connection.h | 15 ++++- tempesta_fw/http.c | 2 +- tempesta_fw/http_frame.c | 50 +++------------ tempesta_fw/http_frame.h | 12 ++-- tempesta_fw/http_types.h | 1 - tempesta_fw/sock_clnt.c | 22 ++++--- tempesta_fw/tls.c | 127 +++++++++++++++++---------------------- tempesta_fw/tls.h | 3 + tls/tls_srv.c | 8 +-- 9 files changed, 104 insertions(+), 136 deletions(-) diff --git a/tempesta_fw/connection.h b/tempesta_fw/connection.h index 60590050a..b4502f94e 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" /* @@ -210,11 +211,19 @@ enum { typedef struct { TfwCliConn cli_conn; TlsCtx tls; - TfwH2Ctx *h2; } TfwTlsConn; -#define tfw_tls_context(conn) (TlsCtx *)(&((TfwTlsConn *)conn)->tls) -#define tfw_h2_context(conn) ((TfwH2Ctx *)((TfwTlsConn *)conn)->h2) +#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 f620b4b0c..81b56532a 100644 --- a/tempesta_fw/http.c +++ b/tempesta_fw/http.c @@ -3931,7 +3931,7 @@ 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_CONN_TLS((TfwConn *)conn) && tfw_h2_context(conn) + r = TFW_CONN_TLS((TfwConn *)conn) && TFW_CONN_H2(conn) ? tfw_h2_frame_process(conn, data) : tfw_http_msg_process_generic(conn, data); } else { diff --git a/tempesta_fw/http_frame.c b/tempesta_fw/http_frame.c index 5f750902f..fd6ac1c77 100644 --- a/tempesta_fw/http_frame.c +++ b/tempesta_fw/http_frame.c @@ -185,52 +185,29 @@ do { \ } \ }) -static struct kmem_cache *h2_ctx_cache; - int tfw_h2_init(void) { - int r = 0; - - h2_ctx_cache = kmem_cache_create("tfw_h2_ctx_cache", sizeof(TfwH2Ctx), - 0, 0, NULL); - if (!h2_ctx_cache) - return -ENOMEM; - - if ((r = tfw_h2_stream_cache_create())) { - kmem_cache_destroy(h2_ctx_cache); - return r; - } - - return 0; + return tfw_h2_stream_cache_create(); } void tfw_h2_cleanup(void) { tfw_h2_stream_cache_destroy(); - kmem_cache_destroy(h2_ctx_cache); } -int -tfw_h2_context_create(TfwTlsConn *conn) +void +tfw_h2_context_init(TfwH2Ctx *ctx) { - TfwH2Ctx *ctx; - TfwSettings *lset; - TfwSettings *rset; + TfwSettings *lset = &ctx->lsettings; + TfwSettings *rset = &ctx->rsettings; - ctx = kmem_cache_alloc(h2_ctx_cache, GFP_ATOMIC | __GFP_ZERO); - if (unlikely(!ctx)) - return -ENOMEM; + bzero_fast(ctx, sizeof(*ctx)); - conn->h2 = ctx; - ctx->conn = (TfwConn *)conn; ctx->state = HTTP2_RECV_CLI_START_SEQ; ctx->loc_wnd = MAX_WND_SIZE; - lset = &ctx->lsettings; - rset = &ctx->rsettings; - lset->hdr_tbl_sz = rset->hdr_tbl_sz = 1 << 12; lset->push = rset->push = 1; lset->max_streams = rset->max_streams = 0xffffffff; @@ -241,20 +218,12 @@ tfw_h2_context_create(TfwTlsConn *conn) * we set it to maximum allowed value. */ lset->wnd_sz = rset->wnd_sz = MAX_WND_SIZE; - - return 0; } void -tfw_h2_context_free(TfwTlsConn *conn) +tfw_h2_context_clear(TfwH2Ctx *ctx) { - TfwH2Ctx *ctx = conn->h2; - - if (!ctx) - return; - tfw_h2_streams_cleanup(&ctx->sched); - kmem_cache_free(h2_ctx_cache, ctx); } static inline void @@ -310,6 +279,7 @@ __tfw_h2_send_frame(TfwH2Ctx *ctx, TfwFrameHdr *hdr, TfwStr *data, bool close) 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; @@ -332,7 +302,7 @@ __tfw_h2_send_frame(TfwH2Ctx *ctx, TfwFrameHdr *hdr, TfwStr *data, bool close) if (close) msg.ss_flags |= SS_F_CONN_CLOSE; - if ((r = tfw_connection_send(ctx->conn, &msg))) + if ((r = tfw_connection_send((TfwConn *)conn, &msg))) goto err; /* * For HTTP/2 we do not close client connection automatically in case @@ -341,7 +311,7 @@ __tfw_h2_send_frame(TfwH2Ctx *ctx, TfwFrameHdr *hdr, TfwStr *data, bool close) * avoid hanged unclosed client connection. */ if (close) - TFW_CONN_TYPE(ctx->conn) |= Conn_Stop; + TFW_CONN_TYPE((TfwConn *)conn) |= Conn_Stop; return 0; diff --git a/tempesta_fw/http_frame.h b/tempesta_fw/http_frame.h index cd5d5d5c8..82dd0e026 100644 --- a/tempesta_fw/http_frame.h +++ b/tempesta_fw/http_frame.h @@ -20,7 +20,7 @@ #ifndef __HTTP_FRAME__ #define __HTTP_FRAME__ -#include "connection.h" +#include "gfsm.h" #include "http_stream.h" #define FRAME_HEADER_SIZE 9 @@ -113,7 +113,6 @@ typedef struct { /** * Context for HTTP/2 frames processing. * - * @conn - pointer to corresponding connection instance; * @lsettings - local settings for HTTP/2 connection; * @rsettings - settings for HTTP/2 connection received from the remote * endpoint; @@ -138,8 +137,7 @@ typedef struct { * @data_off - offset of app data in HEADERS, CONTINUATION and DATA * frames (after all service payloads); */ -struct tfw_h2_ctx_t { - TfwConn *conn; +typedef struct { TfwSettings lsettings; TfwSettings rsettings; unsigned long streams_num; @@ -157,12 +155,12 @@ struct tfw_h2_ctx_t { unsigned char rbuf[FRAME_HEADER_SIZE]; unsigned char padlen; unsigned char data_off; -}; +} TfwH2Ctx; int tfw_h2_init(void); void tfw_h2_cleanup(void); -int tfw_h2_context_create(TfwTlsConn *conn); -void tfw_h2_context_free(TfwTlsConn *conn); +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_types.h b/tempesta_fw/http_types.h index 9e7380e27..0a62215c7 100644 --- a/tempesta_fw/http_types.h +++ b/tempesta_fw/http_types.h @@ -25,6 +25,5 @@ typedef struct tfw_http_sess_t TfwHttpSess; typedef struct tfw_http_msg_t TfwHttpMsg; typedef struct tfw_http_req_t TfwHttpReq; typedef struct tfw_http_resp_t TfwHttpResp; -typedef struct tfw_h2_ctx_t TfwH2Ctx; #endif /* __TFW_HTTP_TYPES_H__ */ diff --git a/tempesta_fw/sock_clnt.c b/tempesta_fw/sock_clnt.c index 87eeb63a7..eada228d4 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 @@ -668,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; } @@ -692,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/tls.c b/tempesta_fw/tls.c index 0c9b43729..1d24d9936 100644 --- a/tempesta_fw/tls.c +++ b/tempesta_fw/tls.c @@ -60,26 +60,6 @@ tfw_tls_chop_skb_rec(TlsCtx *tls, struct sk_buff *skb, TfwFsmData *data) return 0; } -static inline int -tfw_tls_check_create_h2_context(void *c) -{ - int r; - TfwTlsConn *conn = c; - TlsCtx *tls = &conn->tls; - - if (likely(!tls->alpn_chosen - || tls->alpn_chosen->id != TTLS_ALPN_ID_HTTP2 - || conn->h2)) - { - return 0; - } - - if ((r = tfw_h2_context_create(conn))) - return r; - - return 0; -} - static int tfw_tls_msg_process(void *conn, TfwFsmData *data) { @@ -89,10 +69,6 @@ tfw_tls_msg_process(void *conn, TfwFsmData *data) TlsCtx *tls = tfw_tls_context(c); TfwFsmData data_up = {}; - /* Enable HTTP/2 mode if this protocol has been negotiated in APLN. */ - if (tfw_tls_check_create_h2_context(conn)) - return T_DROP; - /* * @off is from TCP layer due to possible, but rare (usually malicious), * sequence numbers overlapping. We have to join the skb into a list @@ -523,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))) @@ -531,7 +508,7 @@ tfw_tls_conn_dtor(void *c) kfree_skb(skb); } - tfw_h2_context_free((TfwTlsConn *)c); + tfw_h2_context_clear(h2); ttls_ctx_clear(tls); tfw_cli_conn_release((TfwCliConn *)c); } @@ -541,9 +518,7 @@ tfw_tls_conn_init(TfwConn *c) { int r; TlsCtx *tls = tfw_tls_context(c); - TfwTlsConn *conn = (TfwTlsConn *)c; - - conn->h2 = NULL; + 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); @@ -553,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; @@ -700,68 +677,76 @@ tfw_tls_cfg_alpn_protos(const char *cfg_str) { ttls_alpn_proto *protos; size_t len = strlen(cfg_str); - int order = 0; -#define CONF_HTTPS "https" -#define CONF_HTTP1 "http1" -#define CONF_HTTP2 "http2" -#define CONF_LEN 5 - if (strncasecmp(cfg_str, CONF_HTTPS, CONF_LEN)) +#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; - if (len == CONF_LEN) - goto found; + protos = kzalloc(TTLS_ALPN_PROTOS * sizeof(ttls_alpn_proto), GFP_ATOMIC); + if (unlikely(!protos)) + return -ENOMEM; + + /* Currently HTTP/2 protocol is default option. */ + PROTO_INIT(0, HTTP2); + tfw_tls.cfg.alpn_list = protos; + + if (len == CONF_HTTPS_LEN) + return 0; - cfg_str += CONF_LEN; + cfg_str += CONF_HTTPS_LEN; if (cfg_str[0] != ':') - return -EINVAL; + goto err; ++cfg_str; - len -= CONF_LEN + 1; - if (len >= CONF_LEN && !strncasecmp(cfg_str, CONF_HTTP1, CONF_LEN)) + len -= CONF_HTTPS_LEN + 1; + if (len >= CONF_HTTP2_LEN + && !strncasecmp(cfg_str, CONF_HTTP2, CONF_HTTP2_LEN)) { - cfg_str += CONF_LEN; - len -= CONF_LEN; - if (!len || (cfg_str[0] == ',' - && !strcasecmp(++cfg_str, CONF_HTTP2))) - { - goto found; + 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_LEN && - !strncasecmp(cfg_str, CONF_HTTP2, CONF_LEN)) + else if (len >= CONF_HTTP1_LEN + && !strncasecmp(cfg_str, CONF_HTTP1, CONF_HTTP1_LEN)) { - order = 1; - cfg_str += CONF_LEN; - len -= CONF_LEN; - if (!len || (cfg_str[0] == ',' - && !strcasecmp(++cfg_str, CONF_HTTP1))) - { - goto found; + 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; } } - return -EINVAL; - -found: - protos = kzalloc(TTLS_ALPN_PROTOS * sizeof(ttls_alpn_proto), GFP_ATOMIC); - if (unlikely(!protos)) - return -ENOMEM; - - protos[order].name = TTLS_ALPN_HTTP1; - protos[order].len = sizeof(TTLS_ALPN_HTTP1) - 1; - protos[order].id = TTLS_ALPN_ID_HTTP1; - protos[!order].name = TTLS_ALPN_HTTP2; - protos[!order].len = sizeof(TTLS_ALPN_HTTP2) - 1; - protos[!order].id = TTLS_ALPN_ID_HTTP2; +err: + tfw_tls.cfg.alpn_list = NULL; + kfree(protos); - tfw_tls.cfg.alpn_list = protos; - - return 0; + return -EINVAL; #undef CONF_HTTPS #undef CONF_HTTP1 #undef CONF_HTTP2 #undef CONF_LEN +#undef PROTO_INIT } void diff --git a/tempesta_fw/tls.h b/tempesta_fw/tls.h index 299fbe595..243b522bf 100644 --- a/tempesta_fw/tls.h +++ b/tempesta_fw/tls.h @@ -38,6 +38,9 @@ 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); diff --git a/tls/tls_srv.c b/tls/tls_srv.c index 18773fd42..7c9174b2e 100644 --- a/tls/tls_srv.c +++ b/tls/tls_srv.c @@ -311,10 +311,10 @@ ttls_parse_alpn_ext(TlsCtx *tls, const unsigned char *buf, size_t len) int i; size_t list_len, cur_len; const unsigned char *theirs, *start, *end; - const ttls_alpn_proto *our; + const ttls_alpn_proto *our, *alpn_list = tls->conf->alpn_list; /* If TLS processing is enabled, ALPN must be configured. */ - BUG_ON(!tls->conf->alpn_list); + BUG_ON(!alpn_list); /* * opaque ProtocolName<1..2^8-1>; @@ -360,8 +360,8 @@ ttls_parse_alpn_ext(TlsCtx *tls, const unsigned char *buf, size_t len) } /* Use our order of preference. */ - for (i = 0; i < TTLS_ALPN_PROTOS; ++i) { - our = &tls->conf->alpn_list[i]; + 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++; From ad183a63807f2c798a2bde0eef258a2db24bed81 Mon Sep 17 00:00:00 2001 From: Alexander Ostapenko Date: Thu, 16 May 2019 23:39:22 +0700 Subject: [PATCH 16/17] Add description in Tempesta FW configuration for HTTP/2 (#309). --- etc/tempesta_fw.conf | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/etc/tempesta_fw.conf b/etc/tempesta_fw.conf index 9c70bed8b..719a83075 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/2 +# 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; From dc1f523ea35895d2118a17a9bda5f4b71d4fb0de Mon Sep 17 00:00:00 2001 From: Alexander Ostapenko Date: Fri, 17 May 2019 03:16:49 +0700 Subject: [PATCH 17/17] Make HTTP/1.1 protocol default (#309). --- etc/tempesta_fw.conf | 6 +++--- tempesta_fw/tls.c | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/etc/tempesta_fw.conf b/etc/tempesta_fw.conf index 719a83075..6ddd135e0 100644 --- a/etc/tempesta_fw.conf +++ b/etc/tempesta_fw.conf @@ -433,9 +433,9 @@ # '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/2 -# is default. If the entire 'proto' option is absent, then plain HTTP protocol -# (over TCP) is supposed by the default. +# 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: diff --git a/tempesta_fw/tls.c b/tempesta_fw/tls.c index 1d24d9936..95c6137c0 100644 --- a/tempesta_fw/tls.c +++ b/tempesta_fw/tls.c @@ -698,8 +698,8 @@ do { \ if (unlikely(!protos)) return -ENOMEM; - /* Currently HTTP/2 protocol is default option. */ - PROTO_INIT(0, HTTP2); + /* Currently HTTP/1.1 protocol is default option. */ + PROTO_INIT(0, HTTP1); tfw_tls.cfg.alpn_list = protos; if (len == CONF_HTTPS_LEN)