From 98f41a21b04c082d9d22a233c25a9e0e2116866e Mon Sep 17 00:00:00 2001 From: Constantine Date: Wed, 21 Jun 2023 19:55:12 +0300 Subject: [PATCH 01/44] For plain http second skb was allocated even if first had space. We should allocate another skb only for tls traffic, because plaint http is zero copy. However, for tls headers can be encrypted inplace, but body not, thus we allocate new skb to avoid copy. There is some performance gain for plain http. Current master: wrk -d 10 -c 1000 -t 2 http://debian Running 10s test @ http://debian 2 threads and 1000 connections Thread Stats Avg Stdev Max +/- Stdev Latency 13.18ms 7.44ms 49.58ms 83.85% Req/Sec 22.40k 1.09k 24.85k 75.52% 444655 requests in 10.04s, 35.54GB read Requests/sec: 44283.16 Transfer/sec: 3.54GB Current commit: wrk -d 10 -c 1000 -t 2 http://debian Running 10s test @ http://debian 2 threads and 1000 connections Thread Stats Avg Stdev Max +/- Stdev Latency 10.16ms 611.25us 22.86ms 87.25% Req/Sec 25.45k 2.19k 29.13k 64.58% 507000 requests in 10.08s, 40.52GB read Requests/sec: 50316.61 Transfer/sec: 4.02GB --- fw/cache.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/fw/cache.c b/fw/cache.c index e70f6a0353..395d2911ac 100644 --- a/fw/cache.c +++ b/fw/cache.c @@ -2798,11 +2798,12 @@ tfw_cache_add_body_page(TfwMsgIter *it, char *p, int sz, bool h2, * to actually reserve any space for h2 frame header. */ static int -tfw_cache_build_resp_body(TDB *db, TdbVRec *trec, TfwMsgIter *it, char *p, - unsigned long body_sz, bool h2) +tfw_cache_build_resp_body(TDB *db, TdbVRec *trec, TfwHttpReq *req, + TfwMsgIter *it, char *p, unsigned long body_sz) { int r; - bool sh_frag = h2 ? false : true; + const bool h2 = TFW_MSG_H2(req), tls = TFW_CONN_TLS(req->conn); + const bool sh_frag = h2 ? false : (true & tls); if (WARN_ON_ONCE(!it->skb_head)) return -EINVAL; @@ -3052,8 +3053,8 @@ tfw_cache_build_resp(TfwHttpReq *req, TfwCacheEntry *ce, long age) /* Fill skb with body from cache for HTTP/2 or HTTP/1.1 response. */ BUG_ON(p != TDB_PTR(db->hdr, ce->body)); if (ce->body_len && req->method != TFW_HTTP_METH_HEAD) { - if (tfw_cache_build_resp_body(db, trec, it, p, ce->body_len, - TFW_MSG_H2(req))) + if (tfw_cache_build_resp_body(db, trec, req, it, p, + ce->body_len)) goto free; } resp->content_length = ce->body_len; From 3aad4e1fdd7b09206b8ac15f7a0d724ce32b3de0 Mon Sep 17 00:00:00 2001 From: Constantine Date: Fri, 23 Jun 2023 16:26:38 +0300 Subject: [PATCH 02/44] Rework HTTP1 response adjusting The main goal of this patch is get rid off skb fragmentation that cause errors during message adjusting. Because `TfwStr` stores pointer to skb that owns data we have problems when that data moves to another skb, it happens when we add data to highly fragmented skb and last fragments move to another skb, that breaks `skb` field of `TfwStr` which stores pointer to moved data. I have decided to mirgate to approach that same with used in HTTP2 message transformation. Unlink skbs that contains headers, except the last one skb, this skb bocomes new response `head`, then copy necessery headers to `head` using `TfwPool`. During copy adjust headers. Response for PURGE request: Now we just copy response headers to new skb skipping response body if it exists, otherwise we just remove headers from current skb using `tfw_http_msg_cutoff_headers()` and use it as skb_head. This approach used because `tfw_http_msg_cutoff_headers()` already is complex adding more logic to this function overcomplicate its. Also this function used for both HTTP1 and HTTP2 protocols if we add logic related to dropping the body of "PURGE responses" it will make an overhead for each HTTP2 response. --- fw/http.c | 606 ++++++++++++++++++++++++++++++++----------------- fw/http.h | 6 +- fw/http_msg.c | 14 +- fw/http_msg.h | 5 +- fw/http_sess.c | 24 +- 5 files changed, 429 insertions(+), 226 deletions(-) diff --git a/fw/http.c b/fw/http.c index f07ed615d1..911a79b1b0 100644 --- a/fw/http.c +++ b/fw/http.c @@ -1751,6 +1751,19 @@ do { \ } } +static void +__tfw_http_msg_cleanup(TfwHttpMsgCleanup *cleanup) +{ + int i; + struct sk_buff *skb; + + while ((skb = ss_skb_dequeue(&cleanup->skb_head))) + __kfree_skb(skb); + + for (i = 0; i < cleanup->pages_sz; i++) + put_page(cleanup->pages[i]); +} + /** * Try to mark server as suspended. * In case of HM is active do it, otherwise left unchanged. @@ -3028,22 +3041,41 @@ tfw_http_msg_create_sibling(TfwHttpMsg *hm, struct sk_buff *skb) ALLOW_ERROR_INJECTION(tfw_http_msg_create_sibling, NULL); /* - * Add 'Date:' header field to an HTTP message. + * Add 'Upgrade:' header for websocket upgrade messages */ static int -tfw_http_set_hdr_date(TfwHttpMsg *hm) +tfw_http_add_hdr_upgrade(TfwHttpMsg *hm, bool is_resp) { - int r; - char *s_date = *this_cpu_ptr(&g_buf); + int r = 0; + static const DEFINE_TFW_STR(hdr, "upgrade: websocket\r\n"); + + if (test_bit(TFW_HTTP_B_UPGRADE_WEBSOCKET, hm->flags)) { + /* + * RFC7230#section-6.7: + * A server that sends a 101 (Switching Protocols) response + * MUST send an Upgrade header field to indicate the new + * protocol(s) to which the connection is being switched; if + * multiple protocol layers are being switched, the sender MUST + * list the protocols in layer-ascending order. + * + * We do expect neither upgrades besides 'websocket' nor + * multilayer upgrades. So we consider extra options as error. + */ + if (is_resp && ((TfwHttpResp *)hm)->status == 101 + && test_bit(TFW_HTTP_B_UPGRADE_EXTRA, hm->flags)) + { + T_ERR("Unable to add uncompliant 'Upgrade:' header " + "to msg [%p]\n", hm); + return -EINVAL; + } + + r = tfw_http_msg_expand_from_pool((TfwHttpResp *)hm, &hdr); + if (unlikely(r)) + T_ERR("Unable to add Upgrade: header to msg [%p]\n", hm); + else + T_DBG2("Added Upgrade: header to msg [%p]\n", hm); + } - tfw_http_prep_date_from(s_date, ((TfwHttpResp *)hm)->date); - r = tfw_http_msg_hdr_xfrm(hm, "date", sizeof("date") - 1, - s_date, SLEN(S_V_DATE), - TFW_HTTP_HDR_RAW, 0); - if (r) - T_ERR("Unable to add Date: header to msg [%p]\n", hm); - else - T_DBG2("Added Date: header to msg [%p]\n", hm); return r; } @@ -3085,36 +3117,6 @@ tfw_http_set_hdr_upgrade(TfwHttpMsg *hm, bool is_resp) return r; } -/* - * Expand HTTP response with 'Date:' header field. - */ -int -tfw_http_expand_hdr_date(TfwHttpResp *resp) -{ - int r; - struct sk_buff **skb_head = &resp->msg.skb_head; - TfwHttpTransIter *mit = &resp->mit; - char *date = *this_cpu_ptr(&g_buf); - TfwStr h_date = { - .chunks = (TfwStr []){ - { .data = S_F_DATE, .len = SLEN(S_F_DATE) }, - { .data = date, .len = SLEN(S_V_DATE) }, - { .data = S_CRLF, .len = SLEN(S_CRLF) } - }, - .len = SLEN(S_F_DATE) + SLEN(S_V_DATE) + SLEN(S_CRLF), - .nchunks = 3 - }; - - tfw_http_prep_date_from(date, resp->date); - r = tfw_http_msg_expand_data(&mit->iter, skb_head, &h_date, NULL); - if (r) - T_ERR("Unable to expand resp [%p] with 'Date:' header\n", resp); - else - T_DBG2("Epanded resp [%p] with 'Date:' header\n", resp); - - return r; -} - /** * Connection is to be closed after response for the request @req is forwarded * to the client. Don't process new requests from the client and update @@ -3240,51 +3242,6 @@ tfw_http_set_hdr_connection(TfwHttpMsg *hm, unsigned long conn_flg) return r; } -/** - * Add/Replace/Remove Keep-Alive header field to/from HTTP message. - */ -static int -tfw_http_set_hdr_keep_alive(TfwHttpMsg *hm, unsigned long conn_flg) -{ - int r; - - BUILD_BUG_ON(BIT_WORD(__TFW_HTTP_MSG_M_CONN) != 0); - if ((hm->flags[0] & __TFW_HTTP_MSG_M_CONN) == conn_flg) - return 0; - - switch (conn_flg) { - case BIT(TFW_HTTP_B_CONN_CLOSE): - r = TFW_HTTP_MSG_HDR_DEL(hm, "Keep-Alive", - TFW_HTTP_HDR_KEEP_ALIVE); - if (unlikely(r && r != -ENOENT)) { - T_WARN("Cannot delete Keep-Alive header (%d)\n", r); - return r; - } - return 0; - case BIT(TFW_HTTP_B_CONN_KA): - /* - * If present, "Keep-Alive" header informs the other side - * of the timeout policy for a connection. Otherwise, it's - * presumed that default policy is in action. - * - * TODO: Add/Replace "Keep-Alive" header when Tempesta - * implements connection timeout policies and the policy - * for the connection differs from default policy. - */ - return 0; - default: - /* - * "Keep-Alive" header mandates that "Connection: keep-alive" - * header in present in HTTP message. HTTP/1.1 connections - * are keep-alive by default. If we want to add "Keep-Alive" - * header then "Connection: keep-alive" header must be added - * as well. TFW_HTTP_F_CONN_KA flag will force the addition - * of "Connection: keep-alive" header to HTTP message. - */ - return 0; - } -} - /* * In case if response is stale, we should pass it with a warning. */ @@ -3308,6 +3265,91 @@ tfw_http_expand_stale_warn(TfwHttpResp *resp) return tfw_http_msg_expand_data(&mit->iter, skb_head, &wh, NULL); } +static inline int +__tfw_http_add_hdr_date(TfwHttpResp *resp, bool cache) +{ + int r; + struct sk_buff **skb_head = &resp->msg.skb_head; + TfwHttpTransIter *mit = &resp->mit; + char *date = *this_cpu_ptr(&g_buf); + TfwStr h_date = { + .chunks = (TfwStr []) { + { .data = S_F_DATE, .len = SLEN(S_F_DATE) }, + { .data = date, .len = SLEN(S_V_DATE) }, + { .data = S_CRLF, .len = SLEN(S_CRLF) } + }, + .len = SLEN(S_F_DATE) + SLEN(S_V_DATE) + SLEN(S_CRLF), + .nchunks = 3 + }; + + tfw_http_prep_date_from(date, resp->date); + + if (!cache) + r = tfw_http_msg_expand_from_pool(resp, &h_date); + else + r = tfw_http_msg_expand_data(&mit->iter, skb_head, &h_date, + NULL); + + if (unlikely(r)) + T_ERR("Unable to add Date: header to resp [%p]\n", resp); + else + T_DBG2("Added Date: header to resp [%p]\n", resp); + + return r; +} + +/* + * Add 'Date:' header field to an HTTP message. + */ +static int +tfw_http_add_hdr_date(TfwHttpResp *resp) +{ + return __tfw_http_add_hdr_date(resp, false); +} + +/* + * Expand HTTP response with 'Date:' header field. + */ +int +tfw_http_expand_hdr_date(TfwHttpResp *resp) +{ + return __tfw_http_add_hdr_date(resp, true); +} + +static int +__tfw_http_add_hdr_server(TfwHttpResp *resp, bool cache) +{ + int r; + struct sk_buff **skb_head = &resp->msg.skb_head; + TfwHttpTransIter *mit = &resp->mit; + static char s_server[] = S_F_SERVER TFW_NAME "/" TFW_VERSION S_CRLF; + TfwStr hdr = { .data = s_server, .len = SLEN(s_server) }; + + if (!cache) + r = tfw_http_msg_expand_from_pool(resp, &hdr); + else + r = tfw_http_msg_expand_data(&mit->iter, skb_head, &hdr, NULL); + + if (unlikely(r)) + T_ERR("Unable to add Server: header to resp [%p]\n", resp); + else + T_DBG2("Added Server: header to resp [%p]\n", resp); + + return r; +} + +static int +tfw_http_add_hdr_server(TfwHttpResp *resp) +{ + return __tfw_http_add_hdr_server(resp, false); +} + +int +tfw_http_expand_hdr_server(TfwHttpResp *resp) +{ + return __tfw_http_add_hdr_server(resp, true); +} + static inline int __tfw_http_add_hdr_via(TfwHttpMsg *hm, int http_version, bool from_cache) { @@ -3337,9 +3379,18 @@ __tfw_http_add_hdr_via(TfwHttpMsg *hm, int http_version, bool from_cache) g_vhost->hdr_via_len); if (!from_cache) { - r = tfw_http_msg_hdr_add(hm, &rh); - } - else { + if (!hm->resp) { + /* Add via to request */ + if (unlikely((r = tfw_http_msg_hdr_add(hm, &rh)))) + return r; + return 0; + } + r = tfw_http_msg_expand_from_pool((TfwHttpResp *)hm, &rh); + if (unlikely(r)) + return r; + r = tfw_http_msg_expand_from_pool((TfwHttpResp *)hm, + &TFW_STR_STRING(S_CRLF)); + } else { struct sk_buff **skb_head = &hm->msg.skb_head; TfwHttpTransIter *mit = &((TfwHttpResp *)hm)->mit; TfwStr crlf = { .data = S_CRLF, .len = SLEN(S_CRLF) }; @@ -3462,6 +3513,40 @@ tfw_http_should_validate_post_req(TfwHttpReq *req) return false; } +static bool +tfw_http_hdr_sub(unsigned short hid, const TfwStr *hdr, + const TfwHdrMods *h_mods) +{ + unsigned int idx; + const TfwHdrModsDesc *desc; + + if (!h_mods) + return false; + + /* Fast path for special headers */ + if (hid >= TFW_HTTP_HDR_REGULAR && hid < TFW_HTTP_HDR_RAW) { + desc = h_mods->spec_hdrs[hid]; + /* Skip only resp_hdr_set headers */ + return desc ? !desc->append : false; + } + + if (hdr->hpack_idx > 0) { + /* Don't touch pseudo-headers. */ + if (hdr->hpack_idx <= HPACK_STATIC_TABLE_REGULAR) + return false; + + return test_bit(hdr->hpack_idx, h_mods->s_tbl); + } + + for (idx = h_mods->spec_num; idx < h_mods->sz; ++idx) { + desc = &h_mods->hdrs[idx]; + if (!desc->append && !__hdr_name_cmp(hdr, desc->hdr)) + return true; + } + + return false; +} + /** * Add local headers (defined by administrator in configuration file) to http/1 * message. @@ -3549,6 +3634,61 @@ tfw_h1_set_loc_hdrs(TfwHttpMsg *hm, bool is_resp, bool from_cache) return 0; } +static int +tfw_h1_add_loc_hdrs(TfwHttpMsg *hm, const TfwHdrMods *h_mods) +{ + unsigned int i; + TfwHttpHdrTbl *ht = hm->h_tbl; + + if (!h_mods) + return 0; + + for (i = 0; i < h_mods->sz; ++i) { + const TfwHdrModsDesc *desc = &h_mods->hdrs[i]; + int r; + unsigned short hid = desc->hid; + TfwStr h_mdf = { + .chunks = (TfwStr []){ + {}, + { .data = S_DLM, .len = SLEN(S_DLM) }, + {} + }, + .len = SLEN(S_DLM), + .nchunks = 2 /* header name + delimeter. */ + }; + + /* FIXME: this is a temporary WA for GCC12, see #1695 for details */ + if (TFW_STR_CHUNK(desc->hdr, 1) == NULL) + continue; + + if (unlikely(desc->append && !TFW_STR_EMPTY(&ht->tbl[hid]) + && (hid < TFW_HTTP_HDR_NONSINGULAR))) + { + T_WARN("Attempt to add already existed singular header '%.*s'\n", + PR_TFW_STR(TFW_STR_CHUNK(desc->hdr, 0))); + continue; + } + + h_mdf.chunks[0] = desc->hdr->chunks[0]; + if (desc->hdr->nchunks == 2) { + h_mdf.chunks[2] = desc->hdr->chunks[1]; + h_mdf.nchunks += 1; + } + h_mdf.len += desc->hdr->len; + h_mdf.flags = desc->hdr->flags; + h_mdf.eolen += desc->hdr->eolen; + r = tfw_http_msg_expand_from_pool((TfwHttpResp *)hm, &h_mdf); + if (unlikely(r)) + return r; + r = tfw_http_msg_expand_from_pool((TfwHttpResp *)hm, + &TFW_STR_STRING(S_CRLF)); + if (unlikely(r)) + return r; + } + + return 0; +} + static int tfw_h1_rewrite_method_to_get(struct sk_buff **head_p, size_t chop_len) { @@ -4223,33 +4363,70 @@ tfw_h2_adjust_req(TfwHttpReq *req) } /* - * Throw away a response body and set "Content-Length" to zero. + * Prepare current response skb_head for cleaning and replace current skb_head + * with new empty skb. With this approach headers will be copied to new skb + * skipping body and logic related to finding the right place for cutting + * headers will be avoided. */ static int -tfw_h1_purge_resp_clean(TfwHttpResp *resp) +tfw_h1_purge_resp_clean(TfwHttpResp *resp, TfwHttpMsgCleanup *cleanup) { - int ret; - TfwStr replacement = { - .chunks = (TfwStr []) { - TFW_STR_STRING("Content-Length"), - TFW_STR_STRING(": "), - TFW_STR_STRING("0"), - }, - .nchunks = 3, - }; - TfwStr *c = replacement.chunks; + struct sk_buff *nskb; - if (!TFW_STR_EMPTY(&resp->body)) { - ret = ss_skb_list_chop_head_tail(&resp->msg.skb_head, - 0, tfw_str_total_len(&resp->body)); - if (ret) - return ret; - TFW_STR_INIT(&resp->body); + cleanup->skb_head = resp->msg.skb_head; + resp->msg.skb_head = NULL; + + nskb = ss_skb_alloc(0); + if (unlikely(!nskb)) + return -ENOMEM; + + ss_skb_queue_tail(&resp->msg.skb_head, nskb); + + return 0; +} + +/** + * Add Connection header to HTTP message @msg depend on @conn_flg. + */ +static int +tfw_http_resp_set_hdr_connection(TfwHttpMsg *hm, unsigned long conn_flg) +{ + int r; + static const DEFINE_TFW_STR(conn_close, "connection: close\r\n"); + static const DEFINE_TFW_STR(conn_ka_up, "connection: keep-alive, \ + upgrade\r\n"); + static const DEFINE_TFW_STR(conn_ka, "connection: keep-alive\r\n"); + static const DEFINE_TFW_STR(conn_up, "connection: upgrade\r\n"); + TfwHttpResp *resp = (TfwHttpResp *)hm; + + BUILD_BUG_ON(BIT_WORD(__TFW_HTTP_MSG_M_CONN) != 0); + + /* + * We can see `TFW_HTTP_B_CONN_CLOSE` here only in case of 4XX + * response with 'Connection: close' option. + * + * For requests conn_flg by default is TFW_HTTP_B_CONN_KA. + */ + if (unlikely(conn_flg == BIT(TFW_HTTP_B_CONN_CLOSE))) + return tfw_http_msg_expand_from_pool(resp, &conn_close); + + if (conn_flg == BIT(TFW_HTTP_B_CONN_KA)) { + if (test_bit(TFW_HTTP_B_UPGRADE_WEBSOCKET, hm->flags) + && test_bit(TFW_HTTP_B_CONN_UPGRADE, hm->flags)) + { + r = tfw_http_msg_expand_from_pool(resp, &conn_ka_up); + } else { + r = tfw_http_msg_expand_from_pool(resp, &conn_ka); + } + } else { + if (test_bit(TFW_HTTP_B_UPGRADE_WEBSOCKET, hm->flags) + && test_bit(TFW_HTTP_B_CONN_UPGRADE, hm->flags)) + { + r = tfw_http_msg_expand_from_pool(resp, &conn_up); + } } - replacement.len = c[0].len + c[1].len + c[2].len; - return tfw_http_msg_hdr_xfrm_str((TfwHttpMsg *)resp, &replacement, - TFW_HTTP_HDR_CONTENT_LENGTH, false); + return r; } /** @@ -4258,10 +4435,17 @@ tfw_h1_purge_resp_clean(TfwHttpResp *resp) static int tfw_http_adjust_resp(TfwHttpResp *resp) { + int r, hdr_start = TFW_HTTP_HDR_CONTENT_LENGTH;; TfwHttpReq *req = resp->req; TfwHttpMsg *hm = (TfwHttpMsg *)resp; unsigned long conn_flg = 0; - int r; + TfwHttpMsgCleanup cleanup = {}; + TfwHttpTransIter *mit = &resp->mit; + TfwStr *pos, *end, *s_line = &resp->h_tbl->tbl[TFW_HTTP_STATUS_LINE]; + static const DEFINE_TFW_STR(crlf, S_CRLF); + const TfwHdrMods *h_mods = tfw_vhost_get_hdr_mods(req->location, + req->vhost, + TFW_VHOST_HDRMOD_RESP); /* * If request violated backend rules, backend may respond with 4xx code @@ -4274,67 +4458,129 @@ tfw_http_adjust_resp(TfwHttpResp *resp) { tfw_http_req_set_conn_close(req); conn_flg = BIT(TFW_HTTP_B_CONN_CLOSE); - } - else - { + } else { if (unlikely(test_bit(TFW_HTTP_B_CONN_CLOSE, req->flags))) conn_flg = BIT(TFW_HTTP_B_CONN_CLOSE); else if (test_bit(TFW_HTTP_B_CONN_KA, req->flags)) conn_flg = BIT(TFW_HTTP_B_CONN_KA); } - if (test_bit(TFW_HTTP_B_REQ_HEAD_TO_GET, req->flags) - && !TFW_STR_EMPTY(&resp->body)) { - r = ss_skb_list_chop_head_tail(&resp->msg.skb_head, 0, - tfw_str_total_len(&resp->body) - + resp->trailers_len); - if (r) - return r; - TFW_STR_INIT(&resp->body); - } + /* Response for PURGE/HEAD request. */ + if (test_bit(TFW_HTTP_B_PURGE_GET, req->flags) || + test_bit(TFW_HTTP_B_REQ_HEAD_TO_GET, req->flags)) { + static const DEFINE_TFW_STR(clen, "Content-Length: 0\r\n"); - if (test_bit(TFW_HTTP_B_PURGE_GET, req->flags)) { - r = tfw_h1_purge_resp_clean(resp); - if (r < 0) - return r; + /* Clean current reponse skb_head if body is exists. */ + if (resp->body.len > 0) { + tfw_h1_purge_resp_clean(resp, &cleanup); + if (unlikely(r)) + goto clean; + + tfw_h2_msg_transform_setup(mit, resp->msg.skb_head); + } else { + /* + * When response doesn't have body, just remove + * headers and use current skb as skb head. + */ + tfw_h2_msg_transform_setup(mit, resp->msg.skb_head); + + r = tfw_http_msg_cutoff_headers(resp, &cleanup); + if (unlikely(r)) + goto clean; + } + + r = tfw_http_msg_expand_from_pool(resp, s_line); + if (unlikely(r)) + goto clean; + r = tfw_http_msg_expand_from_pool(resp, &crlf); + if (unlikely(r)) + goto clean; + + /* + * For a response to PURGE request we drop the body. + * Add "content-length: 0" header. + */ + r = tfw_http_msg_expand_from_pool(resp, &clen); + if (unlikely(r)) + goto clean; + + hdr_start = TFW_HTTP_HDR_CONTENT_TYPE; + } else { + /* Response for regular request. */ + tfw_h2_msg_transform_setup(mit, resp->msg.skb_head); + r = tfw_http_msg_cutoff_headers(resp, &cleanup); + if (unlikely(r)) + goto clean; + + r = tfw_http_msg_expand_from_pool(resp, s_line); + if (unlikely(r)) + goto clean; + r = tfw_http_msg_expand_from_pool(resp, &crlf); + if (unlikely(r)) + goto clean; } - r = tfw_http_sess_resp_process(resp, false); - if (r < 0) - return r; + FOR_EACH_HDR_FIELD_FROM(pos, end, resp, hdr_start) { + int hid = pos - resp->h_tbl->tbl; + TfwStr *dup, *dup_end, *hdr = pos; - r = tfw_http_msg_del_hbh_hdrs(hm); - if (r < 0) - return r; + /* Skip hop-by-hop headers. */ + if (TFW_STR_EMPTY(hdr) || (hdr->flags & TFW_STR_HBH_HDR)) + continue; - r = tfw_http_set_hdr_upgrade(hm, true); - if (r < 0) - return r; + if (TFW_STR_DUP(hdr)) + hdr = TFW_STR_CHUNK(hdr, 0); - r = tfw_http_set_hdr_keep_alive(hm, conn_flg); - if (r < 0) - return r; + if (tfw_http_hdr_sub(hid, hdr, h_mods)) + continue; - r = tfw_http_set_hdr_connection(hm, conn_flg); + TFW_STR_FOR_EACH_DUP(dup, pos, dup_end) { + r = tfw_http_msg_expand_from_pool(resp, dup); + if (unlikely(r)) + goto clean; + tfw_http_msg_expand_from_pool(resp, &crlf); + if (unlikely(r)) + goto clean; + } + } + + r = tfw_http_sess_resp_process(resp, false); if (r < 0) return r; + r = tfw_http_resp_set_hdr_connection(hm, conn_flg); + if (unlikely(r)) + goto clean; + + r = tfw_http_add_hdr_upgrade(hm, true); + if (unlikely(r)) + goto clean; + r = tfw_http_add_hdr_via(hm); - if (r < 0) - return r; + if (unlikely(r)) + goto clean; - r = tfw_h1_set_loc_hdrs(hm, true, false); - if (r < 0) - return r; + r = tfw_h1_add_loc_hdrs(hm, h_mods); + if (unlikely(r)) + goto clean; if (!test_bit(TFW_HTTP_B_HDR_DATE, resp->flags)) { - r = tfw_http_set_hdr_date(hm); - if (r < 0) - return r; + r = tfw_http_add_hdr_date(resp); + if (unlikely(r < 0)) + goto clean; } - return TFW_HTTP_MSG_HDR_XFRM(hm, "Server", TFW_NAME "/" TFW_VERSION, - TFW_HTTP_HDR_SERVER, 0); + r = tfw_http_add_hdr_server(resp); + if (unlikely(r)) + goto clean; + + /* Write last CRLF for headers block. */ + r = tfw_http_msg_expand_from_pool(resp, &crlf); + +clean: + __tfw_http_msg_cleanup(&cleanup); + + return r; } /* @@ -4893,39 +5139,6 @@ tfw_h2_resp_add_loc_hdrs(TfwHttpResp *resp, const TfwHdrMods *h_mods, return 0; } -static bool -tfw_h2_hdr_sub(unsigned short hid, const TfwStr *hdr, const TfwHdrMods *h_mods) -{ - unsigned int idx; - const TfwHdrModsDesc *desc; - - if (!h_mods) - return false; - - /* Fast path for special headers */ - if (hid >= TFW_HTTP_HDR_REGULAR && hid < TFW_HTTP_HDR_RAW) { - desc = h_mods->spec_hdrs[hid]; - /* Skip only resp_hdr_set headers */ - return desc ? !desc->append : false; - } - - if (hdr->hpack_idx > 0) { - /* Don't touch pseudo-headers. */ - if (hdr->hpack_idx <= HPACK_STATIC_TABLE_REGULAR) - return false; - - return test_bit(hdr->hpack_idx, h_mods->s_tbl); - } - - for (idx = h_mods->spec_num; idx < h_mods->sz; ++idx) { - desc = &h_mods->hdrs[idx]; - if (!desc->append && !__hdr_name_cmp(hdr, desc->hdr)) - return true; - } - - return false; -} - static int tfw_h2_hpack_encode_headers(TfwHttpResp *resp, const TfwHdrMods *h_mods) { @@ -4953,7 +5166,7 @@ tfw_h2_hpack_encode_headers(TfwHttpResp *resp, const TfwHdrMods *h_mods) h_mods ? h_mods->sz : 0); /* Don't encode header if it must be substituted from config */ - if (tfw_h2_hdr_sub(hid, tgt, h_mods)) + if (tfw_http_hdr_sub(hid, tgt, h_mods)) continue; /* @@ -5448,19 +5661,6 @@ tfw_http_req_block(TfwHttpReq *req, int status, const char *msg, err_code); } -static void -__tfw_h2_resp_cleanup(TfwHttpRespCleanup *cleanup) -{ - int i; - struct sk_buff *skb; - - while ((skb = ss_skb_dequeue(&cleanup->skb_head))) - __kfree_skb(skb); - - for (i = 0; i < cleanup->pages_sz; i++) - put_page(cleanup->pages[i]); -} - /* * TODO: RFC 7540 8.1.2 * However, header field names MUST be converted to lowercase prior to @@ -5476,7 +5676,7 @@ tfw_h2_resp_encode_headers(TfwHttpResp *resp) int r; TfwHttpReq *req = resp->req; TfwHttpTransIter *mit = &resp->mit; - TfwHttpRespCleanup cleanup = {}; + TfwHttpMsgCleanup cleanup = {}; TfwStr codings = {.data = *this_cpu_ptr(&g_te_buf), .len = 0}; const TfwHdrMods *h_mods = tfw_vhost_get_hdr_mods(req->location, req->vhost, @@ -5511,9 +5711,9 @@ tfw_h2_resp_encode_headers(TfwHttpResp *resp) * adjusting of particular headers. */ WARN_ON_ONCE(mit->acc_len); - tfw_h2_msg_transform_setup(mit, resp->msg.skb_head, true); + tfw_h2_msg_transform_setup(mit, resp->msg.skb_head); - r = tfw_h2_msg_cutoff_headers(resp, &cleanup); + r = tfw_http_msg_cutoff_headers(resp, &cleanup); if (unlikely(r)) goto clean; @@ -5578,11 +5778,11 @@ tfw_h2_resp_encode_headers(TfwHttpResp *resp) req, resp); SS_SKB_QUEUE_DUMP(&resp->msg.skb_head); - __tfw_h2_resp_cleanup(&cleanup); + __tfw_http_msg_cleanup(&cleanup); return 0; clean: - __tfw_h2_resp_cleanup(&cleanup); + __tfw_http_msg_cleanup(&cleanup); return r; } diff --git a/fw/http.h b/fw/http.h index 044732da7d..0b99f142ef 100644 --- a/fw/http.h +++ b/fw/http.h @@ -501,8 +501,7 @@ struct tfw_http_resp_t { }; /** - * Represents the data that should be cleaned up after HTTP1 -> HTTP2 response - * transformation. + * Represents the data that should be cleaned up after message transformation. * * @skb_head - head of skb list that must be freed; * @pages - pages that must be freed; @@ -512,7 +511,7 @@ typedef struct { struct sk_buff *skb_head; struct page *pages[MAX_SKB_FRAGS]; unsigned char pages_sz; -} TfwHttpRespCleanup; +} TfwHttpMsgCleanup; #define TFW_HDR_MAP_INIT_CNT 32 #define TFW_HDR_MAP_SZ(cnt) (sizeof(TfwHttpHdrMap) \ @@ -752,6 +751,7 @@ int tfw_http_expand_stale_warn(TfwHttpResp *resp); int tfw_http_expand_hdr_date(TfwHttpResp *resp); int tfw_http_expand_hbh(TfwHttpResp *resp, unsigned short status); int tfw_http_expand_hdr_via(TfwHttpResp *resp); +int tfw_http_expand_hdr_server(TfwHttpResp *resp); void tfw_h2_resp_fwd(TfwHttpResp *resp); int tfw_h2_hdr_map(TfwHttpResp *resp, const TfwStr *hdr, unsigned int id); int tfw_h2_add_hdr_date(TfwHttpResp *resp, bool cache); diff --git a/fw/http_msg.c b/fw/http_msg.c index 9b0c23111a..3b3a82b3db 100644 --- a/fw/http_msg.c +++ b/fw/http_msg.c @@ -1676,8 +1676,8 @@ tfw_http_msg_expand_from_pool_lc(TfwHttpResp *resp, const TfwStr *str) } static inline void -__tfw_h2_msg_move_frags(struct sk_buff *skb, int frag_idx, - TfwHttpRespCleanup *cleanup) +__tfw_http_msg_move_frags(struct sk_buff *skb, int frag_idx, + TfwHttpMsgCleanup *cleanup) { int i, len; struct page *page; @@ -1697,7 +1697,7 @@ __tfw_h2_msg_move_frags(struct sk_buff *skb, int frag_idx, } static inline void -__tfw_h2_msg_rm_all_frags(struct sk_buff *skb, TfwHttpRespCleanup *cleanup) +__tfw_http_msg_rm_all_frags(struct sk_buff *skb, TfwHttpMsgCleanup *cleanup) { int i, len; struct page *page; @@ -1715,7 +1715,7 @@ __tfw_h2_msg_rm_all_frags(struct sk_buff *skb, TfwHttpRespCleanup *cleanup) } static inline void -__tfw_h2_msg_shrink_frag(struct sk_buff *skb, int frag_idx, const char *nbegin) +__tfw_http_msg_shrink_frag(struct sk_buff *skb, int frag_idx, const char *nbegin) { skb_frag_t *frag = &skb_shinfo(skb)->frags[frag_idx]; const int len = nbegin - (char*)skb_frag_address(frag); @@ -1733,7 +1733,7 @@ __tfw_h2_msg_shrink_frag(struct sk_buff *skb, int frag_idx, const char *nbegin) * as source for message trasformation. */ int -tfw_h2_msg_cutoff_headers(TfwHttpResp *resp, TfwHttpRespCleanup* cleanup) +tfw_http_msg_cutoff_headers(TfwHttpResp *resp, TfwHttpMsgCleanup* cleanup) { int i, ret; char *begin, *end; @@ -1788,7 +1788,7 @@ tfw_h2_msg_cutoff_headers(TfwHttpResp *resp, TfwHttpRespCleanup* cleanup) * Fragment contains headers and body. * Set beginning of frag as beginning of body. */ - __tfw_h2_msg_shrink_frag(it->skb, i, off); + __tfw_http_msg_shrink_frag(it->skb, i, off); } /* @@ -1797,7 +1797,7 @@ tfw_h2_msg_cutoff_headers(TfwHttpResp *resp, TfwHttpRespCleanup* cleanup) * from skb. */ if (i >= 1) - __tfw_h2_msg_move_frags(it->skb, i, cleanup); + __tfw_http_msg_move_frags(it->skb, i, cleanup); goto end; } diff --git a/fw/http_msg.h b/fw/http_msg.h index d8350dd438..1b497db0f0 100644 --- a/fw/http_msg.h +++ b/fw/http_msg.h @@ -106,8 +106,7 @@ tfw_http_msg_alloc_resp_light(TfwHttpReq *req) } static inline void -tfw_h2_msg_transform_setup(TfwHttpTransIter *mit, struct sk_buff *skb, - bool init) +tfw_h2_msg_transform_setup(TfwHttpTransIter *mit, struct sk_buff *skb) { TfwMsgIter *iter = &mit->iter; @@ -176,7 +175,7 @@ int tfw_http_msg_expand_from_pool(TfwHttpResp *resp, const TfwStr *str); int tfw_http_msg_expand_from_pool_lc(TfwHttpResp *resp, const TfwStr *str); int __hdr_name_cmp(const TfwStr *hdr, const TfwStr *cmp_hdr); int __http_hdr_lookup(TfwHttpMsg *hm, const TfwStr *hdr); -int tfw_h2_msg_cutoff_headers(TfwHttpResp *resp, TfwHttpRespCleanup* cleanup); +int tfw_http_msg_cutoff_headers(TfwHttpResp *resp, TfwHttpMsgCleanup* cleanup); int tfw_http_msg_insert(TfwMsgIter *it, char **off, const TfwStr *data); int tfw_http_msg_linear_transform(TfwMsgIter *it); diff --git a/fw/http_sess.c b/fw/http_sess.c index 11363360e2..7f07c4a5fa 100644 --- a/fw/http_sess.c +++ b/fw/http_sess.c @@ -352,6 +352,7 @@ tfw_http_sticky_add(TfwHttpResp *resp, bool cache) .eolen = 2, .nchunks = 3 }; + static const DEFINE_TFW_STR(crlf, S_CRLF); /* See comment from tfw_http_sticky_build_redirect(). */ bin2hex(buf, &ts_be64, sizeof(ts_be64)); @@ -367,21 +368,24 @@ tfw_http_sticky_add(TfwHttpResp *resp, bool cache) else if (cache) { TfwHttpTransIter *mit = &resp->mit; struct sk_buff **skb_head = &resp->msg.skb_head; - TfwStr crlf = { .data = S_CRLF, .len = SLEN(S_CRLF) }; r = tfw_http_msg_expand_data(&mit->iter, skb_head, &set_cookie, NULL); - if (!r) - r = tfw_http_msg_expand_data(&mit->iter, skb_head, - &crlf, NULL); - } - else { - r = tfw_http_msg_hdr_add((TfwHttpMsg *)resp, &set_cookie); + if (unlikely(r)) + goto err; + r = tfw_http_msg_expand_data(&mit->iter, skb_head, &crlf, NULL); + } else { + r = tfw_http_msg_expand_from_pool(resp, &set_cookie); + if (unlikely(r)) + goto err; + r = tfw_http_msg_expand_from_pool(resp, &crlf); } - if (unlikely(r)) - T_WARN("Cannot add '%s' header: val='%.*s=%.*s'\n", name, - PR_TFW_STR(&sticky->name), len, buf); + return 0; + +err: + T_WARN("Cannot add '%s' header: val='%.*s=%.*s'\n", name, + PR_TFW_STR(&sticky->name), len, buf); return r; } From 9d274f91f531afdf678c435b9c07baad7dea4983 Mon Sep 17 00:00:00 2001 From: Constantine Date: Tue, 15 Aug 2023 19:24:05 +0300 Subject: [PATCH 03/44] TfwMsgIter moved to TfwHttpMsg structure(SQUASH ME) `TfwMsgIter Iter` moved from TfwHttpTransIter to TfwHttpMsg because it will be used in response/request transformation and adjusting. --- fw/http.h | 4 ++-- fw/http_msg.c | 22 +++++++++++----------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/fw/http.h b/fw/http.h index 0b99f142ef..f71f298e0b 100644 --- a/fw/http.h +++ b/fw/http.h @@ -272,6 +272,7 @@ typedef struct { * @cache_ctl - cache control data for a message; * @version - HTTP version (1.0 and 1.1 are only supported); * @keep_alive - the value of timeout specified in Keep-Alive header; + * @iter - skb expansion iterator; * @content_length - the value of Content-Length header field; * @flags - message related flags. The flags are tested * concurrently, but concurrent updates aren't @@ -300,6 +301,7 @@ typedef struct { TfwCacheControl cache_ctl; \ unsigned char version; \ unsigned int keep_alive; \ + TfwMsgIter iter; \ unsigned long content_length; \ DECLARE_BITMAP (flags, _TFW_HTTP_FLAGS_NUM); \ TfwConn *conn; \ @@ -455,7 +457,6 @@ typedef struct { * @frame_head - pointer to reserved space for frame header. Used during * http2 framing. Simplifies framing of paged SKBs. * Framing function may not worry about paged and liner SKBs. - * @iter - skb expansion iterator; * @acc_len - accumulated length of transformed message. */ typedef struct { @@ -463,7 +464,6 @@ typedef struct { unsigned int start_off; char *curr_ptr; char *frame_head; - TfwMsgIter iter; unsigned long acc_len; } TfwHttpTransIter; diff --git a/fw/http_msg.c b/fw/http_msg.c index 3b3a82b3db..ea949a3376 100644 --- a/fw/http_msg.c +++ b/fw/http_msg.c @@ -1429,33 +1429,31 @@ tfw_http_msg_expand_data(TfwMsgIter *it, struct sk_buff **skb_head, return 0; } -static int -tfw_http_msg_alloc_from_pool(TfwHttpTransIter *mit, TfwPool* pool, size_t size) +static char * +tfw_http_msg_alloc_from_pool(TfwMsgIter *it, TfwPool* pool, size_t size) { int r; bool np; char* addr; - TfwMsgIter *it = &mit->iter; struct skb_shared_info *si = skb_shinfo(it->skb); addr = tfw_pool_alloc_not_align_np(pool, size, &np); if (!addr) - return -ENOMEM; + return NULL; if (np || it->frag == -1) { it->frag++; r = ss_skb_add_frag(it->skb_head, &it->skb, addr, &it->frag, size); if (unlikely(r)) - return r; + return NULL; } else { skb_frag_size_add(&si->frags[it->frag], size); } ss_skb_adjust_data_len(it->skb, size); - mit->curr_ptr = addr; - return 0; + return addr; } /** @@ -1580,11 +1578,11 @@ static int __tfw_http_msg_expand_from_pool(TfwHttpResp *resp, const TfwStr *str, void cpy(void *dest, const void *src, size_t n)) { + int r; const TfwStr *c, *end; unsigned int room, skb_room, n_copy, rlen, off; - int r; TfwHttpTransIter *mit = &resp->mit; - TfwMsgIter *it = &mit->iter; + TfwMsgIter *it = &resp->iter; TfwPool* pool = resp->pool; BUG_ON(it->skb->len > SS_SKB_MAX_DATA_LEN); @@ -1646,13 +1644,15 @@ __tfw_http_msg_expand_from_pool(TfwHttpResp *resp, const TfwStr *str, n_copy = min(n_copy, skb_room); - r = tfw_http_msg_alloc_from_pool(mit, pool, n_copy); - if (unlikely(r)) + mit->curr_ptr = tfw_http_msg_alloc_from_pool(it, pool, + n_copy); + if (unlikely(!mit->curr_ptr)) return r; cpy(mit->curr_ptr, c->data + off, n_copy); rlen -= n_copy; mit->acc_len += n_copy; + mit->curr_ptr += n_copy; T_DBG3("%s: acc_len=%lu, n_copy=%u, mit->curr_ptr=%pK", __func__, mit->acc_len, From 1343cf9bbb3ebaf849e406f82666fb4b41bfc531 Mon Sep 17 00:00:00 2001 From: Constantine Date: Wed, 9 Aug 2023 14:54:58 +0300 Subject: [PATCH 04/44] Rework HTTP1 request adjusting (Part 1/2) Same as response patch. `TfwHttpMsgCleanup *cleanup` added to TfwHttpReq to have ability to cleanup request on destruction. In future `old_head` must be replaced with `cleanup`. --- fw/cache.c | 2 +- fw/hpack.c | 14 +- fw/http.c | 533 ++++++++++++++------------------------ fw/http.h | 60 ++++- fw/http_msg.c | 85 ++---- fw/http_msg.h | 8 +- fw/http_sess.c | 5 +- fw/http_types.h | 2 +- fw/t/unit/test_http_msg.c | 4 +- 9 files changed, 282 insertions(+), 431 deletions(-) diff --git a/fw/cache.c b/fw/cache.c index 395d2911ac..19e7f68ee2 100644 --- a/fw/cache.c +++ b/fw/cache.c @@ -3005,7 +3005,7 @@ tfw_cache_build_resp(TfwHttpReq *req, TfwCacheEntry *ce, long age) */ if (tfw_http_expand_hbh(resp, ce->resp_status) || tfw_http_expand_hdr_via(resp) - || tfw_h1_set_loc_hdrs((TfwHttpMsg *)resp, true, true) + || tfw_h1_add_loc_hdrs((TfwHttpMsg *)resp, h_mods, true) || (age > ce->lifetime && tfw_http_expand_stale_warn(resp)) || (!test_bit(TFW_HTTP_B_HDR_DATE, resp->flags) diff --git a/fw/hpack.c b/fw/hpack.c index 8ca3ef2377..0455902560 100644 --- a/fw/hpack.c +++ b/fw/hpack.c @@ -3408,7 +3408,8 @@ tfw_hpack_write_idx(TfwHttpResp *__restrict resp, TfwHPackInt *__restrict idx, s_idx.data); if (use_pool) - return tfw_http_msg_expand_from_pool(resp, &s_idx); + return tfw_http_msg_expand_from_pool((TfwHttpMsg *)resp, + &s_idx); return tfw_http_msg_expand_data(iter, skb_head, &s_idx, &mit->start_off); @@ -3425,6 +3426,7 @@ tfw_hpack_hdr_add(TfwHttpResp *__restrict resp, TfwStr *__restrict hdr, int r; TfwHPackInt vlen; TfwStr s_name = {}, s_val = {}, s_vlen = {}; + TfwHttpMsg *msg = (TfwHttpMsg *)resp; if (!hdr) return -EINVAL; @@ -3447,14 +3449,14 @@ tfw_hpack_hdr_add(TfwHttpResp *__restrict resp, TfwStr *__restrict hdr, s_nlen.data = nlen.buf; s_nlen.len = nlen.sz; - r = tfw_http_msg_expand_from_pool(resp, &s_nlen); + r = tfw_http_msg_expand_from_pool(msg, &s_nlen); if (unlikely(r)) return r; if (trans) - r = tfw_http_msg_expand_from_pool_lc(resp, &s_name); + r = tfw_http_msg_expand_from_pool_lc(msg, &s_name); else - r = tfw_http_msg_expand_from_pool(resp, &s_name); + r = tfw_http_msg_expand_from_pool(msg, &s_name); if (unlikely(r)) return r; } @@ -3463,12 +3465,12 @@ tfw_hpack_hdr_add(TfwHttpResp *__restrict resp, TfwStr *__restrict hdr, s_vlen.data = vlen.buf; s_vlen.len = vlen.sz; - r = tfw_http_msg_expand_from_pool(resp, &s_vlen); + r = tfw_http_msg_expand_from_pool(msg, &s_vlen); if (unlikely(r)) return r; if (!TFW_STR_EMPTY(&s_val)) - r = tfw_http_msg_expand_from_pool(resp, &s_val); + r = tfw_http_msg_expand_from_pool(msg, &s_val); return r; } diff --git a/fw/http.c b/fw/http.c index 911a79b1b0..bf4ce295ba 100644 --- a/fw/http.c +++ b/fw/http.c @@ -2713,6 +2713,9 @@ tfw_http_req_destruct(void *msg) if (req->stale_ce) tfw_cache_put_entry(req->node, req->stale_ce); + + if (req->cleanup) + __tfw_http_msg_cleanup(req->cleanup); } /** @@ -3069,7 +3072,7 @@ tfw_http_add_hdr_upgrade(TfwHttpMsg *hm, bool is_resp) return -EINVAL; } - r = tfw_http_msg_expand_from_pool((TfwHttpResp *)hm, &hdr); + r = tfw_http_msg_expand_from_pool(hm, &hdr); if (unlikely(r)) T_ERR("Unable to add Upgrade: header to msg [%p]\n", hm); else @@ -3079,44 +3082,6 @@ tfw_http_add_hdr_upgrade(TfwHttpMsg *hm, bool is_resp) return r; } -/* - * Add 'Upgrade:' header for websocket upgrade messages - */ -static int -tfw_http_set_hdr_upgrade(TfwHttpMsg *hm, bool is_resp) -{ - int r = 0; - - if (test_bit(TFW_HTTP_B_UPGRADE_WEBSOCKET, hm->flags)) { - /* - * RFC7230#section-6.7: - * A server that sends a 101 (Switching Protocols) response - * MUST send an Upgrade header field to indicate the new - * protocol(s) to which the connection is being switched; if - * multiple protocol layers are being switched, the sender MUST - * list the protocols in layer-ascending order. - * - * We do expect neither upgrades besides 'websocket' nor - * multilayer upgrades. So we consider extra options as error. - */ - if (is_resp && ((TfwHttpResp *)hm)->status == 101 - && test_bit(TFW_HTTP_B_UPGRADE_EXTRA, hm->flags)) - { - T_ERR("Unable to add uncompliant 'Upgrade:' header " - "to msg [%p]\n", hm); - return -EINVAL; - } - r = tfw_http_msg_hdr_xfrm(hm, "upgrade", SLEN("upgrade"), - "websocket", SLEN("websocket"), - TFW_HTTP_HDR_UPGRADE, 0); - if (r) - T_ERR("Unable to add Upgrade: header to msg [%p]\n", hm); - else - T_DBG2("Added Upgrade: header to msg [%p]\n", hm); - } - return r; -} - /** * Connection is to be closed after response for the request @req is forwarded * to the client. Don't process new requests from the client and update @@ -3193,14 +3158,13 @@ static int tfw_http_set_hdr_connection(TfwHttpMsg *hm, unsigned long conn_flg) { int r; + static const DEFINE_TFW_STR(conn_close, "connection: close\r\n"); + static const DEFINE_TFW_STR(conn_ka_up, "connection: keep-alive, \ + upgrade\r\n"); + static const DEFINE_TFW_STR(conn_ka, "connection: keep-alive\r\n"); + static const DEFINE_TFW_STR(conn_up, "connection: upgrade\r\n"); + BUILD_BUG_ON(BIT_WORD(__TFW_HTTP_MSG_M_CONN) != 0); - if (((hm->flags[0] & __TFW_HTTP_MSG_M_CONN) == conn_flg) - && (!TFW_STR_EMPTY(&hm->h_tbl->tbl[TFW_HTTP_HDR_CONNECTION])) - && !test_bit(TFW_HTTP_B_CONN_EXTRA, hm->flags) - && !test_bit(TFW_HTTP_B_CONN_UPGRADE, hm->flags)) - { - return 0; - } /* * We can see `TFW_HTTP_B_CONN_CLOSE` here only in case of 4XX @@ -3209,33 +3173,21 @@ tfw_http_set_hdr_connection(TfwHttpMsg *hm, unsigned long conn_flg) * For requests conn_flg by default is TFW_HTTP_B_CONN_KA. */ if (unlikely(conn_flg == BIT(TFW_HTTP_B_CONN_CLOSE))) - return TFW_HTTP_MSG_HDR_XFRM(hm, "Connection", "close", - TFW_HTTP_HDR_CONNECTION, 0); + return tfw_http_msg_expand_from_pool(hm, &conn_close); if (conn_flg == BIT(TFW_HTTP_B_CONN_KA)) { if (test_bit(TFW_HTTP_B_UPGRADE_WEBSOCKET, hm->flags) && test_bit(TFW_HTTP_B_CONN_UPGRADE, hm->flags)) { - r = TFW_HTTP_MSG_HDR_XFRM(hm, "Connection", - "keep-alive, upgrade", - TFW_HTTP_HDR_CONNECTION, 0); - } - else { - r = TFW_HTTP_MSG_HDR_XFRM(hm, "Connection", - "keep-alive", - TFW_HTTP_HDR_CONNECTION, 0); + r = tfw_http_msg_expand_from_pool(hm, &conn_ka_up); + } else { + r = tfw_http_msg_expand_from_pool(hm, &conn_ka); } } else { if (test_bit(TFW_HTTP_B_UPGRADE_WEBSOCKET, hm->flags) && test_bit(TFW_HTTP_B_CONN_UPGRADE, hm->flags)) { - r = TFW_HTTP_MSG_HDR_XFRM(hm, "Connection", - "upgrade", - TFW_HTTP_HDR_CONNECTION, 0); - } - else { - r = TFW_HTTP_MSG_HDR_DEL(hm, "Connection", - TFW_HTTP_HDR_CONNECTION); + r = tfw_http_msg_expand_from_pool(hm, &conn_up); } } @@ -3285,7 +3237,7 @@ __tfw_http_add_hdr_date(TfwHttpResp *resp, bool cache) tfw_http_prep_date_from(date, resp->date); if (!cache) - r = tfw_http_msg_expand_from_pool(resp, &h_date); + r = tfw_http_msg_expand_from_pool((TfwHttpMsg *)resp, &h_date); else r = tfw_http_msg_expand_data(&mit->iter, skb_head, &h_date, NULL); @@ -3326,7 +3278,7 @@ __tfw_http_add_hdr_server(TfwHttpResp *resp, bool cache) TfwStr hdr = { .data = s_server, .len = SLEN(s_server) }; if (!cache) - r = tfw_http_msg_expand_from_pool(resp, &hdr); + r = tfw_http_msg_expand_from_pool((TfwHttpMsg *)resp, &hdr); else r = tfw_http_msg_expand_data(&mit->iter, skb_head, &hdr, NULL); @@ -3379,16 +3331,10 @@ __tfw_http_add_hdr_via(TfwHttpMsg *hm, int http_version, bool from_cache) g_vhost->hdr_via_len); if (!from_cache) { - if (!hm->resp) { - /* Add via to request */ - if (unlikely((r = tfw_http_msg_hdr_add(hm, &rh)))) - return r; - return 0; - } - r = tfw_http_msg_expand_from_pool((TfwHttpResp *)hm, &rh); + r = tfw_http_msg_expand_from_pool(hm, &rh); if (unlikely(r)) - return r; - r = tfw_http_msg_expand_from_pool((TfwHttpResp *)hm, + goto err; + r = tfw_http_msg_expand_from_pool(hm, &TFW_STR_STRING(S_CRLF)); } else { struct sk_buff **skb_head = &hm->msg.skb_head; @@ -3396,13 +3342,20 @@ __tfw_http_add_hdr_via(TfwHttpMsg *hm, int http_version, bool from_cache) TfwStr crlf = { .data = S_CRLF, .len = SLEN(S_CRLF) }; r = tfw_http_msg_expand_data(&mit->iter, skb_head, &rh, NULL); - r |= tfw_http_msg_expand_data(&mit->iter, skb_head, - &crlf, NULL); + if (unlikely(r)) + goto err; + r = tfw_http_msg_expand_data(&mit->iter, skb_head, &crlf, NULL); } - if (r) - T_ERR("Unable to add via: header to msg [%p]\n", hm); - else - T_DBG2("Added via: header to msg [%p]\n", hm); + + if (unlikely(r)) + goto err; + + T_DBG2("Added via: header to msg [%p]\n", hm); + + return 0; + +err: + T_ERR("Unable to add via: header to msg [%p]\n", hm); return r; } @@ -3424,13 +3377,29 @@ tfw_http_add_x_forwarded_for(TfwHttpMsg *hm) { int r; char *p, *buf = *this_cpu_ptr(&g_buf); + static char h_name[] = S_XFF S_DLM; + + TfwStr hdr = { + .chunks = (TfwStr []) { + { .data = h_name, + .len = SLEN(h_name) }, + { .data = buf, + .len = 0 }, + { .data = S_CRLF, + .len = SLEN(S_CRLF) }, + }, + .len = SLEN(h_name) + SLEN(S_CRLF), + .nchunks = 3 + }; p = ss_skb_fmt_src_addr(hm->msg.skb_head, buf); - r = tfw_http_msg_hdr_xfrm(hm, "X-Forwarded-For", - sizeof("X-Forwarded-For") - 1, buf, p - buf, - TFW_HTTP_HDR_X_FORWARDED_FOR, 0); - if (r) + __TFW_STR_CH(&hdr, 1)->len = p - buf; + hdr.len += p - buf; + + r = tfw_http_msg_expand_from_pool(hm, &hdr); + + if (unlikely(r)) T_ERR("can't add X-Forwarded-For header for %.*s to msg %p", (int)(p - buf), buf, hm); else @@ -3483,15 +3452,16 @@ tfw_http_recreate_content_type_multipart_hdr(TfwHttpReq *req) TFW_STR_STRING(": "), TFW_STR_STRING("multipart/form-data; boundary="), req->multipart_boundary_raw, + TFW_STR_STRING(S_CRLF) }, - .nchunks = 4, + .nchunks = 5, }; TfwStr *c = replacement.chunks; BUG_ON(!TFW_STR_PLAIN(&req->multipart_boundary_raw)); - replacement.len = c[0].len + c[1].len + c[2].len + c[3].len; - return tfw_http_msg_hdr_xfrm_str((TfwHttpMsg *)req, &replacement, - TFW_HTTP_HDR_CONTENT_TYPE, false); + replacement.len = c[0].len + c[1].len + c[2].len + c[3].len + c[4].len; + + return tfw_http_msg_expand_from_pool((TfwHttpMsg *)req, &replacement); } static bool @@ -3556,27 +3526,19 @@ tfw_http_hdr_sub(unsigned short hid, const TfwStr *hdr, * @from_cache - The response is created from cache, not applied to requests. */ int -tfw_h1_set_loc_hdrs(TfwHttpMsg *hm, bool is_resp, bool from_cache) +tfw_h1_add_loc_hdrs(TfwHttpMsg *hm, const TfwHdrMods *h_mods, bool from_cache) { - size_t i; - int mod_type = is_resp ? TFW_VHOST_HDRMOD_RESP : TFW_VHOST_HDRMOD_REQ; - TfwHttpReq *req = is_resp ? hm->req : (TfwHttpReq *)hm; - TfwHdrMods *h_mods = tfw_vhost_get_hdr_mods(req->location, req->vhost, - mod_type); + int r = 0; + unsigned int i; + TfwHttpHdrTbl *ht = hm->h_tbl; + static const DEFINE_TFW_STR(crlf, S_CRLF); - if(WARN_ON_ONCE(!is_resp && from_cache)) - return -EINVAL; if (!h_mods) return 0; for (i = 0; i < h_mods->sz; ++i) { - int r; - TfwHdrModsDesc *d = &h_mods->hdrs[i]; - /* - * Header is stored optimized for HTTP2: without delimiter - * between header and value. Add it as separate chunk as - * required for tfw_http_msg_hdr_xfrm_str. - */ + const TfwHdrModsDesc *desc = &h_mods->hdrs[i]; + unsigned short hid = desc->hid; TfwStr h_mdf = { .chunks = (TfwStr []){ {}, @@ -3587,14 +3549,26 @@ tfw_h1_set_loc_hdrs(TfwHttpMsg *hm, bool is_resp, bool from_cache) .nchunks = 2 /* header name + delimeter. */ }; - h_mdf.chunks[0] = d->hdr->chunks[0]; - if (d->hdr->nchunks == 2) { - h_mdf.chunks[2] = d->hdr->chunks[1]; - h_mdf.nchunks += 1; + /* FIXME: this is a temporary WA for GCC12, see #1695 for details */ + if (TFW_STR_CHUNK(desc->hdr, 1) == NULL) + continue; + + if (unlikely(desc->append && !TFW_STR_EMPTY(&ht->tbl[hid]) + && (hid < TFW_HTTP_HDR_NONSINGULAR))) + { + T_WARN("Attempt to add already existed singular header '%.*s'\n", + PR_TFW_STR(TFW_STR_CHUNK(desc->hdr, 0))); + continue; } - h_mdf.len += d->hdr->len; - h_mdf.flags = d->hdr->flags; - h_mdf.eolen += d->hdr->eolen; + + h_mdf.chunks[0] = desc->hdr->chunks[0]; + if (desc->hdr->nchunks == 2) { + h_mdf.chunks[2] = desc->hdr->chunks[1]; + h_mdf.nchunks += 1; + } + h_mdf.len += desc->hdr->len; + h_mdf.flags = desc->hdr->flags; + h_mdf.eolen += desc->hdr->eolen; /* * A response is built from cache. Response is stored in @@ -3602,10 +3576,8 @@ tfw_h1_set_loc_hdrs(TfwHttpMsg *hm, bool is_resp, bool from_cache) * header-by-header right away. */ if (from_cache) { - TfwHttpResp *resp = (TfwHttpResp *)hm; - struct sk_buff **skb_head = &resp->msg.skb_head; - TfwHttpTransIter *mit = &resp->mit; - TfwStr crlf = { .data = S_CRLF, .len = SLEN(S_CRLF) }; + struct sk_buff **skb_head = &hm->msg.skb_head; + TfwMsgIter *it = &hm->iter; /* * Skip the configured header if the header is * configured for deletion (without value chunk). @@ -3613,204 +3585,143 @@ tfw_h1_set_loc_hdrs(TfwHttpMsg *hm, bool is_resp, bool from_cache) if (h_mdf.nchunks < 3) continue; /* h_mdf->eolen is ignored, add explicit CRLF. */ - r = tfw_http_msg_expand_data(&mit->iter, skb_head, - &h_mdf, NULL); - r |= tfw_http_msg_expand_data(&mit->iter, skb_head, - &crlf, NULL); + r = tfw_http_msg_expand_data(it, skb_head, &h_mdf, + NULL); + if (unlikely(r)) + return r; + r |= tfw_http_msg_expand_data(it, skb_head, &crlf, + NULL); } else { - r = tfw_http_msg_hdr_xfrm_str(hm, &h_mdf, d->hid, - d->append); - } - - if (r) { - T_ERR("can't update location-specific header in msg %p\n", - hm); - return r; + r = tfw_http_msg_expand_from_pool(hm, &h_mdf); + if (unlikely(r)) + return r; + r = tfw_http_msg_expand_from_pool(hm, &crlf); } - - T_DBG2("updated location-specific header in msg %p\n", hm); } - return 0; + return r; } +/** + * Adjust the request before proxying it to real server. + */ static int -tfw_h1_add_loc_hdrs(TfwHttpMsg *hm, const TfwHdrMods *h_mods) +tfw_h1_adjust_req(TfwHttpReq *req) { - unsigned int i; - TfwHttpHdrTbl *ht = hm->h_tbl; + int r, cl_sz = sizeof(TfwHttpMsgCleanup); + TfwHttpMsg *hm = (TfwHttpMsg *)req; + TfwHttpMsgCleanup *cleanup = req->cleanup; + const BasicStr *s_meth; + TfwStr *pos, *end, meth = {}; + static const DEFINE_TFW_STR(sp, " "); + static const DEFINE_TFW_STR(crlf, S_CRLF); + static const DEFINE_TFW_STR(ver, " " S_VERSION11 S_CRLF); + const TfwHdrMods *h_mods = tfw_vhost_get_hdr_mods(req->location, + req->vhost, + TFW_VHOST_HDRMOD_REQ); - if (!h_mods) - return 0; + cleanup = (TfwHttpMsgCleanup *)tfw_pool_alloc(hm->pool, cl_sz); + memset(cleanup, 0, cl_sz); - for (i = 0; i < h_mods->sz; ++i) { - const TfwHdrModsDesc *desc = &h_mods->hdrs[i]; - int r; - unsigned short hid = desc->hid; - TfwStr h_mdf = { - .chunks = (TfwStr []){ - {}, - { .data = S_DLM, .len = SLEN(S_DLM) }, - {} - }, - .len = SLEN(S_DLM), - .nchunks = 2 /* header name + delimeter. */ - }; + tfw_msg_transform_setup(&req->iter, req->msg.skb_head); + r = tfw_http_msg_cutoff_headers(hm, cleanup); + if (unlikely(r)) + goto clean; - /* FIXME: this is a temporary WA for GCC12, see #1695 for details */ - if (TFW_STR_CHUNK(desc->hdr, 1) == NULL) - continue; + if (test_bit(TFW_HTTP_B_PURGE_GET, req->flags)) + /* Rewrite PURGE to GET */ + s_meth = tfw_http_method_id2str(TFW_HTTP_METH_GET); + else + s_meth = tfw_http_method_id2str(req->method); - if (unlikely(desc->append && !TFW_STR_EMPTY(&ht->tbl[hid]) - && (hid < TFW_HTTP_HDR_NONSINGULAR))) - { - T_WARN("Attempt to add already existed singular header '%.*s'\n", - PR_TFW_STR(TFW_STR_CHUNK(desc->hdr, 0))); - continue; - } + meth.data = s_meth->data; + meth.len = s_meth->len; - h_mdf.chunks[0] = desc->hdr->chunks[0]; - if (desc->hdr->nchunks == 2) { - h_mdf.chunks[2] = desc->hdr->chunks[1]; - h_mdf.nchunks += 1; - } - h_mdf.len += desc->hdr->len; - h_mdf.flags = desc->hdr->flags; - h_mdf.eolen += desc->hdr->eolen; - r = tfw_http_msg_expand_from_pool((TfwHttpResp *)hm, &h_mdf); - if (unlikely(r)) - return r; - r = tfw_http_msg_expand_from_pool((TfwHttpResp *)hm, - &TFW_STR_STRING(S_CRLF)); - if (unlikely(r)) - return r; - } + r = tfw_http_msg_expand_from_pool(hm, &meth); + if (unlikely(r)) + goto clean; - return 0; -} + r = tfw_http_msg_expand_from_pool(hm, &sp); + if (unlikely(r)) + goto clean; -static int -tfw_h1_rewrite_method_to_get(struct sk_buff **head_p, size_t chop_len) -{ - const char *q = "GET"; - char *p; - struct skb_shared_info *si; - struct sk_buff *skb, *head; - unsigned int f, z; - int ret; + r = tfw_http_msg_expand_from_pool(hm, &req->uri_path); + if (unlikely(r)) + goto clean; - /* Possible if somehow we already sent a response */ - BUG_ON(!*head_p); + r = tfw_http_msg_expand_from_pool(hm, &ver); + if (unlikely(r)) + goto clean; - /* Chop two bytes from the beginning of SKB data. */ - ret = ss_skb_list_chop_head_tail(head_p, chop_len, 0); - if (ret) - return ret; - /* List head element *head_p could change above */ - skb = head = *head_p; + FOR_EACH_HDR_FIELD_FROM(pos, end, hm, TFW_HTTP_HDR_HOST) { + int hid = pos - hm->h_tbl->tbl; + TfwStr *dup, *dup_end, *hdr = pos; - do { - p = skb->data; - z = skb_headlen(skb); - while (z--) { - *p++ = *q++; - if (!*q) - return 0; - } - si = skb_shinfo(skb); - for (f = 0; f < si->nr_frags; ++f) { - p = skb_frag_address(&si->frags[f]); - z = skb_frag_size(&si->frags[f]); - while (z--) { - *p++ = *q++; - if (!*q) - return 0; - } + /* Skip hop-by-hop headers. */ + if (TFW_STR_EMPTY(hdr) || hdr->flags & TFW_STR_HBH_HDR + || hid == TFW_HTTP_HDR_X_FORWARDED_FOR) { + continue; } - skb = skb->next; - } while (skb != head); - T_ERR("Not enough skb data for method rewrite?!\n"); - return -ENOMEM; -} - -/* - * Rewrite HTTP/1 "PURGE" method to "GET" directly inside a request SKB. - */ -static int -tfw_h1_rewrite_purge_to_get(struct sk_buff **head_p) -{ - return tfw_h1_rewrite_method_to_get(head_p, 2); -} + if (TFW_STR_DUP(hdr)) + hdr = TFW_STR_CHUNK(hdr, 0); -/* - * Rewrite HTTP/1 "HEAD" method to "GET" directly inside a request SKB. - */ -static int -tfw_h1_rewrite_head_to_get(struct sk_buff **head_p) -{ - return tfw_h1_rewrite_method_to_get(head_p, 1); -} + if (hid == TFW_HTTP_HDR_CONTENT_TYPE + && req->method == TFW_HTTP_METH_POST + && test_bit(TFW_HTTP_B_CT_MULTIPART, req->flags) + && tfw_http_should_validate_post_req(req)) + { + r = tfw_http_recreate_content_type_multipart_hdr(req); + if (unlikely(r)) + return r; + continue; + } -/** - * Adjust the request before proxying it to real server. - */ -static int -tfw_h1_adjust_req(TfwHttpReq *req) -{ - int r; - unsigned int n_to_strip = 0; - TfwHttpMsg *hm = (TfwHttpMsg *)req; + if (tfw_http_hdr_sub(hid, hdr, h_mods)) + continue; - n_to_strip = !!test_bit(TFW_HTTP_B_NEED_STRIP_LEADING_CR, req->flags) + - !!test_bit(TFW_HTTP_B_NEED_STRIP_LEADING_LF, req->flags); - if (unlikely(n_to_strip)) { - r = ss_skb_list_chop_head_tail(&hm->msg.skb_head, n_to_strip, 0); - if (r) - return r; - } + TFW_STR_FOR_EACH_DUP(dup, pos, dup_end) { + r = tfw_http_msg_expand_from_pool(hm, dup); + if (unlikely(r)) + goto clean; - if (test_bit(TFW_HTTP_B_PURGE_GET, req->flags)) { - r = tfw_h1_rewrite_purge_to_get(&hm->msg.skb_head); - if (unlikely(r)) - return r; - } - else if (test_bit(TFW_HTTP_B_REQ_HEAD_TO_GET, req->flags)) { - r = tfw_h1_rewrite_head_to_get(&hm->msg.skb_head); - if (unlikely(r)) - return r; + tfw_http_msg_expand_from_pool(hm, &crlf); + if (unlikely(r)) + goto clean; + } } r = tfw_http_add_x_forwarded_for(hm); - if (r) + if (unlikely(r)) return r; r = tfw_http_add_hdr_via(hm); - if (r) + if (unlikely(r)) return r; - r = tfw_http_msg_del_hbh_hdrs(hm); - if (r < 0) - return r; + r = tfw_http_add_hdr_upgrade(hm, false); + if (unlikely(r)) + goto clean; - r = tfw_http_set_hdr_upgrade(hm, false); - if (r < 0) - return r; + r = tfw_h1_add_loc_hdrs(hm, h_mods, false); + if (unlikely(r)) + goto clean; - r = tfw_h1_set_loc_hdrs(hm, false, false); - if (r < 0) - return r; + r = tfw_http_set_hdr_connection(hm, BIT(TFW_HTTP_B_CONN_KA)); + if (unlikely(r)) + goto clean; - if (req->method == TFW_HTTP_METH_POST && - test_bit(TFW_HTTP_B_CT_MULTIPART, req->flags) && - tfw_http_should_validate_post_req(req)) - { - r = tfw_http_recreate_content_type_multipart_hdr(req); - if (r) - return r; - } + /* Write last CRLF for headers block. */ + r = tfw_http_msg_expand_from_pool(hm, &crlf); + if (unlikely(r)) + goto clean; - return tfw_http_set_hdr_connection(hm, BIT(TFW_HTTP_B_CONN_KA)); + return r; + +clean: + __tfw_http_msg_cleanup(cleanup); + + return r; } static inline void @@ -4385,50 +4296,6 @@ tfw_h1_purge_resp_clean(TfwHttpResp *resp, TfwHttpMsgCleanup *cleanup) return 0; } -/** - * Add Connection header to HTTP message @msg depend on @conn_flg. - */ -static int -tfw_http_resp_set_hdr_connection(TfwHttpMsg *hm, unsigned long conn_flg) -{ - int r; - static const DEFINE_TFW_STR(conn_close, "connection: close\r\n"); - static const DEFINE_TFW_STR(conn_ka_up, "connection: keep-alive, \ - upgrade\r\n"); - static const DEFINE_TFW_STR(conn_ka, "connection: keep-alive\r\n"); - static const DEFINE_TFW_STR(conn_up, "connection: upgrade\r\n"); - TfwHttpResp *resp = (TfwHttpResp *)hm; - - BUILD_BUG_ON(BIT_WORD(__TFW_HTTP_MSG_M_CONN) != 0); - - /* - * We can see `TFW_HTTP_B_CONN_CLOSE` here only in case of 4XX - * response with 'Connection: close' option. - * - * For requests conn_flg by default is TFW_HTTP_B_CONN_KA. - */ - if (unlikely(conn_flg == BIT(TFW_HTTP_B_CONN_CLOSE))) - return tfw_http_msg_expand_from_pool(resp, &conn_close); - - if (conn_flg == BIT(TFW_HTTP_B_CONN_KA)) { - if (test_bit(TFW_HTTP_B_UPGRADE_WEBSOCKET, hm->flags) - && test_bit(TFW_HTTP_B_CONN_UPGRADE, hm->flags)) - { - r = tfw_http_msg_expand_from_pool(resp, &conn_ka_up); - } else { - r = tfw_http_msg_expand_from_pool(resp, &conn_ka); - } - } else { - if (test_bit(TFW_HTTP_B_UPGRADE_WEBSOCKET, hm->flags) - && test_bit(TFW_HTTP_B_CONN_UPGRADE, hm->flags)) - { - r = tfw_http_msg_expand_from_pool(resp, &conn_up); - } - } - - return r; -} - /** * Adjust the response before proxying it to real client. */ @@ -4484,15 +4351,15 @@ tfw_http_adjust_resp(TfwHttpResp *resp) */ tfw_h2_msg_transform_setup(mit, resp->msg.skb_head); - r = tfw_http_msg_cutoff_headers(resp, &cleanup); + r = tfw_http_msg_cutoff_headers(hm, &cleanup); if (unlikely(r)) goto clean; } - r = tfw_http_msg_expand_from_pool(resp, s_line); + r = tfw_http_msg_expand_from_pool(hm, s_line); if (unlikely(r)) goto clean; - r = tfw_http_msg_expand_from_pool(resp, &crlf); + r = tfw_http_msg_expand_from_pool(hm, &crlf); if (unlikely(r)) goto clean; @@ -4500,7 +4367,7 @@ tfw_http_adjust_resp(TfwHttpResp *resp) * For a response to PURGE request we drop the body. * Add "content-length: 0" header. */ - r = tfw_http_msg_expand_from_pool(resp, &clen); + r = tfw_http_msg_expand_from_pool(hm, &clen); if (unlikely(r)) goto clean; @@ -4508,14 +4375,14 @@ tfw_http_adjust_resp(TfwHttpResp *resp) } else { /* Response for regular request. */ tfw_h2_msg_transform_setup(mit, resp->msg.skb_head); - r = tfw_http_msg_cutoff_headers(resp, &cleanup); + r = tfw_http_msg_cutoff_headers(hm, &cleanup); if (unlikely(r)) goto clean; - r = tfw_http_msg_expand_from_pool(resp, s_line); + r = tfw_http_msg_expand_from_pool(hm, s_line); if (unlikely(r)) goto clean; - r = tfw_http_msg_expand_from_pool(resp, &crlf); + r = tfw_http_msg_expand_from_pool(hm, &crlf); if (unlikely(r)) goto clean; } @@ -4535,10 +4402,10 @@ tfw_http_adjust_resp(TfwHttpResp *resp) continue; TFW_STR_FOR_EACH_DUP(dup, pos, dup_end) { - r = tfw_http_msg_expand_from_pool(resp, dup); + r = tfw_http_msg_expand_from_pool(hm, dup); if (unlikely(r)) goto clean; - tfw_http_msg_expand_from_pool(resp, &crlf); + tfw_http_msg_expand_from_pool(hm, &crlf); if (unlikely(r)) goto clean; } @@ -4548,7 +4415,7 @@ tfw_http_adjust_resp(TfwHttpResp *resp) if (r < 0) return r; - r = tfw_http_resp_set_hdr_connection(hm, conn_flg); + r = tfw_http_set_hdr_connection(hm, conn_flg); if (unlikely(r)) goto clean; @@ -4560,7 +4427,7 @@ tfw_http_adjust_resp(TfwHttpResp *resp) if (unlikely(r)) goto clean; - r = tfw_h1_add_loc_hdrs(hm, h_mods); + r = tfw_h1_add_loc_hdrs(hm, h_mods, false); if (unlikely(r)) goto clean; @@ -4575,7 +4442,7 @@ tfw_http_adjust_resp(TfwHttpResp *resp) goto clean; /* Write last CRLF for headers block. */ - r = tfw_http_msg_expand_from_pool(resp, &crlf); + r = tfw_http_msg_expand_from_pool(hm, &crlf); clean: __tfw_http_msg_cleanup(&cleanup); @@ -5713,7 +5580,7 @@ tfw_h2_resp_encode_headers(TfwHttpResp *resp) WARN_ON_ONCE(mit->acc_len); tfw_h2_msg_transform_setup(mit, resp->msg.skb_head); - r = tfw_http_msg_cutoff_headers(resp, &cleanup); + r = tfw_http_msg_cutoff_headers((TfwHttpMsg *)resp, &cleanup); if (unlikely(r)) goto clean; diff --git a/fw/http.h b/fw/http.h index f71f298e0b..e6c161d606 100644 --- a/fw/http.h +++ b/fw/http.h @@ -339,6 +339,19 @@ typedef struct { long m_date; } TfwHttpCond; +/** + * Represents the data that should be cleaned up after message transformation. + * + * @skb_head - head of skb list that must be freed; + * @pages - pages that must be freed; + * @pages_sz - current number of @pages; + */ +typedef struct { + struct sk_buff *skb_head; + struct page *pages[MAX_SKB_FRAGS]; + unsigned char pages_sz; +} TfwHttpMsgCleanup; + /** * HTTP Request. * @@ -352,6 +365,8 @@ typedef struct { * the response is sent to the client; * @stale_ce - Stale cache entry retrieved from the cache. Must be assigned * only when "cache_use_stale" is configured; + * @cleanup - Original request data. Required for keep request data until + * the response is sent to the client; * @pit - iterator for tracking transformed data allocation (applicable * for HTTP/2 mode only); * @userinfo - userinfo in URI, not mandatory; @@ -388,6 +403,7 @@ struct tfw_http_req_t { TfwClient *peer; struct sk_buff *old_head; void *stale_ce; + TfwHttpMsgCleanup *cleanup; TfwHttpCond cond; TfwMsgParseIter pit; HttpJa5h ja5h; @@ -500,19 +516,6 @@ struct tfw_http_resp_t { int trailers_len; }; -/** - * Represents the data that should be cleaned up after message transformation. - * - * @skb_head - head of skb list that must be freed; - * @pages - pages that must be freed; - * @pages_sz - current number of @pages; - */ -typedef struct { - struct sk_buff *skb_head; - struct page *pages[MAX_SKB_FRAGS]; - unsigned char pages_sz; -} TfwHttpMsgCleanup; - #define TFW_HDR_MAP_INIT_CNT 32 #define TFW_HDR_MAP_SZ(cnt) (sizeof(TfwHttpHdrMap) \ + sizeof(TfwHdrIndex) * (cnt)) @@ -746,7 +749,8 @@ void tfw_http_resp_fwd(TfwHttpResp *resp); void tfw_http_resp_build_error(TfwHttpReq *req); int tfw_cfgop_parse_http_status(const char *status, int *out); void tfw_http_hm_srv_send(TfwServer *srv, char *data, unsigned long len); -int tfw_h1_set_loc_hdrs(TfwHttpMsg *hm, bool is_resp, bool from_cache); +int tfw_h1_add_loc_hdrs(TfwHttpMsg *hm, const TfwHdrMods *h_mods, + bool from_cache); int tfw_http_expand_stale_warn(TfwHttpResp *resp); int tfw_http_expand_hdr_date(TfwHttpResp *resp); int tfw_http_expand_hbh(TfwHttpResp *resp, unsigned short status); @@ -787,4 +791,32 @@ bool tfw_http_mark_is_in_whitlist(unsigned int mark); char *tfw_http_resp_status_line(int status, size_t *len); int tfw_http_on_send_resp(void *conn, struct sk_buff **skb_head); +static inline const BasicStr * +tfw_http_method_id2str(int id) +{ +#define STR_METHOD(name) [TFW_HTTP_METH_ ## name] = { #name, sizeof(#name) - 1 } + + static BasicStr http_methods[] = { + STR_METHOD(COPY), + STR_METHOD(DELETE), + STR_METHOD(GET), + STR_METHOD(HEAD), + STR_METHOD(LOCK), + STR_METHOD(MKCOL), + STR_METHOD(MOVE), + STR_METHOD(OPTIONS), + STR_METHOD(PATCH), + STR_METHOD(POST), + STR_METHOD(PROPFIND), + STR_METHOD(PROPPATCH), + STR_METHOD(PUT), + STR_METHOD(TRACE), + STR_METHOD(UNLOCK), + STR_METHOD(PURGE), + }; + +#undef STR_METHOD + return &http_methods[id]; +} + #endif /* __TFW_HTTP_H__ */ diff --git a/fw/http_msg.c b/fw/http_msg.c index ea949a3376..b5048f9ca4 100644 --- a/fw/http_msg.c +++ b/fw/http_msg.c @@ -1083,31 +1083,6 @@ tfw_http_msg_del_str(TfwHttpMsg *hm, TfwStr *str) return 0; } -/** - * Remove hop-by-hop headers in the message - * - * Connection header should not be removed, tfw_http_set_hdr_connection() - * optimize removal of the header. - */ -int -tfw_http_msg_del_hbh_hdrs(TfwHttpMsg *hm) -{ - TfwHttpHdrTbl *ht = hm->h_tbl; - unsigned int hid = ht->off; - int r = 0; - - do { - hid--; - if (hid == TFW_HTTP_HDR_CONNECTION) - continue; - if (ht->tbl[hid].flags & TFW_STR_HBH_HDR) - if ((r = __hdr_del(hm, hid))) - return r; - } while (hid); - - return 0; -} - /** * Remove flagged data and EOL from skb of TfwHttpMsg->body. * @@ -1130,30 +1105,6 @@ tfw_http_msg_cutoff_body_chunks(TfwHttpResp *resp) return 0; } -/** - * Add a header, probably duplicated, without any checking of current headers. - * In case of response transformation from HTTP/1.1 to HTTP/2, for optimization - * purposes, we use special handling for headers adding (see note for - * @tfw_http_msg_hdr_xfrm_str() for details). - */ -int -tfw_http_msg_hdr_add(TfwHttpMsg *hm, const TfwStr *hdr) -{ - unsigned int hid; - TfwHttpHdrTbl *ht; - - ht = hm->h_tbl; - hid = ht->off; - if (hid == ht->size) { - if (tfw_http_msg_grow_hdr_tbl(hm)) - return -ENOMEM; - ht = hm->h_tbl; - } - ++ht->off; - - return __hdr_add(hm, hdr, hid); -} - /** * Set up @hm with empty SKB space of size @data_len for data writing. * Set up the iterator @it to support consecutive writes. @@ -1504,7 +1455,7 @@ tfw_http_msg_setup_transform_pool(TfwHttpTransIter *mit, TfwPool* pool) * Move body to @nskb if body located in current skb. */ static inline int -__tfw_http_msg_move_body(TfwHttpResp *resp, struct sk_buff *nskb) +__tfw_http_msg_move_body(TfwHttpMsg *resp, struct sk_buff *nskb) { TfwMsgIter *it = &resp->mit.iter; struct sk_buff **body; @@ -1575,15 +1526,15 @@ tfw_http_msg_linear_transform(TfwMsgIter *it) * update pointer to the body skb. */ static int -__tfw_http_msg_expand_from_pool(TfwHttpResp *resp, const TfwStr *str, +__tfw_http_msg_expand_from_pool(TfwHttpMsg *hm, const TfwStr *str, void cpy(void *dest, const void *src, size_t n)) { int r; const TfwStr *c, *end; unsigned int room, skb_room, n_copy, rlen, off; - TfwHttpTransIter *mit = &resp->mit; - TfwMsgIter *it = &resp->iter; - TfwPool* pool = resp->pool; + TfwHttpTransIter *mit = &hm->mit; + TfwMsgIter *it = &hm->iter; + TfwPool* pool = hm->pool; BUG_ON(it->skb->len > SS_SKB_MAX_DATA_LEN); @@ -1617,8 +1568,8 @@ __tfw_http_msg_expand_from_pool(TfwHttpResp *resp, const TfwStr *str, if (!nskb) return -ENOMEM; - if (resp->body.len > 0) { - r = __tfw_http_msg_move_body(resp, + if (hm->body.len > 0) { + r = __tfw_http_msg_move_body(hm, nskb); if (unlikely(r)) { T_WARN("Error during moving body"); @@ -1664,15 +1615,15 @@ __tfw_http_msg_expand_from_pool(TfwHttpResp *resp, const TfwStr *str, } int -tfw_http_msg_expand_from_pool(TfwHttpResp *resp, const TfwStr *str) +tfw_http_msg_expand_from_pool(TfwHttpMsg *hm, const TfwStr *str) { - return __tfw_http_msg_expand_from_pool(resp, str, memcpy_fast); + return __tfw_http_msg_expand_from_pool(hm, str, memcpy_fast); } int -tfw_http_msg_expand_from_pool_lc(TfwHttpResp *resp, const TfwStr *str) +tfw_http_msg_expand_from_pool_lc(TfwHttpMsg *hm, const TfwStr *str) { - return __tfw_http_msg_expand_from_pool(resp, str, tfw_cstrtolower); + return __tfw_http_msg_expand_from_pool(hm, str, tfw_cstrtolower); } static inline void @@ -1727,19 +1678,19 @@ __tfw_http_msg_shrink_frag(struct sk_buff *skb, int frag_idx, const char *nbegin } /* - * Delete SKBs and paged fragments related to @resp that contains response + * Delete SKBs and paged fragments related to @hm that contains message * headers. SKBs and fragments will be "unlinked" and placed to @cleanup. * At this point we can't free SKBs, because data that they contain used * as source for message trasformation. */ int -tfw_http_msg_cutoff_headers(TfwHttpResp *resp, TfwHttpMsgCleanup* cleanup) +tfw_http_msg_cutoff_headers(TfwHttpMsg *hm, TfwHttpMsgCleanup* cleanup) { int i, ret; char *begin, *end; - TfwMsgIter *it = &resp->mit.iter; - char* body = TFW_STR_CHUNK(&resp->body, 0)->data; - TfwStr *crlf = TFW_STR_LAST(&resp->crlf); + TfwMsgIter *it = &hm->mit.iter; + char* body = TFW_STR_CHUNK(&hm->body, 0)->data; + TfwStr *crlf = TFW_STR_LAST(&hm->crlf); char *off = body ? body : crlf->data + (crlf->len - 1); do { @@ -1781,7 +1732,7 @@ tfw_http_msg_cutoff_headers(TfwHttpResp *resp, TfwHttpMsgCleanup* cleanup) * fragments from skb where LF is located. */ if (!body) { - __tfw_h2_msg_rm_all_frags(it->skb, cleanup); + __tfw_http_msg_rm_all_frags(it->skb, cleanup); goto end; } else if (off != begin) { /* @@ -1813,7 +1764,7 @@ tfw_http_msg_cutoff_headers(TfwHttpResp *resp, TfwHttpMsgCleanup* cleanup) BUG_ON(!it->skb_head || !it->skb); it->skb_head = it->skb; - resp->msg.skb_head = it->skb; + hm->msg.skb_head = it->skb; /* Start from zero fragment */ it->frag = -1; diff --git a/fw/http_msg.h b/fw/http_msg.h index 1b497db0f0..76de309ba5 100644 --- a/fw/http_msg.h +++ b/fw/http_msg.h @@ -144,7 +144,6 @@ int __must_check __tfw_http_msg_add_str_data(TfwHttpMsg *hm, TfwStr *str, ss_skb_peek_tail(&hm->msg.skb_head)) unsigned int tfw_http_msg_hdr_lookup(TfwHttpMsg *hm, const TfwStr *hdr); -int tfw_http_msg_hdr_add(TfwHttpMsg *hm, const TfwStr *hdr); int tfw_http_msg_hdr_xfrm_str(TfwHttpMsg *hm, const TfwStr *hdr, unsigned int hid, bool append); int tfw_http_msg_hdr_xfrm(TfwHttpMsg *hm, char *name, size_t n_len, @@ -157,7 +156,6 @@ int tfw_http_msg_hdr_xfrm(TfwHttpMsg *hm, char *name, size_t n_len, tfw_http_msg_hdr_xfrm(hm, name, sizeof(name) - 1, NULL, 0, hid, 0) int tfw_http_msg_del_str(TfwHttpMsg *hm, TfwStr *str); -int tfw_http_msg_del_hbh_hdrs(TfwHttpMsg *hm); int tfw_http_msg_cutoff_body_chunks(TfwHttpResp *resp); int tfw_http_msg_setup(TfwHttpMsg *hm, TfwMsgIter *it, size_t data_len, @@ -171,11 +169,11 @@ void tfw_http_msg_free(TfwHttpMsg *m); int tfw_http_msg_expand_data(TfwMsgIter *it, struct sk_buff **skb_head, const TfwStr *src, unsigned int *start_off); int tfw_http_msg_setup_transform_pool(TfwHttpTransIter *mit, TfwPool* pool); -int tfw_http_msg_expand_from_pool(TfwHttpResp *resp, const TfwStr *str); -int tfw_http_msg_expand_from_pool_lc(TfwHttpResp *resp, const TfwStr *str); +int tfw_http_msg_expand_from_pool(TfwHttpMsg *hm, const TfwStr *str); +int tfw_http_msg_expand_from_pool_lc(TfwHttpMsg *hm, const TfwStr *str); int __hdr_name_cmp(const TfwStr *hdr, const TfwStr *cmp_hdr); int __http_hdr_lookup(TfwHttpMsg *hm, const TfwStr *hdr); -int tfw_http_msg_cutoff_headers(TfwHttpResp *resp, TfwHttpMsgCleanup* cleanup); +int tfw_http_msg_cutoff_headers(TfwHttpMsg *hm, TfwHttpMsgCleanup* cleanup); int tfw_http_msg_insert(TfwMsgIter *it, char **off, const TfwStr *data); int tfw_http_msg_linear_transform(TfwMsgIter *it); diff --git a/fw/http_sess.c b/fw/http_sess.c index 7f07c4a5fa..dcb9b8d4f5 100644 --- a/fw/http_sess.c +++ b/fw/http_sess.c @@ -337,6 +337,7 @@ tfw_http_sticky_add(TfwHttpResp *resp, bool cache) bool to_h2 = TFW_MSG_H2(resp->req); char *name = to_h2 ? S_SET_COOKIE : S_F_SET_COOKIE; unsigned int nm_len = to_h2 ? SLEN(S_SET_COOKIE) : SLEN(S_F_SET_COOKIE); + TfwHttpMsg *hm = (TfwHttpMsg *)resp; TfwHttpSess *sess = resp->req->sess; unsigned long ts_be64 = cpu_to_be64(sess->ts); TfwStickyCookie *sticky = resp->req->vhost->cookie; @@ -375,10 +376,10 @@ tfw_http_sticky_add(TfwHttpResp *resp, bool cache) goto err; r = tfw_http_msg_expand_data(&mit->iter, skb_head, &crlf, NULL); } else { - r = tfw_http_msg_expand_from_pool(resp, &set_cookie); + r = tfw_http_msg_expand_from_pool(hm, &set_cookie); if (unlikely(r)) goto err; - r = tfw_http_msg_expand_from_pool(resp, &crlf); + r = tfw_http_msg_expand_from_pool(hm, &crlf); } return 0; diff --git a/fw/http_types.h b/fw/http_types.h index 08d82e382e..c32a47ee70 100644 --- a/fw/http_types.h +++ b/fw/http_types.h @@ -34,7 +34,7 @@ enum { * * CONN_KA: 'Connection:' header contains 'keep-alive' term. The flag * is not set for HTTP/1.1 connections which are persistent by default. - * CONN_EXTRA: 'Connection:' header contains additional terms. + * CONN_EXTRA: 'Connection:' header contains additional terms.(NOT used) * * CONN_CLOSE and CONN_KA flags are mutual exclusive. */ diff --git a/fw/t/unit/test_http_msg.c b/fw/t/unit/test_http_msg.c index dbee7957ee..0ffdf1490c 100644 --- a/fw/t/unit/test_http_msg.c +++ b/fw/t/unit/test_http_msg.c @@ -152,7 +152,7 @@ TEST(http_msg, expand_from_pool) it = &resp->mit.iter; EXPECT_FALSE(it->skb->data_len == head->len + hdr->len + pgd->len); - tfw_http_msg_expand_from_pool(resp, hdr); + tfw_http_msg_expand_from_pool((TfwHttpMsg *)resp, hdr); /* Linear part MUST be moved to paged fragments */ EXPECT_TRUE(!skb_headlen(it->skb)); @@ -200,7 +200,7 @@ do { \ it = &resp->mit.iter; EXPECT_FALSE(it->skb->data_len == skbsz); - tfw_http_msg_expand_from_pool(resp, &hdr); + tfw_http_msg_expand_from_pool((TfwHttpMsg *)resp, &hdr); skb = it->skb; next = it->skb->next; From 3a49daa36413ad5a0b29a354f3c06c28559e9a07 Mon Sep 17 00:00:00 2001 From: Constantine Date: Tue, 15 Aug 2023 20:59:09 +0300 Subject: [PATCH 05/44] Rework HTTP1 request adjusting (Part 2/2) `tfw_http_msg_expand_from_pool` splitted into two functions first `tfw_http_msg_expand_from_pool` for HTTP1 messages and second `tfw_h2_msg_expand_from_pool` for HTTP2 messages. --- fw/cache.c | 13 ++++----- fw/hpack.c | 24 ++++++++-------- fw/http.c | 40 ++++++++++++-------------- fw/http_msg.c | 59 ++++++++++++++++++++++++++------------- fw/http_msg.h | 13 +++++---- fw/http_sess.c | 13 ++++----- fw/t/unit/test_http_msg.c | 6 ++-- 7 files changed, 93 insertions(+), 75 deletions(-) diff --git a/fw/cache.c b/fw/cache.c index 19e7f68ee2..79dade1de7 100644 --- a/fw/cache.c +++ b/fw/cache.c @@ -784,7 +784,7 @@ tfw_cache_h2_write(TDB *db, TdbVRec **trec, TfwHttpResp *resp, char **data, TfwStr c = { 0 }; TdbVRec *tr = *trec; TfwHttpTransIter *mit = &resp->mit; - TfwMsgIter *it = &mit->iter; + TfwMsgIter *it = &resp->iter; int r = 0, copied = 0; while (1) { @@ -860,7 +860,7 @@ tfw_cache_set_status(TDB *db, TfwCacheEntry *ce, TfwHttpResp *resp, TdbVRec **trec, char **p, unsigned long *acc_len) { int r; - TfwMsgIter *it = &resp->mit.iter; + TfwMsgIter *it = &resp->iter; struct sk_buff **skb_head = &resp->msg.skb_head; bool h2_mode = TFW_MSG_H2(resp->req); TfwDecodeCacheIter dc_iter = {}; @@ -1053,7 +1053,7 @@ tfw_cache_send_304(TfwHttpReq *req, TfwCacheEntry *ce) if (!(resp = tfw_http_msg_alloc_resp_light(req))) goto err_create; - it = &resp->mit.iter; + it = &resp->iter; skb_head = &resp->msg.skb_head; if (!TFW_MSG_H2(req)) { @@ -2858,7 +2858,6 @@ tfw_cache_set_hdr_age(TfwHttpResp *resp, TfwCacheEntry *ce, long age) int r; size_t digs; bool to_h2 = TFW_MSG_H2(resp->req); - TfwHttpTransIter *mit = &resp->mit; struct sk_buff **skb_head = &resp->msg.skb_head; char cstr_age[TFW_ULTOA_BUF_SIZ] = {0}; char *name = to_h2 ? "age" : "age" S_DLM; @@ -2886,11 +2885,11 @@ tfw_cache_set_hdr_age(TfwHttpResp *resp, TfwCacheEntry *ce, long age) if ((r = tfw_hpack_encode(resp, &h_age, false, false))) goto err; } else { - if ((r = tfw_http_msg_expand_data(&mit->iter, skb_head, + if ((r = tfw_http_msg_expand_data(&resp->iter, skb_head, &h_age, NULL))) goto err; - if ((r = tfw_http_msg_expand_data(&mit->iter, skb_head, + if ((r = tfw_http_msg_expand_data(&resp->iter, skb_head, &g_crlf, NULL))) goto err; } @@ -2978,7 +2977,7 @@ tfw_cache_build_resp(TfwHttpReq *req, TfwCacheEntry *ce, long age) mit = &resp->mit; skb_head = &resp->msg.skb_head; WARN_ON_ONCE(mit->acc_len); - it = &mit->iter; + it = &resp->iter; /* * Set 'set-cookie' header if needed, for HTTP/2 or HTTP/1.1 diff --git a/fw/hpack.c b/fw/hpack.c index 0455902560..052b2784ac 100644 --- a/fw/hpack.c +++ b/fw/hpack.c @@ -1872,8 +1872,7 @@ tfw_hpack_cache_decode_expand(TfwHPack *__restrict hp, unsigned int state; int r = T_OK; TfwStr exp_str = {}; - TfwHttpTransIter *mit = &resp->mit; - TfwMsgIter *it = &mit->iter; + TfwMsgIter *it = &resp->iter; const unsigned char *last = src + n; unsigned char *prev = src; struct sk_buff **skb_head = &resp->msg.skb_head; @@ -3396,7 +3395,7 @@ tfw_hpack_write_idx(TfwHttpResp *__restrict resp, TfwHPackInt *__restrict idx, bool use_pool) { TfwHttpTransIter *mit = &resp->mit; - TfwMsgIter *iter = &mit->iter; + TfwMsgIter *iter = &resp->iter; struct sk_buff **skb_head = &resp->msg.skb_head; const TfwStr s_idx = { .data = idx->buf, @@ -3408,8 +3407,8 @@ tfw_hpack_write_idx(TfwHttpResp *__restrict resp, TfwHPackInt *__restrict idx, s_idx.data); if (use_pool) - return tfw_http_msg_expand_from_pool((TfwHttpMsg *)resp, - &s_idx); + return tfw_h2_msg_expand_from_pool((TfwHttpMsg *)resp, + &s_idx, &resp->mit); return tfw_http_msg_expand_data(iter, skb_head, &s_idx, &mit->start_off); @@ -3449,14 +3448,16 @@ tfw_hpack_hdr_add(TfwHttpResp *__restrict resp, TfwStr *__restrict hdr, s_nlen.data = nlen.buf; s_nlen.len = nlen.sz; - r = tfw_http_msg_expand_from_pool(msg, &s_nlen); + r = tfw_h2_msg_expand_from_pool(msg, &s_nlen, &resp->mit); if (unlikely(r)) return r; if (trans) - r = tfw_http_msg_expand_from_pool_lc(msg, &s_name); + r = tfw_h2_msg_expand_from_pool_lc(msg, &s_name, + &resp->mit); else - r = tfw_http_msg_expand_from_pool(msg, &s_name); + r = tfw_h2_msg_expand_from_pool(msg, &s_name, + &resp->mit); if (unlikely(r)) return r; } @@ -3465,12 +3466,12 @@ tfw_hpack_hdr_add(TfwHttpResp *__restrict resp, TfwStr *__restrict hdr, s_vlen.data = vlen.buf; s_vlen.len = vlen.sz; - r = tfw_http_msg_expand_from_pool(msg, &s_vlen); + r = tfw_h2_msg_expand_from_pool(msg, &s_vlen, &resp->mit); if (unlikely(r)) return r; if (!TFW_STR_EMPTY(&s_val)) - r = tfw_http_msg_expand_from_pool(msg, &s_val); + r = tfw_h2_msg_expand_from_pool(msg, &s_val, &resp->mit); return r; } @@ -3486,7 +3487,7 @@ tfw_hpack_hdr_expand(TfwHttpResp *__restrict resp, TfwStr *__restrict hdr, int ret; TfwStr *c, *end; TfwHttpTransIter *mit = &resp->mit; - TfwMsgIter *iter = &mit->iter; + TfwMsgIter *iter = &resp->iter; struct sk_buff **skb_head = &resp->msg.skb_head; TfwStr s_val; @@ -3632,6 +3633,7 @@ __tfw_hpack_encode(TfwHttpResp *__restrict resp, TfwStr *__restrict hdr, r = tfw_hpack_hdr_add(resp, hdr, &idx, name_indexed, trans); else r = tfw_hpack_hdr_expand(resp, hdr, &idx, name_indexed); + return r; } diff --git a/fw/http.c b/fw/http.c index bf4ce295ba..9489840bfc 100644 --- a/fw/http.c +++ b/fw/http.c @@ -628,7 +628,7 @@ tfw_h2_prep_resp(TfwHttpResp *resp, unsigned short status, TfwStr *msg) __TFW_STR_CH(&hdr, 0)->len = name->len - SLEN(S_CRLF) - 2; if (__TFW_STR_CH(msg, i + 1)->nchunks) { - TfwMsgIter *iter = &mit->iter; + TfwMsgIter *iter = &resp->iter; struct sk_buff **skb_head = &resp->msg.skb_head; TfwHPackInt vlen; TfwStr s_vlen = {}; @@ -669,7 +669,7 @@ tfw_h2_prep_resp(TfwHttpResp *resp, unsigned short status, TfwStr *msg) * Responses built locally has room for frame header reserved * in SKB linear data. */ - mit->frame_head = mit->iter.skb->data; + mit->frame_head = resp->iter.skb->data; hdrs_len += mit->acc_len; @@ -3105,7 +3105,6 @@ int tfw_http_expand_hbh(TfwHttpResp *resp, unsigned short status) { TfwHttpReq *req = resp->req; - TfwHttpTransIter *mit = &resp->mit; struct sk_buff **skb_head = &resp->msg.skb_head; bool proxy_close = test_bit(TFW_HTTP_B_CONN_CLOSE, resp->flags) && (status / 100 == 4); @@ -3143,7 +3142,7 @@ tfw_http_expand_hbh(TfwHttpResp *resp, unsigned short status) tfw_http_req_set_conn_close(req); return add_h_conn - ? tfw_http_msg_expand_data(&mit->iter, skb_head, &h_conn, NULL) + ? tfw_http_msg_expand_data(&resp->iter, skb_head, &h_conn, NULL) : 0; } @@ -3202,7 +3201,6 @@ tfw_http_expand_stale_warn(TfwHttpResp *resp) { /* TODO: adjust for #865 */ struct sk_buff **skb_head = &resp->msg.skb_head; - TfwHttpTransIter *mit = &resp->mit; TfwStr wh = { .chunks = (TfwStr []){ { .data = S_WARN, .len = SLEN(S_WARN) }, @@ -3214,7 +3212,7 @@ tfw_http_expand_stale_warn(TfwHttpResp *resp) .nchunks = 4, }; - return tfw_http_msg_expand_data(&mit->iter, skb_head, &wh, NULL); + return tfw_http_msg_expand_data(&resp->iter, skb_head, &wh, NULL); } static inline int @@ -3222,7 +3220,6 @@ __tfw_http_add_hdr_date(TfwHttpResp *resp, bool cache) { int r; struct sk_buff **skb_head = &resp->msg.skb_head; - TfwHttpTransIter *mit = &resp->mit; char *date = *this_cpu_ptr(&g_buf); TfwStr h_date = { .chunks = (TfwStr []) { @@ -3239,7 +3236,7 @@ __tfw_http_add_hdr_date(TfwHttpResp *resp, bool cache) if (!cache) r = tfw_http_msg_expand_from_pool((TfwHttpMsg *)resp, &h_date); else - r = tfw_http_msg_expand_data(&mit->iter, skb_head, &h_date, + r = tfw_http_msg_expand_data(&resp->iter, skb_head, &h_date, NULL); if (unlikely(r)) @@ -3273,14 +3270,13 @@ __tfw_http_add_hdr_server(TfwHttpResp *resp, bool cache) { int r; struct sk_buff **skb_head = &resp->msg.skb_head; - TfwHttpTransIter *mit = &resp->mit; static char s_server[] = S_F_SERVER TFW_NAME "/" TFW_VERSION S_CRLF; TfwStr hdr = { .data = s_server, .len = SLEN(s_server) }; if (!cache) r = tfw_http_msg_expand_from_pool((TfwHttpMsg *)resp, &hdr); else - r = tfw_http_msg_expand_data(&mit->iter, skb_head, &hdr, NULL); + r = tfw_http_msg_expand_data(&resp->iter, skb_head, &hdr, NULL); if (unlikely(r)) T_ERR("Unable to add Server: header to resp [%p]\n", resp); @@ -3338,13 +3334,13 @@ __tfw_http_add_hdr_via(TfwHttpMsg *hm, int http_version, bool from_cache) &TFW_STR_STRING(S_CRLF)); } else { struct sk_buff **skb_head = &hm->msg.skb_head; - TfwHttpTransIter *mit = &((TfwHttpResp *)hm)->mit; + TfwMsgIter *it = &hm->iter; TfwStr crlf = { .data = S_CRLF, .len = SLEN(S_CRLF) }; - r = tfw_http_msg_expand_data(&mit->iter, skb_head, &rh, NULL); + r = tfw_http_msg_expand_data(it, skb_head, &rh, NULL); if (unlikely(r)) goto err; - r = tfw_http_msg_expand_data(&mit->iter, skb_head, &crlf, NULL); + r = tfw_http_msg_expand_data(it, skb_head, &crlf, NULL); } if (unlikely(r)) @@ -4302,12 +4298,12 @@ tfw_h1_purge_resp_clean(TfwHttpResp *resp, TfwHttpMsgCleanup *cleanup) static int tfw_http_adjust_resp(TfwHttpResp *resp) { - int r, hdr_start = TFW_HTTP_HDR_CONTENT_LENGTH;; + int r, hdr_start = TFW_HTTP_HDR_CONTENT_LENGTH; TfwHttpReq *req = resp->req; TfwHttpMsg *hm = (TfwHttpMsg *)resp; unsigned long conn_flg = 0; TfwHttpMsgCleanup cleanup = {}; - TfwHttpTransIter *mit = &resp->mit; + TfwMsgIter *iter = &resp->iter; TfwStr *pos, *end, *s_line = &resp->h_tbl->tbl[TFW_HTTP_STATUS_LINE]; static const DEFINE_TFW_STR(crlf, S_CRLF); const TfwHdrMods *h_mods = tfw_vhost_get_hdr_mods(req->location, @@ -4343,13 +4339,13 @@ tfw_http_adjust_resp(TfwHttpResp *resp) if (unlikely(r)) goto clean; - tfw_h2_msg_transform_setup(mit, resp->msg.skb_head); + tfw_msg_transform_setup(iter, resp->msg.skb_head); } else { /* * When response doesn't have body, just remove * headers and use current skb as skb head. */ - tfw_h2_msg_transform_setup(mit, resp->msg.skb_head); + tfw_msg_transform_setup(iter, resp->msg.skb_head); r = tfw_http_msg_cutoff_headers(hm, &cleanup); if (unlikely(r)) @@ -4374,7 +4370,7 @@ tfw_http_adjust_resp(TfwHttpResp *resp) hdr_start = TFW_HTTP_HDR_CONTENT_TYPE; } else { /* Response for regular request. */ - tfw_h2_msg_transform_setup(mit, resp->msg.skb_head); + tfw_msg_transform_setup(iter, resp->msg.skb_head); r = tfw_http_msg_cutoff_headers(hm, &cleanup); if (unlikely(r)) goto clean; @@ -5074,8 +5070,7 @@ tfw_h2_hpack_encode_headers(TfwHttpResp *resp, const TfwHdrMods *h_mods) static int tfw_h2_append_predefined_body(TfwHttpResp *resp, const TfwStr *body) { - TfwHttpTransIter *mit = &resp->mit; - TfwMsgIter *it = &mit->iter; + TfwMsgIter *it = &resp->iter; size_t len, max_copy = PAGE_SIZE; char *data; int r; @@ -5578,7 +5573,8 @@ tfw_h2_resp_encode_headers(TfwHttpResp *resp) * adjusting of particular headers. */ WARN_ON_ONCE(mit->acc_len); - tfw_h2_msg_transform_setup(mit, resp->msg.skb_head); + BUG_ON(mit->frame_head); + tfw_msg_transform_setup(&resp->iter, resp->msg.skb_head); r = tfw_http_msg_cutoff_headers((TfwHttpMsg *)resp, &cleanup); if (unlikely(r)) @@ -5588,7 +5584,7 @@ tfw_h2_resp_encode_headers(TfwHttpResp *resp) * Alloc room for frame header. After this call resp->pool * must be used only as skb paged data. */ - r = tfw_http_msg_setup_transform_pool(mit, resp->pool); + r = tfw_http_msg_setup_transform_pool(mit, &resp->iter, resp->pool); if (unlikely(r)) goto clean; diff --git a/fw/http_msg.c b/fw/http_msg.c index b5048f9ca4..287003ae5d 100644 --- a/fw/http_msg.c +++ b/fw/http_msg.c @@ -1417,16 +1417,16 @@ tfw_http_msg_alloc_from_pool(TfwMsgIter *it, TfwPool* pool, size_t size) * data, which will split the paged fragment. */ int -tfw_http_msg_setup_transform_pool(TfwHttpTransIter *mit, TfwPool* pool) +tfw_http_msg_setup_transform_pool(TfwHttpTransIter *mit, TfwMsgIter *it, + TfwPool* pool) { int r; char* addr; bool np; - TfwMsgIter *it = &mit->iter; unsigned int room = TFW_POOL_CHUNK_ROOM(pool); BUG_ON(room < 0); - BUG_ON(mit->iter.frag > 0); + BUG_ON(it->frag > 0); /* Alloc a full page if room smaller than MIN_FRAG_SIZE. */ if (room < MIN_HDR_FRAG_SIZE) @@ -1457,7 +1457,7 @@ tfw_http_msg_setup_transform_pool(TfwHttpTransIter *mit, TfwPool* pool) static inline int __tfw_http_msg_move_body(TfwHttpMsg *resp, struct sk_buff *nskb) { - TfwMsgIter *it = &resp->mit.iter; + TfwMsgIter *it = &resp->iter; struct sk_buff **body; int r, frag; char *p; @@ -1527,12 +1527,13 @@ tfw_http_msg_linear_transform(TfwMsgIter *it) */ static int __tfw_http_msg_expand_from_pool(TfwHttpMsg *hm, const TfwStr *str, + unsigned int *copied, void cpy(void *dest, const void *src, size_t n)) { int r; + char *addr; const TfwStr *c, *end; - unsigned int room, skb_room, n_copy, rlen, off; - TfwHttpTransIter *mit = &hm->mit; + unsigned int room, skb_room, n_copy, rlen, off, acc = 0; TfwMsgIter *it = &hm->iter; TfwPool* pool = hm->pool; @@ -1595,35 +1596,55 @@ __tfw_http_msg_expand_from_pool(TfwHttpMsg *hm, const TfwStr *str, n_copy = min(n_copy, skb_room); - mit->curr_ptr = tfw_http_msg_alloc_from_pool(it, pool, - n_copy); - if (unlikely(!mit->curr_ptr)) + addr = tfw_http_msg_alloc_from_pool(it, pool, n_copy); + if (unlikely(!addr)) return r; - cpy(mit->curr_ptr, c->data + off, n_copy); + cpy(addr, c->data + off, n_copy); rlen -= n_copy; - mit->acc_len += n_copy; - mit->curr_ptr += n_copy; + acc += n_copy; - T_DBG3("%s: acc_len=%lu, n_copy=%u, mit->curr_ptr=%pK", - __func__, mit->acc_len, - n_copy, mit->curr_ptr); + T_DBG3("%s: n_copy=%u", __func__, n_copy,); } } + *copied = acc; + return 0; } int tfw_http_msg_expand_from_pool(TfwHttpMsg *hm, const TfwStr *str) { - return __tfw_http_msg_expand_from_pool(hm, str, memcpy_fast); + unsigned int n; + + return __tfw_http_msg_expand_from_pool(hm, str, &n, memcpy_fast); } int -tfw_http_msg_expand_from_pool_lc(TfwHttpMsg *hm, const TfwStr *str) +tfw_h2_msg_expand_from_pool(TfwHttpMsg *hm, const TfwStr *str, + TfwHttpTransIter *mit) { - return __tfw_http_msg_expand_from_pool(hm, str, tfw_cstrtolower); + int r; + unsigned int n; + + r = __tfw_http_msg_expand_from_pool(hm, str, &n, memcpy_fast); + mit->acc_len += n; + + return r; +} + +int +tfw_h2_msg_expand_from_pool_lc(TfwHttpMsg *hm, const TfwStr *str, + TfwHttpTransIter *mit) +{ + int r; + unsigned int n; + + r = __tfw_http_msg_expand_from_pool(hm, str, &n, tfw_cstrtolower); + mit->acc_len += n; + + return r; } static inline void @@ -1688,7 +1709,7 @@ tfw_http_msg_cutoff_headers(TfwHttpMsg *hm, TfwHttpMsgCleanup* cleanup) { int i, ret; char *begin, *end; - TfwMsgIter *it = &hm->mit.iter; + TfwMsgIter *it = &hm->iter; char* body = TFW_STR_CHUNK(&hm->body, 0)->data; TfwStr *crlf = TFW_STR_LAST(&hm->crlf); char *off = body ? body : crlf->data + (crlf->len - 1); diff --git a/fw/http_msg.h b/fw/http_msg.h index 76de309ba5..071e63b9b3 100644 --- a/fw/http_msg.h +++ b/fw/http_msg.h @@ -106,12 +106,9 @@ tfw_http_msg_alloc_resp_light(TfwHttpReq *req) } static inline void -tfw_h2_msg_transform_setup(TfwHttpTransIter *mit, struct sk_buff *skb) +tfw_msg_transform_setup(TfwMsgIter *iter, struct sk_buff *skb) { - TfwMsgIter *iter = &mit->iter; - BUG_ON(!skb); - BUG_ON(mit->frame_head); iter->frag = -1; iter->skb = skb; @@ -168,9 +165,13 @@ int tfw_http_msg_grow_hdr_tbl(TfwHttpMsg *hm); void tfw_http_msg_free(TfwHttpMsg *m); int tfw_http_msg_expand_data(TfwMsgIter *it, struct sk_buff **skb_head, const TfwStr *src, unsigned int *start_off); -int tfw_http_msg_setup_transform_pool(TfwHttpTransIter *mit, TfwPool* pool); +int tfw_http_msg_setup_transform_pool(TfwHttpTransIter *mit, TfwMsgIter *it, + TfwPool* pool); int tfw_http_msg_expand_from_pool(TfwHttpMsg *hm, const TfwStr *str); -int tfw_http_msg_expand_from_pool_lc(TfwHttpMsg *hm, const TfwStr *str); +int tfw_h2_msg_expand_from_pool(TfwHttpMsg *hm, const TfwStr *str, + TfwHttpTransIter *mit); +int tfw_h2_msg_expand_from_pool_lc(TfwHttpMsg *hm, const TfwStr *str, + TfwHttpTransIter *mit); int __hdr_name_cmp(const TfwStr *hdr, const TfwStr *cmp_hdr); int __http_hdr_lookup(TfwHttpMsg *hm, const TfwStr *hdr); int tfw_http_msg_cutoff_headers(TfwHttpMsg *hm, TfwHttpMsgCleanup* cleanup); diff --git a/fw/http_sess.c b/fw/http_sess.c index dcb9b8d4f5..0f3fe4babe 100644 --- a/fw/http_sess.c +++ b/fw/http_sess.c @@ -365,17 +365,16 @@ tfw_http_sticky_add(TfwHttpResp *resp, bool cache) if (to_h2) { set_cookie.hpack_idx = 55; r = tfw_hpack_encode(resp, &set_cookie, !cache, !cache); - } - else if (cache) { - TfwHttpTransIter *mit = &resp->mit; + } else if (cache) { + TfwMsgIter *it = &resp->iter; struct sk_buff **skb_head = &resp->msg.skb_head; - r = tfw_http_msg_expand_data(&mit->iter, skb_head, - &set_cookie, NULL); + r = tfw_http_msg_expand_data(it, skb_head, &set_cookie, NULL); if (unlikely(r)) goto err; - r = tfw_http_msg_expand_data(&mit->iter, skb_head, &crlf, NULL); - } else { + r = tfw_http_msg_expand_data(it, skb_head, &crlf, NULL); + } + else { r = tfw_http_msg_expand_from_pool(hm, &set_cookie); if (unlikely(r)) goto err; diff --git a/fw/t/unit/test_http_msg.c b/fw/t/unit/test_http_msg.c index 0ffdf1490c..878a39472d 100644 --- a/fw/t/unit/test_http_msg.c +++ b/fw/t/unit/test_http_msg.c @@ -103,7 +103,7 @@ __test_resp_alloc(TfwStr *head_data, TfwStr *paged_data, return NULL; skb->next = skb->prev = skb; - it = &hmresp->mit.iter; + it = &hmresp->iter; it->skb = it->skb_head = skb; it->frag = -1; @@ -149,7 +149,7 @@ TEST(http_msg, expand_from_pool) if (!resp) return; - it = &resp->mit.iter; + it = &resp->iter; EXPECT_FALSE(it->skb->data_len == head->len + hdr->len + pgd->len); tfw_http_msg_expand_from_pool((TfwHttpMsg *)resp, hdr); @@ -197,7 +197,7 @@ do { \ if (!resp) return; - it = &resp->mit.iter; + it = &resp->iter; EXPECT_FALSE(it->skb->data_len == skbsz); tfw_http_msg_expand_from_pool((TfwHttpMsg *)resp, &hdr); From de1a185c4185782db01630e267ff57bae91378d4 Mon Sep 17 00:00:00 2001 From: Constantine Date: Wed, 30 Aug 2023 11:37:41 +0300 Subject: [PATCH 06/44] Fix error handling Also fix comments. --- fw/http.c | 13 ++++++------- fw/http_msg.c | 6 +++--- fw/http_sess.c | 3 +++ 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/fw/http.c b/fw/http.c index 9489840bfc..3592a59457 100644 --- a/fw/http.c +++ b/fw/http.c @@ -3098,8 +3098,7 @@ tfw_http_req_set_conn_close(TfwHttpReq *req) * Expand HTTP/1.1 response with hop-by-hop headers. It is implied that this * procedure should be used only for cases when original hop-by-hop headers * is already removed from the response: e.g. creation HTTP/1.1-response from - * the cache (see also comments for tfw_http_set_hdr_connection(), - * tfw_http_set_hdr_keep_alive() and tfw_http_adjust_resp()). + * the cache. */ int tfw_http_expand_hbh(TfwHttpResp *resp, unsigned short status) @@ -3585,7 +3584,7 @@ tfw_h1_add_loc_hdrs(TfwHttpMsg *hm, const TfwHdrMods *h_mods, bool from_cache) NULL); if (unlikely(r)) return r; - r |= tfw_http_msg_expand_data(it, skb_head, &crlf, + r = tfw_http_msg_expand_data(it, skb_head, &crlf, NULL); } else { r = tfw_http_msg_expand_from_pool(hm, &h_mdf); @@ -3681,7 +3680,7 @@ tfw_h1_adjust_req(TfwHttpReq *req) if (unlikely(r)) goto clean; - tfw_http_msg_expand_from_pool(hm, &crlf); + r = tfw_http_msg_expand_from_pool(hm, &crlf); if (unlikely(r)) goto clean; } @@ -4335,7 +4334,7 @@ tfw_http_adjust_resp(TfwHttpResp *resp) /* Clean current reponse skb_head if body is exists. */ if (resp->body.len > 0) { - tfw_h1_purge_resp_clean(resp, &cleanup); + r = tfw_h1_purge_resp_clean(resp, &cleanup); if (unlikely(r)) goto clean; @@ -4401,7 +4400,7 @@ tfw_http_adjust_resp(TfwHttpResp *resp) r = tfw_http_msg_expand_from_pool(hm, dup); if (unlikely(r)) goto clean; - tfw_http_msg_expand_from_pool(hm, &crlf); + r = tfw_http_msg_expand_from_pool(hm, &crlf); if (unlikely(r)) goto clean; } @@ -4670,7 +4669,7 @@ tfw_h2_add_hdr_via(TfwHttpResp *resp) } /* - * Same as @tfw_http_set_hdr_date(), but intended for usage in HTTP/1.1=>HTTP/2 + * Same as @tfw_http_add_hdr_date(), but intended for usage in HTTP/1.1=>HTTP/2 * transformation and for building response from cache. */ int diff --git a/fw/http_msg.c b/fw/http_msg.c index 287003ae5d..c126b6951e 100644 --- a/fw/http_msg.c +++ b/fw/http_msg.c @@ -1598,7 +1598,7 @@ __tfw_http_msg_expand_from_pool(TfwHttpMsg *hm, const TfwStr *str, addr = tfw_http_msg_alloc_from_pool(it, pool, n_copy); if (unlikely(!addr)) - return r; + return -ENOMEM; cpy(addr, c->data + off, n_copy); rlen -= n_copy; @@ -1626,7 +1626,7 @@ tfw_h2_msg_expand_from_pool(TfwHttpMsg *hm, const TfwStr *str, TfwHttpTransIter *mit) { int r; - unsigned int n; + unsigned int n = 0; r = __tfw_http_msg_expand_from_pool(hm, str, &n, memcpy_fast); mit->acc_len += n; @@ -1639,7 +1639,7 @@ tfw_h2_msg_expand_from_pool_lc(TfwHttpMsg *hm, const TfwStr *str, TfwHttpTransIter *mit) { int r; - unsigned int n; + unsigned int n = 0; r = __tfw_http_msg_expand_from_pool(hm, str, &n, tfw_cstrtolower); mit->acc_len += n; diff --git a/fw/http_sess.c b/fw/http_sess.c index 0f3fe4babe..75dd75c3c5 100644 --- a/fw/http_sess.c +++ b/fw/http_sess.c @@ -381,6 +381,9 @@ tfw_http_sticky_add(TfwHttpResp *resp, bool cache) r = tfw_http_msg_expand_from_pool(hm, &crlf); } + if (unlikely(r)) + goto err; + return 0; err: From f9e5ba577d2a61031e0ebdcba1f0acf4cc82f11f Mon Sep 17 00:00:00 2001 From: Constantine Date: Thu, 31 Aug 2023 14:58:44 +0300 Subject: [PATCH 07/44] HTTP 1.1 method parsing Parse HTTP 1.1 method and store it in `msg->h_tbl[TFW_HTTP_METHOD]`. Method will be used during http request forwarding to rebuild the request. --- fw/http.h | 3 +- fw/http_parser.c | 89 +++++++++++++++++++++++++++++++----------------- 2 files changed, 60 insertions(+), 32 deletions(-) diff --git a/fw/http.h b/fw/http.h index e6c161d606..e9bbd77bac 100644 --- a/fw/http.h +++ b/fw/http.h @@ -199,8 +199,9 @@ typedef struct { */ typedef enum { TFW_HTTP_STATUS_LINE, + TFW_HTTP_METHOD = TFW_HTTP_STATUS_LINE, TFW_HTTP_HDR_H2_STATUS = TFW_HTTP_STATUS_LINE, - TFW_HTTP_HDR_H2_METHOD = TFW_HTTP_HDR_H2_STATUS, + TFW_HTTP_HDR_H2_METHOD = TFW_HTTP_METHOD, TFW_HTTP_HDR_H2_SCHEME, TFW_HTTP_HDR_H2_AUTHORITY, TFW_HTTP_HDR_H2_PATH, diff --git a/fw/http_parser.c b/fw/http_parser.c index c635288b7f..8d1b6d4c51 100644 --- a/fw/http_parser.c +++ b/fw/http_parser.c @@ -495,11 +495,17 @@ __FSM_STATE(st, cold) { \ __FSM_JMP(RGen_HdrOtherN); \ } +#define __FSM_METH_fixup_finish(curr_st, n) \ + __msg_hdr_chunk_fixup(data, __data_off(p + n)); \ + __msg_chunk_flags(TFW_STR_VALUE); \ + if (tfw_http_msg_hdr_close(msg)) \ + TFW_PARSER_DROP(curr_st); \ + /* Used for improbable states only, so use cold label. */ #define __FSM_METH_MOVE(st, ch, st_next) \ __FSM_STATE(st, cold) { \ if (likely(c == (ch))) \ - __FSM_MOVE_nofixup(st_next); \ + __FSM_MOVE(st_next); \ __FSM_JMP(Req_MethodUnknown); \ } @@ -508,6 +514,7 @@ __FSM_STATE(st, cold) { \ if (unlikely(c != (ch))) \ __FSM_JMP(Req_MethodUnknown); \ req->method = (m_type); \ + __FSM_METH_fixup_finish(st, 1); \ __FSM_MOVE_nofixup(Req_MUSpace); \ } @@ -4932,6 +4939,15 @@ tfw_http_parse_req(void *req_data, unsigned char *data, unsigned int len, /* HTTP method. */ __FSM_STATE(Req_Method, hot) { + parser->_hdr_tag = TFW_HTTP_METHOD; + /* + * Open header manually. HTTP method is not a header, storing + * it in @msg->h_tbl it's only optimization to not introduce + * new field into TfwHttpReq. Using @tfw_http_msg_hdr_open + * leads to wrong headers counting. + */ + __msg_field_open(&msg->stream->parser.hdr, p); + if (likely(__data_available(p, 9))) { /* * Move most frequent methods forward and do not use @@ -4945,10 +4961,12 @@ tfw_http_parse_req(void *req_data, unsigned char *data, unsigned int len, */ if (PI(p) == TFW_CHAR4_INT('G', 'E', 'T', ' ')) { req->method = TFW_HTTP_METH_GET; + __FSM_METH_fixup_finish(Req_Method, 3); __FSM_MOVE_nofixup_n(Req_Uri, 4); } if (PI(p) == TFW_CHAR4_INT('P', 'O', 'S', 'T')) { req->method = TFW_HTTP_METH_POST; + __FSM_METH_fixup_finish(Req_Method, 4); __FSM_MOVE_nofixup_n(Req_MUSpace, 4); } goto Req_Method_RareMethods; @@ -5502,6 +5520,7 @@ Req_Method_RareMethods: __attribute__((cold)) do { \ req->method = TFW_HTTP_METH_##meth; \ __fsm_n += step_inc; \ + __FSM_METH_fixup_finish(Req_Method_RareMethods, __fsm_n); \ goto match_meth; \ } while (0) @@ -5514,21 +5533,21 @@ do { \ if (likely(*(p + 4) == 'E')) __MATCH_METH(PURGE, 1); req->method = _TFW_HTTP_METH_INCOMPLETE; - __FSM_MOVE_nofixup_n(Req_MethPurg, 4); + __FSM_MOVE_n(Req_MethPurg, 4); case TFW_CHAR4_INT('C', 'O', 'P', 'Y'): __MATCH_METH(COPY, 0); case TFW_CHAR4_INT('D', 'E', 'L', 'E'): if (likely(*(p + 4) == 'T' && *(p + 5) == 'E')) __MATCH_METH(DELETE, 2); req->method = _TFW_HTTP_METH_INCOMPLETE; - __FSM_MOVE_nofixup_n(Req_MethDele, 4); + __FSM_MOVE_n(Req_MethDele, 4); case TFW_CHAR4_INT('L', 'O', 'C', 'K'): __MATCH_METH(LOCK, 0); case TFW_CHAR4_INT('M', 'K', 'C', 'O'): if (likely(*(p + 4) == 'L')) __MATCH_METH(MKCOL, 1); req->method = _TFW_HTTP_METH_INCOMPLETE; - __FSM_MOVE_nofixup_n(Req_MethMkco, 4); + __FSM_MOVE_n(Req_MethMkco, 4); case TFW_CHAR4_INT('M', 'O', 'V', 'E'): __MATCH_METH(MOVE, 0); case TFW_CHAR4_INT('O', 'P', 'T', 'I'): @@ -5536,15 +5555,16 @@ do { \ == TFW_CHAR4_INT('O', 'N', 'S', ' '))) { req->method = TFW_HTTP_METH_OPTIONS; - __FSM_MOVE_nofixup_n(Req_Uri, 8); + __FSM_METH_fixup_finish(Req_Method_RareMethods, 8); + __FSM_MOVE_n(Req_Uri, 8); } req->method = _TFW_HTTP_METH_INCOMPLETE; - __FSM_MOVE_nofixup_n(Req_MethOpti, 4); + __FSM_MOVE_n(Req_MethOpti, 4); case TFW_CHAR4_INT('P', 'A', 'T', 'C'): if (likely(*(p + 4) == 'H')) __MATCH_METH(PATCH, 1); req->method = _TFW_HTTP_METH_INCOMPLETE; - __FSM_MOVE_nofixup_n(Req_MethPatc, 4); + __FSM_MOVE_n(Req_MethPatc, 4); case TFW_CHAR4_INT('P', 'R', 'O', 'P'): if (likely(*((unsigned int *)p + 1) == TFW_CHAR4_INT('F', 'I', 'N', 'D'))) @@ -5558,20 +5578,21 @@ do { \ __MATCH_METH(PROPPATCH, 5); } req->method = _TFW_HTTP_METH_INCOMPLETE; - __FSM_MOVE_nofixup_n(Req_MethProp, 4); + __FSM_MOVE_n(Req_MethProp, 4); case TFW_CHAR4_INT('P', 'U', 'T', ' '): req->method = TFW_HTTP_METH_PUT; - __FSM_MOVE_nofixup_n(Req_Uri, 4); + __FSM_METH_fixup_finish(Req_Method_RareMethods, 4); + __FSM_MOVE_n(Req_Uri, 4); case TFW_CHAR4_INT('T', 'R', 'A', 'C'): if (likely(*(p + 4) == 'E')) __MATCH_METH(TRACE, 1); req->method = _TFW_HTTP_METH_INCOMPLETE; - __FSM_MOVE_nofixup_n(Req_MethTrac, 4); + __FSM_MOVE_n(Req_MethTrac, 4); case TFW_CHAR4_INT('U', 'N', 'L', 'O'): if (likely(*(p + 4) == 'C' && *(p + 5) == 'K')) __MATCH_METH(UNLOCK, 2); req->method = _TFW_HTTP_METH_INCOMPLETE; - __FSM_MOVE_nofixup_n(Req_MethUnlo, 4); + __FSM_MOVE_n(Req_MethUnlo, 4); default: __FSM_JMP(Req_MethodUnknown); } @@ -5584,34 +5605,34 @@ Req_Method_1CharStep: __attribute__((cold)) switch (c) { case 'G': req->method = _TFW_HTTP_METH_INCOMPLETE; - __FSM_MOVE_nofixup(Req_MethG); + __FSM_MOVE(Req_MethG); case 'H': req->method = _TFW_HTTP_METH_INCOMPLETE; - __FSM_MOVE_nofixup(Req_MethH); + __FSM_MOVE(Req_MethH); case 'P': req->method = _TFW_HTTP_METH_INCOMPLETE; - __FSM_MOVE_nofixup(Req_MethP); + __FSM_MOVE(Req_MethP); case 'C': req->method = _TFW_HTTP_METH_INCOMPLETE; - __FSM_MOVE_nofixup(Req_MethC); + __FSM_MOVE(Req_MethC); case 'D': req->method = _TFW_HTTP_METH_INCOMPLETE; - __FSM_MOVE_nofixup(Req_MethD); + __FSM_MOVE(Req_MethD); case 'L': req->method = _TFW_HTTP_METH_INCOMPLETE; - __FSM_MOVE_nofixup(Req_MethL); + __FSM_MOVE(Req_MethL); case 'M': req->method = _TFW_HTTP_METH_INCOMPLETE; - __FSM_MOVE_nofixup(Req_MethM); + __FSM_MOVE(Req_MethM); case 'O': req->method = _TFW_HTTP_METH_INCOMPLETE; - __FSM_MOVE_nofixup(Req_MethO); + __FSM_MOVE(Req_MethO); case 'T': req->method = _TFW_HTTP_METH_INCOMPLETE; - __FSM_MOVE_nofixup(Req_MethT); + __FSM_MOVE(Req_MethT); case 'U': req->method = _TFW_HTTP_METH_INCOMPLETE; - __FSM_MOVE_nofixup(Req_MethU); + __FSM_MOVE(Req_MethU); } __FSM_JMP(Req_MethodUnknown); @@ -5625,13 +5646,13 @@ Req_Method_1CharStep: __attribute__((cold)) __FSM_STATE(Req_MethP, cold) { switch (c) { case 'O': - __FSM_MOVE_nofixup(Req_MethPo); + __FSM_MOVE(Req_MethPo); case 'A': - __FSM_MOVE_nofixup(Req_MethPa); + __FSM_MOVE(Req_MethPa); case 'R': - __FSM_MOVE_nofixup(Req_MethPr); + __FSM_MOVE(Req_MethPr); case 'U': - __FSM_MOVE_nofixup(Req_MethPu); + __FSM_MOVE(Req_MethPu); } __FSM_JMP(Req_MethodUnknown); } @@ -5648,9 +5669,9 @@ Req_Method_1CharStep: __attribute__((cold)) __FSM_STATE(Req_MethProp, cold) { switch (c) { case 'F': - __FSM_MOVE_nofixup(Req_MethPropf); + __FSM_MOVE(Req_MethPropf); case 'P': - __FSM_MOVE_nofixup(Req_MethPropp); + __FSM_MOVE(Req_MethPropp); } __FSM_JMP(Req_MethodUnknown); } @@ -5667,14 +5688,16 @@ Req_Method_1CharStep: __attribute__((cold)) __FSM_STATE(Req_MethPu, cold) { switch (c) { case 'R': - __FSM_MOVE_nofixup(Req_MethPur); + __FSM_MOVE(Req_MethPur); case 'T': /* PUT */ req->method = TFW_HTTP_METH_PUT; - __FSM_MOVE_nofixup(Req_MUSpace); + __FSM_METH_fixup_finish(Req_MethPu, 1); + __FSM_MOVE_nofixup_n(Req_MUSpace, 1); } __FSM_JMP(Req_MethodUnknown); } + /* PURGE */ __FSM_METH_MOVE(Req_MethPur, 'G', Req_MethPurg); __FSM_METH_MOVE_finish(Req_MethPurg, 'E', TFW_HTTP_METH_PURGE); @@ -5700,9 +5723,9 @@ Req_Method_1CharStep: __attribute__((cold)) __FSM_STATE(Req_MethM, cold) { switch (c) { case 'K': - __FSM_MOVE_nofixup(Req_MethMk); + __FSM_MOVE(Req_MethMk); case 'O': - __FSM_MOVE_nofixup(Req_MethMo); + __FSM_MOVE(Req_MethMo); } __FSM_JMP(Req_MethodUnknown); } @@ -5737,6 +5760,8 @@ Req_Method_1CharStep: __attribute__((cold)) __fsm_sz = tfw_match_token(p, __fsm_n); if (likely(__fsm_sz)) { req->method = _TFW_HTTP_METH_UNKNOWN; + __msg_hdr_chunk_fixup(p, __fsm_sz); + __msg_chunk_flags(TFW_STR_VALUE); p += __fsm_sz; } if (unlikely(__fsm_sz == __fsm_n)) { @@ -5752,6 +5777,8 @@ Req_Method_1CharStep: __attribute__((cold)) * then there is zero-length method name * and the request must be dropped. */ + if (tfw_http_msg_hdr_close(msg)) + TFW_PARSER_DROP(Req_MethodUnknown); __FSM_MOVE_nofixup_n(Req_MUSpace, 0); } From 13b2fed15b3d4aaddda7299ecdf5e1c7214c423a Mon Sep 17 00:00:00 2001 From: Constantine Date: Thu, 31 Aug 2023 15:01:32 +0300 Subject: [PATCH 08/44] Fix debug logging --- fw/http_msg.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fw/http_msg.c b/fw/http_msg.c index c126b6951e..401b158741 100644 --- a/fw/http_msg.c +++ b/fw/http_msg.c @@ -1604,7 +1604,7 @@ __tfw_http_msg_expand_from_pool(TfwHttpMsg *hm, const TfwStr *str, rlen -= n_copy; acc += n_copy; - T_DBG3("%s: n_copy=%u", __func__, n_copy,); + T_DBG3("%s: n_copy=%u", __func__, n_copy); } } From 20303213715bbfd689b8f38f988a9b421ad5e427 Mon Sep 17 00:00:00 2001 From: Constantine Date: Thu, 31 Aug 2023 17:08:58 +0300 Subject: [PATCH 09/44] Build http message using parsed method --- fw/http.c | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/fw/http.c b/fw/http.c index 3592a59457..0eac2419a6 100644 --- a/fw/http.c +++ b/fw/http.c @@ -3606,8 +3606,9 @@ tfw_h1_adjust_req(TfwHttpReq *req) int r, cl_sz = sizeof(TfwHttpMsgCleanup); TfwHttpMsg *hm = (TfwHttpMsg *)req; TfwHttpMsgCleanup *cleanup = req->cleanup; - const BasicStr *s_meth; - TfwStr *pos, *end, meth = {}; + TfwStr *pos, *end; + const TfwStr *meth; + static const DEFINE_TFW_STR(meth_get, "GET"); static const DEFINE_TFW_STR(sp, " "); static const DEFINE_TFW_STR(crlf, S_CRLF); static const DEFINE_TFW_STR(ver, " " S_VERSION11 S_CRLF); @@ -3625,14 +3626,11 @@ tfw_h1_adjust_req(TfwHttpReq *req) if (test_bit(TFW_HTTP_B_PURGE_GET, req->flags)) /* Rewrite PURGE to GET */ - s_meth = tfw_http_method_id2str(TFW_HTTP_METH_GET); + meth = &meth_get; else - s_meth = tfw_http_method_id2str(req->method); + meth = &hm->h_tbl->tbl[TFW_HTTP_METHOD]; - meth.data = s_meth->data; - meth.len = s_meth->len; - - r = tfw_http_msg_expand_from_pool(hm, &meth); + r = tfw_http_msg_expand_from_pool(hm, meth); if (unlikely(r)) goto clean; From 3da80b8992dc67ceda587832cdfd52dbadd38240 Mon Sep 17 00:00:00 2001 From: Constantine Date: Thu, 7 Sep 2023 12:04:15 +0300 Subject: [PATCH 10/44] Unified resp_hdr_add logic for HTTP2 and HTTP1 An ability to append values to headers was removed for HTTP2 requests as well as earlier for whole HTTP1 and HTTP2 responses. It was removed because we can't control correctness of appending accordingly to RFC. Values could be appended to headers that doesn't have comma-separated list syntax. --- fw/http.c | 37 +++++++++++++++---------------------- fw/vhost.h | 4 ++-- 2 files changed, 17 insertions(+), 24 deletions(-) diff --git a/fw/http.c b/fw/http.c index 0eac2419a6..553cf0be56 100644 --- a/fw/http.c +++ b/fw/http.c @@ -3756,8 +3756,9 @@ __h2_req_hdrs(TfwHttpReq *req, const TfwStr *hdr, unsigned int hid, bool append) if (WARN_ON_ONCE(!ht)) return -EINVAL; - if (unlikely(append && hid < TFW_HTTP_HDR_NONSINGULAR)) { - T_WARN("Appending to singular header %d\n", hid); + if (unlikely(append && hid < TFW_HTTP_HDR_NONSINGULAR + && !TFW_STR_EMPTY(&ht->tbl[hid]))) + { return -ENOENT; } @@ -3814,25 +3815,6 @@ __h2_req_hdrs(TfwHttpReq *req, const TfwStr *hdr, unsigned int hid, bool append) return 0; } - if (append) { - TfwStr h_app = { - .chunks = (TfwStr []){ - { .data = ", ", .len = 2 }, - { .data = s_val->data, .len = s_val->len } - }, - .len = s_val->len + 2, - .nchunks = 2 - }; - /* - * Concatenate only the first duplicate header, there is no need - * to produce more duplicates. - */ - if (TFW_STR_DUP(orig_hdr)) - orig_hdr = __TFW_STR_CH(orig_hdr, 0); - - it->hdrs_len += h_app.len; - return tfw_strcat(req->pool, orig_hdr, &h_app); - } /* * The remaining case is the substitution, since we have both: existing * original header and the new header to insert. @@ -3858,7 +3840,18 @@ tfw_h2_req_set_loc_hdrs(TfwHttpReq *req) int r; TfwHdrModsDesc *d = &h_mods->hdrs[i]; - if ((r = __h2_req_hdrs(req, d->hdr, d->hid, d->append))) { + if ((r = __h2_req_hdrs(req, d->hdr, d->hid, d->append))) { + /* + * Attempt to add duplicated singular header. + * Just go to next header. + */ + if (r == -ENOENT) { + T_WARN("Attempt to add already existed singular header '%.*s'\n", + PR_TFW_STR(TFW_STR_CHUNK(d->hdr, 0))); + continue; + } + + /* Other error that can't be handled here. */ T_ERR("HTTP/2: can't update location-specific header in" " the request [%p]\n", req); return r; diff --git a/fw/vhost.h b/fw/vhost.h index 822dbafa9c..13a37d20e9 100644 --- a/fw/vhost.h +++ b/fw/vhost.h @@ -86,8 +86,8 @@ typedef struct { * * @hdr - Header string, see @tfw_http_msg_hdr_xfrm_str(); * @hid - Header index in the header table; - * @append - if set the value is added to the end of an existing header, - * otherwise the original value is overwritten with given one. + * @append - if set the value is added to the end of the response + * otherwise the original header is overwritten with given one. */ struct tfw_hdr_mods_desc_t { TfwStr *hdr; From 8f9b6f2b9718ddb0cee033b7cc0996e45cb990ca Mon Sep 17 00:00:00 2001 From: Constantine Date: Thu, 7 Sep 2023 14:28:14 +0300 Subject: [PATCH 11/44] Comments fixed --- fw/http.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/fw/http.c b/fw/http.c index 553cf0be56..1435a2b71b 100644 --- a/fw/http.c +++ b/fw/http.c @@ -3146,8 +3146,7 @@ tfw_http_expand_hbh(TfwHttpResp *resp, unsigned short status) } /** - * Remove Connection header from HTTP message @msg if @conn_flg is zero, - * and replace or set a new header value otherwise. + * Add Connection header to HTTP message @msg depending on @conn_flg. * * SKBs may be shared by several HTTP messages. A shared SKB is not copied * but safely modified. Thus, a shared SKB is still owned by one CPU. @@ -3517,7 +3516,7 @@ tfw_http_hdr_sub(unsigned short hid, const TfwStr *hdr, * message. * * @hm - Message to be updated; - * @is_resp - Message represents response, not request; + * @h_mods - Headers modification info; * @from_cache - The response is created from cache, not applied to requests. */ int From a2ce8ad2d0bd1f4fcedd93f3d3aac5c8133ff0bb Mon Sep 17 00:00:00 2001 From: Constantine Date: Thu, 7 Sep 2023 14:37:12 +0300 Subject: [PATCH 12/44] Fixed return value for TFW_HTTP_MSG_HDR_XFRM --- fw/http.c | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/fw/http.c b/fw/http.c index 1435a2b71b..f843b040b6 100644 --- a/fw/http.c +++ b/fw/http.c @@ -3154,7 +3154,6 @@ tfw_http_expand_hbh(TfwHttpResp *resp, unsigned short status) static int tfw_http_set_hdr_connection(TfwHttpMsg *hm, unsigned long conn_flg) { - int r; static const DEFINE_TFW_STR(conn_close, "connection: close\r\n"); static const DEFINE_TFW_STR(conn_ka_up, "connection: keep-alive, \ upgrade\r\n"); @@ -3176,19 +3175,17 @@ tfw_http_set_hdr_connection(TfwHttpMsg *hm, unsigned long conn_flg) if (test_bit(TFW_HTTP_B_UPGRADE_WEBSOCKET, hm->flags) && test_bit(TFW_HTTP_B_CONN_UPGRADE, hm->flags)) { - r = tfw_http_msg_expand_from_pool(hm, &conn_ka_up); + return tfw_http_msg_expand_from_pool(hm, &conn_ka_up); } else { - r = tfw_http_msg_expand_from_pool(hm, &conn_ka); + return tfw_http_msg_expand_from_pool(hm, &conn_ka); } - } else { - if (test_bit(TFW_HTTP_B_UPGRADE_WEBSOCKET, hm->flags) + } else if (test_bit(TFW_HTTP_B_UPGRADE_WEBSOCKET, hm->flags) && test_bit(TFW_HTTP_B_CONN_UPGRADE, hm->flags)) - { - r = tfw_http_msg_expand_from_pool(hm, &conn_up); - } + { + return tfw_http_msg_expand_from_pool(hm, &conn_up); } - return r; + return 0; } /* From a9824efd69598c17bd9b1a3ca05fcd619d102941 Mon Sep 17 00:00:00 2001 From: Constantine Date: Thu, 7 Sep 2023 14:39:38 +0300 Subject: [PATCH 13/44] Remove unused `TfwHttpTransIter.curr_ptr` `curr_ptr` is not used because we expanding the last fragment of skb from pool or adding new fragment from pool as well. --- fw/http.h | 2 -- fw/http_msg.c | 1 - 2 files changed, 3 deletions(-) diff --git a/fw/http.h b/fw/http.h index e9bbd77bac..1b4b32e53e 100644 --- a/fw/http.h +++ b/fw/http.h @@ -470,7 +470,6 @@ typedef struct { * @map - indirection map for tracking headers order in skb; * @start_off - initial offset during copying response data into * skb (for subsequent insertion of HTTP/2 frame header); - * @curr_ptr - pointer in the skb to write the current header; * @frame_head - pointer to reserved space for frame header. Used during * http2 framing. Simplifies framing of paged SKBs. * Framing function may not worry about paged and liner SKBs. @@ -479,7 +478,6 @@ typedef struct { typedef struct { TfwHttpHdrMap *map; unsigned int start_off; - char *curr_ptr; char *frame_head; unsigned long acc_len; } TfwHttpTransIter; diff --git a/fw/http_msg.c b/fw/http_msg.c index 401b158741..e6e9026f14 100644 --- a/fw/http_msg.c +++ b/fw/http_msg.c @@ -1446,7 +1446,6 @@ tfw_http_msg_setup_transform_pool(TfwHttpTransIter *mit, TfwMsgIter *it, ss_skb_adjust_data_len(it->skb, FRAME_HEADER_SIZE); mit->frame_head = addr; - mit->curr_ptr = addr + FRAME_HEADER_SIZE; return 0; } From b3a33848eda71edca1fefc6323c757278a686f42 Mon Sep 17 00:00:00 2001 From: Constantine Date: Thu, 7 Sep 2023 14:59:48 +0300 Subject: [PATCH 14/44] Remove unused constants TFW_HTTP_B_NEED_STRIP_LEADING_LF and TFW_HTTP_B_NEED_STRIP_LEADING_CR was used during HTTP1 request forwarding to cutoff first CR LF in skb. --- fw/http_parser.c | 18 ++++++------------ fw/http_types.h | 4 ---- fw/t/unit/test_http1_parser.c | 6 ------ fw/t/unit/test_http_parser_common.h | 8 -------- 4 files changed, 6 insertions(+), 30 deletions(-) diff --git a/fw/http_parser.c b/fw/http_parser.c index 8d1b6d4c51..f7e505ca99 100644 --- a/fw/http_parser.c +++ b/fw/http_parser.c @@ -4914,24 +4914,18 @@ tfw_http_parse_req(void *req_data, unsigned char *data, unsigned int len, * CRLFs before the request line. */ __FSM_STATE(Req_0, hot) { - if (unlikely(c == '\r')) { - __set_bit(TFW_HTTP_B_NEED_STRIP_LEADING_CR, - req->flags); + if (unlikely(c == '\r')) __FSM_MOVE_nofixup(Req_0_Wait_LF); - } - if (unlikely(c == '\n')) { - __set_bit(TFW_HTTP_B_NEED_STRIP_LEADING_LF, - req->flags); + + if (unlikely(c == '\n')) __FSM_MOVE_nofixup(Req_Method); - } + __FSM_JMP(Req_Method); } __FSM_STATE(Req_0_Wait_LF) { - if (likely(c == '\n')) { - __set_bit(TFW_HTTP_B_NEED_STRIP_LEADING_LF, - req->flags); + if (likely(c == '\n')) __FSM_MOVE_nofixup(Req_Method); - } + TFW_PARSER_DROP(Req_0_Wait_LF); } diff --git a/fw/http_types.h b/fw/http_types.h index c32a47ee70..3f9300ec4d 100644 --- a/fw/http_types.h +++ b/fw/http_types.h @@ -104,10 +104,6 @@ enum { TFW_HTTP_B_REQ_DROP, /* Request is PURGE with an 'X-Tempesta-Cache: get' header. */ TFW_HTTP_B_PURGE_GET, - /* Need strip 1 leading CR */ - TFW_HTTP_B_NEED_STRIP_LEADING_CR, - /* Need strip 1 leading LF */ - TFW_HTTP_B_NEED_STRIP_LEADING_LF, /* * Request should be challenged, but requested resourse * is non-challengeable. Try to service such request diff --git a/fw/t/unit/test_http1_parser.c b/fw/t/unit/test_http1_parser.c index 0df8472d1e..11d4e78180 100644 --- a/fw/t/unit/test_http1_parser.c +++ b/fw/t/unit/test_http1_parser.c @@ -73,12 +73,6 @@ TEST(http1_parser, leading_eol) { - FOR_REQ(EMPTY_REQ) - EXPECT_EQ(number_to_strip(req), 0); - FOR_REQ("\n" EMPTY_REQ) - EXPECT_EQ(number_to_strip(req), 1); - FOR_REQ("\r\n" EMPTY_REQ) - EXPECT_EQ(number_to_strip(req), 2); EXPECT_BLOCK_REQ("\r\n\r\n" EMPTY_REQ); EXPECT_BLOCK_REQ("\n\n" EMPTY_REQ); EXPECT_BLOCK_REQ("\n\n\n" EMPTY_REQ); diff --git a/fw/t/unit/test_http_parser_common.h b/fw/t/unit/test_http_parser_common.h index 513814aa93..1a4c4e8517 100644 --- a/fw/t/unit/test_http_parser_common.h +++ b/fw/t/unit/test_http_parser_common.h @@ -658,14 +658,6 @@ do { \ void test_string_split(const TfwStr *expected, const TfwStr *parsed); -static inline int -number_to_strip(TfwHttpReq *req) -{ - return - !!test_bit(TFW_HTTP_B_NEED_STRIP_LEADING_CR, req->flags) + - !!test_bit(TFW_HTTP_B_NEED_STRIP_LEADING_LF, req->flags); -} - TfwStr get_next_str_val(TfwStr *str); #endif /* __TFW_HTTP_PARSER_COMMON_H__ */ From c8dff9e746e6df71486c03610a0097d6e7147777 Mon Sep 17 00:00:00 2001 From: Constantine Date: Thu, 7 Sep 2023 15:48:51 +0300 Subject: [PATCH 15/44] Remove unused macro --- fw/http_msg.h | 6 ------ 1 file changed, 6 deletions(-) diff --git a/fw/http_msg.h b/fw/http_msg.h index 071e63b9b3..ee1f920b74 100644 --- a/fw/http_msg.h +++ b/fw/http_msg.h @@ -146,12 +146,6 @@ int tfw_http_msg_hdr_xfrm_str(TfwHttpMsg *hm, const TfwStr *hdr, int tfw_http_msg_hdr_xfrm(TfwHttpMsg *hm, char *name, size_t n_len, char *val, size_t v_len, unsigned int hid, bool append); -#define TFW_HTTP_MSG_HDR_XFRM(hm, name, val, hid, append) \ - tfw_http_msg_hdr_xfrm(hm, name, sizeof(name) - 1, val, \ - sizeof(val) - 1, hid, append) -#define TFW_HTTP_MSG_HDR_DEL(hm, name, hid) \ - tfw_http_msg_hdr_xfrm(hm, name, sizeof(name) - 1, NULL, 0, hid, 0) - int tfw_http_msg_del_str(TfwHttpMsg *hm, TfwStr *str); int tfw_http_msg_cutoff_body_chunks(TfwHttpResp *resp); From fc77c0fb76c4685f3013e52446c122897125d059 Mon Sep 17 00:00:00 2001 From: Constantine Date: Thu, 7 Sep 2023 15:55:26 +0300 Subject: [PATCH 16/44] Removed comment for AUTO_SEGS_N value not changed AUTO_SEGS_N not changed after #1103 fix, because most of responses has large size and with this value (8) we can cover higher percentage of responses --- fw/tls.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/fw/tls.c b/fw/tls.c index 80f10a6126..434ecc408c 100644 --- a/fw/tls.c +++ b/fw/tls.c @@ -218,10 +218,6 @@ int tfw_tls_encrypt(struct sock *sk, struct sk_buff *skb, unsigned int mss_now, unsigned int limit) { - /* - * TODO #1103 currently even trivial 500-bytes HTTP message generates - * 6 segment skb. After the fix the number probably should be decreased. - */ #define AUTO_SEGS_N 8 #define MAX_SEG_N 64 From 54e34293d57d7cb3b3687df3fcaa68579f241e32 Mon Sep 17 00:00:00 2001 From: Constantine Date: Fri, 8 Sep 2023 11:51:28 +0300 Subject: [PATCH 17/44] `old_head` removed from TfwHttpReq Now we use cleanup field instead of `old_head`, cleanup also used during http1 request adjusting. --- fw/http.c | 24 ++++++++++++++---------- fw/http.h | 3 --- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/fw/http.c b/fw/http.c index f843b040b6..fd93377d40 100644 --- a/fw/http.c +++ b/fw/http.c @@ -2708,9 +2708,6 @@ tfw_http_req_destruct(void *msg) if (req->peer) tfw_client_put(req->peer); - if (req->old_head) - ss_skb_queue_purge(&req->old_head); - if (req->stale_ce) tfw_cache_put_entry(req->node, req->stale_ce); @@ -3599,9 +3596,8 @@ tfw_h1_add_loc_hdrs(TfwHttpMsg *hm, const TfwHdrMods *h_mods, bool from_cache) static int tfw_h1_adjust_req(TfwHttpReq *req) { - int r, cl_sz = sizeof(TfwHttpMsgCleanup); + int r; TfwHttpMsg *hm = (TfwHttpMsg *)req; - TfwHttpMsgCleanup *cleanup = req->cleanup; TfwStr *pos, *end; const TfwStr *meth; static const DEFINE_TFW_STR(meth_get, "GET"); @@ -3612,11 +3608,13 @@ tfw_h1_adjust_req(TfwHttpReq *req) req->vhost, TFW_VHOST_HDRMOD_REQ); - cleanup = (TfwHttpMsgCleanup *)tfw_pool_alloc(hm->pool, cl_sz); - memset(cleanup, 0, cl_sz); + req->cleanup = tfw_pool_alloc(hm->pool, sizeof(TfwHttpMsgCleanup)); + if (unlikely(!req->cleanup)) + return -ENOMEM; + memset(req->cleanup, 0, sizeof(TfwHttpMsgCleanup)); tfw_msg_transform_setup(&req->iter, req->msg.skb_head); - r = tfw_http_msg_cutoff_headers(hm, cleanup); + r = tfw_http_msg_cutoff_headers(hm, req->cleanup); if (unlikely(r)) goto clean; @@ -3708,7 +3706,7 @@ tfw_h1_adjust_req(TfwHttpReq *req) return r; clean: - __tfw_http_msg_cleanup(cleanup); + __tfw_http_msg_cleanup(req->cleanup); return r; } @@ -4002,6 +4000,11 @@ tfw_h2_adjust_req(TfwHttpReq *req) bool need_cl = req->body.len && TFW_STR_EMPTY(&ht->tbl[TFW_HTTP_HDR_CONTENT_LENGTH]); + req->cleanup = tfw_pool_alloc(req->pool, sizeof(TfwHttpMsgCleanup)); + if (unlikely(!req->cleanup)) + return -ENOMEM; + memset(req->cleanup, 0, sizeof(TfwHttpMsgCleanup)); + if (need_cl) { cl_data_len = tfw_ultoa(req->body.len, cl_data, TFW_ULTOA_BUF_SIZ); if (!cl_data_len) @@ -4207,7 +4210,7 @@ tfw_h2_adjust_req(TfwHttpReq *req) T_DBG3("%s: req [%p] converted to http1.1\n", __func__, req); old_head = req->msg.skb_head; - req->old_head = old_head; + req->cleanup->skb_head = old_head; req->msg.skb_head = new_head; /* Http chains might add a mark for the message, keep it. */ @@ -4250,6 +4253,7 @@ tfw_h2_adjust_req(TfwHttpReq *req) return 0; err: ss_skb_queue_purge(&new_head); + __tfw_http_msg_cleanup(req->cleanup); T_DBG3("%s: req [%p] convertation to http1.1 has failed\n", __func__, req); return -EINVAL; diff --git a/fw/http.h b/fw/http.h index 1b4b32e53e..8ffcfb011c 100644 --- a/fw/http.h +++ b/fw/http.h @@ -362,8 +362,6 @@ typedef struct { * @peer - end-to-end peer. The peer is not set if * hop-by-hop peer (TfwConnection->peer) and end-to-end peer are * the same; - * @old_head - Original request head. Required for keep request data until - * the response is sent to the client; * @stale_ce - Stale cache entry retrieved from the cache. Must be assigned * only when "cache_use_stale" is configured; * @cleanup - Original request data. Required for keep request data until @@ -402,7 +400,6 @@ struct tfw_http_req_t { TfwLocation *location; TfwHttpSess *sess; TfwClient *peer; - struct sk_buff *old_head; void *stale_ce; TfwHttpMsgCleanup *cleanup; TfwHttpCond cond; From 4642f1e7d86d0dcc14e6237133e5afe960ede6db Mon Sep 17 00:00:00 2001 From: Constantine Date: Wed, 20 Sep 2023 16:36:26 +0300 Subject: [PATCH 18/44] Dynamic table unknown method processing Allowed to use unknown method for headers that stored in dynamic table. Before if tempesta received such request it was dropped. Unknown method is method that processed as _TFW_HTTP_METH_UNKNOWN. --- fw/hpack.c | 4 ---- fw/http_parser.c | 4 ---- 2 files changed, 8 deletions(-) diff --git a/fw/hpack.c b/fw/hpack.c index 052b2784ac..885adfdf34 100644 --- a/fw/hpack.c +++ b/fw/hpack.c @@ -1289,10 +1289,6 @@ tfw_hpack_hdr_set(TfwHPack *__restrict hp, TfwHttpReq *__restrict req, * RFC 7541 6.2.1 * */ req->method = tfw_http_meth_str2id(s_hdr); - if (unlikely(req->method == _TFW_HTTP_METH_UNKNOWN)) { - WARN_ON_ONCE(1); - return -EINVAL; - } } parser->_hdr_tag = TFW_HTTP_HDR_H2_METHOD; break; diff --git a/fw/http_parser.c b/fw/http_parser.c index f7e505ca99..0797d1bfbf 100644 --- a/fw/http_parser.c +++ b/fw/http_parser.c @@ -10971,8 +10971,6 @@ enum { /** * Obtain HTTP method id from TfwStr chunked string. - * Code here relies on http parser, which should - * filter out illegal 'method' headers. * Used exclusively by HPACK related code. */ unsigned char @@ -11005,7 +11003,6 @@ tfw_http_meth_str2id(const TfwStr *m_hdr) case 'P': return TFW_HTTP_METH_POST; default: - WARN_ON(1); return _TFW_HTTP_METH_UNKNOWN; } case TFW_HTTP_MLEN_5C: @@ -11024,7 +11021,6 @@ tfw_http_meth_str2id(const TfwStr *m_hdr) ? TFW_HTTP_METH_PATCH : TFW_HTTP_METH_PURGE; default: - WARN_ON(1); return _TFW_HTTP_METH_UNKNOWN; } case TFW_HTTP_MLEN_6C: From d0f88ad5bfe87b4b9d446125a33a6d71b5247e14 Mon Sep 17 00:00:00 2001 From: Constantine Date: Thu, 21 Sep 2023 15:17:02 +0300 Subject: [PATCH 19/44] Fix type and identation, remove unused function --- fw/http.c | 8 ++++---- fw/http.h | 30 +----------------------------- 2 files changed, 5 insertions(+), 33 deletions(-) diff --git a/fw/http.c b/fw/http.c index fd93377d40..bcce88021f 100644 --- a/fw/http.c +++ b/fw/http.c @@ -3550,10 +3550,10 @@ tfw_h1_add_loc_hdrs(TfwHttpMsg *hm, const TfwHdrMods *h_mods, bool from_cache) } h_mdf.chunks[0] = desc->hdr->chunks[0]; - if (desc->hdr->nchunks == 2) { - h_mdf.chunks[2] = desc->hdr->chunks[1]; - h_mdf.nchunks += 1; - } + if (desc->hdr->nchunks == 2) { + h_mdf.chunks[2] = desc->hdr->chunks[1]; + h_mdf.nchunks += 1; + } h_mdf.len += desc->hdr->len; h_mdf.flags = desc->hdr->flags; h_mdf.eolen += desc->hdr->eolen; diff --git a/fw/http.h b/fw/http.h index 8ffcfb011c..741718f810 100644 --- a/fw/http.h +++ b/fw/http.h @@ -364,7 +364,7 @@ typedef struct { * the same; * @stale_ce - Stale cache entry retrieved from the cache. Must be assigned * only when "cache_use_stale" is configured; - * @cleanup - Original request data. Required for keep request data until + * @cleanup - Original request data. Required for keeping request data until * the response is sent to the client; * @pit - iterator for tracking transformed data allocation (applicable * for HTTP/2 mode only); @@ -787,32 +787,4 @@ bool tfw_http_mark_is_in_whitlist(unsigned int mark); char *tfw_http_resp_status_line(int status, size_t *len); int tfw_http_on_send_resp(void *conn, struct sk_buff **skb_head); -static inline const BasicStr * -tfw_http_method_id2str(int id) -{ -#define STR_METHOD(name) [TFW_HTTP_METH_ ## name] = { #name, sizeof(#name) - 1 } - - static BasicStr http_methods[] = { - STR_METHOD(COPY), - STR_METHOD(DELETE), - STR_METHOD(GET), - STR_METHOD(HEAD), - STR_METHOD(LOCK), - STR_METHOD(MKCOL), - STR_METHOD(MOVE), - STR_METHOD(OPTIONS), - STR_METHOD(PATCH), - STR_METHOD(POST), - STR_METHOD(PROPFIND), - STR_METHOD(PROPPATCH), - STR_METHOD(PUT), - STR_METHOD(TRACE), - STR_METHOD(UNLOCK), - STR_METHOD(PURGE), - }; - -#undef STR_METHOD - return &http_methods[id]; -} - #endif /* __TFW_HTTP_H__ */ From 233c380d52e8ebb7e7a334853ca46a95fe295115 Mon Sep 17 00:00:00 2001 From: Constantine Date: Fri, 22 Sep 2023 16:33:15 +0300 Subject: [PATCH 20/44] TfwHttpMsgCleanup aligning fix --- fw/http.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/fw/http.h b/fw/http.h index 741718f810..8db2f09744 100644 --- a/fw/http.h +++ b/fw/http.h @@ -348,9 +348,9 @@ typedef struct { * @pages_sz - current number of @pages; */ typedef struct { - struct sk_buff *skb_head; - struct page *pages[MAX_SKB_FRAGS]; - unsigned char pages_sz; + struct sk_buff *skb_head; + struct page *pages[MAX_SKB_FRAGS]; + unsigned char pages_sz; } TfwHttpMsgCleanup; /** From 2e8f02812717904da1077689aaefeaf51c54b4ff Mon Sep 17 00:00:00 2001 From: Constantine Date: Fri, 22 Sep 2023 17:22:35 +0300 Subject: [PATCH 21/44] Fix after rebase --- fw/http_msg.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/fw/http_msg.c b/fw/http_msg.c index e6e9026f14..7954a4942e 100644 --- a/fw/http_msg.c +++ b/fw/http_msg.c @@ -1454,7 +1454,7 @@ tfw_http_msg_setup_transform_pool(TfwHttpTransIter *mit, TfwMsgIter *it, * Move body to @nskb if body located in current skb. */ static inline int -__tfw_http_msg_move_body(TfwHttpMsg *resp, struct sk_buff *nskb) +__tfw_http_msg_move_body(TfwHttpResp *resp, struct sk_buff *nskb) { TfwMsgIter *it = &resp->iter; struct sk_buff **body; @@ -1564,12 +1564,13 @@ __tfw_http_msg_expand_from_pool(TfwHttpMsg *hm, const TfwStr *str, if (unlikely(skb_room == 0 || nr_frags == MAX_SKB_FRAGS)) { struct sk_buff *nskb = ss_skb_alloc(0); + TfwHttpResp *resp = (TfwHttpResp *)hm; if (!nskb) return -ENOMEM; if (hm->body.len > 0) { - r = __tfw_http_msg_move_body(hm, + r = __tfw_http_msg_move_body(resp, nskb); if (unlikely(r)) { T_WARN("Error during moving body"); From 9173d91f44c22fff0019cefb202cf429b10c10f3 Mon Sep 17 00:00:00 2001 From: Constantine Date: Tue, 28 May 2024 10:59:27 +0300 Subject: [PATCH 22/44] Code cleanup --- fw/cache.c | 4 ++-- fw/http.c | 3 ++- fw/http_msg.c | 4 ++-- fw/http_msg.h | 4 +--- fw/http_parser.c | 5 ++--- fw/http_sess.c | 6 ++---- 6 files changed, 11 insertions(+), 15 deletions(-) diff --git a/fw/cache.c b/fw/cache.c index 79dade1de7..548f160038 100644 --- a/fw/cache.c +++ b/fw/cache.c @@ -2802,8 +2802,8 @@ tfw_cache_build_resp_body(TDB *db, TdbVRec *trec, TfwHttpReq *req, TfwMsgIter *it, char *p, unsigned long body_sz) { int r; - const bool h2 = TFW_MSG_H2(req), tls = TFW_CONN_TLS(req->conn); - const bool sh_frag = h2 ? false : (true & tls); + const bool h2 = TFW_MSG_H2(req); + const bool sh_frag = !h2 && TFW_CONN_TLS(req->conn); if (WARN_ON_ONCE(!it->skb_head)) return -EINVAL; diff --git a/fw/http.c b/fw/http.c index bcce88021f..f0ca13c91e 100644 --- a/fw/http.c +++ b/fw/http.c @@ -3611,7 +3611,8 @@ tfw_h1_adjust_req(TfwHttpReq *req) req->cleanup = tfw_pool_alloc(hm->pool, sizeof(TfwHttpMsgCleanup)); if (unlikely(!req->cleanup)) return -ENOMEM; - memset(req->cleanup, 0, sizeof(TfwHttpMsgCleanup)); + req->cleanup->pages_sz = 0; + req->cleanup->skb_head = NULL; tfw_msg_transform_setup(&req->iter, req->msg.skb_head); r = tfw_http_msg_cutoff_headers(hm, req->cleanup); diff --git a/fw/http_msg.c b/fw/http_msg.c index 7954a4942e..50074efa8c 100644 --- a/fw/http_msg.c +++ b/fw/http_msg.c @@ -975,7 +975,7 @@ __hdr_sub(TfwHttpMsg *hm, const TfwStr *hdr, unsigned int hid) * header for going through the HTTP/2 transformation (i.e. search in the * HPACK encoder dynamic index). */ -int +static int tfw_http_msg_hdr_xfrm_str(TfwHttpMsg *hm, const TfwStr *hdr, unsigned int hid, bool append) { @@ -1380,7 +1380,7 @@ tfw_http_msg_expand_data(TfwMsgIter *it, struct sk_buff **skb_head, return 0; } -static char * +static void * tfw_http_msg_alloc_from_pool(TfwMsgIter *it, TfwPool* pool, size_t size) { int r; diff --git a/fw/http_msg.h b/fw/http_msg.h index ee1f920b74..7c691e38d0 100644 --- a/fw/http_msg.h +++ b/fw/http_msg.h @@ -2,7 +2,7 @@ * Tempesta FW * * Copyright (C) 2014 NatSys Lab. (info@natsys-lab.com). - * Copyright (C) 2015-2023 Tempesta Technologies, Inc. + * Copyright (C) 2015-2024 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 @@ -141,8 +141,6 @@ int __must_check __tfw_http_msg_add_str_data(TfwHttpMsg *hm, TfwStr *str, ss_skb_peek_tail(&hm->msg.skb_head)) unsigned int tfw_http_msg_hdr_lookup(TfwHttpMsg *hm, const TfwStr *hdr); -int tfw_http_msg_hdr_xfrm_str(TfwHttpMsg *hm, const TfwStr *hdr, - unsigned int hid, bool append); int tfw_http_msg_hdr_xfrm(TfwHttpMsg *hm, char *name, size_t n_len, char *val, size_t v_len, unsigned int hid, bool append); diff --git a/fw/http_parser.c b/fw/http_parser.c index 0797d1bfbf..a2b91e0b07 100644 --- a/fw/http_parser.c +++ b/fw/http_parser.c @@ -4906,11 +4906,10 @@ tfw_http_parse_req(void *req_data, unsigned char *data, unsigned int len, __FSM_START(parser->state); - /* - Skipping and stripping leading CRLFs - */ + /* - Skipping leading CRLFs - */ /* The parser accepts 1 optional CRLF or LF before the request line. - * The parser stores the fact of presense of it for subsequent - * stripping. The parser drops the request if it contains additional + * The parser drops the request if it contains additional * CRLFs before the request line. */ __FSM_STATE(Req_0, hot) { diff --git a/fw/http_sess.c b/fw/http_sess.c index 75dd75c3c5..0a35af5854 100644 --- a/fw/http_sess.c +++ b/fw/http_sess.c @@ -381,10 +381,8 @@ tfw_http_sticky_add(TfwHttpResp *resp, bool cache) r = tfw_http_msg_expand_from_pool(hm, &crlf); } - if (unlikely(r)) - goto err; - - return 0; + if (likely(!r)) + return 0; err: T_WARN("Cannot add '%s' header: val='%.*s=%.*s'\n", name, From 3edf4a0db28aebc6945888f77119a70832fadd61 Mon Sep 17 00:00:00 2001 From: Constantine Date: Tue, 28 May 2024 16:04:09 +0300 Subject: [PATCH 23/44] Remove unused functions and introduce new one `tfw_http_add_hdr_clen()` replaced by new function `tfw_http_resp_term_add_hdr_clen()`. Now it constructs content-length header into headers table, without skb modification as it done before. The headers copies to response in the tfw_http_adjust_resp() along with other headers. `tfw_http_msg_hdr_xfrm()` - deleted, because it's un- used. Some of functions that were used only by `tfw_http_msg_hdr_xfrm()/tfw_http_msg_hdr_xfrm_str()` not deleted, because we consider them as library functions. For instance: `tfw_strcpy_comp_ext()`. `ss_skb_get_room()` could be lib function, but it's just a wrapper for `ss_skb_get_room_w_frag()`. `__hdr_is_singular()` was used during adding headers configured by administrator, but we treat adding singular RAW headers via `resp_hdr_add` as misconfiguration, there is no sense to check it for each header. --- fw/http.c | 69 +++++++----- fw/http.h | 12 +- fw/http_msg.c | 307 -------------------------------------------------- fw/http_msg.h | 2 - fw/ss_skb.c | 10 -- fw/ss_skb.h | 4 +- fw/str.c | 3 +- fw/str.h | 16 --- fw/vhost.h | 2 +- 9 files changed, 52 insertions(+), 373 deletions(-) diff --git a/fw/http.c b/fw/http.c index f0ca13c91e..e916f6cb5f 100644 --- a/fw/http.c +++ b/fw/http.c @@ -175,6 +175,7 @@ unsigned int max_header_list_size = 0; #define S_XFF "x-forwarded-for" #define S_WARN "warning" +#define S_CL_NAME "content-length" #define S_F_HOST "host: " #define S_F_DATE "date: " @@ -3396,28 +3397,6 @@ tfw_http_add_x_forwarded_for(TfwHttpMsg *hm) return r; } -static int -tfw_http_add_hdr_clen(TfwHttpMsg *hm) -{ - int r; - char *buf = *this_cpu_ptr(&g_buf); - size_t cl_valsize = tfw_ultoa(hm->body.len, buf, - TFW_ULTOA_BUF_SIZ); - - r = tfw_http_msg_hdr_xfrm(hm, "Content-Length", - SLEN("Content-Length"), buf, cl_valsize, - TFW_HTTP_HDR_CONTENT_LENGTH, 0); - - if (unlikely(r)) - T_ERR("%s: unable to add 'content-length' header (msg=[%p])\n", - __func__, hm); - else - T_DBG3("%s: added 'content-length' header, msg=[%p]\n", - __func__, hm); - - return r; -} - /** * Compose Content-Type header field from scratch. * @@ -3537,7 +3516,6 @@ tfw_h1_add_loc_hdrs(TfwHttpMsg *hm, const TfwHdrMods *h_mods, bool from_cache) .nchunks = 2 /* header name + delimeter. */ }; - /* FIXME: this is a temporary WA for GCC12, see #1695 for details */ if (TFW_STR_CHUNK(desc->hdr, 1) == NULL) continue; @@ -6740,11 +6718,50 @@ tfw_http_resp_cache(TfwHttpMsg *hmresp) return T_OK; } +/* + * Allocate memory from pool and construct content-length header, place this + * header to headers table. The header will be copied to response in the + * tfw_http_adjust_resp(). + */ +static int +tfw_http_resp_term_add_hdr_clen(TfwHttpMsg *hm) +{ + char *val; + unsigned short nchunks = 3; + TfwStr *hdr = &hm->h_tbl->tbl[TFW_HTTP_HDR_CONTENT_LENGTH]; + size_t v_len, size = sizeof(TfwStr) * nchunks + TFW_ULTOA_BUF_SIZ; + + hdr->chunks = tfw_pool_alloc(hm->pool, size); + if (unlikely(!hdr->chunks)) { + T_ERR("%s: unable to add 'content-length' header (msg=[%p])\n", + __func__, hm); + return -ENOMEM; + } + val = (char *)(hdr->chunks + nchunks); + v_len = tfw_ultoa(hm->body.len, val, TFW_ULTOA_BUF_SIZ); + + hdr->chunks[0].data = S_CL_NAME; + hdr->chunks[0].len = SLEN(S_CL_NAME); + hdr->chunks[1].data = S_DLM; + hdr->chunks[1].len = SLEN(S_DLM); + hdr->chunks[2].data = val; + hdr->chunks[2].len = v_len; + + hdr->len = SLEN(S_CL_NAME) + SLEN(S_DLM) + v_len; + hdr->hpack_idx = 28; + hdr->nchunks = nchunks; + + T_DBG3("%s: added 'content-length' header, msg=[%p]\n", __func__, hm); + + return 0; +} + /* * Finish a response that is terminated by closing the connection. * * Http/1 response is terminated by connection close and lacks of framing - * information. H2 connections have their own framing happening just before + * information(response doesn't have contnent-length header or transfer-encoding + * chunked). H2 connections have their own framing happening just before * forwarding message to network, but h1 connections still require explicit * framing. */ @@ -6763,10 +6780,10 @@ tfw_http_resp_terminate(TfwHttpMsg *hm) if (test_bit(TFW_HTTP_B_CHUNKED_APPLIED, hm->flags)) set_bit(TFW_HTTP_B_CONN_CLOSE, hm->req->flags); + /* Add explicit framing information. */ if (!TFW_MSG_H2(hm->req)) { - int r; + int r = tfw_http_resp_term_add_hdr_clen(hm); - r = tfw_http_add_hdr_clen(hm); if (r) { TfwHttpReq *req = hm->req; diff --git a/fw/http.h b/fw/http.h index 8db2f09744..5fe585060b 100644 --- a/fw/http.h +++ b/fw/http.h @@ -182,12 +182,12 @@ typedef struct { * Http headers table. * * Singular headers (in terms of RFC 7230 3.2.2) go first to protect header - * repetition attacks. See __hdr_is_singular() and don't forget to update the - * static headers array when add a new singular header here. If the new header - * is hop-by-hop (must not be forwarded and cached by Tempesta) it must be - * listed in tfw_http_init_parser_req()/tfw_http_init_parser_resp() - * for unconditionally hop-by-hop header or in __parse_connection() otherwise. - * If the header is end-to-end it must be listed in __hbh_parser_add_data(). + * repetition attacks. Don't forget to update the static headers array when add + * a new singular header here. If the new header is hop-by-hop (must not be + * forwarded and cached by Tempesta) it must be listed in + * tfw_http_init_parser_req()/tfw_http_init_parser_resp() for unconditionally + * hop-by-hop header or in __parse_connection() otherwise. If the header is + * end-to-end it must be listed in __hbh_parser_add_data(). * * Note: don't forget to update __http_msg_hdr_val() and * tfw_http_msg_(resp|req)_spec_hid() upon adding a new header. diff --git a/fw/http_msg.c b/fw/http_msg.c index 50074efa8c..eb7aa7b816 100644 --- a/fw/http_msg.c +++ b/fw/http_msg.c @@ -343,28 +343,6 @@ __h2_msg_hdr_val(TfwStr *hdr, TfwStr *out_val) TFW_STR_INIT(out_val); } -/** - * Slow check of generic (raw) header for singularity. - * Some of the header should be special and moved to tfw_http_hdr_t enum, - * so linear search is Ok here. - * @return true for headers which must never have duplicates. - */ -static inline bool -__hdr_is_singular(const TfwStr *hdr) -{ - static const TfwStr hdr_singular[] = { - TFW_STR_STRING("authorization:"), - TFW_STR_STRING("from:"), - TFW_STR_STRING("if-unmodified-since:"), - TFW_STR_STRING("location:"), - TFW_STR_STRING("max-forwards:"), - TFW_STR_STRING("proxy-authorization:"), - TFW_STR_STRING("referer:"), - }; - - return tfw_http_msg_find_hdr(hdr, hdr_singular); -} - /** * Lookup for the header @hdr in already collected headers table @ht, * i.e. check whether the header is duplicate. @@ -389,32 +367,6 @@ tfw_http_msg_hdr_lookup(TfwHttpMsg *hm, const TfwStr *hdr) return id; } -/** - * Certain header fields are strictly singular and may not be repeated in - * an HTTP message. Duplicate of a singular header fields is a bug worth - * blocking the whole HTTP message. - * - * TODO: with the current HTTP-parser implementation (parsing header name, - * colon, LWS and value into different chunks) we can avoid slow string - * matcher, which is used in @tfw_http_msg_hdr_lookup(), and can compare - * strings just by chunks (including searching the stop character) for both - * HTTP/2 and HTTP/1.1 formatted headers (see @__hdr_name_cmp() below). - * Thus, @__h1_hdr_lookup() and @tfw_http_msg_hdr_lookup() procedures should - * be unified to @__hdr_name_cmp() and @__http_hdr_lookup() in order to - * substitute current mess of multiple partially duplicated procedures with - * one simple interface. - */ -static inline unsigned int -__h1_hdr_lookup(TfwHttpMsg *hm, const TfwStr *hdr) -{ - unsigned int id = tfw_http_msg_hdr_lookup(hm, hdr); - - if ((id < hm->h_tbl->off) && __hdr_is_singular(hdr)) - __set_bit(TFW_HTTP_B_FIELD_DUPENTRY, hm->flags); - - return id; -} - /** * Special procedure comparing the name or HPACK static index of @cmp_hdr (can * be in HTTP/2 or HTTP/1.1 format) against the header @hdr which also can be @@ -805,265 +757,6 @@ tfw_http_msg_grow_hdr_tbl(TfwHttpMsg *hm) return 0; } -/** - * Add new header @hdr to the message @hm just before CRLF. - */ -static int -__hdr_add(TfwHttpMsg *hm, const TfwStr *hdr, unsigned int hid) -{ - int r; - TfwStr *dst; - TfwStr it = {}; - TfwStr *h = TFW_STR_CHUNK(&hm->crlf, 0); - - r = ss_skb_get_room(hm->msg.skb_head, hm->crlf.skb, h->data, - tfw_str_total_len(hdr), &it); - if (r) - return r; - - tfw_str_fixup_eol(&it, tfw_str_eolen(hdr)); - dst = tfw_strcpy_comp_ext(hm->pool, &it, hdr); - if (unlikely(!dst)) - return -ENOMEM; - - /* - * Initialize the header table item by the iterator chunks. - * While the data references in the item are valid, some conventions - * (e.g. header name and value are placed in different chunks) aren't - * satisfied. So don't consider the header for normal HTTP processing. - */ - hm->h_tbl->tbl[hid] = *dst; - - return 0; -} - -/** - * Expand @orig_hdr by appending or replacing with the @hdr. - * (CRLF is not accounted in TfwStr representation of HTTP headers). - * - * Expand the first duplicate header, do not produce more duplicates. - */ -static int -__hdr_expand(TfwHttpMsg *hm, TfwStr *orig_hdr, const TfwStr *hdr, bool append) -{ - int r; - TfwStr *h, it = {}; - - if (TFW_STR_DUP(orig_hdr)) - orig_hdr = __TFW_STR_CH(orig_hdr, 0); - BUG_ON(!append && (hdr->len < orig_hdr->len)); - - h = TFW_STR_LAST(orig_hdr); - r = ss_skb_get_room(hm->msg.skb_head, h->skb, h->data + h->len, - append ? hdr->len : hdr->len - orig_hdr->len, &it); - if (r) - return r; - - if ((r = tfw_strcat(hm->pool, orig_hdr, &it))) { - T_WARN("Cannot concatenate hdr %.*s with %.*s\n", - PR_TFW_STR(orig_hdr), PR_TFW_STR(hdr)); - return r; - } - - return tfw_strcpy(append ? &it : orig_hdr, hdr); -} - -/** - * Delete header with identifier @hid from skb data and header table. - */ -static int -__hdr_del(TfwHttpMsg *hm, unsigned int hid) -{ - int r = 0; - TfwHttpHdrTbl *ht = hm->h_tbl; - TfwStr *dup, *end, *hdr = &ht->tbl[hid]; - - /* Delete the underlying data. */ - TFW_STR_FOR_EACH_DUP(dup, hdr, end) { - if ((r = ss_skb_cutoff_data(hm->msg.skb_head, dup, 0, - tfw_str_eolen(dup)))) - return r; - }; - - /* Delete the header from header table. */ - if (hid < TFW_HTTP_HDR_RAW) { - TFW_STR_INIT(&ht->tbl[hid]); - } else { - if (hid < ht->off - 1) - memmove(&ht->tbl[hid], &ht->tbl[hid + 1], - (ht->off - hid - 1) * sizeof(TfwStr)); - --ht->off; - } - - return 0; -} - -/** - * Substitute header value. - * - * The original header may have LF or CRLF as it's EOL and such bytes are - * not a part of a header field string in Tempesta (at the moment). While - * substitution, we may want to follow the EOL pattern of the original. So, - * if the substitute string without the EOL fits into original header, then - * the fast path can be used. Otherwise, original header is expanded to fit - * substitute. - */ -static int -__hdr_sub(TfwHttpMsg *hm, const TfwStr *hdr, unsigned int hid) -{ - int r; - TfwHttpHdrTbl *ht = hm->h_tbl; - TfwStr *dst, *tmp, *end, *orig_hdr = &ht->tbl[hid]; - - TFW_STR_FOR_EACH_DUP(dst, orig_hdr, end) { - if (dst->len < hdr->len) - continue; - /* - * Adjust @dst to have no more than @hdr.len bytes and rewrite - * the header in-place. Do not call @ss_skb_cutoff_data if no - * adjustment is needed. - */ - if (dst->len != hdr->len - && (r = ss_skb_cutoff_data(hm->msg.skb_head, dst, - hdr->len, 0))) - return r; - if ((r = tfw_strcpy(dst, hdr))) - return r; - goto cleanup; - } - - if ((r = __hdr_expand(hm, orig_hdr, hdr, false))) - return r; - dst = TFW_STR_DUP(orig_hdr) ? __TFW_STR_CH(orig_hdr, 0) : orig_hdr; - -cleanup: - TFW_STR_FOR_EACH_DUP(tmp, orig_hdr, end) { - if (tmp != dst - && (r = ss_skb_cutoff_data(hm->msg.skb_head, tmp, 0, - tfw_str_eolen(tmp)))) - return r; - } - - *orig_hdr = *dst; - return 0; -} - -/** - * Transform HTTP message @hm header with identifier @hid. - * @hdr must be compound string and contain two or three parts: - * header name, colon and header value. If @hdr value is empty, - * then the header will be deleted from @hm. - * If @hm already has the header it will be replaced by the new header - * unless @append. - * If @append is true, then @val will be concatenated to current - * header with @hid and @name, otherwise a new header will be created - * if the message has no the header. - * - * Note: The substitute string @hdr should have CRLF as EOL. The original - * string @orig_hdr may have a single LF as EOL. We may want to follow - * the EOL pattern of the original. For that, the EOL of @hdr needs - * to be made the same as in the original header field string. - * - * Note: In case of response transformation from HTTP/1.1 to HTTP/2, for - * optimization purposes, we use special add/replace procedures to adjust - * headers and create HTTP/2 representation at once; for headers deletion - * procedure there is no sense to use special HTTP/2 handling (the header - * must not exist in the resulting response); in case of headers appending - * we at first create the usual HTTP/1.1 representation of the final header - * and then transform it into HTTP/2 form at the common stage of response - * HTTP/2 transformation - we have no other choice, since we need a full - * header for going through the HTTP/2 transformation (i.e. search in the - * HPACK encoder dynamic index). - */ -static int -tfw_http_msg_hdr_xfrm_str(TfwHttpMsg *hm, const TfwStr *hdr, unsigned int hid, - bool append) -{ - int r; - TfwHttpHdrTbl *ht = hm->h_tbl; - TfwStr *orig_hdr = NULL; - const TfwStr *s_val = TFW_STR_CHUNK(hdr, 2); - - if (unlikely(!ht)) { - T_WARN("Try to adjust lightweight response."); - return -EINVAL; - } - - /* Firstly, get original message header to transform. */ - if (hid < TFW_HTTP_HDR_RAW) { - orig_hdr = &ht->tbl[hid]; - if (TFW_STR_EMPTY(orig_hdr) && !s_val) - /* Not found, nothing to delete. */ - return 0; - } else { - hid = __h1_hdr_lookup(hm, hdr); - if (hid == ht->off && !s_val) - /* Not found, nothing to delete. */ - return 0; - if (hid == ht->size) { - if ((r = tfw_http_msg_grow_hdr_tbl(hm))) - return r; - ht = hm->h_tbl; - } - if (hid == ht->off) - ++ht->off; - else - orig_hdr = &ht->tbl[hid]; - } - - if (unlikely(append && hid < TFW_HTTP_HDR_NONSINGULAR)) { - T_WARN("Appending to singular header '%.*s'\n", - PR_TFW_STR(TFW_STR_CHUNK(hdr, 0))); - return 0; - } - - if (!orig_hdr || TFW_STR_EMPTY(orig_hdr)) { - if (unlikely(!s_val)) - return 0; - return __hdr_add(hm, hdr, hid); - } - - if (!s_val) - return __hdr_del(hm, hid); - - if (append) { - TfwStr hdr_app = { - .chunks = (TfwStr []){ - { .data = ", ", .len = 2 }, - { .data = s_val->data, .len = s_val->len } - }, - .len = s_val->len + 2, - .nchunks = 2 - }; - return __hdr_expand(hm, orig_hdr, &hdr_app, true); - } - - return __hdr_sub(hm, hdr, hid); -} - -/** - * Same as @tfw_http_msg_hdr_xfrm_str() but use c-strings as argument. - */ -int -tfw_http_msg_hdr_xfrm(TfwHttpMsg *hm, char *name, size_t n_len, - char *val, size_t v_len, unsigned int hid, bool append) -{ - TfwStr new_hdr = { - .chunks = (TfwStr []){ - { .data = name, .len = n_len }, - { .data = S_DLM, .len = SLEN(S_DLM) }, - { .data = val, .len = v_len }, - }, - .len = n_len + SLEN(S_DLM) + v_len, - .eolen = 2, - .nchunks = (val ? 3 : 2) - }; - - BUG_ON(!val && v_len); - - return tfw_http_msg_hdr_xfrm_str(hm, &new_hdr, hid, append); -} - /** * Delete @str (any parsed part of HTTP message) from skb data and * init @str. diff --git a/fw/http_msg.h b/fw/http_msg.h index 7c691e38d0..ab7b2b5a03 100644 --- a/fw/http_msg.h +++ b/fw/http_msg.h @@ -141,8 +141,6 @@ int __must_check __tfw_http_msg_add_str_data(TfwHttpMsg *hm, TfwStr *str, ss_skb_peek_tail(&hm->msg.skb_head)) unsigned int tfw_http_msg_hdr_lookup(TfwHttpMsg *hm, const TfwStr *hdr); -int tfw_http_msg_hdr_xfrm(TfwHttpMsg *hm, char *name, size_t n_len, - char *val, size_t v_len, unsigned int hid, bool append); int tfw_http_msg_del_str(TfwHttpMsg *hm, TfwStr *str); int tfw_http_msg_cutoff_body_chunks(TfwHttpResp *resp); diff --git a/fw/ss_skb.c b/fw/ss_skb.c index 868fd3daed..fea6e2c74d 100644 --- a/fw/ss_skb.c +++ b/fw/ss_skb.c @@ -43,7 +43,6 @@ * * Only the source IP address is printed to @out_buf, and the TCP/SCTP * port is not printed. That is done because: - * - Less output bytes means more chance for fast path in __hdr_add(). * - RFC7239 says the port is optional. * - Most proxy servers don't put it to the field. * - Usually you get a random port of an outbound connection there, @@ -802,15 +801,6 @@ ss_skb_get_room_w_frag(struct sk_buff *skb_head, struct sk_buff *skb, char *pspt return r <= 0 ? r : -ENOMEM; } -int -ss_skb_get_room(struct sk_buff *skb_head, struct sk_buff *skb, char *pspt, - unsigned int len, TfwStr *it) -{ - int _; - - return ss_skb_get_room_w_frag(skb_head, skb, pspt, len, it, &_); -} - /** * Expand the @skb data, including frags, by @head and @tail: the head is * reserved within TCP_MAX_HEADER, so skb->data is just moved, the tail diff --git a/fw/ss_skb.h b/fw/ss_skb.h index 3f29bcb9bc..3ca7b27965 100644 --- a/fw/ss_skb.h +++ b/fw/ss_skb.h @@ -3,7 +3,7 @@ * * Synchronous Sockets API for Linux socket buffers manipulation. * - * Copyright (C) 2015-2023 Tempesta Technologies, Inc. + * Copyright (C) 2015-2024 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 @@ -445,8 +445,6 @@ char *ss_skb_fmt_src_addr(const struct sk_buff *skb, char *out_buf); int ss_skb_alloc_data(struct sk_buff **skb_head, size_t len, unsigned int tx_flags); struct sk_buff *ss_skb_split(struct sk_buff *skb, int len); -int ss_skb_get_room(struct sk_buff *skb_head, struct sk_buff *skb, - char *pspt, unsigned int len, TfwStr *it); int ss_skb_get_room_w_frag(struct sk_buff *skb_head, struct sk_buff *skb, char *pspt, unsigned int len, TfwStr *it, int *fragn); int ss_skb_expand_head_tail(struct sk_buff *skb_head, struct sk_buff *skb, diff --git a/fw/str.c b/fw/str.c index 61f04ea3ff..ea59678f5f 100644 --- a/fw/str.c +++ b/fw/str.c @@ -873,8 +873,7 @@ tfw_strdup_desc(TfwPool *pool, const TfwStr *src) * buffer provided by @data_str. * * @pool - pool descriptor - * @data_str - plain TfwStr which points to target data buffer, - * e.g. returned from ss_skb_get_room() call + * @data_str - plain TfwStr which points to target data buffer. * @src - source string */ TfwStr * diff --git a/fw/str.h b/fw/str.h index 2251e391f9..25a4b9da3d 100644 --- a/fw/str.h +++ b/fw/str.h @@ -403,22 +403,6 @@ tfw_str_total_len(const TfwStr *s) return s->len + s->eolen; } -/** - * Reduce @str length by @eolen bytes and fill the EOL. - */ -static inline void -tfw_str_fixup_eol(TfwStr *str, int eolen) -{ - BUG_ON(eolen > 2); /* eolen = 0 is a legit value */ - BUG_ON(!TFW_STR_PLAIN(str)); - - str->len -= (str->eolen = eolen); - if (eolen == 1) - *(str->data + str->len) = 0x0a; /* LF, '\n' */ - else if (eolen == 2) - *(short *)(str->data + str->len) = 0x0a0d; /* CRLF, '\r\n' */ -} - static inline void __tfw_str_set_data(TfwStr *str, void *data, struct sk_buff *skb) { diff --git a/fw/vhost.h b/fw/vhost.h index 13a37d20e9..6b5666bc1e 100644 --- a/fw/vhost.h +++ b/fw/vhost.h @@ -84,7 +84,7 @@ typedef struct { /** * Headers modification description. * - * @hdr - Header string, see @tfw_http_msg_hdr_xfrm_str(); + * @hdr - Header string, see @tfw_http_msg_make_hdr(); * @hid - Header index in the header table; * @append - if set the value is added to the end of the response * otherwise the original header is overwritten with given one. From b947fd71daab789aef234dbf19d42d9676ea7944 Mon Sep 17 00:00:00 2001 From: Constantine Date: Fri, 31 May 2024 10:45:42 +0300 Subject: [PATCH 24/44] Headers modification optimization In this patch we optimize header skipping logic that applies in header modification for HTTP1 request/re- sponse and HTTP2 response including cache. Overview of current behavior. It consists of two parts: skipping and addition. During response transformation or copying we skipping headers that must be substituted or deleted using `tfw_http_hdr_skip()`, when all headers from original response are copied we add new headers (directive (resp/req)_hdr_(add/set)) and add headers that must be substituted ((resp/req)_hdr_set). What have done: 1. `tfw_hdr_mods_t::spec_hdrs` turned into bitmap. 2. `tfw_hdr_mods_t::spec_hdrs` contains used only for *_hdr_set directives, headers related to *_hdr_add must not be skipped, thus excluded from `spec_hdrs`. 2. `tfw_http_hdr_skip()` only iterates over *_hdr_set headers and doesn't check `append` flag. 3. added `tfw_hdr_mods_t::scan_off`, this is offset where we start itarating headers and comparing the name. It's the case when modification not found by special header index or hpack index. 4. changed the layout of `tfw_hdr_mods_t::hdrs`, now we storing headers in following way: .----------------------------------------. | `tfw_hdr_mods_t::hdrs` | :----------------------------------------: | Special headers *hdr_set | :----------------------------------------: | Hpack indexed raw headers *hdr_set | :----------------------------------------: | Non-indexed raw headers *hdr_set | :----------------------------------------: | All headers *hdr_add | '----------------------------------------' due this structure we can iterate only over unindexed raw headers. Note: Looks like iterating over headers modification table especially for removing is more profit approach, because if modification applied we will not iterate over this opearation again, we using it during http2 request headers modification, but it has disadvantages. For instance we must realloc header table or split skipping and adition(similar with current approach) and such approach can't be used with current cache architecture. --- fw/cache.c | 11 ++++------ fw/http.c | 34 +++++++++++++++++------------- fw/vhost.c | 61 +++++++++++++++++++++++++++++++++--------------------- fw/vhost.h | 22 +++++++++++++------- 4 files changed, 76 insertions(+), 52 deletions(-) diff --git a/fw/cache.c b/fw/cache.c index 548f160038..717bd6cc4d 100644 --- a/fw/cache.c +++ b/fw/cache.c @@ -931,11 +931,8 @@ tfw_cache_skip_hdr(const TfwCStr *str, char *p, const TfwHdrMods *h_mods) .len = str->name_len }; /* Fast path for special headers */ - if (str->flags & TFW_CSTR_SPEC_IDX) { - desc = h_mods->spec_hdrs[str->idx]; - /* Skip only resp_hdr_set headers */ - return desc ? !desc->append : false; - } + if (str->flags & TFW_CSTR_SPEC_IDX) + return test_bit(str->idx, h_mods->spec_hdrs); if (str->idx) { unsigned short hpack_idx = str->idx; @@ -946,13 +943,13 @@ tfw_cache_skip_hdr(const TfwCStr *str, char *p, const TfwHdrMods *h_mods) return test_bit(hpack_idx, h_mods->s_tbl); } - for (i = h_mods->spec_num; i < h_mods->sz; ++i) { + for (i = h_mods->scan_off; i < h_mods->set_num; ++i) { char* mod_hdr_name; size_t mod_hdr_len; desc = &h_mods->hdrs[i]; mod_hdr_len = TFW_STR_CHUNK(desc->hdr, 0)->len; - if (desc->append || mod_hdr_len != hdr.len) + if (mod_hdr_len != hdr.len) continue; mod_hdr_name = TFW_STR_CHUNK(desc->hdr, 0)->data; diff --git a/fw/http.c b/fw/http.c index e916f6cb5f..adb14f1346 100644 --- a/fw/http.c +++ b/fw/http.c @@ -3450,22 +3450,27 @@ tfw_http_should_validate_post_req(TfwHttpReq *req) return false; } +/** + * Skip header for subsequent substitution. + * + * Headers substitution splitted into two parts: first tfw_http_hdr_skip() + * here we just skip header and don't add it to message. + * Second tfw_h1_add_loc_hdrs()/tfw_h2_resp_add_loc_hdrs() where we adding + * all headers. + */ static bool -tfw_http_hdr_sub(unsigned short hid, const TfwStr *hdr, - const TfwHdrMods *h_mods) +tfw_http_hdr_skip(unsigned short hid, const TfwStr *hdr, + const TfwHdrMods *h_mods) { - unsigned int idx; + int idx; const TfwHdrModsDesc *desc; if (!h_mods) return false; /* Fast path for special headers */ - if (hid >= TFW_HTTP_HDR_REGULAR && hid < TFW_HTTP_HDR_RAW) { - desc = h_mods->spec_hdrs[hid]; - /* Skip only resp_hdr_set headers */ - return desc ? !desc->append : false; - } + if (hid >= TFW_HTTP_HDR_REGULAR && hid < TFW_HTTP_HDR_RAW) + return test_bit(hid, h_mods->spec_hdrs); if (hdr->hpack_idx > 0) { /* Don't touch pseudo-headers. */ @@ -3475,9 +3480,10 @@ tfw_http_hdr_sub(unsigned short hid, const TfwStr *hdr, return test_bit(hdr->hpack_idx, h_mods->s_tbl); } - for (idx = h_mods->spec_num; idx < h_mods->sz; ++idx) { + /* Skip only resp_hdr_set headers */ + for (idx = h_mods->scan_off; idx < h_mods->set_num; ++idx) { desc = &h_mods->hdrs[idx]; - if (!desc->append && !__hdr_name_cmp(hdr, desc->hdr)) + if (!__hdr_name_cmp(hdr, desc->hdr)) return true; } @@ -3643,7 +3649,7 @@ tfw_h1_adjust_req(TfwHttpReq *req) continue; } - if (tfw_http_hdr_sub(hid, hdr, h_mods)) + if (tfw_http_hdr_skip(hid, hdr, h_mods)) continue; TFW_STR_FOR_EACH_DUP(dup, pos, dup_end) { @@ -3840,7 +3846,7 @@ tfw_h2_req_set_loc_hdrs(TfwHttpReq *req) * and can be used instead of `Host` header during request modification * when forwarding to backend. */ - if (h_mods->spec_hdrs[TFW_HTTP_HDR_HOST]) { + if (h_mods && test_bit(TFW_HTTP_HDR_HOST, h_mods->spec_hdrs)) { TfwStr *host = &req->h_tbl->tbl[TFW_HTTP_HDR_HOST]; req->h_tbl->tbl[TFW_HTTP_HDR_H2_AUTHORITY] = *host; @@ -4363,7 +4369,7 @@ tfw_http_adjust_resp(TfwHttpResp *resp) if (TFW_STR_DUP(hdr)) hdr = TFW_STR_CHUNK(hdr, 0); - if (tfw_http_hdr_sub(hid, hdr, h_mods)) + if (tfw_http_hdr_skip(hid, hdr, h_mods)) continue; TFW_STR_FOR_EACH_DUP(dup, pos, dup_end) { @@ -4998,7 +5004,7 @@ tfw_h2_hpack_encode_headers(TfwHttpResp *resp, const TfwHdrMods *h_mods) h_mods ? h_mods->sz : 0); /* Don't encode header if it must be substituted from config */ - if (tfw_http_hdr_sub(hid, tgt, h_mods)) + if (tfw_http_hdr_skip(hid, tgt, h_mods)) continue; /* diff --git a/fw/vhost.c b/fw/vhost.c index b4c0113f57..f4b91f5235 100644 --- a/fw/vhost.c +++ b/fw/vhost.c @@ -17,8 +17,6 @@ * with this program; if not, write to the Free Software Foundation, Inc., * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ -#include -#include #include #undef DEBUG @@ -762,21 +760,28 @@ tfw_cfgop_mod_hdr_add(TfwLocation *loc, const char *name, const char *value, ? tfw_http_msg_resp_spec_hid(TFW_STR_CHUNK(hdr, 0)) : tfw_http_msg_req_spec_hid(TFW_STR_CHUNK(hdr, 0)); - if (desc->hid < TFW_HTTP_HDR_RAW) { - if (h_mods->spec_hdrs[desc->hid]) { - T_WARN_NL("Duplicated header modification.\n"); - return -EINVAL; + hdr->hpack_idx = tfw_hpack_find_hdr_idx(TFW_STR_CHUNK(hdr, 0)); + + /* Add resp/req_hdr_set to map of special headers. */ + if (!append) { + if (desc->hid < TFW_HTTP_HDR_RAW) { + if (test_bit(desc->hid, h_mods->spec_hdrs)) { + T_WARN_NL("Duplicated header modification.\n"); + return -EINVAL; + } + set_bit(desc->hid, h_mods->spec_hdrs); + ++h_mods->scan_off; + } + if (hdr->hpack_idx > 0) { + set_bit(hdr->hpack_idx, h_mods->s_tbl); + ++h_mods->scan_off; } - h_mods->spec_hdrs[desc->hid] = desc; - ++h_mods->spec_num; + ++h_mods->set_num; } - hdr->hpack_idx = tfw_hpack_find_hdr_idx(TFW_STR_CHUNK(hdr, 0)); desc->hdr = hdr; desc->append = append; ++h_mods->sz; - if (!append) - set_bit(hdr->hpack_idx, h_mods->s_tbl); return 0; } @@ -917,8 +922,17 @@ tfw_mod_hdr_cmp(const void *l, const void *r) { TfwHdrModsDesc *desc_l = (TfwHdrModsDesc *)l; TfwHdrModsDesc *desc_r = (TfwHdrModsDesc *)r; + int type, hid; + + if ((type = (int)desc_l->append - (int)desc_r->append)) + return type; - return (int)desc_l->hid - (int)desc_r->hid; + hid = (int)desc_l->hid - (int)desc_r->hid; + + if (!hid && desc_l->hid >= TFW_HTTP_HDR_RAW) + return (int)desc_r->hdr->hpack_idx - (int)desc_l->hdr->hpack_idx; + + return hid; } static void @@ -931,6 +945,14 @@ tfw_mod_hdr_sort(TfwLocation *loc) sort(h_mods->hdrs, h_mods->sz, sizeof(h_mods->hdrs[0]), tfw_mod_hdr_cmp, NULL); + + /* + * scan_off contains number of special headers and hpack indexed + * headers. Here we turn the count to the real offset that + * points to non-indexed raw headers. + */ + if (h_mods->scan_off > 0) + h_mods->scan_off--; } } @@ -1438,8 +1460,7 @@ tfw_location_init(TfwLocation *loc, tfw_match_t op, const char *arg, size_t size = sizeof(FrangVhostCfg) + sizeof(TfwCaPolicy *) * TFW_CAPOLICY_ARRAY_SZ + sizeof(TfwNipDef *) * TFW_NIPDEF_ARRAY_SZ - + sizeof(TfwHdrModsDesc) * TFW_USRHDRS_ARRAY_SZ * 2 - + sizeof(TfwHdrModsDesc *) * TFW_HTTP_HDR_RAW * 2; + + sizeof(TfwHdrModsDesc) * TFW_USRHDRS_ARRAY_SZ * 2; if ((argmem = kmalloc(len + 1, GFP_KERNEL)) == NULL) return -ENOMEM; @@ -1464,17 +1485,8 @@ tfw_location_init(TfwLocation *loc, tfw_match_t op, const char *arg, loc->mod_hdrs[TFW_VHOST_HDRMOD_REQ].hdrs = (TfwHdrModsDesc *)(loc->nipdef + TFW_NIPDEF_ARRAY_SZ); - loc->mod_hdrs[TFW_VHOST_HDRMOD_REQ].spec_hdrs = - (TfwHdrModsDesc **)(loc->mod_hdrs[TFW_VHOST_HDRMOD_REQ].hdrs - + TFW_USRHDRS_ARRAY_SZ); - loc->mod_hdrs[TFW_VHOST_HDRMOD_RESP].hdrs = - (TfwHdrModsDesc *)(loc->mod_hdrs[TFW_VHOST_HDRMOD_REQ].spec_hdrs - + TFW_HTTP_HDR_RAW); - - loc->mod_hdrs[TFW_VHOST_HDRMOD_RESP].spec_hdrs = - (TfwHdrModsDesc **)(loc->mod_hdrs[TFW_VHOST_HDRMOD_RESP].hdrs - + TFW_USRHDRS_ARRAY_SZ); + loc->mod_hdrs[TFW_VHOST_HDRMOD_REQ].hdrs + TFW_USRHDRS_ARRAY_SZ; memcpy((void *)loc->arg, (void *)arg, len + 1); @@ -2627,6 +2639,7 @@ tfw_vhost_cfgend(void) vh_dflt = tfw_vhosts_reconfig->vhost_dflt; r = tfw_frang_cfg_inherit(vh_dflt->loc_dflt->frang_cfg, &tfw_frang_vhost_reconfig); + tfw_mod_hdr_sort(vh_dflt->loc_dflt); if (r) goto err; sg_def = tfw_sg_lookup_reconfig(TFW_VH_DFT_NAME, SLEN(TFW_VH_DFT_NAME)); diff --git a/fw/vhost.h b/fw/vhost.h index 6b5666bc1e..129dd4dd2d 100644 --- a/fw/vhost.h +++ b/fw/vhost.h @@ -26,6 +26,9 @@ #include "server.h" #include "tls.h" +/* Size of special headers bitmap. */ +#define TFW_MOD_SPEC_HDR_NUM 32 + /** * Non-Idempotent Request definition. * @@ -99,18 +102,23 @@ struct tfw_hdr_mods_desc_t { * Headers modification before forwarding HTTP message. * * @sz - Total number of headers to modify; - * @spec_num - Number of special headers to modify; + * @set_num - Number of headers to modify using req/resp_hdr_set directive; + * @scan_off - Offset in @hdrs to start finding header to modify by name + * comparision; * @hdrs - Headers to modify; - * @spec_hdrs - Lookup table of special headers; + * @spec_hdrs - Bitmap of special headers; * @s_tbl - Bitmap of headers from static table. Static table index - * equals to bit number of the bitmap; + * equals to bit number of the bitmap. The size of the bitmap is + * HPACK_STATIC_ENTRIES plus one entry, because static table + * indexed from one, zero bit always set to zero; */ struct tfw_hdr_mods_t { - unsigned int sz; - unsigned int spec_num; + unsigned int sz:16; + unsigned int set_num:8; + unsigned int scan_off:8; TfwHdrModsDesc *hdrs; - TfwHdrModsDesc **spec_hdrs; - DECLARE_BITMAP (s_tbl, HPACK_STATIC_ENTRIES); + DECLARE_BITMAP (spec_hdrs, TFW_MOD_SPEC_HDR_NUM); + DECLARE_BITMAP (s_tbl, HPACK_STATIC_ENTRIES + 1); }; enum { From d5fabc952b2e524112fc3303ce2f3673a53956a7 Mon Sep 17 00:00:00 2001 From: Constantine Date: Tue, 4 Jun 2024 16:11:58 +0300 Subject: [PATCH 25/44] Unified resp_hdr_add logic for HTTP2 and HTTP1 fix Just fix to commit with logic unifying, this patch adds abillity to adding headers to HTTP2 request, without this header will be substituted even if req_hdr_add directive was used. --- fw/http.c | 47 +++++++++++++++++++++++++++++++---------------- 1 file changed, 31 insertions(+), 16 deletions(-) diff --git a/fw/http.c b/fw/http.c index adb14f1346..556f7d03a4 100644 --- a/fw/http.c +++ b/fw/http.c @@ -3735,12 +3735,6 @@ __h2_req_hdrs(TfwHttpReq *req, const TfwStr *hdr, unsigned int hid, bool append) if (WARN_ON_ONCE(!ht)) return -EINVAL; - if (unlikely(append && hid < TFW_HTTP_HDR_NONSINGULAR - && !TFW_STR_EMPTY(&ht->tbl[hid]))) - { - return -ENOENT; - } - if (hid < TFW_HTTP_HDR_RAW) { orig_hdr = &ht->tbl[hid]; /* @@ -3784,6 +3778,7 @@ __h2_req_hdrs(TfwHttpReq *req, const TfwStr *hdr, unsigned int hid, bool append) } BUG_ON(TFW_STR_EMPTY(orig_hdr)); + /* * The original header exists, but we have nothing to insert, thus, * the original header should be evicted. @@ -3794,6 +3789,23 @@ __h2_req_hdrs(TfwHttpReq *req, const TfwStr *hdr, unsigned int hid, bool append) return 0; } + /* The original header exists, add duplicate to it. */ + if (append) { + TfwStr *new_hdr = tfw_str_add_duplicate(hm->pool, orig_hdr); + + if (unlikely(!new_hdr)) { + T_WARN("Cannot add duplicated header '%.*s'\n", + PR_TFW_STR(TFW_STR_CHUNK(orig_hdr, 0))); + return -ENOMEM; + } + + ++it->hdrs_cnt; + it->hdrs_len += hdr->len; + *new_hdr = *hdr; + + return 0; + } + /* * The remaining case is the substitution, since we have both: existing * original header and the new header to insert. @@ -3810,6 +3822,7 @@ static int tfw_h2_req_set_loc_hdrs(TfwHttpReq *req) { int i; + TfwHttpHdrTbl *ht = req->h_tbl; TfwHdrMods *h_mods = tfw_vhost_get_hdr_mods(req->location, req->vhost, TFW_VHOST_HDRMOD_REQ); if (!h_mods) @@ -3819,17 +3832,19 @@ tfw_h2_req_set_loc_hdrs(TfwHttpReq *req) int r; TfwHdrModsDesc *d = &h_mods->hdrs[i]; - if ((r = __h2_req_hdrs(req, d->hdr, d->hid, d->append))) { - /* - * Attempt to add duplicated singular header. - * Just go to next header. - */ - if (r == -ENOENT) { - T_WARN("Attempt to add already existed singular header '%.*s'\n", - PR_TFW_STR(TFW_STR_CHUNK(d->hdr, 0))); - continue; - } + /* + * Attempt to add duplicated singular header. + * Just go to next header. + */ + if (unlikely(d->append && d->hid < TFW_HTTP_HDR_NONSINGULAR + && !TFW_STR_EMPTY(&ht->tbl[d->hid]))) + { + T_WARN("Attempt to add already existed singular header '%.*s'\n", + PR_TFW_STR(TFW_STR_CHUNK(d->hdr, 0))); + continue; + } + if ((r = __h2_req_hdrs(req, d->hdr, d->hid, d->append))) { /* Other error that can't be handled here. */ T_ERR("HTTP/2: can't update location-specific header in" " the request [%p]\n", req); From 9606b1b8e77783399aac6c1914417c823b513b9f Mon Sep 17 00:00:00 2001 From: Constantine Date: Wed, 5 Jun 2024 13:35:42 +0300 Subject: [PATCH 26/44] fix commit [HTTP 1.1 method parsing] Add a new method intended to close parsed HTTP1.1 method. `tfw_http_msg_hdr_close()` is not suitable in this case, it has a lot of logic that unnecessary for method. --- fw/http_msg.h | 15 +++++++++++++++ fw/http_parser.c | 9 +++------ 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/fw/http_msg.h b/fw/http_msg.h index ab7b2b5a03..920ec3da19 100644 --- a/fw/http_msg.h +++ b/fw/http_msg.h @@ -151,6 +151,21 @@ int tfw_http_msg_add_data(TfwMsgIter *it, TfwHttpMsg *hm, TfwStr *field, const TfwStr *data); void tfw_http_msg_hdr_open(TfwHttpMsg *hm, unsigned char *hdr_start); int tfw_http_msg_hdr_close(TfwHttpMsg *hm); +/** +* Special case for storing HTTP1.1 method to HTTP message headers list. +*/ +static inline void +tfw_http_msg_method_close(TfwHttpMsg *hm) +{ + TfwHttpParser *parser = &hm->stream->parser; + + BUG_ON(parser->_hdr_tag != TFW_HTTP_METHOD); + + /* Close just parsed method. */ + parser->hdr.flags |= TFW_STR_COMPLETE; + hm->h_tbl->tbl[parser->_hdr_tag] = parser->hdr; + TFW_STR_INIT(&parser->hdr); +} int tfw_http_msg_grow_hdr_tbl(TfwHttpMsg *hm); void tfw_http_msg_free(TfwHttpMsg *m); int tfw_http_msg_expand_data(TfwMsgIter *it, struct sk_buff **skb_head, diff --git a/fw/http_parser.c b/fw/http_parser.c index a2b91e0b07..9a731c2caf 100644 --- a/fw/http_parser.c +++ b/fw/http_parser.c @@ -2,7 +2,7 @@ * Tempesta FW * * Copyright (C) 2014 NatSys Lab. (info@natsys-lab.com). - * Copyright (C) 2015-2024 Tempesta Technologies, Inc. + * Copyright (C) 2015-2025 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 @@ -497,9 +497,7 @@ __FSM_STATE(st, cold) { \ #define __FSM_METH_fixup_finish(curr_st, n) \ __msg_hdr_chunk_fixup(data, __data_off(p + n)); \ - __msg_chunk_flags(TFW_STR_VALUE); \ - if (tfw_http_msg_hdr_close(msg)) \ - TFW_PARSER_DROP(curr_st); \ + tfw_http_msg_method_close(msg); \ /* Used for improbable states only, so use cold label. */ #define __FSM_METH_MOVE(st, ch, st_next) \ @@ -5770,8 +5768,7 @@ Req_Method_1CharStep: __attribute__((cold)) * then there is zero-length method name * and the request must be dropped. */ - if (tfw_http_msg_hdr_close(msg)) - TFW_PARSER_DROP(Req_MethodUnknown); + tfw_http_msg_method_close(msg); __FSM_MOVE_nofixup_n(Req_MUSpace, 0); } From 1032c19b31b68cc2db9831814080fb55033e7341 Mon Sep 17 00:00:00 2001 From: Constantine Date: Wed, 5 Jun 2024 13:48:17 +0300 Subject: [PATCH 27/44] fix commit [Rework HTTP1 request adjusting] fix 1: Skip trailer headers during copying regular headers to not duplicate them in headers and in trailer. fix 2: Call `mark_spec_hbh` for headers with only LF in the end of a message. fix 3: Headers `connection` and `keep-alive` marked as hop-by-hop during parsing, it's allowed to remove following conditions: ``` if (hid == TFW_HTTP_HDR_KEEP_ALIVE || hid == TFW_HTTP_HDR_CONNECTION) ``` do only simple `if (tgt->flags & TFW_STR_HBH_HDR)`. Before this patch `keep-alive` was marked as HBH, but only if `keep-alive` specified in `connection` header. RFC 9110 7.6.1 say that intermediaries SHOULD remove or replace such headers even if the not listed in Connection. --- fw/http.c | 29 ++++++++++++++--------------- fw/http_parser.c | 13 ++++++++----- fw/t/unit/test_http1_parser.c | 2 +- 3 files changed, 23 insertions(+), 21 deletions(-) diff --git a/fw/http.c b/fw/http.c index 556f7d03a4..56ee5f1668 100644 --- a/fw/http.c +++ b/fw/http.c @@ -3631,7 +3631,8 @@ tfw_h1_adjust_req(TfwHttpReq *req) /* Skip hop-by-hop headers. */ if (TFW_STR_EMPTY(hdr) || hdr->flags & TFW_STR_HBH_HDR - || hid == TFW_HTTP_HDR_X_FORWARDED_FOR) { + || hid == TFW_HTTP_HDR_X_FORWARDED_FOR) + { continue; } @@ -3653,10 +3654,14 @@ tfw_h1_adjust_req(TfwHttpReq *req) continue; TFW_STR_FOR_EACH_DUP(dup, pos, dup_end) { + /* + * Skip trailer header. + */ + if (unlikely(dup->flags & TFW_STR_TRAILER)) + continue; r = tfw_http_msg_expand_from_pool(hm, dup); if (unlikely(r)) goto clean; - r = tfw_http_msg_expand_from_pool(hm, &crlf); if (unlikely(r)) goto clean; @@ -4378,7 +4383,7 @@ tfw_http_adjust_resp(TfwHttpResp *resp) TfwStr *dup, *dup_end, *hdr = pos; /* Skip hop-by-hop headers. */ - if (TFW_STR_EMPTY(hdr) || (hdr->flags & TFW_STR_HBH_HDR)) + if (TFW_STR_EMPTY(hdr) || hdr->flags & TFW_STR_HBH_HDR) continue; if (TFW_STR_DUP(hdr)) @@ -4388,6 +4393,11 @@ tfw_http_adjust_resp(TfwHttpResp *resp) continue; TFW_STR_FOR_EACH_DUP(dup, pos, dup_end) { + /* + * Skip trailer header. + */ + if (unlikely(dup->flags & TFW_STR_TRAILER)) + continue; r = tfw_http_msg_expand_from_pool(hm, dup); if (unlikely(r)) goto clean; @@ -5026,18 +5036,7 @@ tfw_h2_hpack_encode_headers(TfwHttpResp *resp, const TfwHdrMods *h_mods) * Remove 'Connection', 'Keep-Alive' headers and all hop-by-hop * headers from the HTTP/2 response. */ - if (hid == TFW_HTTP_HDR_KEEP_ALIVE - || hid == TFW_HTTP_HDR_CONNECTION - || tgt->flags & TFW_STR_HBH_HDR) - continue; - - /* - * 'Server' header must be replaced; thus, remove the original - * header (and all its duplicates) skipping it here; the new - * header will be written later, during new headers' addition - * stage. - */ - if (hid == TFW_HTTP_HDR_SERVER) + if (tgt->flags & TFW_STR_HBH_HDR) continue; r = tfw_hpack_transform(resp, tgt); diff --git a/fw/http_parser.c b/fw/http_parser.c index 9a731c2caf..8bd1ba27ab 100644 --- a/fw/http_parser.c +++ b/fw/http_parser.c @@ -1403,6 +1403,7 @@ do { \ } \ if (c == '\n') { \ if (!msg->crlf.data) { \ + mark_spec_hbh(msg); \ /* \ * Set data and length explicitly for a single \ * LF w/o calling complex __msg_field_fixup(). \ @@ -4851,9 +4852,10 @@ tfw_http_init_parser_req(TfwHttpReq *req) * - raw: * none; * - spec: - * Connection: RFC 7230 6.1. + * Connection, Keep-alive: RFC 9110 7.6.1. */ - hbh_hdrs->spec = 0x1 << TFW_HTTP_HDR_CONNECTION; + hbh_hdrs->spec = 0x1 << TFW_HTTP_HDR_CONNECTION | + 0x1 << TFW_HTTP_HDR_KEEP_ALIVE; } @@ -11706,12 +11708,13 @@ tfw_http_init_parser_resp(TfwHttpResp *resp) * - raw: * none; * - spec: - * Connection: RFC 7230 6.1. + * Connection, Keep-alive: RFC 9110 7.6.1. * Server: Server header isn't defined as hop-by-hop by the RFC, * but we don't show protected server to world. */ - hbh_hdrs->spec = (0x1 << TFW_HTTP_HDR_CONNECTION) | - (0x1 << TFW_HTTP_HDR_SERVER); + hbh_hdrs->spec = 0x1 << TFW_HTTP_HDR_CONNECTION | + 0x1 << TFW_HTTP_HDR_KEEP_ALIVE | + 0x1 << TFW_HTTP_HDR_SERVER; } /** diff --git a/fw/t/unit/test_http1_parser.c b/fw/t/unit/test_http1_parser.c index 11d4e78180..622a5ea40a 100644 --- a/fw/t/unit/test_http1_parser.c +++ b/fw/t/unit/test_http1_parser.c @@ -2,7 +2,7 @@ * Tempesta FW * * Copyright (C) 2014 NatSys Lab. (info@natsys-lab.com). - * Copyright (C) 2015-2024 Tempesta Technologies, Inc. + * Copyright (C) 2015-2025 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 From 5e6e63e6b08627004a41325d29bf7f545f7fea54 Mon Sep 17 00:00:00 2001 From: Constantine Date: Fri, 7 Jun 2024 13:01:05 +0300 Subject: [PATCH 28/44] Fix header table corruption We had a bug during the suffix match in function `tfw_http_search_cookie()`, before comparing the name we store current chunk `t = *chunk`, then modify and compare the chunk, after we rostore the chunk `*chunk = t;` that leads to header corruption, because macros `TFW_STR_EQ_CSTR` may assign new address to `chunk`, therefore restoration happens to a newly assigned adress instead of original adress in `chunk`. Fixed by storing address to temporary variable and then restore `data` and `len` by this adress. `__tfw_str_eq_cstr` rewritten to inline function. Inline functions is more clear, less error prone and suitable in this case, because we don't need to define tmp vars and explicitly save values, only move `chunk` pointer forward. Generated assembly will be the same. --- fw/http_match.c | 65 ++++++++++++++++++++++++++----------------------- 1 file changed, 35 insertions(+), 30 deletions(-) diff --git a/fw/http_match.c b/fw/http_match.c index 67ae151c6f..8ebab706b3 100644 --- a/fw/http_match.c +++ b/fw/http_match.c @@ -876,6 +876,31 @@ tfw_http_verify_hdr_field(tfw_http_match_fld_t field, const char **hdr_name, return 0; } +/* Simple version of tfw_str_eq_cstr. */ +static bool __always_inline +__tfw_str_eq_cstr(const char *cstr, unsigned long clen, TfwStr **pos, + const TfwStr *end) +{ + TfwStr *chunk = *pos; + + while (chunk != end) { + int len = min(clen, chunk->len); + + if (memcmp_fast(cstr, chunk->data, len)) + break; + + cstr += len; + clen -= len; + if (!clen) + break; + chunk++; + } + + *pos = chunk; + + return !clen; +} + /* * Search for cookie in `Set-Cookie`/`Cookie` header value @cookie * and save the cookie value into @val. @@ -902,26 +927,6 @@ tfw_http_search_cookie(const char *cstr, unsigned long clen, { TfwStr *chunk; -/* Simple version of tfw_str_eq_cstr. */ -#define TFW_STR_EQ_CSTR(end) \ -({ \ - const char *tmp_cstr = cstr; \ - unsigned long tmp_clen = clen; \ - \ - while (chunk != end) { \ - int len = min(tmp_clen, chunk->len); \ - if (memcmp_fast(tmp_cstr, chunk->data, len)) \ - break; \ - \ - tmp_cstr += len; \ - tmp_clen -= len; \ - if (!tmp_clen) \ - break; \ - chunk++; \ - } \ - !tmp_clen; \ -}) - /* Search cookie name. */ for (chunk = *pos; chunk != end; ++chunk) { if (!(chunk->flags & TFW_STR_NAME)) @@ -933,18 +938,18 @@ tfw_http_search_cookie(const char *cstr, unsigned long clen, if (op == TFW_HTTP_MATCH_O_PREFIX || op == TFW_HTTP_MATCH_O_EQ) { - if (TFW_STR_EQ_CSTR(end)) + if (__tfw_str_eq_cstr(cstr, clen, &chunk, end)) break; while ((chunk + 1 != end) && ((chunk + 1)->flags & TFW_STR_NAME)) ++chunk; } else if (op == TFW_HTTP_MATCH_O_SUFFIX) { - TfwStr *name, t; - unsigned int len = 0, name_n = 0; + TfwStr *name, *orig; + unsigned int len = 0; ssize_t offset; - for (name = chunk; name != end; ++name, ++name_n) { + for (name = chunk; name != end; ++name) { if (!(name->flags & TFW_STR_NAME)) break; len += name->len; @@ -952,7 +957,7 @@ tfw_http_search_cookie(const char *cstr, unsigned long clen, offset = len - clen; if (!offset) { - if (TFW_STR_EQ_CSTR(name)) + if (__tfw_str_eq_cstr(cstr, clen, &chunk, name)) break; } else if (offset > 0) { bool equal; @@ -961,11 +966,13 @@ tfw_http_search_cookie(const char *cstr, unsigned long clen, offset -= chunk->len; ++chunk; } - t = *chunk; + orig = chunk; chunk->data += offset; chunk->len -= offset; - equal = TFW_STR_EQ_CSTR(name); - *chunk = t; + equal = __tfw_str_eq_cstr(cstr, clen, &chunk, + name); + orig->data -= offset; + orig->len += offset; if (equal) break; } @@ -997,6 +1004,4 @@ tfw_http_search_cookie(const char *cstr, unsigned long clen, } *pos = tfw_str_collect_cmp(chunk, end, val, ";"); return 1; - -#undef TFW_STR_EQ_CSTR } From 5b85fa913746c0ede0b75fc0fcd5ea34e3bc36c7 Mon Sep 17 00:00:00 2001 From: Constantine Date: Fri, 7 Jun 2024 13:56:40 +0300 Subject: [PATCH 29/44] Preserve skb mark when copy headers We must copy a mark from old skb head to new, otherwise we will lose the mark. --- fw/http_msg.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/fw/http_msg.c b/fw/http_msg.c index eb7aa7b816..c0ba4d910d 100644 --- a/fw/http_msg.c +++ b/fw/http_msg.c @@ -1406,6 +1406,7 @@ tfw_http_msg_cutoff_headers(TfwHttpMsg *hm, TfwHttpMsgCleanup* cleanup) char* body = TFW_STR_CHUNK(&hm->body, 0)->data; TfwStr *crlf = TFW_STR_LAST(&hm->crlf); char *off = body ? body : crlf->data + (crlf->len - 1); + unsigned int mark = hm->msg.skb_head->mark; do { struct sk_buff *skb; @@ -1479,6 +1480,7 @@ tfw_http_msg_cutoff_headers(TfwHttpMsg *hm, TfwHttpMsgCleanup* cleanup) it->skb_head = it->skb; hm->msg.skb_head = it->skb; + hm->msg.skb_head->mark = mark; /* Start from zero fragment */ it->frag = -1; From 5e03141936fc057b1a36f63d885843ef14ae82ed Mon Sep 17 00:00:00 2001 From: Constantine Date: Mon, 10 Jun 2024 16:08:32 +0300 Subject: [PATCH 30/44] `mark_spec_hbh`: Do not walk over headers table Call `mark_spec_hbh` only for parsed special header and do not iterate over headers table. --- fw/http_parser.c | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/fw/http_parser.c b/fw/http_parser.c index 8bd1ba27ab..36618f6f19 100644 --- a/fw/http_parser.c +++ b/fw/http_parser.c @@ -785,18 +785,14 @@ static const TfwStr ete_spec_raw_hdrs[] = { * listed in Connection header or in @tfw_http_init_parser_* function. */ static void -mark_spec_hbh(TfwHttpMsg *hm) +mark_spec_hbh(TfwHttpMsg *hm, TfwStr *hdr, unsigned int id) { TfwHttpHbhHdrs *hbh_hdrs = &hm->stream->parser.hbh_parser; - unsigned int id; - for (id = 0; id < TFW_HTTP_HDR_RAW; ++id) { - TfwStr *hdr = &hm->h_tbl->tbl[id]; - if ((hbh_hdrs->spec & (0x1 << id)) && (!TFW_STR_EMPTY(hdr))) { - T_DBG3("%s: hm %pK, tbl[%u] flags +TFW_STR_HBH_HDR\n", - __func__, hm, id); - hdr->flags |= TFW_STR_HBH_HDR; - } + if (hbh_hdrs->spec & (0x1 << id)) { + T_DBG3("%s: hm %pK, tbl[%u] flags +TFW_STR_HBH_HDR\n", + __func__, hm, id); + hdr->flags |= TFW_STR_HBH_HDR; } } @@ -1403,7 +1399,6 @@ do { \ } \ if (c == '\n') { \ if (!msg->crlf.data) { \ - mark_spec_hbh(msg); \ /* \ * Set data and length explicitly for a single \ * LF w/o calling complex __msg_field_fixup(). \ @@ -1425,7 +1420,6 @@ do { \ __FSM_STATE(RGen_CRLFCR, hot) { \ if (unlikely(c != '\n')) \ TFW_PARSER_DROP(RGen_CRLFCR); \ - mark_spec_hbh(msg); \ if (!(msg->crlf.flags & TFW_STR_COMPLETE)) { \ BUG_ON(!msg->crlf.data); \ __msg_field_finish(&msg->crlf, p + 1); \ @@ -1479,6 +1473,7 @@ __FSM_STATE(st_curr) { \ /* The header value is fully parsed, move forward. */ \ if (saveval) \ __msg_hdr_chunk_fixup(p, __fsm_n); \ + mark_spec_hbh(msg, &parser->hdr, id); \ r = process_trailer_hdr(msg, &parser->hdr, id); \ if (r < 0 && r != CSTR_POSTPONE) \ TFW_PARSER_DROP(st_curr); \ From ee20bbaac58f5193aee6996ae686e930342aa05c Mon Sep 17 00:00:00 2001 From: Constantine Date: Tue, 18 Jun 2024 16:01:06 +0300 Subject: [PATCH 31/44] Host parsing Validating a syntax of the `Host` header, to catch protocol violations. --- fw/http_limits.c | 6 +-- fw/http_parser.c | 96 ++++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 83 insertions(+), 19 deletions(-) diff --git a/fw/http_limits.c b/fw/http_limits.c index e82dc16afd..5f8a1390b8 100644 --- a/fw/http_limits.c +++ b/fw/http_limits.c @@ -754,9 +754,10 @@ frang_http_host_check(const TfwHttpReq *req, FrangAcc *ra) } break; case TFW_HTTP_VER_11: - /* This is pure HTTP/1.1 check, that would never trigger for + /* + * This is pure HTTP/1.1 check, that would never trigger for * HTTP/2 because it cannot have an absolute URI. - * Also this MUST be removed after #1870 is complete*/ + */ if (test_bit(TFW_HTTP_B_ABSOLUTE_URI, req->flags)) { TfwStr host; @@ -801,7 +802,6 @@ frang_http_host_check(const TfwHttpReq *req, FrangAcc *ra) "\n"); return T_BLOCK; } - /* Check that SNI for TLS connection matches host header. */ if (TFW_CONN_TLS(req->conn)) port = req->host_port ? : 443; else diff --git a/fw/http_parser.c b/fw/http_parser.c index 36618f6f19..99a0baac83 100644 --- a/fw/http_parser.c +++ b/fw/http_parser.c @@ -632,6 +632,23 @@ __parse_ulong(unsigned char *__restrict data, size_t len, return CSTR_POSTPONE; } +/** + * Parse an integer followed by a slash or a space. Used only for parsing port + * in URI. + */ +static __always_inline int +__parse_uri_port(unsigned char *__restrict data, size_t len, + unsigned long *__restrict acc, unsigned long limit) +{ + /* Characters: + * ' ' (0x20) space (SPC) + * '/' (0x2f) slash */ + static const unsigned long dm_a[] ____cacheline_aligned = { + 0x0000800100000000UL, 0, 0, 0 + }; + return __parse_ulong(data, len, dm_a, acc, limit); +} + /** * Parse an integer followed by a white space. */ @@ -3283,33 +3300,61 @@ __req_parse_host(TfwHttpReq *req, unsigned char *data, size_t len) } __FSM_STATE(Req_I_H_Port) { - /* See Req_UriPort processing. */ - if (unlikely(IS_CRLFWS(c))) { - if (!req->host_port) - /* Header ended before port was parsed. */ - return CSTR_NEQ; - return __data_off(p); - } __fsm_sz = __data_remain(p); __fsm_n = __parse_ulong_ws(p, __data_remain(p), &parser->_acc, USHRT_MAX); + switch (__fsm_n) { + case CSTR_EQ: case CSTR_BADLEN: case CSTR_NEQ: return CSTR_NEQ; case CSTR_POSTPONE: - req->host_port = parser->_acc; - __FSM_I_MOVE_fixup(Req_I_H_Port, __fsm_sz, TFW_STR_VALUE); + if (likely(!test_bit(TFW_HTTP_B_ABSOLUTE_URI, + req->flags))) + { + req->host_port = parser->_acc; + } + __FSM_I_MOVE_fixup(Req_I_H_Port, __fsm_sz, + TFW_STR_VALUE); default: - req->host_port = parser->_acc; - if (!req->host_port) - return CSTR_NEQ; + /* + * Don't override `host_port` when URI is absolute + * `host_port` taken from it. + */ + if (likely(!test_bit(TFW_HTTP_B_ABSOLUTE_URI, + req->flags))) + { + req->host_port = parser->_acc; + if (!req->host_port) + return CSTR_NEQ; + } + parser->_acc = 0; - __FSM_I_MOVE_fixup(Req_I_H_Port, __fsm_n, TFW_STR_VALUE); + __FSM_I_MOVE_fixup(Req_I_H_PortEnd, __fsm_n, + TFW_STR_VALUE); } return CSTR_NEQ; } + __FSM_STATE(Req_I_H_PortEnd) { + /* See Req_UriPort processing. */ + if (IS_CRLFWS(c)) { + /* + * Don't validate `host_port` when URI is absolute, + * it be validated during URI parsing. Here we validate + * only port from host header, but port from URI will + * be used for message forwarding. + */ + if (unlikely(!req->host_port && + !test_bit(TFW_HTTP_B_ABSOLUTE_URI, req->flags))) { + /* Header ended before port was parsed. */ + return CSTR_NEQ; + } + return __data_off(p); + } + } + done: return r; } @@ -5951,8 +5996,27 @@ Req_Method_1CharStep: __attribute__((cold)) /* Host port in URI */ __FSM_STATE(Req_UriPort, cold) { - if (likely(isdigit(c))) - __FSM_MOVE_f(Req_UriPort, &req->host); + __fsm_sz = __data_remain(p); + __fsm_n = __parse_uri_port(p, __data_remain(p), + &parser->_acc, USHRT_MAX); + + switch (__fsm_n) { + case CSTR_BADLEN: + case CSTR_NEQ: + TFW_PARSER_DROP(Req_UriPort); + case CSTR_POSTPONE: + req->host_port = parser->_acc; + __FSM_MOVE_nf(Req_UriPort, __fsm_sz, &req->host); + default: + req->host_port = parser->_acc; + if (!req->host_port) + TFW_PARSER_DROP(Req_UriPort); + parser->_acc = 0; + __FSM_MOVE_nf(Req_UriPortEnd, __fsm_n, &req->host); + } + } + + __FSM_STATE(Req_UriPortEnd, cold) { __msg_field_finish(&req->host, p); if (likely(c == '/')) { __msg_field_open(&req->uri_path, p); @@ -5961,7 +6025,7 @@ Req_Method_1CharStep: __attribute__((cold)) else if (c == ' ') { __FSM_MOVE_nofixup(Req_HttpVer); } - TFW_PARSER_DROP(Req_UriPort); + TFW_PARSER_DROP(Req_UriPortEnd); } /* Parse HTTP version (1.1 and 1.0 are supported). */ From 15f217595e76b41e4a6600ea7b854bf55bb99623 Mon Sep 17 00:00:00 2001 From: Constantine Date: Thu, 4 Jul 2024 18:14:45 +0300 Subject: [PATCH 32/44] Review fixes: - return replaced with `goto clean` in `tfw_h1_adjust_req()` --- fw/http.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/fw/http.c b/fw/http.c index 56ee5f1668..5d37ff4970 100644 --- a/fw/http.c +++ b/fw/http.c @@ -3646,7 +3646,7 @@ tfw_h1_adjust_req(TfwHttpReq *req) { r = tfw_http_recreate_content_type_multipart_hdr(req); if (unlikely(r)) - return r; + goto clean; continue; } @@ -3670,11 +3670,11 @@ tfw_h1_adjust_req(TfwHttpReq *req) r = tfw_http_add_x_forwarded_for(hm); if (unlikely(r)) - return r; + goto clean; r = tfw_http_add_hdr_via(hm); if (unlikely(r)) - return r; + goto clean; r = tfw_http_add_hdr_upgrade(hm, false); if (unlikely(r)) From c0b00c7eab7d6e2861c8dbe7843edeeda1751d57 Mon Sep 17 00:00:00 2001 From: Constantine Date: Thu, 12 Dec 2024 10:48:29 +0200 Subject: [PATCH 33/44] Fix Host parsing --- fw/http_parser.c | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/fw/http_parser.c b/fw/http_parser.c index 99a0baac83..e1b4f849b9 100644 --- a/fw/http_parser.c +++ b/fw/http_parser.c @@ -3305,7 +3305,6 @@ __req_parse_host(TfwHttpReq *req, unsigned char *data, size_t len) USHRT_MAX); switch (__fsm_n) { - case CSTR_EQ: case CSTR_BADLEN: case CSTR_NEQ: return CSTR_NEQ; @@ -5969,7 +5968,7 @@ Req_Method_1CharStep: __attribute__((cold)) __FSM_STATE(Req_UriAuthorityEnd, cold) { if (c == ':') - __FSM_MOVE_f(Req_UriPortBegin, &req->host); + __FSM_MOVE_f(Req_UriPort, &req->host); /* Authority End */ __msg_field_finish(&req->host, p); T_DBG3("Userinfo len = %i, host len = %i\n", @@ -5984,16 +5983,6 @@ Req_Method_1CharStep: __attribute__((cold)) TFW_PARSER_DROP(Req_UriAuthorityEnd); } - /* - * This state is necessary to drop requests with empty port like - * GET http://tempesta-tech.com: - */ - __FSM_STATE(Req_UriPortBegin) { - if (likely(isdigit(c))) - __FSM_MOVE_f(Req_UriPort, &req->host); - TFW_PARSER_DROP(Req_UriPortBegin); - } - /* Host port in URI */ __FSM_STATE(Req_UriPort, cold) { __fsm_sz = __data_remain(p); @@ -6005,7 +5994,6 @@ Req_Method_1CharStep: __attribute__((cold)) case CSTR_NEQ: TFW_PARSER_DROP(Req_UriPort); case CSTR_POSTPONE: - req->host_port = parser->_acc; __FSM_MOVE_nf(Req_UriPort, __fsm_sz, &req->host); default: req->host_port = parser->_acc; From 090e80742e463923627bb0fd8a9c79e5e82094b6 Mon Sep 17 00:00:00 2001 From: Constantine Date: Wed, 18 Dec 2024 15:07:38 +0200 Subject: [PATCH 34/44] Host comparison Before this patch we compared the Host header and uri's host as strings including port part. However, in case when explicitly specified default port in one of them, but not specified in the second we had error. Example: uri: tempesta-tech.com host: tempesta-tech.com:80 In this case uri and host must be treated as the same, due to 80 port is default and can be omitted. In this patch we parse uri and host in the same way, we mark host part from the uri and from Host header as TFW_STR_VALUE excluding the port (e.g ":80") part. Port we parse and save into `TfwHttpReq::uri_port` for uri port and into `TfwHttpReq::host` for host respectively. Then in `frang_http_host_check()` we compare host parts that marked as TFW_STR_VALUE (use ":" as stop symbol) and separately compare ports. --- fw/http.h | 5 ++++- fw/http_limits.c | 33 ++++++++++++++++++++++++-------- fw/http_parser.c | 50 +++++++++++++++++------------------------------- 3 files changed, 47 insertions(+), 41 deletions(-) diff --git a/fw/http.h b/fw/http.h index 5fe585060b..d082abba4c 100644 --- a/fw/http.h +++ b/fw/http.h @@ -385,6 +385,8 @@ typedef struct { * @hash - hash value for caching calculated for the request; * @frang_st - current state of FRANG classifier; * @chunk_cnt - header or body chunk count for Frang classifier; + * @host_port - Port parsed from Host header. + * @uri_port - Port parser from request's URI. * @node - NUMA node where request is serviced; * @retries - the number of re-send attempts; * @method - HTTP request method, one of GET/PORT/HEAD/etc; @@ -422,7 +424,8 @@ struct tfw_http_req_t { unsigned long hash; unsigned int frang_st; unsigned int chunk_cnt; - unsigned int host_port; + unsigned short host_port; + unsigned short uri_port; unsigned short node; unsigned short retries; unsigned char method; diff --git a/fw/http_limits.c b/fw/http_limits.c index 5f8a1390b8..9981d72284 100644 --- a/fw/http_limits.c +++ b/fw/http_limits.c @@ -724,7 +724,8 @@ static int frang_http_host_check(const TfwHttpReq *req, FrangAcc *ra) { TfwAddr addr; - unsigned short port; + unsigned short host_port; + unsigned short uri_port; unsigned short real_port; TfwStr authority, host; TfwStr prim_trim = { 0 }, prim_name = { 0 }; /* primary source */ @@ -732,6 +733,14 @@ frang_http_host_check(const TfwHttpReq *req, FrangAcc *ra) BUG_ON(!req); BUG_ON(!req->h_tbl); + if (TFW_CONN_TLS(req->conn)) { + host_port = req->host_port ? : 443; + uri_port = req->uri_port ? : 443; + } else { + host_port = req->host_port ? : 80; + uri_port = req->uri_port ? : 80; + } + switch (req->version) { case TFW_HTTP_VER_20: __h2_msg_hdr_val(&req->h_tbl->tbl[TFW_HTTP_HDR_H2_AUTHORITY], @@ -764,7 +773,18 @@ frang_http_host_check(const TfwHttpReq *req, FrangAcc *ra) tfw_http_msg_clnthdr_val(req, &req->h_tbl->tbl[TFW_HTTP_HDR_HOST], TFW_HTTP_HDR_HOST, &host); - if (tfw_strcmp(&req->host, &host) != 0) { + + /* Check that ports are the same. */ + if (unlikely(uri_port != host_port)) { + frang_msg_lock(&ra->lock, "port from host header doesn't" + " match port from uri", + &FRANG_ACC2CLI(ra)->addr, + ": %d (%d)\n", host_port, + uri_port); + return T_BLOCK; + } + + if (tfw_stricmpspn(&req->host, &host, ':') != 0) { frang_msg_lock(&ra->lock, "Request host from" " absolute URI differs from Host" " header", @@ -802,19 +822,16 @@ frang_http_host_check(const TfwHttpReq *req, FrangAcc *ra) "\n"); return T_BLOCK; } - if (TFW_CONN_TLS(req->conn)) - port = req->host_port ? : 443; - else - port = req->host_port ? : 80; + /* * TfwClient instance can be reused across multiple connections, * check the port number of the current connection, not the first one. */ real_port = be16_to_cpu(inet_sk(req->conn->sk)->inet_sport); - if (unlikely(port != real_port)) { + if (unlikely(host_port != real_port)) { frang_msg_lock(&ra->lock, "port from host header doesn't" " match real port", &FRANG_ACC2CLI(ra)->addr, - ": %d (%d)\n", port, real_port); + ": %d (%d)\n", host_port, real_port); return T_BLOCK; } diff --git a/fw/http_parser.c b/fw/http_parser.c index e1b4f849b9..cc89fa7f98 100644 --- a/fw/http_parser.c +++ b/fw/http_parser.c @@ -3309,25 +3309,12 @@ __req_parse_host(TfwHttpReq *req, unsigned char *data, size_t len) case CSTR_NEQ: return CSTR_NEQ; case CSTR_POSTPONE: - if (likely(!test_bit(TFW_HTTP_B_ABSOLUTE_URI, - req->flags))) - { - req->host_port = parser->_acc; - } __FSM_I_MOVE_fixup(Req_I_H_Port, __fsm_sz, TFW_STR_VALUE); default: - /* - * Don't override `host_port` when URI is absolute - * `host_port` taken from it. - */ - if (likely(!test_bit(TFW_HTTP_B_ABSOLUTE_URI, - req->flags))) - { - req->host_port = parser->_acc; - if (!req->host_port) - return CSTR_NEQ; - } + req->host_port = parser->_acc; + if (!req->host_port) + return CSTR_NEQ; parser->_acc = 0; __FSM_I_MOVE_fixup(Req_I_H_PortEnd, __fsm_n, @@ -3339,17 +3326,9 @@ __req_parse_host(TfwHttpReq *req, unsigned char *data, size_t len) __FSM_STATE(Req_I_H_PortEnd) { /* See Req_UriPort processing. */ if (IS_CRLFWS(c)) { - /* - * Don't validate `host_port` when URI is absolute, - * it be validated during URI parsing. Here we validate - * only port from host header, but port from URI will - * be used for message forwarding. - */ - if (unlikely(!req->host_port && - !test_bit(TFW_HTTP_B_ABSOLUTE_URI, req->flags))) { + if (unlikely(!req->host_port)) /* Header ended before port was parsed. */ return CSTR_NEQ; - } return __data_off(p); } } @@ -5958,6 +5937,7 @@ Req_Method_1CharStep: __attribute__((cold)) __FSM_STATE(Req_UriAuthorityResetHost, cold) { if (likely(isalnum(c) || c == '.' || c == '-')) { __msg_field_open(&req->host, p); + __msg_field_chunk_flags(&req->host, TFW_STR_VALUE); __FSM_MOVE_f(Req_UriAuthority, &req->host); } else if (c == '[') { __msg_field_open(&req->host, p); @@ -5967,8 +5947,13 @@ Req_Method_1CharStep: __attribute__((cold)) } __FSM_STATE(Req_UriAuthorityEnd, cold) { - if (c == ':') - __FSM_MOVE_f(Req_UriPort, &req->host); + if (c == ':') { + /* Fixup host part using TFW_STR_VALUE flag. */ + __msg_field_fixup(&req->host, p); + /* Fixup ":" with zero flag, move to port */ + __msg_field_fixup_pos(&req->host, p, 1); + __FSM_MOVE_nofixup(Req_UriPort); + } /* Authority End */ __msg_field_finish(&req->host, p); T_DBG3("Userinfo len = %i, host len = %i\n", @@ -5994,18 +5979,19 @@ Req_Method_1CharStep: __attribute__((cold)) case CSTR_NEQ: TFW_PARSER_DROP(Req_UriPort); case CSTR_POSTPONE: - __FSM_MOVE_nf(Req_UriPort, __fsm_sz, &req->host); + __msg_field_fixup_pos(&req->host, p, __fsm_sz); + __FSM_MOVE_nofixup_n(Req_UriPort, __fsm_sz); default: - req->host_port = parser->_acc; - if (!req->host_port) + req->uri_port = parser->_acc; + if (!req->uri_port) TFW_PARSER_DROP(Req_UriPort); parser->_acc = 0; - __FSM_MOVE_nf(Req_UriPortEnd, __fsm_n, &req->host); + __msg_field_finish_pos(&req->host, p, __fsm_n); + __FSM_MOVE_nofixup_n(Req_UriPortEnd, __fsm_n); } } __FSM_STATE(Req_UriPortEnd, cold) { - __msg_field_finish(&req->host, p); if (likely(c == '/')) { __msg_field_open(&req->uri_path, p); __FSM_MOVE_f(Req_UriAbsPath, &req->uri_path); From 36fab2341867f2eb1620fd30b047f3ff0bd9fce1 Mon Sep 17 00:00:00 2001 From: Constantine Date: Wed, 18 Dec 2024 16:53:39 +0200 Subject: [PATCH 35/44] Add comment about http1.1 in the request to upstream --- fw/http.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/fw/http.c b/fw/http.c index 5d37ff4970..df364e3f80 100644 --- a/fw/http.c +++ b/fw/http.c @@ -3576,6 +3576,10 @@ tfw_h1_add_loc_hdrs(TfwHttpMsg *hm, const TfwHdrMods *h_mods, bool from_cache) /** * Adjust the request before proxying it to real server. + * + * We alway "upgrade" request to HTTP1.1, even if the client has sent HTTP1.0. + * Do so to be able to use persistent connection with upstream and also to use + * extended conditional headers mechanism. */ static int tfw_h1_adjust_req(TfwHttpReq *req) From 05780c23904b1de6265b46167948f1caf1b2e383 Mon Sep 17 00:00:00 2001 From: Constantine Date: Wed, 18 Dec 2024 18:27:52 +0200 Subject: [PATCH 36/44] Host header overriding This commit adds special case for setting Host header in the request using `req_hdr_set` directive. We simply write the new Host header to the request skipping the old one during request adjusting. However we don't update original Host header in headers table, to be able to use original header. We need original header during caching response, in this case we make the key using original Host header. We do so, because we always find response in the cache using original host header. --- fw/http.c | 91 ++++++++++++++++++++++++++++++++++++++---------------- fw/vhost.c | 14 +++++++++ fw/vhost.h | 21 ++++++++++++- 3 files changed, 99 insertions(+), 27 deletions(-) diff --git a/fw/http.c b/fw/http.c index df364e3f80..7f3fc8ac8e 100644 --- a/fw/http.c +++ b/fw/http.c @@ -3522,6 +3522,13 @@ tfw_h1_add_loc_hdrs(TfwHttpMsg *hm, const TfwHdrMods *h_mods, bool from_cache) .nchunks = 2 /* header name + delimeter. */ }; + /* + * Skip Host header addition, it already added during request + * adjusting. + */ + if (desc->hid == TFW_HTTP_HDR_HOST) + continue; + if (TFW_STR_CHUNK(desc->hdr, 1) == NULL) continue; @@ -3574,6 +3581,38 @@ tfw_h1_add_loc_hdrs(TfwHttpMsg *hm, const TfwHdrMods *h_mods, bool from_cache) return r; } +/** + * Add Host header to request. + * + * Write Host header into response, but not override @TfwHttpReq::host, it + * may be used on response path. + */ +static int +tfw_http_add_hdr_host(TfwHttpReq *req, const TfwHdrMods *h_mods) +{ + int r; + TfwHttpMsg *hm = (TfwHttpMsg *)req; + static const DEFINE_TFW_STR(host_n, "Host: "); + static const DEFINE_TFW_STR(crlf, S_CRLF); + + r = tfw_http_msg_expand_from_pool(hm, &host_n); + if (unlikely(r)) + return r; + + if (h_mods && test_bit(TFW_HTTP_HDR_HOST, h_mods->spec_hdrs)) { + TfwHdrModsDesc *desc = &h_mods->hdrs[h_mods->host_off]; + + r = tfw_http_msg_expand_from_pool(hm, &desc->hdr->chunks[1]); + } else { + r = tfw_http_msg_expand_from_pool(hm, &req->host); + } + + if (unlikely(r)) + return r; + + return tfw_http_msg_expand_from_pool(hm, &crlf); +} + /** * Adjust the request before proxying it to real server. * @@ -3629,7 +3668,11 @@ tfw_h1_adjust_req(TfwHttpReq *req) if (unlikely(r)) goto clean; - FOR_EACH_HDR_FIELD_FROM(pos, end, hm, TFW_HTTP_HDR_HOST) { + r = tfw_http_add_hdr_host(req, h_mods); + if (unlikely(r)) + goto clean; + + FOR_EACH_HDR_FIELD_FROM(pos, end, hm, TFW_HTTP_HDR_CONTENT_LENGTH) { int hid = pos - hm->h_tbl->tbl; TfwStr *dup, *dup_end, *hdr = pos; @@ -3828,12 +3871,11 @@ __h2_req_hdrs(TfwHttpReq *req, const TfwStr *hdr, unsigned int hid, bool append) } static int -tfw_h2_req_set_loc_hdrs(TfwHttpReq *req) +tfw_h2_req_set_loc_hdrs(TfwHttpReq *req, TfwHdrMods *h_mods) { int i; TfwHttpHdrTbl *ht = req->h_tbl; - TfwHdrMods *h_mods = tfw_vhost_get_hdr_mods(req->location, req->vhost, - TFW_VHOST_HDRMOD_REQ); + if (!h_mods) return 0; @@ -3861,21 +3903,6 @@ tfw_h2_req_set_loc_hdrs(TfwHttpReq *req) } } - /* - * When header modifications contains `req_hdr_set` rule for `Host` - * header, __h2_req_hdrs modifies only TFW_HTTP_HDR_HOST leaves - * TFW_HTTP_HDR_H2_AUTHORITY untouched. We manualy assign new `Host` - * to TFW_HTTP_HDR_H2_AUTHORITY for consistency between `Host` and - * `authority:`. Espicially because `authority:` has higher priotiy - * and can be used instead of `Host` header during request modification - * when forwarding to backend. - */ - if (h_mods && test_bit(TFW_HTTP_HDR_HOST, h_mods->spec_hdrs)) { - TfwStr *host = &req->h_tbl->tbl[TFW_HTTP_HDR_HOST]; - - req->h_tbl->tbl[TFW_HTTP_HDR_H2_AUTHORITY] = *host; - } - return 0; } @@ -3955,9 +3982,11 @@ tfw_h2_adjust_req(TfwHttpReq *req) TfwHttpHdrTbl *ht = req->h_tbl; bool auth, host; size_t pseudo_num; - TfwStr host_val = {}, *field, *end; + TfwStr tmp_host = {}, *host_val, *field, *end; struct sk_buff *new_head = NULL, *old_head = NULL; TfwMsgIter it; + TfwHdrMods *h_mods = tfw_vhost_get_hdr_mods(req->location, req->vhost, + TFW_VHOST_HDRMOD_REQ); static const DEFINE_TFW_STR(sp, " "); static const DEFINE_TFW_STR(dlm, S_DLM); static const DEFINE_TFW_STR(crlf, S_CRLF); @@ -4032,7 +4061,7 @@ tfw_h2_adjust_req(TfwHttpReq *req) * warning about performance impact, so just live it as is, a more * robust algorithm will be used here if really required. */ - if ((r = tfw_h2_req_set_loc_hdrs(req))) + if (unlikely((r = tfw_h2_req_set_loc_hdrs(req, h_mods)))) return r; /* * tfw_h2_req_set_loc_hdrs() may realloc header table and user may @@ -4132,12 +4161,22 @@ tfw_h2_adjust_req(TfwHttpReq *req) r |= tfw_msg_write(&it, &sp); r |= tfw_msg_write(&it, &req->uri_path); r |= tfw_msg_write(&it, &fl_end); /* start of Host: header */ - if (auth) - __h2_msg_hdr_val(&ht->tbl[TFW_HTTP_HDR_H2_AUTHORITY], &host_val); - else if (host) - __h2_msg_hdr_val(&ht->tbl[TFW_HTTP_HDR_HOST], &host_val); - r |= tfw_msg_write(&it, &host_val); + if (h_mods && test_bit(TFW_HTTP_HDR_HOST, h_mods->spec_hdrs)) { + host_val = &h_mods->hdrs[h_mods->host_off].hdr->chunks[1]; + } + else if (auth) { + __h2_msg_hdr_val(&ht->tbl[TFW_HTTP_HDR_H2_AUTHORITY], + &tmp_host); + host_val = &tmp_host; + } + else if (host) { + __h2_msg_hdr_val(&ht->tbl[TFW_HTTP_HDR_HOST], &tmp_host); + host_val = &tmp_host; + } + r |= tfw_msg_write(&it, host_val); r |= tfw_msg_write(&it, &crlf); + if (unlikely(r)) + goto err; /* Skip host header: it's already written. */ FOR_EACH_HDR_FIELD_FROM(field, end, req, TFW_HTTP_HDR_REGULAR) { diff --git a/fw/vhost.c b/fw/vhost.c index f4b91f5235..c514bcd1c2 100644 --- a/fw/vhost.c +++ b/fw/vhost.c @@ -939,6 +939,8 @@ static void tfw_mod_hdr_sort(TfwLocation *loc) { unsigned short type; + TfwHdrMods *req_h_mods; + int i; for (type = 0; type < TFW_VHOST_HDRMOD_NUM; type++) { TfwHdrMods *h_mods = &loc->mod_hdrs[type]; @@ -954,6 +956,18 @@ tfw_mod_hdr_sort(TfwLocation *loc) if (h_mods->scan_off > 0) h_mods->scan_off--; } + + /* + * Save offset of Host header, to be able to add the header to the + * head of the request during the request adjusting. + */ + req_h_mods = &loc->mod_hdrs[TFW_VHOST_HDRMOD_REQ]; + for (i = 0; i < req_h_mods->sz; i++) { + if (req_h_mods->hdrs[i].hid == TFW_HTTP_HDR_HOST) { + req_h_mods->host_off = i; + break; + } + } } static void diff --git a/fw/vhost.h b/fw/vhost.h index 129dd4dd2d..e7423ebb54 100644 --- a/fw/vhost.h +++ b/fw/vhost.h @@ -98,13 +98,31 @@ struct tfw_hdr_mods_desc_t { bool append; }; +/* + * Layout of headers list in tfw_hdr_mods_t::hdrs. It allows to iterate over + * "Non-indexed raw headers *hdr_set". See @tfw_http_hdr_skip(). + * + * .----------------------------------------. + * | Special headers *hdr_set | + * :----------------------------------------: + * | Hpack indexed raw headers *hdr_set | + * :----------------------------------------: + * | Non-indexed raw headers *hdr_set | + * :----------------------------------------: + * | All headers *hdr_add | + * '----------------------------------------' + * + */ + /** + * * Headers modification before forwarding HTTP message. * * @sz - Total number of headers to modify; * @set_num - Number of headers to modify using req/resp_hdr_set directive; * @scan_off - Offset in @hdrs to start finding header to modify by name * comparision; + * @host_off - Offset of the Host header in @hdrs. * @hdrs - Headers to modify; * @spec_hdrs - Bitmap of special headers; * @s_tbl - Bitmap of headers from static table. Static table index @@ -113,9 +131,10 @@ struct tfw_hdr_mods_desc_t { * indexed from one, zero bit always set to zero; */ struct tfw_hdr_mods_t { - unsigned int sz:16; + unsigned int sz:8; unsigned int set_num:8; unsigned int scan_off:8; + unsigned int host_off:8; TfwHdrModsDesc *hdrs; DECLARE_BITMAP (spec_hdrs, TFW_MOD_SPEC_HDR_NUM); DECLARE_BITMAP (s_tbl, HPACK_STATIC_ENTRIES + 1); From b9a19b090bbf7caa824dd34a9c34753ae36200f9 Mon Sep 17 00:00:00 2001 From: Constantine Date: Thu, 19 Dec 2024 17:03:17 +0200 Subject: [PATCH 37/44] Add HEAD to GET overriding Add this overriding it exists in original version of tfw_h1_adjust_req(). --- fw/http.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/fw/http.c b/fw/http.c index 7f3fc8ac8e..3a6bfeb521 100644 --- a/fw/http.c +++ b/fw/http.c @@ -3646,8 +3646,9 @@ tfw_h1_adjust_req(TfwHttpReq *req) if (unlikely(r)) goto clean; - if (test_bit(TFW_HTTP_B_PURGE_GET, req->flags)) - /* Rewrite PURGE to GET */ + if (test_bit(TFW_HTTP_B_REQ_HEAD_TO_GET, req->flags) || + test_bit(TFW_HTTP_B_PURGE_GET, req->flags)) + /* Rewrite PURGE or HEAD to GET */ meth = &meth_get; else meth = &hm->h_tbl->tbl[TFW_HTTP_METHOD]; From fb9693920482745d66446da73774ef5a68462e30 Mon Sep 17 00:00:00 2001 From: Constantine Date: Wed, 15 Jan 2025 13:40:07 +0200 Subject: [PATCH 38/44] Forward request that has empty `uri_path` In case when request has absolute URI that looks like this: `http://tempesta-tech.com` - without trailing slash, `uri_path` will be empty, this commit handles this case. --- fw/http.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/fw/http.c b/fw/http.c index 3a6bfeb521..435f26d6f3 100644 --- a/fw/http.c +++ b/fw/http.c @@ -3628,6 +3628,7 @@ tfw_h1_adjust_req(TfwHttpReq *req) TfwStr *pos, *end; const TfwStr *meth; static const DEFINE_TFW_STR(meth_get, "GET"); + static const DEFINE_TFW_STR(slash, "/"); static const DEFINE_TFW_STR(sp, " "); static const DEFINE_TFW_STR(crlf, S_CRLF); static const DEFINE_TFW_STR(ver, " " S_VERSION11 S_CRLF); @@ -3661,7 +3662,11 @@ tfw_h1_adjust_req(TfwHttpReq *req) if (unlikely(r)) goto clean; - r = tfw_http_msg_expand_from_pool(hm, &req->uri_path); + /* uri_path is empty when uri is absolute and doesn't have slash */ + if (TFW_STR_EMPTY(&req->uri_path)) + r = tfw_http_msg_expand_from_pool(hm, &slash); + else + r = tfw_http_msg_expand_from_pool(hm, &req->uri_path); if (unlikely(r)) goto clean; From 5e599348791b984e2131291df220c88204f4152a Mon Sep 17 00:00:00 2001 From: Constantine Date: Thu, 16 Jan 2025 16:53:21 +0200 Subject: [PATCH 39/44] Prohibit empty URI host and userinfo component Userinfo subcomponent is depricated by RFC 9110 4.2.4 Empty host in URI prohibited by RFC 9110 4.2.1. Example of URI with empty host: "http:///path" --- fw/http_parser.c | 43 ++++------------------------------- fw/t/unit/test_http1_parser.c | 38 ++++++++----------------------- 2 files changed, 14 insertions(+), 67 deletions(-) diff --git a/fw/http_parser.c b/fw/http_parser.c index cc89fa7f98..8e69679704 100644 --- a/fw/http_parser.c +++ b/fw/http_parser.c @@ -5885,17 +5885,6 @@ Req_Method_1CharStep: __attribute__((cold)) __set_bit(TFW_HTTP_B_ABSOLUTE_URI, req->flags); __msg_field_open(&req->host, p); __FSM_MOVE_f(Req_UriAuthority, &req->host); - } else if (likely(c == '/')) { - /* - * The case where "Host:" header value is empty. - * A special TfwStr{} string is created that has - * a valid pointer and the length of zero. - */ - T_DBG3("Handling http:///path\n"); - tfw_http_msg_set_str_data(msg, &req->host, p); - req->host.flags |= TFW_STR_COMPLETE; - __msg_field_open(&req->uri_path, p); - __FSM_MOVE_f(Req_UriAbsPath, &req->uri_path); } else if (c == '[') { __set_bit(TFW_HTTP_B_ABSOLUTE_URI, req->flags); __msg_field_open(&req->host, p); @@ -5905,22 +5894,12 @@ Req_Method_1CharStep: __attribute__((cold)) } __FSM_STATE(Req_UriAuthority, cold) { - if (likely(isalnum(c) || c == '.' || c == '-' || c == '@')) { - if (unlikely(c == '@')) { - if (!TFW_STR_EMPTY(&req->userinfo)) { - T_DBG("Second '@' in authority\n"); - TFW_PARSER_DROP(Req_UriAuthority); - } - T_DBG3("Authority contains userinfo\n"); - /* copy current host to userinfo */ - req->userinfo = req->host; - __msg_field_finish(&req->userinfo, p); - TFW_STR_INIT(&req->host); - - __FSM_MOVE_nofixup(Req_UriAuthorityResetHost); - } - + if (likely(isalnum(c) || c == '.' || c == '-')) __FSM_MOVE_f(Req_UriAuthority, &req->host); + + if (unlikely(c == '@')) { + T_DBG("Depricated component userinfo in authority\n"); + TFW_PARSER_DROP(Req_UriAuthority); } __FSM_JMP(Req_UriAuthorityEnd); } @@ -5934,18 +5913,6 @@ Req_Method_1CharStep: __attribute__((cold)) TFW_PARSER_DROP(Req_UriAuthorityIPv6); } - __FSM_STATE(Req_UriAuthorityResetHost, cold) { - if (likely(isalnum(c) || c == '.' || c == '-')) { - __msg_field_open(&req->host, p); - __msg_field_chunk_flags(&req->host, TFW_STR_VALUE); - __FSM_MOVE_f(Req_UriAuthority, &req->host); - } else if (c == '[') { - __msg_field_open(&req->host, p); - __FSM_MOVE_f(Req_UriAuthorityIPv6, &req->host); - } - TFW_PARSER_DROP(Req_UriAuthorityResetHost); - } - __FSM_STATE(Req_UriAuthorityEnd, cold) { if (c == ':') { /* Fixup host part using TFW_STR_VALUE flag. */ diff --git a/fw/t/unit/test_http1_parser.c b/fw/t/unit/test_http1_parser.c index 622a5ea40a..4a9018d134 100644 --- a/fw/t/unit/test_http1_parser.c +++ b/fw/t/unit/test_http1_parser.c @@ -220,36 +220,19 @@ TEST(http1_parser, parses_req_uri) TEST_FULL_REQ("natsys-lab.com", "/foo/"); TEST_FULL_REQ("natsys-lab.com:8080", "/cgi-bin/show.pl?entry=tempesta"); - FOR_REQ("GET http://userame@natsys-lab.com HTTP/1.1\r\n\r\n") - { - EXPECT_TFWSTR_EQ(&req->host, "natsys-lab.com"); - } + EXPECT_BLOCK_REQ("GET http://userame@natsys-lab.com HTTP/1.1\r\n\r\n"); - FOR_REQ("GET https://userame@natsys-lab.com HTTP/1.1\r\n\r\n") - { - EXPECT_TFWSTR_EQ(&req->host, "natsys-lab.com"); - } + EXPECT_BLOCK_REQ("GET https://userame@natsys-lab.com HTTP/1.1\r\n\r\n"); - FOR_REQ("GET ws://userame@natsys-lab.com HTTP/1.1\r\n\r\n") - { - EXPECT_TFWSTR_EQ(&req->host, "natsys-lab.com"); - } + EXPECT_BLOCK_REQ("GET ws://userame@natsys-lab.com HTTP/1.1\r\n\r\n"); - FOR_REQ("GET wss://userame@natsys-lab.com HTTP/1.1\r\n\r\n") - { - EXPECT_TFWSTR_EQ(&req->host, "natsys-lab.com"); - } + EXPECT_BLOCK_REQ("GET wss://userame@natsys-lab.com HTTP/1.1\r\n\r\n"); - FOR_REQ("GET http://user@tempesta-tech.com/ HTTP/1.1\r\n" - "Host: bad.com\r\n\r\n") - { - EXPECT_TFWSTR_EQ(&req->host, "tempesta-tech.com"); - } + EXPECT_BLOCK_REQ("GET http://user@tempesta-tech.com/ HTTP/1.1\r\n" + "Host: bad.com\r\n\r\n"); - FOR_REQ("GET http://user@-x/ HTTP/1.1\r\nHost: bad.com\r\n\r\n") - { - EXPECT_TFWSTR_EQ(&req->host, "-x"); - } + EXPECT_BLOCK_REQ("GET http://user@-x/ HTTP/1.1\r\n" + "Host: bad.com\r\n\r\n"); FOR_REQ("GET http://tempesta-tech.com/ HTTP/1.1\r\n" "Host: bad.com\r\n\r\n") @@ -257,10 +240,7 @@ TEST(http1_parser, parses_req_uri) EXPECT_TFWSTR_EQ(&req->host, "tempesta-tech.com"); } - FOR_REQ("GET http:///path HTTP/1.1\r\nHost: localhost\r\n\r\n") - { - EXPECT_TFWSTR_EQ(&req->host, "localhost"); - } + EXPECT_BLOCK_REQ("GET http:///path HTTP/1.1\r\nHost: localhost\r\n\r\n"); FOR_REQ("OPTIONS * HTTP/1.1\r\n\r\n"); From 41b3f048361ead1bd861ce9cb614aa53e5498d1f Mon Sep 17 00:00:00 2001 From: Constantine Date: Wed, 22 Jan 2025 12:30:52 +0200 Subject: [PATCH 40/44] Don't mask error code --- fw/http_msg.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/fw/http_msg.c b/fw/http_msg.c index c0ba4d910d..15ce823737 100644 --- a/fw/http_msg.c +++ b/fw/http_msg.c @@ -4,7 +4,7 @@ * HTTP message manipulation helpers for the protocol processing. * * Copyright (C) 2014 NatSys Lab. (info@natsys-lab.com). - * Copyright (C) 2015-2024 Tempesta Technologies, Inc. + * Copyright (C) 2015-2025 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 @@ -1078,19 +1078,19 @@ tfw_http_msg_alloc_from_pool(TfwMsgIter *it, TfwPool* pool, size_t size) { int r; bool np; - char* addr; + void *addr; struct skb_shared_info *si = skb_shinfo(it->skb); addr = tfw_pool_alloc_not_align_np(pool, size, &np); if (!addr) - return NULL; + return ERR_PTR(-ENOMEM); if (np || it->frag == -1) { it->frag++; r = ss_skb_add_frag(it->skb_head, &it->skb, addr, &it->frag, size); if (unlikely(r)) - return NULL; + return ERR_PTR(r); } else { skb_frag_size_add(&si->frags[it->frag], size); } @@ -1223,7 +1223,7 @@ __tfw_http_msg_expand_from_pool(TfwHttpMsg *hm, const TfwStr *str, void cpy(void *dest, const void *src, size_t n)) { int r; - char *addr; + void *addr; const TfwStr *c, *end; unsigned int room, skb_room, n_copy, rlen, off, acc = 0; TfwMsgIter *it = &hm->iter; @@ -1290,8 +1290,8 @@ __tfw_http_msg_expand_from_pool(TfwHttpMsg *hm, const TfwStr *str, n_copy = min(n_copy, skb_room); addr = tfw_http_msg_alloc_from_pool(it, pool, n_copy); - if (unlikely(!addr)) - return -ENOMEM; + if (IS_ERR(addr)) + return PTR_ERR(addr); cpy(addr, c->data + off, n_copy); rlen -= n_copy; From 137cc5c45996e725193cad36188a6b49412b9c1c Mon Sep 17 00:00:00 2001 From: Constantine Date: Wed, 22 Jan 2025 14:50:17 +0200 Subject: [PATCH 41/44] Safe request cleanup Free cleanup structure in safe way, to prevent use-after-free. We free clean structure immediately after error to get free memory to create the response. --- fw/http.c | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/fw/http.c b/fw/http.c index 435f26d6f3..601b5bec22 100644 --- a/fw/http.c +++ b/fw/http.c @@ -1753,7 +1753,7 @@ do { \ } static void -__tfw_http_msg_cleanup(TfwHttpMsgCleanup *cleanup) +__tfw_http_free_cleanup(TfwHttpMsgCleanup *cleanup) { int i; struct sk_buff *skb; @@ -1765,6 +1765,15 @@ __tfw_http_msg_cleanup(TfwHttpMsgCleanup *cleanup) put_page(cleanup->pages[i]); } +static void +__tfw_http_req_cleanup(TfwHttpReq *req) +{ + if (!req->cleanup) + return; + __tfw_http_free_cleanup(req->cleanup); + req->cleanup = NULL; +} + /** * Try to mark server as suspended. * In case of HM is active do it, otherwise left unchanged. @@ -2712,8 +2721,7 @@ tfw_http_req_destruct(void *msg) if (req->stale_ce) tfw_cache_put_entry(req->node, req->stale_ce); - if (req->cleanup) - __tfw_http_msg_cleanup(req->cleanup); + __tfw_http_req_cleanup(req); } /** @@ -3749,7 +3757,9 @@ tfw_h1_adjust_req(TfwHttpReq *req) return r; clean: - __tfw_http_msg_cleanup(req->cleanup); + T_DBG("%s: req [%p] adjusting has failed with code %i\n", __func__, req, + r); + __tfw_http_req_cleanup(req); return r; } @@ -4307,8 +4317,8 @@ tfw_h2_adjust_req(TfwHttpReq *req) return 0; err: ss_skb_queue_purge(&new_head); - __tfw_http_msg_cleanup(req->cleanup); - T_DBG3("%s: req [%p] convertation to http1.1 has failed\n", + __tfw_http_req_cleanup(req); + T_DBG("%s: req [%p] convertation to http1.1 has failed\n", __func__, req); return -EINVAL; } @@ -4490,7 +4500,7 @@ tfw_http_adjust_resp(TfwHttpResp *resp) r = tfw_http_msg_expand_from_pool(hm, &crlf); clean: - __tfw_http_msg_cleanup(&cleanup); + __tfw_http_free_cleanup(&cleanup); return r; } @@ -5679,11 +5689,11 @@ tfw_h2_resp_encode_headers(TfwHttpResp *resp) req, resp); SS_SKB_QUEUE_DUMP(&resp->msg.skb_head); - __tfw_http_msg_cleanup(&cleanup); + __tfw_http_free_cleanup(&cleanup); return 0; clean: - __tfw_http_msg_cleanup(&cleanup); + __tfw_http_free_cleanup(&cleanup); return r; } From aad56345e014ed51edbcba9d87c1f671528320aa Mon Sep 17 00:00:00 2001 From: Constantine Date: Wed, 22 Jan 2025 15:17:27 +0200 Subject: [PATCH 42/44] Update copyright year --- fw/cache.c | 2 +- fw/hpack.c | 2 +- fw/http.h | 2 +- fw/http_limits.c | 2 +- fw/http_match.c | 2 +- fw/http_msg.h | 2 +- fw/http_sess.c | 2 +- fw/http_types.h | 2 +- fw/ss_skb.h | 2 +- fw/str.c | 2 +- fw/str.h | 2 +- fw/t/unit/test_http_msg.c | 2 +- fw/t/unit/test_http_parser_common.h | 2 +- fw/vhost.c | 2 +- fw/vhost.h | 2 +- 15 files changed, 15 insertions(+), 15 deletions(-) diff --git a/fw/cache.c b/fw/cache.c index 717bd6cc4d..3b1c3cd09f 100644 --- a/fw/cache.c +++ b/fw/cache.c @@ -4,7 +4,7 @@ * HTTP cache (RFC 7234). * * Copyright (C) 2014 NatSys Lab. (info@natsys-lab.com). - * Copyright (C) 2015-2024 Tempesta Technologies, Inc. + * Copyright (C) 2015-2025 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 diff --git a/fw/hpack.c b/fw/hpack.c index 885adfdf34..320be2db61 100644 --- a/fw/hpack.c +++ b/fw/hpack.c @@ -1,7 +1,7 @@ /** * Tempesta FW * - * Copyright (C) 2019-2024 Tempesta Technologies, Inc. + * Copyright (C) 2019-2025 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 diff --git a/fw/http.h b/fw/http.h index d082abba4c..2ebee80eeb 100644 --- a/fw/http.h +++ b/fw/http.h @@ -2,7 +2,7 @@ * Tempesta FW * * Copyright (C) 2014 NatSys Lab. (info@natsys-lab.com). - * Copyright (C) 2015-2024 Tempesta Technologies, Inc. + * Copyright (C) 2015-2025 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 diff --git a/fw/http_limits.c b/fw/http_limits.c index 9981d72284..201d60e6e5 100644 --- a/fw/http_limits.c +++ b/fw/http_limits.c @@ -4,7 +4,7 @@ * Interface to classification modules. * * Copyright (C) 2014 NatSys Lab. (info@natsys-lab.com). - * Copyright (C) 2015-2024 Tempesta Technologies, Inc. + * Copyright (C) 2015-2025 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 diff --git a/fw/http_match.c b/fw/http_match.c index 8ebab706b3..be8941708e 100644 --- a/fw/http_match.c +++ b/fw/http_match.c @@ -51,7 +51,7 @@ * - Case-sensitive matching for headers when required by RFC. * * Copyright (C) 2014 NatSys Lab. (info@natsys-lab.com). - * Copyright (C) 2015-2024 Tempesta Technologies, Inc. + * Copyright (C) 2015-2025 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 diff --git a/fw/http_msg.h b/fw/http_msg.h index 920ec3da19..d063985f01 100644 --- a/fw/http_msg.h +++ b/fw/http_msg.h @@ -2,7 +2,7 @@ * Tempesta FW * * Copyright (C) 2014 NatSys Lab. (info@natsys-lab.com). - * Copyright (C) 2015-2024 Tempesta Technologies, Inc. + * Copyright (C) 2015-2025 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 diff --git a/fw/http_sess.c b/fw/http_sess.c index 0a35af5854..861287cfd9 100644 --- a/fw/http_sess.c +++ b/fw/http_sess.c @@ -29,7 +29,7 @@ * JS challenge client should execute it and send new request with * appropriate cookie just in time. * - * Copyright (C) 2015-2024 Tempesta Technologies, Inc. + * Copyright (C) 2015-2025 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 diff --git a/fw/http_types.h b/fw/http_types.h index 3f9300ec4d..3a3710a1c2 100644 --- a/fw/http_types.h +++ b/fw/http_types.h @@ -1,7 +1,7 @@ /** * Tempesta FW * - * Copyright (C) 2018-2024 Tempesta Technologies, Inc. + * Copyright (C) 2018-2025 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 diff --git a/fw/ss_skb.h b/fw/ss_skb.h index 3ca7b27965..002aa56018 100644 --- a/fw/ss_skb.h +++ b/fw/ss_skb.h @@ -3,7 +3,7 @@ * * Synchronous Sockets API for Linux socket buffers manipulation. * - * Copyright (C) 2015-2024 Tempesta Technologies, Inc. + * Copyright (C) 2015-2025 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 diff --git a/fw/str.c b/fw/str.c index ea59678f5f..632ce12703 100644 --- a/fw/str.c +++ b/fw/str.c @@ -6,7 +6,7 @@ * configuration phase. * * Copyright (C) 2014 NatSys Lab. (info@natsys-lab.com). - * Copyright (C) 2015-2024 Tempesta Technologies, Inc. + * Copyright (C) 2015-2025 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 diff --git a/fw/str.h b/fw/str.h index 25a4b9da3d..0ae5e0070c 100644 --- a/fw/str.h +++ b/fw/str.h @@ -44,7 +44,7 @@ * the number of chunks in a compound string. Zero means a plain string. * Copyright (C) 2014 NatSys Lab. (info@natsys-lab.com). - * Copyright (C) 2015-2024 Tempesta Technologies, Inc. + * Copyright (C) 2015-2025 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 diff --git a/fw/t/unit/test_http_msg.c b/fw/t/unit/test_http_msg.c index 878a39472d..34734a2313 100644 --- a/fw/t/unit/test_http_msg.c +++ b/fw/t/unit/test_http_msg.c @@ -1,7 +1,7 @@ /** * Tempesta FW * - * Copyright (C) 2023 Tempesta Technologies, Inc. + * Copyright (C) 2023-2025 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 diff --git a/fw/t/unit/test_http_parser_common.h b/fw/t/unit/test_http_parser_common.h index 1a4c4e8517..ec617a7e3f 100644 --- a/fw/t/unit/test_http_parser_common.h +++ b/fw/t/unit/test_http_parser_common.h @@ -2,7 +2,7 @@ * Tempesta FW * * Copyright (C) 2014 NatSys Lab. (info@natsys-lab.com). - * Copyright (C) 2015-2024 Tempesta Technologies, Inc. + * Copyright (C) 2015-2025 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 diff --git a/fw/vhost.c b/fw/vhost.c index c514bcd1c2..fdf4641de1 100644 --- a/fw/vhost.c +++ b/fw/vhost.c @@ -1,7 +1,7 @@ /** * Tempesta FW * - * Copyright (C) 2016-2024 Tempesta Technologies, Inc. + * Copyright (C) 2016-2025 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 diff --git a/fw/vhost.h b/fw/vhost.h index e7423ebb54..efbbcc0dfc 100644 --- a/fw/vhost.h +++ b/fw/vhost.h @@ -1,7 +1,7 @@ /** * Tempesta FW * - * Copyright (C) 2016-2024 Tempesta Technologies, Inc. + * Copyright (C) 2016-2025 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 From 701a6b5ff2eb6cf9af636faff7ae7a0cc4ed1323 Mon Sep 17 00:00:00 2001 From: Constantine Date: Thu, 23 Jan 2025 12:25:01 +0200 Subject: [PATCH 43/44] access_log: Write method as string Write method string directly to access log instead of using method id and table with static names. It gives an abillity to write to log methods that not listed in `tfw_http_meth_t`. --- fw/access_log.c | 51 ++++++++----------------------------------------- 1 file changed, 8 insertions(+), 43 deletions(-) diff --git a/fw/access_log.c b/fw/access_log.c index fa5905d72d..23993346e5 100644 --- a/fw/access_log.c +++ b/fw/access_log.c @@ -45,7 +45,7 @@ FIXED(" \"") \ UNTRUNCATABLE(vhost) \ FIXED("\" \"") \ - UNTRUNCATABLE(method) \ + TRUNCATABLE(method) \ FIXED(" ") \ TRUNCATABLE(uri) \ FIXED(" ") \ @@ -136,33 +136,6 @@ get_http_header_value(char http_version, TfwStr *line) return result; } - -/* Helpers for const=>name conversions for http methods and versions */ -static const struct { -# define MAX_HTTP_METHOD_NAME_LEN 10 - char name[MAX_HTTP_METHOD_NAME_LEN]; - u8 len; -} http_methods[] = { -#define STR_METHOD(name) [TFW_HTTP_METH_ ## name] = { #name, sizeof(#name) - 1 } - STR_METHOD(COPY), - STR_METHOD(DELETE), - STR_METHOD(GET), - STR_METHOD(HEAD), - STR_METHOD(LOCK), - STR_METHOD(MKCOL), - STR_METHOD(MOVE), - STR_METHOD(OPTIONS), - STR_METHOD(PATCH), - STR_METHOD(POST), - STR_METHOD(PROPFIND), - STR_METHOD(PROPPATCH), - STR_METHOD(PUT), - STR_METHOD(TRACE), - STR_METHOD(UNLOCK), - STR_METHOD(PURGE), -#undef STR_METHOD -}; - static const struct { # define MAX_HTTP_VERSION_LEN 9 char name[MAX_HTTP_VERSION_LEN]; @@ -405,7 +378,7 @@ do_access_log_req_dmesg(TfwHttpReq *req, int resp_status, unsigned long resp_con { char *buf = this_cpu_ptr(access_log_buf); char *p = buf, *end = buf + ACCESS_LOG_BUF_SIZE; - BasicStr client_ip, vhost, method, version; + BasicStr client_ip, vhost, version; /* These fields are only here to hold estimation of appropriate fields * length in characters */ BasicStr status, content_length, ja5_tls, ja5_http; @@ -436,18 +409,6 @@ do_access_log_req_dmesg(TfwHttpReq *req, int resp_status, unsigned long resp_con #define ARG_vhost , (int)vhost.len, vhost.data vhost = req->vhost && req->vhost->name.len ? req->vhost->name : missing; - /* method */ -#define FMT_method "%.*s" -#define ARG_method , (int)method.len, method.data - if (req->method < sizeof(http_methods) / sizeof(*http_methods) - && http_methods[req->method].len != 0) - { - method.data = (char *)http_methods[req->method].name; - method.len = http_methods[req->method].len; - } else { - method = missing; - } - /* http version */ #define FMT_version "%.*s" #define ARG_version , (int)version.len, version.data @@ -479,6 +440,12 @@ do_access_log_req_dmesg(TfwHttpReq *req, int resp_status, unsigned long resp_con #define ADD_HDR(id, tfw_hdr_id) \ truncated_in[id] = get_http_header_value(req->version, \ req->h_tbl->tbl + tfw_hdr_id); + + if (!TFW_MSG_H2(req)) + truncated_in[idx_method] = req->h_tbl->tbl[TFW_HTTP_METHOD]; + else + ADD_HDR(idx_method, TFW_HTTP_METHOD); + ADD_HDR(idx_referer, TFW_HTTP_HDR_REFERER); ADD_HDR(idx_user_agent, TFW_HTTP_HDR_USER_AGENT); @@ -538,8 +505,6 @@ do_access_log_req_dmesg(TfwHttpReq *req, int resp_status, unsigned long resp_con #undef FMT_status #undef ARG_version #undef FMT_version -#undef ARG_method -#undef FMT_method #undef ARG_vhost #undef FMT_vhost #undef ARG_client_ip From b6372fcf2f2de226f09bb7ab75c90dfae974925c Mon Sep 17 00:00:00 2001 From: Constantine Date: Wed, 29 Jan 2025 15:25:27 +0200 Subject: [PATCH 44/44] Fix HEAD to GET overriding Separate HEAD to GET from PURGE to GET logic --- fw/http.c | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/fw/http.c b/fw/http.c index 601b5bec22..772c449364 100644 --- a/fw/http.c +++ b/fw/http.c @@ -4330,7 +4330,7 @@ tfw_h2_adjust_req(TfwHttpReq *req) * headers will be avoided. */ static int -tfw_h1_purge_resp_clean(TfwHttpResp *resp, TfwHttpMsgCleanup *cleanup) +tfw_h1_resp_set_empty_skb_head(TfwHttpResp *resp, TfwHttpMsgCleanup *cleanup) { struct sk_buff *nskb; @@ -4340,6 +4340,7 @@ tfw_h1_purge_resp_clean(TfwHttpResp *resp, TfwHttpMsgCleanup *cleanup) nskb = ss_skb_alloc(0); if (unlikely(!nskb)) return -ENOMEM; + nskb->mark = resp->msg.skb_head->mark; ss_skb_queue_tail(&resp->msg.skb_head, nskb); @@ -4389,7 +4390,7 @@ tfw_http_adjust_resp(TfwHttpResp *resp) /* Clean current reponse skb_head if body is exists. */ if (resp->body.len > 0) { - r = tfw_h1_purge_resp_clean(resp, &cleanup); + r = tfw_h1_resp_set_empty_skb_head(resp, &cleanup); if (unlikely(r)) goto clean; @@ -4413,15 +4414,17 @@ tfw_http_adjust_resp(TfwHttpResp *resp) if (unlikely(r)) goto clean; - /* - * For a response to PURGE request we drop the body. - * Add "content-length: 0" header. - */ - r = tfw_http_msg_expand_from_pool(hm, &clen); - if (unlikely(r)) - goto clean; + if (test_bit(TFW_HTTP_B_PURGE_GET, req->flags)) { + /* + * For a response to PURGE request we drop the body. + * Add "content-length: 0" header. + */ + r = tfw_http_msg_expand_from_pool(hm, &clen); + if (unlikely(r)) + goto clean; - hdr_start = TFW_HTTP_HDR_CONTENT_TYPE; + hdr_start = TFW_HTTP_HDR_CONTENT_TYPE; + } } else { /* Response for regular request. */ tfw_msg_transform_setup(iter, resp->msg.skb_head);