summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMatus Fabian <matfabia@cisco.com>2024-11-21 17:01:45 +0100
committerFlorin Coras <florin.coras@gmail.com>2024-12-10 05:10:41 +0000
commit82b3cc18260abc8e77375c337cf3f62aafb132ab (patch)
tree3438ee9175feda4940b40ccd497626919b5d014e
parentc4b4cd5e77cc5f3305c90eecf802c601392b7975 (diff)
http: connection upgrade mechanism
Handle "Connection" and "Upgrade" headers in http transport layer which are used to create a tunnel for some other protocol on the same connection. Type: improvement Change-Id: Icf5479f36fbcc7259b157eaad957211be5ea2aae Signed-off-by: Matus Fabian <matfabia@cisco.com>
-rw-r--r--extras/hs-test/http_test.go72
-rw-r--r--extras/hs-test/infra/suite_vpp_proxy.go2
-rw-r--r--src/plugins/hs_apps/http_cli.c36
-rw-r--r--src/plugins/hs_apps/http_client.c23
-rw-r--r--src/plugins/http/http.c204
-rw-r--r--src/plugins/http/http.h270
-rw-r--r--src/plugins/http/http_plugin.rst79
7 files changed, 480 insertions, 206 deletions
diff --git a/extras/hs-test/http_test.go b/extras/hs-test/http_test.go
index 8127fdca0e7..e84e67fe395 100644
--- a/extras/hs-test/http_test.go
+++ b/extras/hs-test/http_test.go
@@ -36,7 +36,7 @@ func init() {
HttpClientErrRespTest, HttpClientPostFormTest, HttpClientGet128kbResponseTest, HttpClientGetResponseBodyTest,
HttpClientGetNoResponseBodyTest, HttpClientPostFileTest, HttpClientPostFilePtrTest, HttpUnitTest,
HttpRequestLineTest, HttpClientGetTimeout, HttpStaticFileHandlerWrkTest, HttpStaticUrlHandlerWrkTest, HttpConnTimeoutTest,
- HttpClientGetRepeat, HttpClientPostRepeat)
+ HttpClientGetRepeat, HttpClientPostRepeat, HttpIgnoreH2UpgradeTest)
RegisterNoTopoSoloTests(HttpStaticPromTest, HttpGetTpsTest, HttpGetTpsInterruptModeTest, PromConcurrentConnectionsTest,
PromMemLeakTest, HttpClientPostMemLeakTest, HttpInvalidClientRequestMemLeakTest, HttpPostTpsTest, HttpPostTpsInterruptModeTest,
PromConsecutiveConnectionsTest)
@@ -1227,12 +1227,12 @@ func HttpContentLengthTest(s *NoTopoSuite) {
validatePostInterfaceStats(s, resp)
resp, err = TcpSendReceive(serverAddress+":80",
- "POST /interface_stats.json HTTP/1.1\r\n Content-Length: 4 \r\n\r\n"+ifName)
+ "POST /interface_stats.json HTTP/1.1\r\nContent-Length: 4 \r\n\r\n"+ifName)
s.AssertNil(err, fmt.Sprint(err))
validatePostInterfaceStats(s, resp)
resp, err = TcpSendReceive(serverAddress+":80",
- "POST /interface_stats.json HTTP/1.1\r\n\tContent-Length:\t\t4\r\n\r\n"+ifName)
+ "POST /interface_stats.json HTTP/1.1\r\nContent-Length:\t\t4\r\n\r\n"+ifName)
s.AssertNil(err, fmt.Sprint(err))
validatePostInterfaceStats(s, resp)
}
@@ -1289,13 +1289,42 @@ func HttpHeadersTest(s *NoTopoSuite) {
serverAddress := s.VppAddr()
vpp.Vppctl("http cli server")
- resp, err := TcpSendReceive(
- serverAddress+":80",
- "GET /show/version HTTP/1.1\r\nHost:"+serverAddress+":80\r\nUser-Agent:test\r\nAccept:text/xml\r\nAccept:\ttext/plain\t \r\nAccept:text/html\r\n\r\n")
+ transport := http.DefaultTransport
+ transport.(*http.Transport).Proxy = nil
+ transport.(*http.Transport).DisableKeepAlives = false
+ client := &http.Client{
+ Transport: transport,
+ Timeout: time.Second * 30,
+ }
+
+ req, err := http.NewRequest("GET", "http://"+serverAddress+":80/show/version", nil)
s.AssertNil(err, fmt.Sprint(err))
- s.AssertContains(resp, "HTTP/1.1 200 OK")
- s.AssertContains(resp, "Content-Type: text/plain")
- s.AssertNotContains(resp, "<html>", "html content received instead of plain text")
+ req.Header.Add("Accept", "text/xml")
+ req.Header.Add("Accept-Language", "*")
+ req.Header.Add("Accept", "text/plain")
+ req.Header.Add("Accept", "text/html")
+ resp, err := client.Do(req)
+ s.AssertNil(err, fmt.Sprint(err))
+ defer resp.Body.Close()
+ s.Log(DumpHttpResp(resp, true))
+ s.AssertHttpStatus(resp, 200)
+ s.AssertHttpHeaderWithValue(resp, "Content-Type", "text/plain")
+ data, err := io.ReadAll(resp.Body)
+ s.AssertNil(err, fmt.Sprint(err))
+ s.AssertNotContains(string(data), "<html>", "html content received instead of plain text")
+
+ req2, err := http.NewRequest("GET", "http://"+serverAddress+":80/show/version", nil)
+ s.AssertNil(err, fmt.Sprint(err))
+ req2.Header.Add("Accept", "text/html")
+ resp2, err := client.Do(req2)
+ s.AssertNil(err, fmt.Sprint(err))
+ defer resp2.Body.Close()
+ s.Log(DumpHttpResp(resp2, true))
+ s.AssertHttpStatus(resp2, 200)
+ s.AssertHttpHeaderWithValue(resp2, "Content-Type", "text/html")
+ data2, err := io.ReadAll(resp2.Body)
+ s.AssertNil(err, fmt.Sprint(err))
+ s.AssertContains(string(data2), "<html>", "html content not received")
}
func HttpInvalidHeadersTest(s *NoTopoSuite) {
@@ -1382,3 +1411,28 @@ func HttpConnTimeoutTest(s *NoTopoSuite) {
_, err = conn.Read(reply)
s.AssertMatchError(err, io.EOF, "connection not closed by server")
}
+
+func HttpIgnoreH2UpgradeTest(s *NoTopoSuite) {
+ vpp := s.GetContainerByName("vpp").VppInstance
+ serverAddress := s.VppAddr()
+ s.Log(vpp.Vppctl("http static server uri tcp://" + serverAddress + "/80 url-handlers"))
+
+ transport := http.DefaultTransport
+ transport.(*http.Transport).Proxy = nil
+ transport.(*http.Transport).DisableKeepAlives = false
+ client := &http.Client{
+ Transport: transport,
+ Timeout: time.Second * 30,
+ }
+
+ req, err := http.NewRequest("GET", "http://"+serverAddress+":80/version.json", nil)
+ s.AssertNil(err, fmt.Sprint(err))
+ req.Header.Add("Connection", "Upgrade")
+ req.Header.Add("Upgrade", "HTTP/2.0")
+ resp, err := client.Do(req)
+ s.AssertNil(err, fmt.Sprint(err))
+ defer resp.Body.Close()
+ s.Log(DumpHttpResp(resp, true))
+ s.AssertHttpStatus(resp, 200)
+ s.AssertHttpHeaderNotPresent(resp, "Upgrade")
+}
diff --git a/extras/hs-test/infra/suite_vpp_proxy.go b/extras/hs-test/infra/suite_vpp_proxy.go
index d696109b31b..57308dfe076 100644
--- a/extras/hs-test/infra/suite_vpp_proxy.go
+++ b/extras/hs-test/infra/suite_vpp_proxy.go
@@ -150,6 +150,7 @@ func (s *VppProxySuite) CurlDownloadResourceViaTunnel(uri string, proxyUri strin
s.AssertContains(writeOut, "GET response code: 200")
s.AssertNotContains(log, "bytes remaining to read")
s.AssertNotContains(log, "Operation timed out")
+ s.AssertNotContains(log, "Upgrade:")
}
func (s *VppProxySuite) CurlUploadResourceViaTunnel(uri, proxyUri, file string) {
@@ -158,6 +159,7 @@ func (s *VppProxySuite) CurlUploadResourceViaTunnel(uri, proxyUri, file string)
s.AssertContains(writeOut, "CONNECT response code: 200")
s.AssertContains(writeOut, "PUT response code: 201")
s.AssertNotContains(log, "Operation timed out")
+ s.AssertNotContains(log, "Upgrade:")
}
var _ = Describe("VppProxySuite", Ordered, ContinueOnFailure, func() {
diff --git a/src/plugins/hs_apps/http_cli.c b/src/plugins/hs_apps/http_cli.c
index dfa90f9eced..3ca86d24673 100644
--- a/src/plugins/hs_apps/http_cli.c
+++ b/src/plugins/hs_apps/http_cli.c
@@ -51,6 +51,7 @@ typedef struct
u8 *tx_buf;
u32 tx_offset;
u32 vpp_session_index;
+ http_header_table_t req_headers;
http_header_t *resp_headers;
} hcs_session_t;
@@ -177,7 +178,7 @@ start_send_data (hcs_session_t *hs, http_status_code_t status)
if (vec_len (hs->resp_headers))
{
headers_buf = http_serialize_headers (hs->resp_headers);
- vec_free (hs->resp_headers);
+ vec_reset_length (hs->resp_headers);
msg.data.headers_offset = 0;
msg.data.headers_len = vec_len (headers_buf);
}
@@ -370,7 +371,8 @@ hcs_ts_rx_callback (session_t *ts)
hs = hcs_session_get (ts->thread_index, ts->opaque);
hs->tx_buf = 0;
- hs->resp_headers = 0;
+ vec_reset_length (hs->resp_headers);
+ http_reset_header_table (&hs->req_headers);
/* Read the http message header */
rv = svm_fifo_dequeue (ts->rx_fifo, sizeof (msg), (u8 *) &msg);
@@ -413,30 +415,22 @@ hcs_ts_rx_callback (session_t *ts)
if (msg.data.headers_len)
{
- u8 *headers = 0;
- http_header_table_t *ht;
- vec_validate (headers, msg.data.headers_len - 1);
+ http_init_header_table_buf (&hs->req_headers, msg);
rv = svm_fifo_peek (ts->rx_fifo, msg.data.headers_offset,
- msg.data.headers_len, headers);
+ msg.data.headers_len, hs->req_headers.buf);
ASSERT (rv == msg.data.headers_len);
- if (http_parse_headers (headers, &ht))
+ http_build_header_table (&hs->req_headers, msg);
+ const http_header_t *accept = http_get_header (
+ &hs->req_headers, http_header_name_token (HTTP_HEADER_ACCEPT));
+ if (accept)
{
- start_send_data (hs, HTTP_STATUS_BAD_REQUEST);
- vec_free (args.buf);
- vec_free (headers);
- goto done;
- }
- const char *accept_value =
- http_get_header (ht, http_header_name_str (HTTP_HEADER_ACCEPT));
- if (accept_value)
- {
- HCS_DBG ("client accept: %s", accept_value);
+ HCS_DBG ("client accept: %U", format_http_bytes, accept->value.base,
+ accept->value.len);
/* just for testing purpose, we don't care about precedence */
- if (strstr (accept_value, "text/plain"))
+ if (http_token_contains (accept->value.base, accept->value.len,
+ http_token_lit ("text/plain")))
args.plain_text = 1;
}
- http_free_header_table (ht);
- vec_free (headers);
}
args.hs_index = hs->session_index;
@@ -547,6 +541,8 @@ hcs_ts_cleanup_callback (session_t *s, session_cleanup_ntf_t ntf)
return;
vec_free (hs->tx_buf);
+ vec_free (hs->resp_headers);
+ http_free_header_table (&hs->req_headers);
hcs_session_free (hs);
}
diff --git a/src/plugins/hs_apps/http_client.c b/src/plugins/hs_apps/http_client.c
index a86bf1f3db3..cfa5e9c1001 100644
--- a/src/plugins/hs_apps/http_client.c
+++ b/src/plugins/hs_apps/http_client.c
@@ -40,6 +40,12 @@ typedef struct
typedef struct
{
+ u8 *name;
+ u8 *value;
+} hc_http_header_t;
+
+typedef struct
+{
u32 app_index;
u32 cli_node_index;
u8 attached;
@@ -52,7 +58,7 @@ typedef struct
u8 *resp_headers;
u8 *http_response;
u8 *response_status;
- http_header_ht_t *custom_header;
+ hc_http_header_t *custom_header;
u8 is_file;
u8 use_ptr;
u8 *filename;
@@ -181,7 +187,7 @@ hc_session_connected_callback (u32 app_index, u32 hc_session_index,
hc_main_t *hcm = &hc_main;
hc_worker_t *wrk;
u32 new_hc_index;
- http_header_ht_t *header;
+ hc_http_header_t *header;
HTTP_DBG (1, "ho hc_index: %d", hc_session_index);
if (err)
@@ -344,7 +350,6 @@ hc_rx_callback (session_t *s)
format (0, "%U", format_http_status_code, msg.code);
svm_fifo_dequeue_drop (s->rx_fifo, msg.data.headers_offset);
- http_header_table_t *ht;
vec_validate (hcm->resp_headers, msg.data.headers_len - 1);
vec_set_len (hcm->resp_headers, msg.data.headers_len);
rv = svm_fifo_dequeue (s->rx_fifo, msg.data.headers_len,
@@ -352,14 +357,6 @@ hc_rx_callback (session_t *s)
ASSERT (rv == msg.data.headers_len);
HTTP_DBG (1, (char *) format (0, "%v", hcm->resp_headers));
- if (http_parse_headers (hcm->resp_headers, &ht))
- {
- clib_warning ("invalid headers received");
- vlib_process_signal_event_mt (
- wrk->vlib_main, hcm->cli_node_index, HC_GENERIC_ERR, 0);
- return -1;
- }
- http_free_header_table (ht);
msg.data.body_offset -=
msg.data.headers_len + msg.data.headers_offset;
}
@@ -666,7 +663,7 @@ hc_cleanup ()
HTTP_DBG (1, "cleanup");
hc_main_t *hcm = &hc_main;
hc_worker_t *wrk;
- http_header_ht_t *header;
+ hc_http_header_t *header;
vec_foreach (wrk, hcm->wrk)
hcc_worker_cleanup (wrk);
@@ -696,7 +693,7 @@ hc_command_fn (vlib_main_t *vm, unformat_input_t *input,
unformat_input_t _line_input, *line_input = &_line_input;
u8 *path = 0;
u8 *file_data;
- http_header_ht_t new_header;
+ hc_http_header_t new_header;
u8 *name;
u8 *value;
int rv;
diff --git a/src/plugins/http/http.c b/src/plugins/http/http.c
index 7c0b55e26d2..c5cd894cb59 100644
--- a/src/plugins/http/http.c
+++ b/src/plugins/http/http.c
@@ -17,6 +17,7 @@
#include <vnet/session/session.h>
#include <http/http_timer.h>
#include <http/http_status_codes.h>
+#include <http/http_header_names.h>
static http_main_t http_main;
@@ -35,6 +36,12 @@ const http_buffer_type_t msg_to_buf_type[] = {
[HTTP_MSG_DATA_PTR] = HTTP_BUFFER_PTR,
};
+const char *http_upgrade_proto_str[] = { "",
+#define _(sym, str) str,
+ foreach_http_upgrade_proto
+#undef _
+};
+
static u8 *
format_http_req_state (u8 *s, va_list *va)
{
@@ -446,6 +453,9 @@ static const char *http_response_template = "HTTP/1.1 %s\r\n"
static const char *content_len_template = "Content-Length: %llu\r\n";
+static const char *connection_upgrade_template = "Connection: upgrade\r\n"
+ "Upgrade: %s\r\n";
+
/**
* http request boilerplate
*/
@@ -706,6 +716,7 @@ http_parse_request_line (http_req_t *req, http_status_code_t *ec)
{
HTTP_DBG (0, "CONNECT method");
req->method = HTTP_REQ_CONNECT;
+ req->upgrade_proto = HTTP_UPGRADE_PROTO_NA;
req->target_path_offset = method_offset + 8;
req->is_tunnel = 1;
}
@@ -892,7 +903,17 @@ http_parse_status_line (http_req_t *req)
static int
http_identify_headers (http_req_t *req, http_status_code_t *ec)
{
- int i;
+ int rv;
+ u8 *p, *end, *name_start, *value_start;
+ u32 name_len, value_len;
+ http_field_line_t *field_line;
+ uword header_index;
+
+ vec_reset_length (req->headers);
+ req->content_len_header_index = ~0;
+ req->connection_header_index = ~0;
+ req->upgrade_header_index = ~0;
+ req->headers_offset = req->rx_buf_offset;
/* check if we have any header */
if ((req->rx_buf[req->rx_buf_offset] == '\r') &&
@@ -905,16 +926,53 @@ http_identify_headers (http_req_t *req, http_status_code_t *ec)
return 0;
}
- /* find empty line indicating end of header section */
- i = v_find_index (req->rx_buf, req->rx_buf_offset, 0, "\r\n\r\n");
- if (i < 0)
+ end = req->rx_buf + vec_len (req->rx_buf);
+ p = req->rx_buf + req->rx_buf_offset;
+
+ while (1)
{
- clib_warning ("cannot find header section end");
- *ec = HTTP_STATUS_BAD_REQUEST;
- return -1;
- }
- req->headers_offset = req->rx_buf_offset;
- req->headers_len = i - req->rx_buf_offset + 2;
+ rv = _parse_field_name (&p, end, &name_start, &name_len);
+ if (rv != 0)
+ {
+ *ec = HTTP_STATUS_BAD_REQUEST;
+ return -1;
+ }
+ rv = _parse_field_value (&p, end, &value_start, &value_len);
+ if (rv != 0 || (end - p) < 2)
+ {
+ *ec = HTTP_STATUS_BAD_REQUEST;
+ return -1;
+ }
+
+ vec_add2 (req->headers, field_line, 1);
+ field_line->name_offset =
+ (name_start - req->rx_buf) - req->headers_offset;
+ field_line->name_len = name_len;
+ field_line->value_offset =
+ (value_start - req->rx_buf) - req->headers_offset;
+ field_line->value_len = value_len;
+ header_index = field_line - req->headers;
+
+ /* find headers that will be used later in preprocessing */
+ if (req->content_len_header_index == ~0 &&
+ http_token_is ((const char *) name_start, name_len,
+ http_header_name_token (HTTP_HEADER_CONTENT_LENGTH)))
+ req->content_len_header_index = header_index;
+ else if (req->connection_header_index == ~0 &&
+ http_token_is ((const char *) name_start, name_len,
+ http_header_name_token (HTTP_HEADER_CONNECTION)))
+ req->connection_header_index = header_index;
+ else if (req->upgrade_header_index == ~0 &&
+ http_token_is ((const char *) name_start, name_len,
+ http_header_name_token (HTTP_HEADER_UPGRADE)))
+ req->upgrade_header_index = header_index;
+
+ /* are we done? */
+ if (*p == '\r' && *(p + 1) == '\n')
+ break;
+ }
+
+ req->headers_len = p - (req->rx_buf + req->headers_offset);
req->control_data_len += (req->headers_len + 2);
HTTP_DBG (2, "headers length: %u", req->headers_len);
HTTP_DBG (2, "headers offset: %u", req->headers_offset);
@@ -925,9 +983,10 @@ http_identify_headers (http_req_t *req, http_status_code_t *ec)
static int
http_identify_message_body (http_req_t *req, http_status_code_t *ec)
{
- int i, value_len;
- u8 *end, *p, *value_start;
+ int i;
+ u8 *p;
u64 body_len = 0, digit;
+ http_field_line_t *field_line;
req->body_len = 0;
@@ -944,67 +1003,15 @@ http_identify_message_body (http_req_t *req, http_status_code_t *ec)
/* TODO check for chunked transfer coding */
- /* try to find Content-Length header */
- i = v_find_index (req->rx_buf, req->headers_offset, req->headers_len,
- "Content-Length:");
- if (i < 0)
+ if (req->content_len_header_index == ~0)
{
HTTP_DBG (2, "Content-Length header not present, no message-body");
return 0;
}
- req->rx_buf_offset = i + 15;
+ field_line = vec_elt_at_index (req->headers, req->content_len_header_index);
- i = v_find_index (req->rx_buf, req->rx_buf_offset, req->headers_len, "\r\n");
- if (i < 0)
- {
- clib_warning ("end of line missing");
- *ec = HTTP_STATUS_BAD_REQUEST;
- return -1;
- }
- value_len = i - req->rx_buf_offset;
- if (value_len < 1)
- {
- clib_warning ("invalid header, content length value missing");
- *ec = HTTP_STATUS_BAD_REQUEST;
- return -1;
- }
-
- end = req->rx_buf + req->rx_buf_offset + value_len;
- p = req->rx_buf + req->rx_buf_offset;
- /* skip leading whitespace */
- while (1)
- {
- if (p == end)
- {
- clib_warning ("value not found");
- *ec = HTTP_STATUS_BAD_REQUEST;
- return -1;
- }
- else if (*p != ' ' && *p != '\t')
- {
- break;
- }
- p++;
- value_len--;
- }
- value_start = p;
- /* skip trailing whitespace */
- p = value_start + value_len - 1;
- while (*p == ' ' || *p == '\t')
- {
- p--;
- value_len--;
- }
-
- if (value_len < 1)
- {
- clib_warning ("value not found");
- *ec = HTTP_STATUS_BAD_REQUEST;
- return -1;
- }
-
- p = value_start;
- for (i = 0; i < value_len; i++)
+ p = req->rx_buf + req->headers_offset + field_line->value_offset;
+ for (i = 0; i < field_line->value_len; i++)
{
/* check for digit */
if (!isdigit (*p))
@@ -1095,6 +1102,7 @@ http_req_state_wait_transport_reply (http_conn_t *hc,
msg.data.body_len = hc->req.body_len;
msg.data.type = HTTP_MSG_DATA_INLINE;
msg.data.len = len;
+ msg.data.headers_ctx = pointer_to_uword (hc->req.headers);
svm_fifo_seg_t segs[2] = { { (u8 *) &msg, sizeof (msg) },
{ hc->req.rx_buf, len } };
@@ -1130,6 +1138,49 @@ error:
return HTTP_SM_ERROR;
}
+#define http_field_line_value_token(_fl, _req) \
+ (const char *) ((_req)->rx_buf + (_req)->headers_offset + \
+ (_fl)->value_offset), \
+ (_fl)->value_len
+
+static void
+http_check_connection_upgrade (http_req_t *req)
+{
+ http_field_line_t *connection, *upgrade;
+ u8 skip;
+
+ skip = (req->method != HTTP_REQ_GET) + (req->connection_header_index == ~0) +
+ (req->upgrade_header_index == ~0);
+ if (skip)
+ return;
+
+ connection = vec_elt_at_index (req->headers, req->connection_header_index);
+ /* connection options are case-insensitive (RFC9110 7.6.1) */
+ if (http_token_is_case (http_field_line_value_token (connection, req),
+ http_token_lit ("upgrade")))
+ {
+ upgrade = vec_elt_at_index (req->headers, req->upgrade_header_index);
+
+ /* check upgrade protocol, we want to ignore something like upgrade to
+ * newer HTTP version, only tunnels are supported */
+ if (0)
+ ;
+#define _(sym, str) \
+ else if (http_token_is (http_field_line_value_token (upgrade, req), \
+ http_token_lit (str))) req->upgrade_proto = \
+ HTTP_UPGRADE_PROTO_##sym;
+ foreach_http_upgrade_proto
+#undef _
+ else return;
+
+ HTTP_DBG (1, "connection upgrade: %U", format_http_bytes,
+ req->rx_buf + req->headers_offset + upgrade->value_offset,
+ upgrade->value_len);
+ req->is_tunnel = 1;
+ req->method = HTTP_REQ_CONNECT;
+ }
+}
+
static http_sm_result_t
http_req_state_wait_transport_method (http_conn_t *hc,
transport_send_params_t *sp)
@@ -1164,6 +1215,8 @@ http_req_state_wait_transport_method (http_conn_t *hc,
if (rv)
goto error;
+ http_check_connection_upgrade (&hc->req);
+
rv = http_identify_message_body (&hc->req, &ec);
if (rv)
goto error;
@@ -1196,6 +1249,8 @@ http_req_state_wait_transport_method (http_conn_t *hc,
msg.data.headers_len = hc->req.headers_len;
msg.data.body_offset = hc->req.body_offset;
msg.data.body_len = hc->req.body_len;
+ msg.data.headers_ctx = pointer_to_uword (hc->req.headers);
+ msg.data.upgrade_proto = hc->req.upgrade_proto;
svm_fifo_seg_t segs[2] = { { (u8 *) &msg, sizeof (msg) },
{ hc->req.rx_buf, len } };
@@ -1286,15 +1341,21 @@ http_req_state_wait_app_reply (http_conn_t *hc, transport_send_params_t *sp)
/* Server */
hc->app_name);
- /* RFC9110 9.3.6: A server MUST NOT send Content-Length header field in a
- * 2xx (Successful) response to CONNECT. */
- if (hc->req.is_tunnel && http_status_code_str[msg.code][0] == '2')
+ /* RFC9110 8.6: A server MUST NOT send Content-Length header field in a
+ * 2xx (Successful) response to CONNECT or with a status code of 101
+ * (Switching Protocols). */
+ if (hc->req.is_tunnel && (http_status_code_str[msg.code][0] == '2' ||
+ msg.code == HTTP_STATUS_SWITCHING_PROTOCOLS))
{
ASSERT (msg.data.body_len == 0);
next_state = HTTP_REQ_STATE_TUNNEL;
+ if (hc->req.upgrade_proto > HTTP_UPGRADE_PROTO_NA)
+ response = format (response, connection_upgrade_template,
+ http_upgrade_proto_str[hc->req.upgrade_proto]);
/* cleanup some stuff we don't need anymore in tunnel mode */
http_conn_timer_stop (hc);
vec_free (hc->req.rx_buf);
+ vec_free (hc->req.headers);
http_buffer_free (&hc->req.tx_buf);
}
else
@@ -1860,6 +1921,7 @@ http_ts_cleanup_callback (session_t *ts, session_cleanup_ntf_t ntf)
HTTP_DBG (1, "going to free hc [%u]%x", ts->thread_index, ts->opaque);
vec_free (hc->req.rx_buf);
+ vec_free (hc->req.headers);
http_buffer_free (&hc->req.tx_buf);
diff --git a/src/plugins/http/http.h b/src/plugins/http/http.h
index f54b4a040a2..bcee69b7bb4 100644
--- a/src/plugins/http/http.h
+++ b/src/plugins/http/http.h
@@ -352,12 +352,36 @@ typedef enum http_header_name_
#undef _
} http_header_name_t;
+#define HTTP_BOOLEAN_TRUE "?1"
+
+#define foreach_http_upgrade_proto \
+ _ (CONNECT_UDP, "connect-udp") \
+ _ (CONNECT_IP, "connect-ip") \
+ _ (WEBSOCKET, "websocket")
+
+typedef enum http_upgrade_proto_
+{
+ HTTP_UPGRADE_PROTO_NA =
+ 0, /* indicating standard CONNECT where protocol is omitted */
+#define _(sym, str) HTTP_UPGRADE_PROTO_##sym,
+ foreach_http_upgrade_proto
+#undef _
+} http_upgrade_proto_t;
+
typedef enum http_msg_data_type_
{
HTTP_MSG_DATA_INLINE,
HTTP_MSG_DATA_PTR
} http_msg_data_type_t;
+typedef struct http_field_line_
+{
+ u32 name_offset;
+ u32 name_len;
+ u32 value_offset;
+ u32 value_len;
+} http_field_line_t;
+
typedef struct http_msg_data_
{
http_msg_data_type_t type;
@@ -371,6 +395,8 @@ typedef struct http_msg_data_
u32 headers_len;
u32 body_offset;
u64 body_len;
+ uword headers_ctx;
+ http_upgrade_proto_t upgrade_proto;
u8 data[0];
} http_msg_data_t;
@@ -422,6 +448,13 @@ typedef struct http_req_
u32 body_offset;
u64 body_len;
+
+ http_field_line_t *headers;
+ uword content_len_header_index;
+ uword connection_header_index;
+ uword upgrade_header_index;
+
+ http_upgrade_proto_t upgrade_proto;
} http_req_t;
typedef struct http_tc_
@@ -477,6 +510,27 @@ typedef struct http_main_
u32 fifo_size;
} http_main_t;
+always_inline u8 *
+format_http_bytes (u8 *s, va_list *va)
+{
+ u8 *bytes = va_arg (*va, u8 *);
+ int n_bytes = va_arg (*va, int);
+ uword i;
+
+ if (n_bytes == 0)
+ return s;
+
+ for (i = 0; i < n_bytes; i++)
+ {
+ if (isprint (bytes[i]))
+ s = format (s, "%c", bytes[i]);
+ else
+ s = format (s, "\\x%02x", bytes[i]);
+ }
+
+ return s;
+}
+
always_inline int
_validate_target_syntax (u8 *target, u32 len, int is_query, int *is_encoded)
{
@@ -767,22 +821,94 @@ _parse_field_value (u8 **pos, u8 *end, u8 **field_value_start,
typedef struct
{
- u8 *name;
- u8 *value;
-} http_header_ht_t;
-
-typedef struct
-{
http_token_t name;
http_token_t value;
} http_header_t;
typedef struct
{
- http_header_ht_t *headers;
+ http_header_t *headers;
uword *value_by_name;
+ u8 *buf;
+ char **concatenated_values;
} http_header_table_t;
+#define HTTP_HEADER_TABLE_NULL \
+ { \
+ .headers = 0, .value_by_name = 0, .buf = 0, .concatenated_values = 0, \
+ }
+
+always_inline u8
+http_token_is (const char *actual, uword actual_len, const char *expected,
+ uword expected_len)
+{
+ ASSERT (actual != 0);
+ if (actual_len != expected_len)
+ return 0;
+ return memcmp (actual, expected, expected_len) == 0 ? 1 : 0;
+}
+
+always_inline u8
+http_token_is_case (const char *actual, uword actual_len, const char *expected,
+ uword expected_len)
+{
+ uword i;
+ ASSERT (actual != 0);
+ if (actual_len != expected_len)
+ return 0;
+ for (i = 0; i < expected_len; i++)
+ {
+ if (tolower (actual[i]) != expected[i])
+ return 0;
+ }
+ return 1;
+}
+
+always_inline u8
+http_token_contains (const char *haystack, uword haystack_len,
+ const char *needle, uword needle_len)
+{
+ uword end_index, i;
+ ASSERT (haystack != 0);
+ if (haystack_len < needle_len)
+ return 0;
+ end_index = haystack_len - needle_len;
+ for (i = 0; i <= end_index; i++)
+ {
+ if (!memcmp (haystack + i, needle, needle_len))
+ return 1;
+ }
+ return 0;
+}
+
+/**
+ * Reset header table before reuse.
+ *
+ * @param ht Header table to reset.
+ */
+always_inline void
+http_reset_header_table (http_header_table_t *ht)
+{
+ int i;
+ for (i = 0; i < vec_len (ht->concatenated_values); i++)
+ vec_free (ht->concatenated_values[i]);
+ vec_reset_length (ht->concatenated_values);
+ vec_reset_length (ht->headers);
+ vec_reset_length (ht->buf);
+ hash_free (ht->value_by_name);
+}
+
+/**
+ * Initialize header table input buffer.
+ * @param ht Header table.
+ * @param msg HTTP transport message metadata.
+ */
+always_inline void
+http_init_header_table_buf (http_header_table_t *ht, http_msg_t msg)
+{
+ vec_validate (ht->buf, msg.data.headers_len - 1);
+}
+
/**
* Free header table's memory.
*
@@ -791,86 +917,88 @@ typedef struct
always_inline void
http_free_header_table (http_header_table_t *ht)
{
- http_header_ht_t *header;
- vec_foreach (header, ht->headers)
- {
- vec_free (header->name);
- vec_free (header->value);
- }
+ int i;
+ for (i = 0; i < vec_len (ht->concatenated_values); i++)
+ vec_free (ht->concatenated_values[i]);
+ vec_free (ht->concatenated_values);
vec_free (ht->headers);
+ vec_free (ht->buf);
hash_free (ht->value_by_name);
clib_mem_free (ht);
}
+static uword
+_http_ht_hash_key_sum (hash_t *h, uword key)
+{
+ http_token_t *name = uword_to_pointer (key, http_token_t *);
+ return hash_memory (name->base, name->len, 0);
+}
+
+static uword
+_http_ht_hash_key_equal (hash_t *h, uword key1, uword key2)
+{
+ http_token_t *name1 = uword_to_pointer (key1, http_token_t *);
+ http_token_t *name2 = uword_to_pointer (key2, http_token_t *);
+ return name1 && name2 &&
+ http_token_is (name1->base, name1->len, name2->base, name2->len);
+}
+
/**
- * Parse headers in given vector.
+ * Build header table.
*
- * @param headers Vector to parse.
- * @param [out] header_table Parsed headers in case of success.
- *
- * @return @c 0 on success.
+ * @param header_table Header table with loaded buffer.
+ * @param msg HTTP transport message metadata.
*
- * The caller is responsible to free the returned @c header_table
- * using @c http_free_header_table .
+ * @note If reusing already allocated header table use
+ * @c http_reset_header_table first.
*/
-always_inline int
-http_parse_headers (u8 *headers, http_header_table_t **header_table)
+always_inline void
+http_build_header_table (http_header_table_t *ht, http_msg_t msg)
{
- u8 *pos, *end, *name_start, *value_start, *name;
- u32 name_len, value_len;
- int rv;
- http_header_ht_t *header;
- http_header_table_t *ht;
+ http_token_t name;
+ http_header_t *header;
+ http_field_line_t *field_lines, *field_line;
uword *p;
- end = headers + vec_len (headers);
- pos = headers;
+ ASSERT (ht);
+ field_lines = uword_to_pointer (msg.data.headers_ctx, http_field_line_t *);
+ ht->value_by_name =
+ hash_create2 (0, 0, sizeof (uword), _http_ht_hash_key_sum,
+ _http_ht_hash_key_equal, 0, 0);
- ht = clib_mem_alloc (sizeof (*ht));
- ht->value_by_name = hash_create_string (0, sizeof (uword));
- ht->headers = 0;
- do
+ vec_foreach (field_line, field_lines)
{
- rv = _parse_field_name (&pos, end, &name_start, &name_len);
- if (rv != 0)
- {
- http_free_header_table (ht);
- return rv;
- }
- rv = _parse_field_value (&pos, end, &value_start, &value_len);
- if (rv != 0)
- {
- http_free_header_table (ht);
- return rv;
- }
- name = vec_new (u8, name_len);
- clib_memcpy (name, name_start, name_len);
- vec_terminate_c_string (name);
+ name.base = (char *) (ht->buf + field_line->name_offset);
+ name.len = field_line->name_len;
/* check if header is repeated */
- p = hash_get_mem (ht->value_by_name, name);
+ p = hash_get_mem (ht->value_by_name, &name);
if (p)
{
- /* if yes combine values */
+ char *new_value = 0;
header = vec_elt_at_index (ht->headers, p[0]);
- vec_pop (header->value); /* drop null byte */
- header->value = format (header->value, ", %U%c", format_ascii_bytes,
- value_start, value_len, 0);
- vec_free (name);
+ u32 new_len = header->value.len + field_line->value_len + 2;
+ vec_validate (new_value, new_len - 1);
+ clib_memcpy (new_value, header->value.base, header->value.len);
+ new_value[header->value.len] = ',';
+ new_value[header->value.len + 1] = ' ';
+ clib_memcpy (new_value + header->value.len + 2,
+ ht->buf + field_line->value_offset,
+ field_line->value_len);
+ vec_add1 (ht->concatenated_values, new_value);
+ header->value.base = new_value;
+ header->value.len = new_len;
continue;
}
/* or create new record */
- vec_add2 (ht->headers, header, sizeof (*header));
- header->name = name;
- header->value = vec_new (u8, value_len);
- clib_memcpy (header->value, value_start, value_len);
- vec_terminate_c_string (header->value);
- hash_set_mem (ht->value_by_name, header->name, header - ht->headers);
+ vec_add2 (ht->headers, header, 1);
+ header->name.base = name.base;
+ header->name.len = name.len;
+ header->value.base = (char *) (ht->buf + field_line->value_offset);
+ header->value.len = field_line->value_len;
+ HTTP_DBG (1, "value: %U", format_http_bytes, header->value.base,
+ header->value.len);
+ hash_set_mem (ht->value_by_name, &header->name, header - ht->headers);
}
- while (pos != end);
-
- *header_table = ht;
-
- return 0;
}
/**
@@ -881,17 +1009,19 @@ http_parse_headers (u8 *headers, http_header_table_t **header_table)
*
* @return Header's value in case of success, @c 0 otherwise.
*/
-always_inline const char *
-http_get_header (http_header_table_t *header_table, const char *name)
+always_inline const http_header_t *
+http_get_header (http_header_table_t *header_table, const char *name,
+ uword name_len)
{
uword *p;
- http_header_ht_t *header;
+ http_header_t *header;
+ http_token_t name_token = { (char *) name, name_len };
- p = hash_get_mem (header_table->value_by_name, name);
+ p = hash_get_mem (header_table->value_by_name, &name_token);
if (p)
{
header = vec_elt_at_index (header_table->headers, p[0]);
- return (const char *) header->value;
+ return header;
}
return 0;
diff --git a/src/plugins/http/http_plugin.rst b/src/plugins/http/http_plugin.rst
index f86c796bd83..bf414cf96ef 100644
--- a/src/plugins/http/http_plugin.rst
+++ b/src/plugins/http/http_plugin.rst
@@ -15,9 +15,11 @@ Usage
-----
The plugin exposes following inline functions: ``http_validate_abs_path_syntax``, ``http_validate_query_syntax``,
-``http_percent_decode``, ``http_path_remove_dot_segments``, ``http_parse_headers``, ``http_get_header``,
-``http_free_header_table``, ``http_add_header``, ``http_serialize_headers``, ``http_parse_authority_form_target``,
-``http_serialize_authority_form_target``, ``http_parse_absolute_form``, ``http_parse_masque_host_port``.
+``http_percent_decode``, ``http_path_remove_dot_segments``, ``http_build_header_table``, ``http_get_header``,
+``http_reset_header_table``, ``http_free_header_table``, ``http_add_header``,
+``http_serialize_headers``, ``http_parse_authority_form_target``, ``http_serialize_authority_form_target``,
+``http_parse_absolute_form``, ``http_parse_masque_host_port``, ``http_decap_udp_payload_datagram``,
+``http_encap_udp_payload_datagram``. ``http_token_is``, ``http_token_is_case``, ``http_token_contains``
It relies on the hoststack constructs and uses ``http_msg_data_t`` data structure for passing metadata to/from applications.
@@ -125,24 +127,60 @@ Following example shows how to parse headers:
#include <http/http_header_names.h>
if (msg.data.headers_len)
{
- u8 *headers = 0;
- http_header_table_t *ht;
- vec_validate (headers, msg.data.headers_len - 1);
+ http_header_table_t ht = HTTP_HEADER_TABLE_NULL;
+ http_init_header_table_buf (&ht, msg);
rv = svm_fifo_peek (ts->rx_fifo, msg.data.headers_offset,
- msg.data.headers_len, headers);
+ msg.data.headers_len, ht.buf);
ASSERT (rv == msg.data.headers_len);
- if (http_parse_headers (headers, &ht))
+ http_build_header_table (&ht, msg);
+ /* get Accept header */
+ const http_header_t *accept = http_get_header (&ht, http_header_name_token (HTTP_HEADER_ACCEPT));
+ if (accept_value)
{
- /* your error handling */
+ /* do something interesting */
}
+ http_free_header_table (&ht);
+ }
+
+Allocated header table memory can be reused, you just need to reset it using ``http_reset_header_table`` before reuse.
+We will add following member to our session context structure:
+
+.. code-block:: C
+
+ typedef struct
+ {
+ /* ... */
+ http_header_table_t ht;
+ } session_ctx_t;
+
+Don't forget to zero allocated session context.
+
+And in ``session_cleanup_callback`` we free header table memory:
+
+.. code-block:: C
+
+ http_free_header_table (&ctx->ht);
+
+Modified example above:
+
+.. code-block:: C
+
+ #include <http/http_header_names.h>
+ http_reset_header_table (&ctx->ht);
+ /* ... */
+ if (msg.data.headers_len)
+ {
+ http_init_header_table_buf (&ctx->ht, msg);
+ rv = svm_fifo_peek (ts->rx_fifo, msg.data.headers_offset,
+ msg.data.headers_len, ctx->ht.buf);
+ ASSERT (rv == msg.data.headers_len);
+ http_build_header_table (&ctx->ht, msg);
/* get Accept header */
- const char *accept_value = http_get_header (ht, http_header_name_str (HTTP_HEADER_ACCEPT));
+ const http_header_t *accept = http_get_header (&ctx->ht, http_header_name_token (HTTP_HEADER_ACCEPT));
if (accept_value)
{
/* do something interesting */
}
- http_free_header_table (ht);
- vec_free (headers);
}
Finally application reads body (if any), which might be received in multiple pieces (depends on size), so we might need some state machine in ``builtin_app_rx_callback``.
@@ -437,24 +475,19 @@ Following example shows how to parse headers:
#include <http/http_header_names.h>
if (msg.data.headers_len)
{
- u8 *headers = 0;
- http_header_table_t *ht;
- vec_validate (headers, msg.data.headers_len - 1);
+ http_header_table_t ht = HTTP_HEADER_TABLE_NULL;
+ http_init_header_table_buf (&ht, msg);
rv = svm_fifo_peek (ts->rx_fifo, msg.data.headers_offset,
- msg.data.headers_len, headers);
+ msg.data.headers_len, ht.buf);
ASSERT (rv == msg.data.headers_len);
- if (http_parse_headers (headers, &ht))
- {
- /* your error handling */
- }
+ http_build_header_table (&ht, msg);
/* get Content-Type header */
- const char *content_type = http_get_header (ht, http_header_name_str (HTTP_HEADER_CONTENT_TYPE));
+ const http_header_t *content_type = http_get_header (&ht, http_header_name_token (HTTP_HEADER_CONTENT_TYPE));
if (content_type)
{
/* do something interesting */
}
- http_free_header_table (ht);
- vec_free (headers);
+ http_free_header_table (&ht);
}
Finally application reads body, which might be received in multiple pieces (depends on size), so we might need some state machine in ``builtin_app_rx_callback``.