Skip to content

Commit 7047bf8

Browse files
authored
Merge pull request #2343 from tempesta-tech/kt-533-expect
Expect header
2 parents 36db27b + 56f2aa1 commit 7047bf8

File tree

6 files changed

+355
-31
lines changed

6 files changed

+355
-31
lines changed

fw/http.c

Lines changed: 256 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@ bool allow_empty_body_content_type;
155155
#define S_HTTP "http://"
156156
#define S_HTTPS "https://"
157157

158+
#define S_100 "HTTP/1.1 100 Continue"
158159
#define S_200 "HTTP/1.1 200 OK"
159160
#define S_301 "HTTP/1.1 301 Moved Permanently"
160161
#define S_302 "HTTP/1.1 302 Found"
@@ -204,6 +205,30 @@ bool allow_empty_body_content_type;
204205
* Array with predefined response data
205206
*/
206207
static TfwStr http_predef_resps[RESP_NUM] = {
208+
[RESP_100] = {
209+
.chunks = (TfwStr []){
210+
{ .data = S_100, .len = SLEN(S_100) },
211+
{ .data = S_CRLF S_F_DATE,
212+
.len = SLEN(S_CRLF S_F_DATE), .hpack_idx = 33 },
213+
{ .data = NULL, .len = SLEN(S_V_DATE) },
214+
{ .data = S_CRLF S_F_CONTENT_LENGTH,
215+
.len = SLEN(S_CRLF S_F_CONTENT_LENGTH),
216+
.hpack_idx = 28 },
217+
{ .data = "0", .len = SLEN("0") },
218+
{ .data = S_CRLF S_F_SERVER,
219+
.len = SLEN(S_CRLF S_F_SERVER), .hpack_idx = 54 },
220+
{ .data = TFW_NAME "/" TFW_VERSION,
221+
.len = SLEN(TFW_NAME "/" TFW_VERSION) },
222+
{ .data = S_CRLF S_CRLF, .len = SLEN(S_CRLF S_CRLF) },
223+
{ .data = NULL, .len = 0 }, /* Reserved for Connection */
224+
{ .data = NULL, .len = 0 }, /* Reserved for CRLFCRLF */
225+
{ .data = NULL, .len = 0 }, /* Body */
226+
},
227+
.len = SLEN(S_200 S_CRLF S_F_DATE S_V_DATE S_CRLF
228+
S_F_CONTENT_LENGTH "0" S_CRLF S_F_SERVER TFW_NAME
229+
"/" TFW_VERSION S_CRLF S_CRLF),
230+
.nchunks = 11
231+
},
207232
[RESP_200] = {
208233
.chunks = (TfwStr []){
209234
{ .data = S_200, .len = SLEN(S_200) },
@@ -688,6 +713,33 @@ tfw_h2_prep_resp(TfwHttpResp *resp, unsigned short status, TfwStr *msg)
688713
return r;
689714
}
690715

716+
static int
717+
tfw_h1_write_resp(TfwHttpResp *resp, unsigned short status, TfwStr *msg)
718+
{
719+
TfwMsgIter it;
720+
TfwStr *body = NULL;
721+
int r = 0;
722+
TfwStr *c, *end, *field_c, *field_end;
723+
724+
if ((r = tfw_http_msg_setup((TfwHttpMsg *)resp, &it, msg->len, 0)))
725+
return r;
726+
727+
body = TFW_STR_BODY_CH(msg);
728+
resp->status = status;
729+
resp->content_length = body->len;
730+
731+
TFW_STR_FOR_EACH_CHUNK(c, msg, end) {
732+
if (c->data) {
733+
TFW_STR_FOR_EACH_CHUNK(field_c, c, field_end) {
734+
if ((r = tfw_msg_write(&it, field_c)))
735+
return r;
736+
}
737+
}
738+
}
739+
740+
return r;
741+
}
742+
691743
/*
692744
* Preparing custom HTTP1 response to a client.
693745
* Set the "Connection:" header field if it was present in the request.
@@ -696,10 +748,6 @@ static int
696748
tfw_h1_prep_resp(TfwHttpResp *resp, unsigned short status, TfwStr *msg)
697749
{
698750
TfwHttpReq *req = resp->req;
699-
TfwMsgIter it;
700-
TfwStr *body = NULL;
701-
int r = 0;
702-
TfwStr *c, *end, *field_c, *field_end;
703751

704752
/* Set "Connection:" header field if needed. */
705753
if (test_bit(TFW_HTTP_B_CONN_CLOSE, req->flags)) {
@@ -720,23 +768,7 @@ tfw_h1_prep_resp(TfwHttpResp *resp, unsigned short status, TfwStr *msg)
720768
msg->len += SLEN(S_CRLF S_F_CONNECTION) + SLEN(S_V_CONN_KA);
721769
}
722770

723-
if ((r = tfw_http_msg_setup((TfwHttpMsg *)resp, &it, msg->len, 0)))
724-
return r;
725-
726-
body = TFW_STR_BODY_CH(msg);
727-
resp->status = status;
728-
resp->content_length = body->len;
729-
730-
TFW_STR_FOR_EACH_CHUNK(c, msg, end) {
731-
if (c->data) {
732-
TFW_STR_FOR_EACH_CHUNK(field_c, c, field_end) {
733-
if ((r = tfw_msg_write(&it, field_c)))
734-
return r;
735-
}
736-
}
737-
}
738-
739-
return r;
771+
return tfw_h1_write_resp(resp, status, msg);
740772
}
741773

742774
/**
@@ -1033,6 +1065,8 @@ static inline resp_code_t
10331065
tfw_http_enum_resp_code(int status)
10341066
{
10351067
switch(status) {
1068+
case 100:
1069+
return RESP_100;
10361070
case 200:
10371071
return RESP_200;
10381072
case 400:
@@ -2072,6 +2106,7 @@ tfw_http_conn_fwd_unsent(TfwSrvConn *srv_conn, struct list_head *eq)
20722106

20732107
list_for_each_entry_safe_from(req, tmp, fwd_queue, fwd_list) {
20742108
int ret = tfw_http_req_fwd_single(srv_conn, srv, req, eq);
2109+
20752110
/*
20762111
* In case of busy work queue and absence of forwarded but
20772112
* unanswered request(s) in connection, the forwarding procedure
@@ -3605,6 +3640,18 @@ tfw_h1_rewrite_head_to_get(struct sk_buff **head_p)
36053640
return tfw_h1_rewrite_method_to_get(head_p, 1);
36063641
}
36073642

3643+
static int
3644+
tfw_h1_req_del_expect_hdr(TfwHttpMsg *hm)
3645+
{
3646+
static TfwStr val = {};
3647+
3648+
if (test_bit(TFW_HTTP_B_EXPECT_CONTINUE, hm->flags))
3649+
return tfw_http_msg_hdr_xfrm_str(hm, &val, TFW_HTTP_HDR_EXPECT,
3650+
false);
3651+
3652+
return 0;
3653+
}
3654+
36083655
/**
36093656
* Adjust the request before proxying it to real server.
36103657
*/
@@ -3634,6 +3681,10 @@ tfw_h1_adjust_req(TfwHttpReq *req)
36343681
return r;
36353682
}
36363683

3684+
r = tfw_h1_req_del_expect_hdr(hm);
3685+
if (r)
3686+
return r;
3687+
36373688
r = tfw_http_add_x_forwarded_for(hm);
36383689
if (r)
36393690
return r;
@@ -4346,16 +4397,25 @@ __tfw_http_resp_fwd(TfwCliConn *cli_conn, struct list_head *ret_queue)
43464397
TfwHttpReq *req, *tmp;
43474398

43484399
list_for_each_entry_safe(req, tmp, ret_queue, msg.seq_list) {
4400+
bool send_cont;
4401+
43494402
BUG_ON(!req->resp);
4350-
tfw_http_resp_init_ss_flags(req->resp);
4403+
send_cont = test_bit(TFW_HTTP_B_CONTINUE_RESP,
4404+
req->resp->flags);
4405+
if (!send_cont)
4406+
tfw_http_resp_init_ss_flags(req->resp);
43514407
if (tfw_cli_conn_send(cli_conn, (TfwMsg *)req->resp)) {
4408+
TFW_INC_STAT_BH(serv.msgs_otherr);
43524409
tfw_connection_close((TfwConn *)cli_conn, true);
43534410
return;
43544411
}
43554412
TFW_INC_STAT_BH(serv.msgs_forwarded);
43564413
tfw_inc_global_hm_stats(req->resp->status);
43574414
list_del_init(&req->msg.seq_list);
4358-
tfw_http_resp_pair_free(req);
4415+
if (!send_cont)
4416+
tfw_http_resp_pair_free(req);
4417+
else
4418+
tfw_http_msg_free(req->pair);
43594419
}
43604420
}
43614421

@@ -4462,9 +4522,6 @@ tfw_http_resp_fwd(TfwHttpResp *resp)
44624522

44634523
__tfw_http_resp_fwd(cli_conn, &ret_queue);
44644524

4465-
spin_unlock_bh(&cli_conn->ret_qlock);
4466-
tfw_connection_put((TfwConn *)(cli_conn));
4467-
44684525
/* Zap request/responses that were not sent due to an error. */
44694526
if (!list_empty(&ret_queue)) {
44704527
TfwHttpReq *tmp;
@@ -4473,10 +4530,17 @@ tfw_http_resp_fwd(TfwHttpResp *resp)
44734530
__func__, cli_conn, req->resp);
44744531
BUG_ON(!req->resp);
44754532
list_del_init(&req->msg.seq_list);
4476-
tfw_http_resp_pair_free(req);
4533+
if (!test_bit(TFW_HTTP_B_CONTINUE_RESP,
4534+
req->resp->flags))
4535+
tfw_http_resp_pair_free(req);
4536+
else
4537+
tfw_http_msg_free(req->pair);
44774538
TFW_INC_STAT_BH(serv.msgs_otherr);
44784539
}
44794540
}
4541+
4542+
spin_unlock_bh(&cli_conn->ret_qlock);
4543+
tfw_connection_put((TfwConn *)(cli_conn));
44804544
}
44814545

44824546
int
@@ -6009,6 +6073,159 @@ tfw_http_check_ja5h_req_limit(TfwHttpReq *req)
60096073
return rate > limit;
60106074
}
60116075

6076+
/*
6077+
* Whether we should delete request with ready 100-continue response from
6078+
* @seq_queue. Delete request when the body or its part received, but request
6079+
* still in @seq_queue with ready 100-continue response that not sent to client.
6080+
*
6081+
* RFC 9110 10.1.1:
6082+
* A server MAY omit sending a 100 (Continue) response if it has already
6083+
* received some or all of the content for the corresponding request, or
6084+
* if the framing indicates that there is no content.
6085+
*/
6086+
static bool
6087+
tfw_http_should_del_continuation_seq_queue(TfwHttpReq *req)
6088+
{
6089+
return test_bit(TFW_HTTP_B_CONTINUE_QUEUED, req->flags);
6090+
}
6091+
6092+
/*
6093+
* Remove request with ready 100-continue response from @seq_queue and free
6094+
* the response.
6095+
*/
6096+
static void
6097+
tfw_http_del_continuation_seq_queue(TfwCliConn *cli_conn, TfwHttpReq *req)
6098+
{
6099+
struct list_head *seq_queue = &cli_conn->seq_queue;
6100+
TfwHttpReq *queued_req = NULL;
6101+
6102+
clear_bit(TFW_HTTP_B_CONTINUE_QUEUED, req->flags);
6103+
6104+
/* Remove request from @seq_queue only if we ensure that it's there.
6105+
* Otherwise request might be in @ret_queue, therefore we can't do
6106+
* that under @seq_qlock.
6107+
*/
6108+
spin_lock_bh(&cli_conn->seq_qlock);
6109+
list_for_each_entry(queued_req, seq_queue, msg.seq_list) {
6110+
if (queued_req != req)
6111+
continue;
6112+
6113+
list_del_init(&req->msg.seq_list);
6114+
tfw_http_msg_free((TfwHttpMsg *)req->resp);
6115+
spin_unlock_bh(&cli_conn->seq_qlock);
6116+
return;
6117+
}
6118+
spin_unlock_bh(&cli_conn->seq_qlock);
6119+
6120+
spin_lock_bh(&cli_conn->ret_qlock);
6121+
/*
6122+
* Need this section to ensure that request sent or removed from
6123+
* @ret_queue due to error. We can't move forward if request still in
6124+
* @ret_queue. In this case we just spin until @ret_queue drained.
6125+
*/
6126+
BUG_ON(!list_empty(&req->msg.seq_list));
6127+
spin_unlock_bh(&cli_conn->ret_qlock);
6128+
}
6129+
6130+
/**
6131+
* Send 100-continue response to the client.
6132+
*
6133+
* When request is the first in the sequence (no pipelined requests), then
6134+
* immediately send 100-continue response to the client, otherwise place
6135+
* request into @seq_queue, the response will be sent later when one of
6136+
* the queued responses will be forwarded by @tfw_http_resp_fwd.
6137+
*/
6138+
static int
6139+
tfw_http_send_continuation(TfwCliConn *cli_conn, TfwHttpReq *req)
6140+
{
6141+
TfwHttpResp *resp;
6142+
struct list_head *seq_queue = &cli_conn->seq_queue;
6143+
TfwStr msg = MAX_PREDEF_RESP;
6144+
6145+
tfw_http_prep_err_resp(req, 100, &msg);
6146+
6147+
if (!(resp = tfw_http_msg_alloc_resp_light(req)))
6148+
goto err;
6149+
6150+
if (tfw_h1_write_resp(resp, 100, &msg)) {
6151+
tfw_http_msg_free((TfwHttpMsg *)resp);
6152+
goto err;
6153+
}
6154+
6155+
spin_lock_bh(&cli_conn->seq_qlock);
6156+
if (list_empty(seq_queue)) {
6157+
/*
6158+
* A queue is empty, don't hold a lock. Next request can be
6159+
* added to the queue only on the current CPU when this
6160+
* request will be processed.
6161+
*/
6162+
spin_unlock_bh(&cli_conn->seq_qlock);
6163+
tfw_connection_get((TfwConn *)(cli_conn));
6164+
if (tfw_cli_conn_send(cli_conn, (TfwMsg *)resp)) {
6165+
tfw_http_msg_free((TfwHttpMsg *)resp);
6166+
tfw_connection_put((TfwConn *)(cli_conn));
6167+
goto err;
6168+
}
6169+
tfw_inc_global_hm_stats(resp->status);
6170+
tfw_http_msg_free((TfwHttpMsg *)resp);
6171+
tfw_connection_put((TfwConn *)(cli_conn));
6172+
} else {
6173+
set_bit(TFW_HTTP_B_CONTINUE_QUEUED, req->flags);
6174+
set_bit(TFW_HTTP_B_CONTINUE_RESP, resp->flags);
6175+
set_bit(TFW_HTTP_B_RESP_READY, resp->flags);
6176+
list_add_tail(&req->msg.seq_list, seq_queue);
6177+
spin_unlock_bh(&cli_conn->seq_qlock);
6178+
}
6179+
6180+
return 0;
6181+
6182+
err:
6183+
TFW_INC_STAT_BH(serv.msgs_otherr);
6184+
return T_BAD;
6185+
}
6186+
6187+
/**
6188+
* Whether we should send 100-continue response.
6189+
*
6190+
* Circumstances in which Tempesta must respond with 100-continue code:
6191+
* 1. Headers are fully parsed.
6192+
* 2. "Expect" header is present in request.
6193+
* 3. Vesrion is HTTP/1.1.
6194+
*
6195+
* RFC 9110 10.1.1:
6196+
* - A server that receives a 100-continue expectation in an HTTP/1.0 request
6197+
* MUST ignore that expectation.
6198+
* - A server MAY omit sending a 100 (Continue) response if it has already
6199+
* received some or all of the content for the corresponding request, or if the
6200+
* framing indicates that there is no content.
6201+
*/
6202+
static bool
6203+
tfw_http_should_handle_expect(TfwHttpReq *req)
6204+
{
6205+
return test_bit(TFW_HTTP_B_HEADERS_PARSED, req->flags) &&
6206+
test_bit(TFW_HTTP_B_EXPECT_CONTINUE, req->flags) &&
6207+
req->version == TFW_HTTP_VER_11;
6208+
}
6209+
6210+
/*
6211+
* Handle `Expect: 100-continue` in the request.
6212+
*/
6213+
static int
6214+
tfw_http_handle_expect_request(TfwCliConn *conn, TfwHttpReq *req)
6215+
{
6216+
if (!req->body.len)
6217+
return tfw_http_send_continuation(conn, req);
6218+
else if (tfw_http_should_del_continuation_seq_queue(req))
6219+
/**
6220+
* Part of the body received, but 100-continue didn't send,
6221+
* however handled. It implies it was queued, try to remove it
6222+
* from queue.
6223+
*/
6224+
tfw_http_del_continuation_seq_queue(conn, req);
6225+
6226+
return T_OK;
6227+
}
6228+
60126229
/**
60136230
* @return zero on success and negative value otherwise.
60146231
* TODO enter the function depending on current GFSM state.
@@ -6147,6 +6364,14 @@ tfw_http_req_process(TfwConn *conn, TfwStream *stream, struct sk_buff *skb,
61476364
"postponed request has been filtered out",
61486365
HTTP2_ECODE_PROTO);
61496366
}
6367+
6368+
if (tfw_http_should_handle_expect(req)) {
6369+
r = tfw_http_handle_expect_request((TfwCliConn *)conn,
6370+
req);
6371+
if (unlikely(r))
6372+
return r;
6373+
}
6374+
61506375
/*
61516376
* T_POSTPONE status means that parsing succeeded
61526377
* but more data is needed to complete it. Lower layers
@@ -6168,6 +6393,10 @@ tfw_http_req_process(TfwConn *conn, TfwStream *stream, struct sk_buff *skb,
61686393
}
61696394
}
61706395

6396+
/* The body received, remove 100-continue from queue. */
6397+
if (unlikely(tfw_http_should_del_continuation_seq_queue(req)))
6398+
tfw_http_del_continuation_seq_queue((TfwCliConn *)conn, req);
6399+
61716400
req->ja5h.method = req->method;
61726401

61736402
if (tfw_http_check_ja5h_req_limit(req)) {

0 commit comments

Comments
 (0)