From 70d2a08e7efa0ecb255f2174e6a57b6511016059 Mon Sep 17 00:00:00 2001 From: Aritra Basu Date: Wed, 28 Aug 2024 14:02:34 -0700 Subject: vcl: add http support to vcl_test_protos Type: improvement Change-Id: Ibb493f1d7713d0e10b8bd1d5ff17b89967b53b8a Signed-off-by: Aritra Basu --- extras/hs-test/vcl_test.go | 11 +- src/plugins/hs_apps/vcl/vcl_test.h | 2 +- src/plugins/hs_apps/vcl/vcl_test_client.c | 9 +- src/plugins/hs_apps/vcl/vcl_test_protos.c | 429 ++++++++++++++++++++++++++++++ src/vcl/vppcom.c | 7 + src/vcl/vppcom.h | 1 + test/asf/test_vcl.py | 53 ++++ 7 files changed, 501 insertions(+), 11 deletions(-) diff --git a/extras/hs-test/vcl_test.go b/extras/hs-test/vcl_test.go index 3b413b26bed..81da0c533b9 100644 --- a/extras/hs-test/vcl_test.go +++ b/extras/hs-test/vcl_test.go @@ -1,14 +1,15 @@ package main import ( - . "fd.io/hs-test/infra" "fmt" "time" + + . "fd.io/hs-test/infra" ) func init() { RegisterVethTests(XEchoVclClientUdpTest, XEchoVclClientTcpTest, XEchoVclServerUdpTest, - XEchoVclServerTcpTest, VclEchoTcpTest, VclEchoUdpTest, VclRetryAttachTest) + XEchoVclServerTcpTest, VclEchoTcpTest, VclEchoUdpTest, VclHttpPostTest, VclRetryAttachTest) } func getVclConfig(c *Container, ns_id_optional ...string) string { @@ -89,7 +90,7 @@ func testVclEcho(s *VethsSuite, proto string) { srvAppCont.CreateFile("/vcl.conf", getVclConfig(srvVppCont)) srvAppCont.AddEnvVar("VCL_CONFIG", "/vcl.conf") - srvAppCont.ExecServer("vcl_test_server " + port) + srvAppCont.ExecServer("vcl_test_server -p " + proto + " " + port) serverVeth := s.GetInterfaceByName(ServerInterfaceName) serverVethAddress := serverVeth.Ip4AddressString() @@ -111,6 +112,10 @@ func VclEchoUdpTest(s *VethsSuite) { testVclEcho(s, "udp") } +func VclHttpPostTest(s *VethsSuite) { + testVclEcho(s, "http") +} + func VclRetryAttachTest(s *VethsSuite) { testRetryAttach(s, "tcp") } diff --git a/src/plugins/hs_apps/vcl/vcl_test.h b/src/plugins/hs_apps/vcl/vcl_test.h index 2b45339099e..11667fb144a 100644 --- a/src/plugins/hs_apps/vcl/vcl_test.h +++ b/src/plugins/hs_apps/vcl/vcl_test.h @@ -124,7 +124,7 @@ typedef struct typedef struct { - const vcl_test_proto_vft_t *protos[VPPCOM_PROTO_SRTP + 1]; + const vcl_test_proto_vft_t *protos[VPPCOM_PROTO_HTTP + 1]; uint32_t ckpair_index; hs_test_cfg_t cfg; vcl_test_wrk_t *wrk; diff --git a/src/plugins/hs_apps/vcl/vcl_test_client.c b/src/plugins/hs_apps/vcl/vcl_test_client.c index a4a10b562ff..8bac1f00b9d 100644 --- a/src/plugins/hs_apps/vcl/vcl_test_client.c +++ b/src/plugins/hs_apps/vcl/vcl_test_client.c @@ -419,13 +419,8 @@ vtc_worker_run_select (vcl_test_client_worker_t *wrk) if (vcm->incremental_stats) vtc_inc_stats_check (ts); } - if ((!check_rx && ts->stats.tx_bytes >= ts->cfg.total_bytes) || - (check_rx && ts->stats.rx_bytes >= ts->cfg.total_bytes)) - { - clock_gettime (CLOCK_REALTIME, &ts->stats.stop); - ts->is_done = 1; - n_active_sessions--; - } + if (vtc_session_check_is_done (ts, check_rx)) + n_active_sessions -= 1; } } diff --git a/src/plugins/hs_apps/vcl/vcl_test_protos.c b/src/plugins/hs_apps/vcl/vcl_test_protos.c index cd1ac2b24f4..9c81c5f17a1 100644 --- a/src/plugins/hs_apps/vcl/vcl_test_protos.c +++ b/src/plugins/hs_apps/vcl/vcl_test_protos.c @@ -14,6 +14,23 @@ */ #include +#include +#include +#include + +typedef enum vcl_test_http_state_ +{ + VCL_TEST_HTTP_IDLE = 0, + VCL_TEST_HTTP_IN_PROGRESS, + VCL_TEST_HTTP_COMPLETED, +} vcl_test_http_state_t; + +typedef struct vcl_test_http_ctx_t +{ + u8 is_server; + vcl_test_http_state_t test_state; + u64 rem_data; +} vcl_test_http_ctx_t; static int vt_tcp_connect (vcl_test_session_t *ts, vppcom_endpt_t *endpt) @@ -978,6 +995,418 @@ static const vcl_test_proto_vft_t vcl_test_srtp = { VCL_TEST_REGISTER_PROTO (VPPCOM_PROTO_SRTP, vcl_test_srtp); +static void +vt_http_session_init (vcl_test_session_t *ts, u8 is_server) +{ + vcl_test_http_ctx_t *http_ctx; + + http_ctx = malloc (sizeof (vcl_test_http_ctx_t)); + memset (http_ctx, 0, sizeof (*http_ctx)); + http_ctx->is_server = is_server; + ts->opaque = http_ctx; +} + +static inline void +vt_http_send_reply_msg (vcl_test_session_t *ts, http_status_code_t status) +{ + http_msg_t msg; + int rv = 0; + + memset (&msg, 0, sizeof (http_msg_t)); + msg.type = HTTP_MSG_REPLY; + msg.code = status; + + vppcom_data_segment_t segs[1] = { { (u8 *) &msg, sizeof (msg) } }; + + do + { + rv = vppcom_session_write_segments (ts->fd, segs, 1); + + if (rv < 0) + { + errno = -rv; + if (errno == EAGAIN || errno == EWOULDBLOCK) + continue; + + vterr ("vppcom_session_write()", -errno); + break; + } + } + while (rv <= 0); +} + +static inline int +vt_process_http_server_read_msg (vcl_test_session_t *ts, void *buf, + uint32_t nbytes) +{ + http_msg_t msg; + u8 *target_path = 0; + vcl_test_http_ctx_t *vcl_test_http_ctx = (vcl_test_http_ctx_t *) ts->opaque; + vcl_test_stats_t *stats = &ts->stats; + int rv = 0; + + do + { + stats->rx_xacts++; + rv = vppcom_session_read (ts->fd, buf, nbytes); + + if (rv <= 0) + { + errno = -rv; + if (errno == EAGAIN || errno == EWOULDBLOCK) + { + stats->rx_eagain++; + continue; + } + + vterr ("vppcom_session_read()", -errno); + return 0; + } + + if (PREDICT_TRUE (vcl_test_http_ctx->test_state == + VCL_TEST_HTTP_IN_PROGRESS)) + { + vcl_test_http_ctx->rem_data -= rv; + + if (vcl_test_http_ctx->rem_data == 0) + { + vcl_test_http_ctx->test_state = VCL_TEST_HTTP_COMPLETED; + vt_http_send_reply_msg (ts, HTTP_STATUS_OK); + } + } + else if (PREDICT_FALSE (vcl_test_http_ctx->test_state == + VCL_TEST_HTTP_IDLE)) + { + msg = *(http_msg_t *) buf; + + /* verify that we have received http post request from client */ + if (msg.type != HTTP_MSG_REQUEST || msg.method_type != HTTP_REQ_POST) + { + vt_http_send_reply_msg (ts, HTTP_STATUS_METHOD_NOT_ALLOWED); + vterr ("error! only POST requests allowed from client", 0); + return 0; + } + + if (msg.data.target_form != HTTP_TARGET_ORIGIN_FORM) + { + vt_http_send_reply_msg (ts, HTTP_STATUS_BAD_REQUEST); + vterr ("error! http target not in origin form", 0); + return 0; + } + + /* validate target path syntax */ + if (msg.data.target_path_len) + { + vec_validate (target_path, msg.data.target_path_len - 1); + memcpy (target_path, + buf + sizeof (msg) + msg.data.target_path_offset - 1, + msg.data.target_path_len + 1); + if (http_validate_abs_path_syntax (target_path, 0)) + { + vt_http_send_reply_msg (ts, HTTP_STATUS_BAD_REQUEST); + vterr ("error! target path is not absolute", 0); + vec_free (target_path); + return 0; + } + vec_free (target_path); + } + + /* read body */ + if (msg.data.body_len) + { + vcl_test_http_ctx->rem_data = msg.data.body_len; + /* | | | | | */ + vcl_test_http_ctx->rem_data -= + (rv - sizeof (msg) - msg.data.body_offset); + vcl_test_http_ctx->test_state = VCL_TEST_HTTP_IN_PROGRESS; + } + } + + if (rv < nbytes) + stats->rx_incomp++; + } + while (rv <= 0); + + stats->rx_bytes += rv; + return (rv); +} + +static inline int +vt_process_http_client_read_msg (vcl_test_session_t *ts, void *buf, + uint32_t nbytes) +{ + http_msg_t msg; + int rv = 0; + + do + { + rv = vppcom_session_read (ts->fd, buf, nbytes); + + if (rv < 0) + { + errno = -rv; + if (errno == EAGAIN || errno == EWOULDBLOCK) + continue; + + vterr ("vppcom_session_read()", -errno); + break; + } + } + while (!rv); + + msg = *(http_msg_t *) buf; + + if (msg.type == HTTP_MSG_REPLY && msg.code == HTTP_STATUS_OK) + vtinf ("received 200 OK from server"); + else + vterr ("received unexpected reply from server", 0); + + return (rv); +} + +static inline int +vt_process_http_client_write_msg (vcl_test_session_t *ts, void *buf, + uint32_t nbytes) +{ + http_msg_t msg; + http_header_t *req_headers = 0; + u8 *headers_buf = 0; + u8 *target; + vcl_test_http_ctx_t *vcl_test_http_ctx = (vcl_test_http_ctx_t *) ts->opaque; + vcl_test_stats_t *stats = &ts->stats; + int rv = 0; + + if (PREDICT_TRUE (vcl_test_http_ctx->test_state == + VCL_TEST_HTTP_IN_PROGRESS)) + { + do + { + rv = vppcom_session_write ( + ts->fd, buf, clib_min (nbytes, vcl_test_http_ctx->rem_data)); + + if (rv <= 0) + { + errno = -rv; + if (errno == EAGAIN || errno == EWOULDBLOCK) + { + stats->tx_eagain++; + continue; + } + + vterr ("vppcom_session_write()", -errno); + return 0; + } + + vcl_test_http_ctx->rem_data -= rv; + + if (vcl_test_http_ctx->rem_data == 0) + { + vcl_test_http_ctx->test_state = VCL_TEST_HTTP_COMPLETED; + vtinf ("client finished sending %ld bytes of data", + ts->cfg.total_bytes); + } + + if (rv < nbytes) + stats->tx_incomp++; + } + while (rv <= 0); + } + + else if (PREDICT_FALSE (vcl_test_http_ctx->test_state == VCL_TEST_HTTP_IDLE)) + { + http_add_header ( + &req_headers, http_header_name_token (HTTP_HEADER_CONTENT_TYPE), + http_content_type_token (HTTP_CONTENT_APP_OCTET_STREAM)); + headers_buf = http_serialize_headers (req_headers); + vec_free (req_headers); + + memset (&msg, 0, sizeof (http_msg_t)); + msg.type = HTTP_MSG_REQUEST; + msg.method_type = HTTP_REQ_POST; + + /* target */ + msg.data.target_form = HTTP_TARGET_ORIGIN_FORM; + target = (u8 *) "/vcl_test_http\0"; + msg.data.target_path_len = strlen ((char *) target); + + /* headers */ + msg.data.headers_offset = msg.data.target_path_len; + msg.data.headers_len = vec_len (headers_buf); + + /* body */ + msg.data.body_offset = msg.data.headers_offset + msg.data.headers_len; + msg.data.body_len = ts->cfg.total_bytes; + + msg.data.len = + msg.data.target_path_len + msg.data.headers_len + msg.data.body_len; + msg.data.type = HTTP_MSG_DATA_INLINE; + + vppcom_data_segment_t segs[3] = { { (u8 *) &msg, sizeof (msg) }, + { target, strlen ((char *) target) }, + { headers_buf, + vec_len (headers_buf) } }; + + do + { + rv = vppcom_session_write_segments (ts->fd, segs, 3); + + if (rv <= 0) + { + errno = -rv; + if (errno == EAGAIN || errno == EWOULDBLOCK) + { + stats->tx_eagain++; + continue; + } + + vterr ("vppcom_session_write_segments()", -errno); + vec_free (headers_buf); + return 0; + } + } + while (rv <= 0); + + vcl_test_http_ctx->test_state = VCL_TEST_HTTP_IN_PROGRESS; + vcl_test_http_ctx->rem_data = ts->cfg.total_bytes; + vec_free (headers_buf); + } + + stats->tx_bytes += rv; + return (rv); +} + +static inline int +vt_process_http_server_write_msg (vcl_test_session_t *ts, void *buf, + uint32_t nbytes) +{ + return 0; +} + +static inline int +vt_http_read (vcl_test_session_t *ts, void *buf, uint32_t nbytes) +{ + vcl_test_http_ctx_t *vcl_test_http_ctx = (vcl_test_http_ctx_t *) ts->opaque; + + if (vcl_test_http_ctx->is_server) + return vt_process_http_server_read_msg (ts, buf, nbytes); + else + return vt_process_http_client_read_msg (ts, buf, nbytes); +} + +static inline int +vt_http_write (vcl_test_session_t *ts, void *buf, uint32_t nbytes) +{ + vcl_test_http_ctx_t *vcl_test_http_ctx = (vcl_test_http_ctx_t *) ts->opaque; + + if (vcl_test_http_ctx->is_server) + return vt_process_http_server_write_msg (ts, buf, nbytes); + else + return vt_process_http_client_write_msg (ts, buf, nbytes); +} + +static int +vt_http_connect (vcl_test_session_t *ts, vppcom_endpt_t *endpt) +{ + uint32_t flags, flen; + int rv; + + ts->fd = vppcom_session_create (VPPCOM_PROTO_HTTP, ts->noblk_connect); + if (ts->fd < 0) + { + vterr ("vppcom_session_create()", ts->fd); + return ts->fd; + } + + rv = vppcom_session_connect (ts->fd, endpt); + if (rv < 0 && rv != VPPCOM_EINPROGRESS) + { + vterr ("vppcom_session_connect()", rv); + return rv; + } + + ts->read = vt_http_read; + ts->write = vt_http_write; + + if (!ts->noblk_connect) + { + flags = O_NONBLOCK; + flen = sizeof (flags); + vppcom_session_attr (ts->fd, VPPCOM_ATTR_SET_FLAGS, &flags, &flen); + vtinf ("Test session %d (fd %d) connected.", ts->session_index, ts->fd); + } + + vt_http_session_init (ts, 0 /* is_server */); + + return 0; +} + +static int +vt_http_listen (vcl_test_session_t *ts, vppcom_endpt_t *endpt) +{ + int rv; + + ts->fd = vppcom_session_create (VPPCOM_PROTO_HTTP, 1 /* is_nonblocking */); + if (ts->fd < 0) + { + vterr ("vppcom_session_create()", ts->fd); + return ts->fd; + } + + rv = vppcom_session_bind (ts->fd, endpt); + if (rv < 0) + { + vterr ("vppcom_session_bind()", rv); + return rv; + } + + rv = vppcom_session_listen (ts->fd, 10); + if (rv < 0) + { + vterr ("vppcom_session_listen()", rv); + return rv; + } + + return 0; +} + +static int +vt_http_accept (int listen_fd, vcl_test_session_t *ts) +{ + int client_fd; + + client_fd = vppcom_session_accept (listen_fd, &ts->endpt, 0); + if (client_fd < 0) + { + vterr ("vppcom_session_accept()", client_fd); + return client_fd; + } + + ts->fd = client_fd; + ts->is_open = 1; + ts->read = vt_http_read; + ts->write = vt_http_write; + + vt_http_session_init (ts, 1 /* is_server */); + + return 0; +} + +static int +vt_http_close (vcl_test_session_t *ts) +{ + free (ts->opaque); + return 0; +} + +static const vcl_test_proto_vft_t vcl_test_http = { + .open = vt_http_connect, + .listen = vt_http_listen, + .accept = vt_http_accept, + .close = vt_http_close, +}; + +VCL_TEST_REGISTER_PROTO (VPPCOM_PROTO_HTTP, vcl_test_http); + /* * fd.io coding-style-patch-verification: ON * diff --git a/src/vcl/vppcom.c b/src/vcl/vppcom.c index 5408fb79186..2ebb1d8e5b6 100644 --- a/src/vcl/vppcom.c +++ b/src/vcl/vppcom.c @@ -1754,6 +1754,10 @@ vppcom_unformat_proto (uint8_t * proto, char *proto_str) *proto = VPPCOM_PROTO_SRTP; else if (!strcmp (proto_str, "srtp")) *proto = VPPCOM_PROTO_SRTP; + else if (!strcmp (proto_str, "HTTP")) + *proto = VPPCOM_PROTO_HTTP; + else if (!strcmp (proto_str, "http")) + *proto = VPPCOM_PROTO_HTTP; else return 1; return 0; @@ -4708,6 +4712,9 @@ vppcom_proto_str (vppcom_proto_t proto) case VPPCOM_PROTO_SRTP: proto_str = "SRTP"; break; + case VPPCOM_PROTO_HTTP: + proto_str = "HTTP"; + break; default: proto_str = "UNKNOWN"; break; diff --git a/src/vcl/vppcom.h b/src/vcl/vppcom.h index 3de2934adc1..164dc376ad8 100644 --- a/src/vcl/vppcom.h +++ b/src/vcl/vppcom.h @@ -58,6 +58,7 @@ typedef enum vppcom_proto_ VPPCOM_PROTO_QUIC, VPPCOM_PROTO_DTLS, VPPCOM_PROTO_SRTP, + VPPCOM_PROTO_HTTP, } vppcom_proto_t; typedef enum diff --git a/test/asf/test_vcl.py b/test/asf/test_vcl.py index 8368a9f922f..124ea14089b 100644 --- a/test/asf/test_vcl.py +++ b/test/asf/test_vcl.py @@ -757,6 +757,59 @@ class VCLThruHostStackQUIC(VCLTestCase): self.logger.debug(self.vapi.cli("show app mq")) +@unittest.skipIf( + "hs_apps" in config.excluded_plugins, "Exclude tests requiring hs_apps plugin" +) +class VCLThruHostStackHTTPPost(VCLTestCase): + """VCL Thru Host Stack HTTP Post""" + + @classmethod + def setUpClass(cls): + cls.extra_vpp_plugin_config.append("plugin http_plugin.so { enable }") + super(VCLThruHostStackHTTPPost, cls).setUpClass() + + @classmethod + def tearDownClass(cls): + super(VCLThruHostStackHTTPPost, cls).tearDownClass() + + def setUp(self): + super(VCLThruHostStackHTTPPost, self).setUp() + + self.thru_host_stack_setup() + self.client_uni_dir_http_post_timeout = 20 + self.server_http_post_args = ["-p", "http", self.server_port] + self.client_uni_dir_http_post_test_args = [ + "-N", + "10000", + "-U", + "-X", + "-p", + "http", + self.loop0.local_ip4, + self.server_port, + ] + + def test_vcl_thru_host_stack_http_post_uni_dir(self): + """run VCL thru host stack uni-directional HTTP POST test""" + + self.timeout = self.client_uni_dir_http_post_timeout + self.thru_host_stack_test( + "vcl_test_server", + self.server_http_post_args, + "vcl_test_client", + self.client_uni_dir_http_post_test_args, + ) + + def tearDown(self): + self.thru_host_stack_tear_down() + super(VCLThruHostStackHTTPPost, self).tearDown() + + def show_commands_at_teardown(self): + self.logger.debug(self.vapi.cli("show app server")) + self.logger.debug(self.vapi.cli("show session verbose 2")) + self.logger.debug(self.vapi.cli("show app mq")) + + @unittest.skipIf( "hs_apps" in config.excluded_plugins, "Exclude tests requiring hs_apps plugin" ) -- cgit 1.2.3-korg