Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Frame forwarded h2 message to never overflow max frame size on receive side #1400

Merged
merged 17 commits into from
May 28, 2020
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
162 changes: 98 additions & 64 deletions tempesta_fw/cache.c
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* HTTP cache (RFC 7234).
*
* Copyright (C) 2014 NatSys Lab. (info@natsys-lab.com).
* Copyright (C) 2015-2019 Tempesta Technologies, Inc.
* Copyright (C) 2015-2020 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
Expand Down Expand Up @@ -744,9 +744,7 @@ tfw_cache_send_304(TfwHttpReq *req, TfwCacheEntry *ce)
int r, i;
krizhanovsky marked this conversation as resolved.
Show resolved Hide resolved
TfwMsgIter *it;
TfwHttpResp *resp;
TfwFrameHdr frame_hdr;
struct sk_buff **skb_head;
unsigned char buf[FRAME_HEADER_SIZE];
unsigned int stream_id = 0;
unsigned long h_len = 0;
TdbVRec *trec = &ce->trec;
Expand Down Expand Up @@ -805,17 +803,9 @@ tfw_cache_send_304(TfwHttpReq *req, TfwCacheEntry *ce)
return;
}

if (h_len > FRAME_MAX_LENGTH)
if (tfw_h2_make_frames(resp, stream_id, h_len, true, false))
krizhanovsky marked this conversation as resolved.
Show resolved Hide resolved
goto err_setup;

frame_hdr.stream_id = stream_id;
frame_hdr.length = h_len;
frame_hdr.type = HTTP2_HEADERS;
frame_hdr.flags = HTTP2_F_END_HEADERS | HTTP2_F_END_STREAM;

tfw_h2_pack_frame_header(buf, &frame_hdr);
memcpy_fast((*skb_head)->data, buf, sizeof(buf));

tfw_h2_resp_fwd(resp);

return;
Expand Down Expand Up @@ -1847,54 +1837,124 @@ tfw_cache_purge_method(TfwHttpReq *req)
tfw_http_send_resp(req, 200, "purge: success");
}

/**
* Add page from cache into response. Different strategies are used to
* avoid extra data copying depending on client connection type:
* - for http connections - pages are reused in skbs and SKBTX_SHARED_FRAG is
* set to avoid any data copies.
* - for https connections - pages are reused in skbs and SKBTX_SHARED_FRAG is
* set, but in-place crypto operations are not allowed, so data copy happens
* right before data is pushed into network.
* - for h2 connections - copying happens on constructing responses from
* cache, and SKBTX_SHARED_FRAG is left unset allowing in-place crypto
* operations.
*
* Since we can't encrypt shared data in-place we always copy it, so we need
* reserve some space in cached pages to avoid extra skb fragmentation. Since
* body fragments are stored in cache by pages with at least cache record
* header preceding, which is bigger than h2 frame header, it's always possible
* to fit body fragment and h2 header into a single page. So there is no need
* to actually reserve any space for h2 frame header.
*/
static int
tfw_cache_add_body_page(TfwMsgIter *it, char *p, int sz, TfwFrameHdr *frame_hdr,
bool h2, bool last_frag)
{
int off;
struct page *page;

/*
* @sz is guarantied to be bigger than FRAME_HEADER_SIZE if frame header
* is stored in cache before data. True for first fragment.
*/
if (h2) {
char *new_p;
if (!(page = alloc_page(GFP_ATOMIC))) {
return -ENOMEM;
}
new_p = page_address(page);
off = 0;
frame_hdr->flags = last_frag ? HTTP2_F_END_STREAM : 0;
frame_hdr->length = sz;
memcpy_fast(new_p + FRAME_HEADER_SIZE, p, sz);
sz += FRAME_HEADER_SIZE;
tfw_h2_pack_frame_header(new_p, frame_hdr);
}
else {
off = ((unsigned long)p & ~PAGE_MASK);
page = virt_to_page(p);
}

skb_fill_page_desc(it->skb, it->frag, page, off, sz);
skb_frag_ref(it->skb, it->frag);
ss_skb_adjust_data_len(it->skb, sz);
++it->frag;

return 0;
}

/**
* Build the message body as paged fragments of skb.
* See do_tcp_sendpages() as reference.
*/
static int
tfw_cache_build_resp_body(TDB *db, TdbVRec *trec, TfwMsgIter *it, char *p)
tfw_cache_build_resp_body(TDB *db, TdbVRec *trec, TfwMsgIter *it, char *p,
unsigned long body_sz, bool h2, unsigned int stream_id)
{
int r;
TfwFrameHdr frame_hdr = {.stream_id = stream_id, .type = HTTP2_DATA};

if (WARN_ON_ONCE(!it->skb_head))
return -EINVAL;
/*
* If all skbs/frags are used up (see @tfw_http_msg_expand_data()),
* create new skb with empty frags to reference the cached body;
* otherwise, use next empty frag in current skb.
* otherwise, use next empty frag in current skb. Constructing h2
* responses from cache always imply data copies, for SKBTX_SHARED_FRAG
* skbs, copy will happen twice. If body is relatively small keep
* using existing skb and allow double copy, otherwise allocate a new
* skb even if previous is not fully populated.
*/
if (!it->skb) {
if (!it->skb || (++it->frag >= MAX_SKB_FRAGS)
|| (h2 && (skb_shinfo(it->skb)->tx_flags & SKBTX_SHARED_FRAG)
&& body_sz > PAGE_SIZE))
krizhanovsky marked this conversation as resolved.
Show resolved Hide resolved
{
if ((r = tfw_msg_iter_append_skb(it)))
return r;
} else {
++it->frag;
if (it->frag >= MAX_SKB_FRAGS
&& (r = tfw_msg_iter_append_skb(it)))
return r;
if (h2)
skb_shinfo(it->skb)->tx_flags &= ~SKBTX_SHARED_FRAG;
}
BUG_ON(it->frag < 0);
if (WARN_ON_ONCE(it->frag < 0))
return -EINVAL;

while (1) {
int off, f_size;

/* TDB keeps data by pages and we can reuse the pages. */
off = (unsigned long)p & ~PAGE_MASK;
f_size = trec->data + trec->len - p;
/*
* The body is stored in native h2 format, with reserved space
* for h2 headers. Skip page fragment if it's too short to store
* header.
*/
if (f_size) {
skb_fill_page_desc(it->skb, it->frag, virt_to_page(p),
off, f_size);
skb_frag_ref(it->skb, it->frag);
ss_skb_adjust_data_len(it->skb, f_size);
++it->frag;
body_sz -= f_size;
r = tfw_cache_add_body_page(it, p, f_size, &frame_hdr,
h2, !body_sz);
if (r)
return r;
}
if (!(trec = tdb_next_rec_chunk(db, trec)))
break;
BUG_ON(trec && !f_size);
if (WARN_ON_ONCE(!f_size))
return -EINVAL;
krizhanovsky marked this conversation as resolved.
Show resolved Hide resolved
p = trec->data;

if (it->frag == MAX_SKB_FRAGS
&& (r = tfw_msg_iter_append_skb(it)))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems we should here also set SKBTX_SHARED_FRAG flag as we do this above if sh_frag

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, we don't need to. tfw_msg_iter_append_skb() not only add a new empty skb to the list, but also sets the same tx_flags last skb had.

{
return r;
}
}

return 0;
Expand Down Expand Up @@ -1981,14 +2041,12 @@ tfw_cache_build_resp(TfwHttpReq *req, TfwCacheEntry *ce, time_t lifetime,
int h;
TfwMsgIter *it;
TfwHttpResp *resp;
char *p, *head_ptr;
char *p;
TfwHttpTransIter *mit;
TfwFrameHdr frame_hdr;
TDB *db = node_db();
unsigned long h_len = 0;
struct sk_buff **skb_head;
TdbVRec *trec = &ce->trec;
unsigned char buf[FRAME_HEADER_SIZE];
TfwHdrMods *h_mods = tfw_vhost_get_hdr_mods(req->location, req->vhost,
TFW_VHOST_HDRMOD_RESP);
/*
Expand Down Expand Up @@ -2074,46 +2132,22 @@ tfw_cache_build_resp(TfwHttpReq *req, TfwCacheEntry *ce, time_t lifetime,

h_len += mit->acc_len;

if (h_len > FRAME_MAX_LENGTH || ce->body_len > FRAME_MAX_LENGTH)
goto free;

frame_hdr.stream_id = stream_id;
/*
* Set header for HTTP/2 DATA frame, if body part of response
* exists.
* Split response to h2 frames. Mustn't write to body skb fragments,
* since they're shared between all the clients.
*/
if (ce->body_len) {
TfwStr h_body = {
.data = buf,
.len = sizeof(buf)
};

frame_hdr.length = ce->body_len;
frame_hdr.type = HTTP2_DATA;
frame_hdr.flags = HTTP2_F_END_STREAM;
tfw_h2_pack_frame_header(buf, &frame_hdr);

if (tfw_http_msg_expand_data(it, skb_head, &h_body, NULL))
goto free;
}

/* Set header for HTTP/2 HEADERS frame. */
frame_hdr.length = h_len;
frame_hdr.type = HTTP2_HEADERS;
frame_hdr.flags = HTTP2_F_END_HEADERS;

if (!ce->body_len)
frame_hdr.flags |= HTTP2_F_END_STREAM;

head_ptr = (*skb_head)->data;
tfw_h2_pack_frame_header(buf, &frame_hdr);
memcpy_fast(head_ptr, buf, sizeof(buf));
resp->body.len = ce->body_len;
if (tfw_h2_make_frames(resp, stream_id, h_len, true, true))
goto free;
it->skb = ss_skb_peek_tail(&it->skb_head);
it->frag = skb_shinfo(it->skb)->nr_frags - 1;

write_body:
/* 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) {
if (tfw_cache_build_resp_body(db, trec, it, p))
if (tfw_cache_build_resp_body(db, trec, it, p, ce->body_len,
TFW_MSG_H2(req), stream_id))
goto free;
if (!TFW_MSG_H2(req)
&& test_bit(TFW_HTTP_B_CHUNKED, resp->flags)
Expand Down
Loading