From 0b039ae97659e8b14c729e8316c91062bb8cdb04 Mon Sep 17 00:00:00 2001 From: Matus Fabian Date: Wed, 14 May 2025 12:33:43 -0400 Subject: [PATCH] tls: add ALPN support App can pass ALPN protocols list via alpn_protos member of transport_endpt_crypto_cfg_t. For server it should be ordered by preference. If all set to zeros ALPN negotiation is disabled. App can get selected protocol via tls_get_alpn_selected, it returns TLS_ALPN_PROTO_NONE if no protocol has been selected (peer do not used ALPN). In case that server supports no protocols that client advertised, then server respond with fatal "no_application_protocol" alert (TLS handshake fail). Type: feature Change-Id: If030672bfb7a6a9cc9a8d7b1fdd30e2776ae2c3f Signed-off-by: Matus Fabian --- docs/spelling_wordlist.txt | 1 + extras/hs-test/tls_test.go | 81 ++++++++ src/plugins/hs_apps/CMakeLists.txt | 2 + src/plugins/hs_apps/alpn_client.c | 356 +++++++++++++++++++++++++++++++++++ src/plugins/hs_apps/alpn_server.c | 261 +++++++++++++++++++++++++ src/plugins/tlsopenssl/tls_openssl.c | 56 ++++++ src/vnet/CMakeLists.txt | 1 + src/vnet/session/transport_types.h | 1 + src/vnet/tls/tls.c | 97 +++++++++- src/vnet/tls/tls.h | 4 + src/vnet/tls/tls_types.h | 72 +++++++ 11 files changed, 929 insertions(+), 3 deletions(-) create mode 100644 extras/hs-test/tls_test.go create mode 100644 src/plugins/hs_apps/alpn_client.c create mode 100644 src/plugins/hs_apps/alpn_server.c create mode 100644 src/vnet/tls/tls_types.h diff --git a/docs/spelling_wordlist.txt b/docs/spelling_wordlist.txt index ecfebfb8eaa..b407b39c8c6 100644 --- a/docs/spelling_wordlist.txt +++ b/docs/spelling_wordlist.txt @@ -26,6 +26,7 @@ alloc allocatable allocator allowlist +alpn Altivec amine analyse diff --git a/extras/hs-test/tls_test.go b/extras/hs-test/tls_test.go new file mode 100644 index 00000000000..4265f3612e1 --- /dev/null +++ b/extras/hs-test/tls_test.go @@ -0,0 +1,81 @@ +package main + +import ( + . "fd.io/hs-test/infra" +) + +func init() { + RegisterVethTests(TlsAlpMatchTest, TlsAlpnOverlapMatchTest, TlsAlpnServerPriorityMatchTest, TlsAlpnMismatchTest, TlsAlpnEmptyServerListTest, TlsAlpnEmptyClientListTest) +} + +func TlsAlpMatchTest(s *VethsSuite) { + s.Log(s.Containers.ServerVpp.VppInstance.Vppctl("test alpn server alpn-proto1 2 uri tls://0.0.0.0:123")) + + uri := "tls://" + s.Interfaces.Server.Ip4AddressString() + ":123" + o := s.Containers.ClientVpp.VppInstance.Vppctl("test alpn client alpn-proto1 2 uri " + uri) + s.Log(o) + s.AssertNotContains(o, "connect failed") + s.AssertNotContains(o, "timeout") + // selected based on 1:1 match + s.AssertContains(o, "ALPN selected: h2") +} + +func TlsAlpnOverlapMatchTest(s *VethsSuite) { + s.Log(s.Containers.ServerVpp.VppInstance.Vppctl("test alpn server alpn-proto1 2 alpn-proto2 1 uri tls://0.0.0.0:123")) + + uri := "tls://" + s.Interfaces.Server.Ip4AddressString() + ":123" + o := s.Containers.ClientVpp.VppInstance.Vppctl("test alpn client alpn-proto1 3 alpn-proto2 2 uri " + uri) + s.Log(o) + s.AssertNotContains(o, "connect failed") + s.AssertNotContains(o, "timeout") + // selected based on overlap + s.AssertContains(o, "ALPN selected: h2") +} + +func TlsAlpnServerPriorityMatchTest(s *VethsSuite) { + s.Log(s.Containers.ServerVpp.VppInstance.Vppctl("test alpn server alpn-proto1 2 alpn-proto2 1 uri tls://0.0.0.0:123")) + + uri := "tls://" + s.Interfaces.Server.Ip4AddressString() + ":123" + o := s.Containers.ClientVpp.VppInstance.Vppctl("test alpn client alpn-proto1 1 alpn-proto2 2 uri " + uri) + s.Log(o) + s.AssertNotContains(o, "connect failed") + s.AssertNotContains(o, "timeout") + // selected based on server priority + s.AssertContains(o, "ALPN selected: h2") +} + +func TlsAlpnMismatchTest(s *VethsSuite) { + s.Log(s.Containers.ServerVpp.VppInstance.Vppctl("test alpn server alpn-proto1 2 alpn-proto2 1 uri tls://0.0.0.0:123")) + + uri := "tls://" + s.Interfaces.Server.Ip4AddressString() + ":123" + o := s.Containers.ClientVpp.VppInstance.Vppctl("test alpn client alpn-proto1 3 alpn-proto2 4 uri " + uri) + s.Log(o) + s.AssertNotContains(o, "timeout") + s.AssertNotContains(o, "ALPN selected") + // connection refused on mismatch + s.AssertContains(o, "connect error failed tls handshake") +} + +func TlsAlpnEmptyServerListTest(s *VethsSuite) { + s.Log(s.Containers.ServerVpp.VppInstance.Vppctl("test alpn server uri tls://0.0.0.0:123")) + + uri := "tls://" + s.Interfaces.Server.Ip4AddressString() + ":123" + o := s.Containers.ClientVpp.VppInstance.Vppctl("test alpn client alpn-proto1 1 alpn-proto2 2 uri " + uri) + s.Log(o) + s.AssertNotContains(o, "connect failed") + s.AssertNotContains(o, "timeout") + // no alpn negotiation + s.AssertContains(o, "ALPN selected: none") +} + +func TlsAlpnEmptyClientListTest(s *VethsSuite) { + s.Log(s.Containers.ServerVpp.VppInstance.Vppctl("test alpn server alpn-proto1 2 alpn-proto2 1 uri tls://0.0.0.0:123")) + + uri := "tls://" + s.Interfaces.Server.Ip4AddressString() + ":123" + o := s.Containers.ClientVpp.VppInstance.Vppctl("test alpn client uri " + uri) + s.Log(o) + s.AssertNotContains(o, "connect failed") + s.AssertNotContains(o, "timeout") + // no alpn negotiation + s.AssertContains(o, "ALPN selected: none") +} diff --git a/src/plugins/hs_apps/CMakeLists.txt b/src/plugins/hs_apps/CMakeLists.txt index 3e80a84aae4..1d83175f64a 100644 --- a/src/plugins/hs_apps/CMakeLists.txt +++ b/src/plugins/hs_apps/CMakeLists.txt @@ -16,6 +16,8 @@ ############################################################################## add_vpp_plugin(hs_apps SOURCES + alpn_client.c + alpn_server.c echo_client.c echo_server.c hs_apps.c diff --git a/src/plugins/hs_apps/alpn_client.c b/src/plugins/hs_apps/alpn_client.c new file mode 100644 index 00000000000..8f744e17471 --- /dev/null +++ b/src/plugins/hs_apps/alpn_client.c @@ -0,0 +1,356 @@ +/* SPDX-License-Identifier: Apache-2.0 + * Copyright(c) 2025 Cisco Systems, Inc. + */ + +#include +#include +#include +#include + +typedef struct +{ + u32 app_index; + u8 *uri; + u32 tls_engine; + u32 ckpair_index; + u32 cli_node_index; + session_endpoint_cfg_t connect_sep; + tls_alpn_proto_t alpn_proto_selected; + u8 alpn_protos[4]; + vlib_main_t *vlib_main; +} alpn_client_main_t; + +typedef enum +{ + AC_CLI_TEST_DONE = 1, + AC_CLI_CONNECT_FAILED, +} ac_cli_signal_t; + +alpn_client_main_t alpn_client_main; + +static int +ac_ts_rx_callback (session_t *ts) +{ + clib_warning ("called..."); + return -1; +} + +static int +ac_ts_tx_callback (session_t *ts) +{ + clib_warning ("called..."); + return -1; +} + +static int +ac_ts_accept_callback (session_t *ts) +{ + clib_warning ("called..."); + return -1; +} + +static int +ac_ts_connected_callback (u32 app_index, u32 api_context, session_t *s, + session_error_t err) +{ + alpn_client_main_t *cm = &alpn_client_main; + vnet_disconnect_args_t _a = { 0 }, *a = &_a; + + if (err) + { + vlib_process_signal_event_mt (cm->vlib_main, cm->cli_node_index, + AC_CLI_CONNECT_FAILED, err); + return -1; + } + + cm->alpn_proto_selected = tls_get_alpn_selected (s->connection_index); + + a->handle = session_handle (s); + a->app_index = cm->app_index; + vnet_disconnect_session (a); + + vlib_process_signal_event_mt (cm->vlib_main, cm->cli_node_index, + AC_CLI_TEST_DONE, 0); + + return 0; +} + +static void +ac_ts_disconnect_callback (session_t *s) +{ + alpn_client_main_t *cm = &alpn_client_main; + vnet_disconnect_args_t _a = { 0 }, *a = &_a; + + a->handle = session_handle (s); + a->app_index = cm->app_index; + vnet_disconnect_session (a); +} + +static void +ac_ts_reset_callback (session_t *s) +{ + alpn_client_main_t *cm = &alpn_client_main; + vnet_disconnect_args_t _a = { 0 }, *a = &_a; + + a->handle = session_handle (s); + a->app_index = cm->app_index; + vnet_disconnect_session (a); +} + +static void +ac_ts_cleanup_callback (session_t *s, session_cleanup_ntf_t ntf) +{ + return; +} + +static int +ac_add_segment_callback (u32 client_index, u64 segment_handle) +{ + return 0; +} + +static int +ac_del_segment_callback (u32 client_index, u64 segment_handle) +{ + return 0; +} + +static session_cb_vft_t ac_session_cb_vft = { + .session_accept_callback = ac_ts_accept_callback, + .session_disconnect_callback = ac_ts_disconnect_callback, + .session_connected_callback = ac_ts_connected_callback, + .add_segment_callback = ac_add_segment_callback, + .del_segment_callback = ac_del_segment_callback, + .builtin_app_rx_callback = ac_ts_rx_callback, + .builtin_app_tx_callback = ac_ts_tx_callback, + .session_reset_callback = ac_ts_reset_callback, + .session_cleanup_callback = ac_ts_cleanup_callback, +}; + +static int +ac_attach () +{ + alpn_client_main_t *cm = &alpn_client_main; + vnet_app_attach_args_t _a, *a = &_a; + u64 options[18]; + vnet_app_add_cert_key_pair_args_t _ck_pair, *ck_pair = &_ck_pair; + + clib_memset (a, 0, sizeof (*a)); + clib_memset (options, 0, sizeof (options)); + + a->api_client_index = ~0; + a->name = format (0, "test_alpn_client"); + a->session_cb_vft = &ac_session_cb_vft; + a->options = options; + a->options[APP_OPTIONS_SEGMENT_SIZE] = 128 << 20; + a->options[APP_OPTIONS_ADD_SEGMENT_SIZE] = 128 << 20; + a->options[APP_OPTIONS_RX_FIFO_SIZE] = 8 << 10; + a->options[APP_OPTIONS_TX_FIFO_SIZE] = 8 << 10; + a->options[APP_OPTIONS_FLAGS] = APP_OPTIONS_FLAGS_IS_BUILTIN; + a->options[APP_OPTIONS_PREALLOC_FIFO_PAIRS] = 0; + a->options[APP_OPTIONS_TLS_ENGINE] = cm->tls_engine; + + if (vnet_application_attach (a)) + return -1; + + cm->app_index = a->app_index; + vec_free (a->name); + + clib_memset (ck_pair, 0, sizeof (*ck_pair)); + ck_pair->cert = (u8 *) test_srv_crt_rsa; + ck_pair->key = (u8 *) test_srv_key_rsa; + ck_pair->cert_len = test_srv_crt_rsa_len; + ck_pair->key_len = test_srv_key_rsa_len; + vnet_app_add_cert_key_pair (ck_pair); + cm->ckpair_index = ck_pair->index; + + return 0; +} + +static int +ac_connect_rpc (void *rpc_args) +{ + vnet_connect_args_t *a = rpc_args; + int rv; + + rv = vnet_connect (a); + if (rv) + clib_warning (0, "connect returned: %U", format_session_error, rv); + + session_endpoint_free_ext_cfgs (&a->sep_ext); + vec_free (a); + + return rv; +} + +static void +ac_program_connect (vnet_connect_args_t *a) +{ + session_send_rpc_evt_to_thread_force (transport_cl_thread (), ac_connect_rpc, + a); +} + +static void +ac_connect () +{ + alpn_client_main_t *cm = &alpn_client_main; + vnet_connect_args_t *a = 0; + transport_endpt_ext_cfg_t *ext_cfg; + + vec_validate (a, 0); + clib_memset (a, 0, sizeof (a[0])); + + clib_memcpy (&a->sep_ext, &cm->connect_sep, sizeof (cm->connect_sep)); + a->app_index = cm->app_index; + + ext_cfg = + session_endpoint_add_ext_cfg (&a->sep_ext, TRANSPORT_ENDPT_EXT_CFG_CRYPTO, + sizeof (transport_endpt_crypto_cfg_t)); + ext_cfg->crypto.ckpair_index = cm->ckpair_index; + clib_memcpy (ext_cfg->crypto.alpn_protos, cm->alpn_protos, 4); + + ac_program_connect (a); +} + +static clib_error_t * +ac_run (vlib_main_t *vm) +{ + alpn_client_main_t *cm = &alpn_client_main; + uword event_type, *event_data = 0; + clib_error_t *error = 0; + + if (ac_attach ()) + return clib_error_return (0, "attach failed"); + + ac_connect (); + + vlib_process_wait_for_event_or_clock (vm, 10); + event_type = vlib_process_get_events (vm, &event_data); + switch (event_type) + { + case ~0: + error = clib_error_return (0, "timeout"); + break; + case AC_CLI_TEST_DONE: + vlib_cli_output (vm, "ALPN selected: %U", format_tls_alpn_proto, + cm->alpn_proto_selected); + break; + case AC_CLI_CONNECT_FAILED: + error = clib_error_return (0, "connect error %U", format_session_error, + event_data[0]); + break; + default: + error = clib_error_return (0, "unexpected event %d", event_type); + break; + } + + vec_free (event_data); + return error; +} + +static int +ac_detach () +{ + alpn_client_main_t *cm = &alpn_client_main; + vnet_app_detach_args_t _da, *da = &_da; + int rv; + + if (cm->app_index == APP_INVALID_INDEX) + return 0; + + da->app_index = cm->app_index; + da->api_client_index = ~0; + rv = vnet_application_detach (da); + cm->app_index = APP_INVALID_INDEX; + + return rv; +} + +static clib_error_t * +alpn_client_run_command_fn (vlib_main_t *vm, unformat_input_t *input, + vlib_cli_command_t *cmd) +{ + alpn_client_main_t *cm = &alpn_client_main; + unformat_input_t _line_input, *line_input = &_line_input; + clib_error_t *error = 0; + + cm->tls_engine = CRYPTO_ENGINE_OPENSSL; + + if (!unformat_user (input, unformat_line_input, line_input)) + return clib_error_return (0, "expected URI"); + + while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT) + { + if (unformat (line_input, "uri %_%v%_", &cm->uri)) + ; + else if (unformat (line_input, "tls-engine %d", &cm->tls_engine)) + ; + else if (unformat (line_input, "alpn-proto1 %d", &cm->alpn_protos[0])) + ; + else if (unformat (line_input, "alpn-proto2 %d", &cm->alpn_protos[1])) + ; + else if (unformat (line_input, "alpn-proto3 %d", &cm->alpn_protos[2])) + ; + else if (unformat (line_input, "alpn-proto4 %d", &cm->alpn_protos[3])) + ; + else + { + error = clib_error_return (0, "failed: unknown input `%U'", + format_unformat_error, line_input); + goto done; + } + } + + cm->cli_node_index = vlib_get_current_process (vm)->node_runtime.node_index; + + if (cm->uri == 0) + { + error = clib_error_return (0, "uri not defined"); + goto done; + } + + if (parse_uri ((char *) cm->uri, &cm->connect_sep)) + { + error = clib_error_return (0, "invalid uri"); + goto done; + } + + session_enable_disable_args_t args = { .is_en = 1, + .rt_engine_type = + RT_BACKEND_ENGINE_RULE_TABLE }; + vlib_worker_thread_barrier_sync (vm); + vnet_session_enable_disable (vm, &args); + vlib_worker_thread_barrier_release (vm); + + error = ac_run (vm); + + if (ac_detach ()) + { + if (!error) + error = clib_error_return (0, "detach failed"); + else + clib_warning ("detach failed"); + } + +done: + vec_free (cm->uri); + unformat_free (line_input); + return error; +} + +VLIB_CLI_COMMAND (alpn_client_run_command, static) = { + .path = "test alpn client", + .short_help = "test alpn client [uri ] [tls-engine %d]", + .function = alpn_client_run_command_fn, +}; + +clib_error_t * +alpn_client_main_init (vlib_main_t *vm) +{ + alpn_client_main_t *cm = &alpn_client_main; + cm->vlib_main = vm; + cm->app_index = APP_INVALID_INDEX; + return 0; +} + +VLIB_INIT_FUNCTION (alpn_client_main_init); diff --git a/src/plugins/hs_apps/alpn_server.c b/src/plugins/hs_apps/alpn_server.c new file mode 100644 index 00000000000..ba0cc144bca --- /dev/null +++ b/src/plugins/hs_apps/alpn_server.c @@ -0,0 +1,261 @@ +/* SPDX-License-Identifier: Apache-2.0 + * Copyright(c) 2025 Cisco Systems, Inc. + */ + +#include +#include +#include + +typedef struct +{ + u32 app_index; + u32 listener_handle; + u8 *uri; + u32 tls_engine; + u32 ckpair_index; + u8 alpn_protos[4]; + vlib_main_t *vlib_main; +} alpn_server_main_t; + +alpn_server_main_t alpn_server_main; + +static int +as_ts_rx_callback (session_t *ts) +{ + clib_warning ("called..."); + return -1; +} + +static int +as_ts_tx_callback (session_t *ts) +{ + clib_warning ("called..."); + return -1; +} + +static int +as_ts_accept_callback (session_t *ts) +{ + tls_alpn_proto_t alpn_proto; + + ts->session_state = SESSION_STATE_READY; + + alpn_proto = tls_get_alpn_selected (ts->connection_index); + clib_warning ("ALPN selected: %U", format_tls_alpn_proto, alpn_proto); + + return 0; +} + +static int +as_ts_connected_callback (u32 app_index, u32 api_context, session_t *s, + session_error_t err) +{ + clib_warning ("called..."); + return -1; +} + +static void +as_ts_disconnect_callback (session_t *s) +{ + alpn_server_main_t *sm = &alpn_server_main; + vnet_disconnect_args_t _a = { 0 }, *a = &_a; + + a->handle = session_handle (s); + a->app_index = sm->app_index; + vnet_disconnect_session (a); +} + +static void +as_ts_reset_callback (session_t *s) +{ + alpn_server_main_t *sm = &alpn_server_main; + vnet_disconnect_args_t _a = { 0 }, *a = &_a; + + a->handle = session_handle (s); + a->app_index = sm->app_index; + vnet_disconnect_session (a); +} + +static void +as_ts_cleanup_callback (session_t *s, session_cleanup_ntf_t ntf) +{ + return; +} + +static int +as_add_segment_callback (u32 client_index, u64 segment_handle) +{ + return 0; +} + +static int +as_del_segment_callback (u32 client_index, u64 segment_handle) +{ + return 0; +} + +static session_cb_vft_t as_session_cb_vft = { + .session_accept_callback = as_ts_accept_callback, + .session_disconnect_callback = as_ts_disconnect_callback, + .session_connected_callback = as_ts_connected_callback, + .add_segment_callback = as_add_segment_callback, + .del_segment_callback = as_del_segment_callback, + .builtin_app_rx_callback = as_ts_rx_callback, + .builtin_app_tx_callback = as_ts_tx_callback, + .session_reset_callback = as_ts_reset_callback, + .session_cleanup_callback = as_ts_cleanup_callback, +}; + +static int +as_attach () +{ + alpn_server_main_t *sm = &alpn_server_main; + vnet_app_add_cert_key_pair_args_t _ck_pair, *ck_pair = &_ck_pair; + u64 options[APP_OPTIONS_N_OPTIONS]; + vnet_app_attach_args_t _a, *a = &_a; + + clib_memset (a, 0, sizeof (*a)); + clib_memset (options, 0, sizeof (options)); + + a->api_client_index = ~0; + a->name = format (0, "test_alpn_server"); + a->session_cb_vft = &as_session_cb_vft; + a->options = options; + a->options[APP_OPTIONS_SEGMENT_SIZE] = 128 << 20; + a->options[APP_OPTIONS_ADD_SEGMENT_SIZE] = 128 << 20; + a->options[APP_OPTIONS_RX_FIFO_SIZE] = 8 << 10; + a->options[APP_OPTIONS_TX_FIFO_SIZE] = 8 << 10; + a->options[APP_OPTIONS_FLAGS] = APP_OPTIONS_FLAGS_IS_BUILTIN; + a->options[APP_OPTIONS_PREALLOC_FIFO_PAIRS] = 0; + a->options[APP_OPTIONS_TLS_ENGINE] = sm->tls_engine; + + if (vnet_application_attach (a)) + { + vec_free (a->name); + return -1; + } + + vec_free (a->name); + sm->app_index = a->app_index; + + clib_memset (ck_pair, 0, sizeof (*ck_pair)); + ck_pair->cert = (u8 *) test_srv_crt_rsa; + ck_pair->key = (u8 *) test_srv_key_rsa; + ck_pair->cert_len = test_srv_crt_rsa_len; + ck_pair->key_len = test_srv_key_rsa_len; + vnet_app_add_cert_key_pair (ck_pair); + sm->ckpair_index = ck_pair->index; + + return 0; +} + +static int +as_listen () +{ + alpn_server_main_t *sm = &alpn_server_main; + vnet_listen_args_t _a, *a = &_a; + transport_endpt_ext_cfg_t *ext_cfg; + char *uri; + int rv; + + clib_memset (a, 0, sizeof (*a)); + a->app_index = sm->app_index; + + uri = (char *) sm->uri; + ASSERT (uri); + + if (parse_uri (uri, &a->sep_ext)) + return -1; + + ext_cfg = + session_endpoint_add_ext_cfg (&a->sep_ext, TRANSPORT_ENDPT_EXT_CFG_CRYPTO, + sizeof (transport_endpt_crypto_cfg_t)); + ext_cfg->crypto.ckpair_index = sm->ckpair_index; + clib_memcpy (ext_cfg->crypto.alpn_protos, sm->alpn_protos, 4); + + rv = vnet_listen (a); + if (rv == 0) + { + sm->listener_handle = a->handle; + } + + session_endpoint_free_ext_cfgs (&a->sep_ext); + + return rv; +} + +static clib_error_t * +alpn_server_create_command_fn (vlib_main_t *vm, unformat_input_t *input, + vlib_cli_command_t *cmd) +{ + alpn_server_main_t *sm = &alpn_server_main; + unformat_input_t _line_input, *line_input = &_line_input; + clib_error_t *error = 0; + + sm->tls_engine = CRYPTO_ENGINE_OPENSSL; + + if (!unformat_user (input, unformat_line_input, line_input)) + return clib_error_return (0, "expected URI"); + + while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT) + { + if (unformat (line_input, "uri %_%v%_", &sm->uri)) + ; + else if (unformat (line_input, "tls-engine %d", &sm->tls_engine)) + ; + else if (unformat (line_input, "alpn-proto1 %d", &sm->alpn_protos[0])) + ; + else if (unformat (line_input, "alpn-proto2 %d", &sm->alpn_protos[1])) + ; + else if (unformat (line_input, "alpn-proto3 %d", &sm->alpn_protos[2])) + ; + else if (unformat (line_input, "alpn-proto4 %d", &sm->alpn_protos[3])) + ; + else + { + error = clib_error_return (0, "failed: unknown input `%U'", + format_unformat_error, line_input); + goto done; + } + } + + session_enable_disable_args_t args = { .is_en = 1, + .rt_engine_type = + RT_BACKEND_ENGINE_RULE_TABLE }; + vnet_session_enable_disable (vm, &args); + + if (as_attach ()) + { + error = clib_error_return (0, "attach failed"); + goto done; + } + if (as_listen ()) + { + error = clib_error_return (0, "lsiten failed"); + goto done; + } + + vlib_cli_output (vm, "server started"); + +done: + vec_free (sm->uri); + unformat_free (line_input); + + return error; +} + +VLIB_CLI_COMMAND (alpn_server_create_command, static) = { + .path = "test alpn server", + .short_help = "test alpn server uri [tls-engine %d]", + .function = alpn_server_create_command_fn, +}; + +clib_error_t * +alpn_server_main_init (vlib_main_t *vm) +{ + alpn_server_main_t *sm = &alpn_server_main; + sm->vlib_main = vm; + return 0; +} + +VLIB_INIT_FUNCTION (alpn_server_main_init); diff --git a/src/plugins/tlsopenssl/tls_openssl.c b/src/plugins/tlsopenssl/tls_openssl.c index 1337919ea0b..825aa91ee99 100644 --- a/src/plugins/tlsopenssl/tls_openssl.c +++ b/src/plugins/tlsopenssl/tls_openssl.c @@ -269,6 +269,14 @@ openssl_write_from_fifo_into_ssl (svm_fifo_t *f, tls_ctx_t *ctx, return wrote; } +u8 * +format_openssl_alpn_proto (u8 *s, va_list *va) +{ + const unsigned char *proto = va_arg (*va, const unsigned char *); + unsigned int proto_len = va_arg (*va, unsigned int); + return format (s, "%U", format_ascii_bytes, proto, proto_len); +} + void openssl_handle_handshake_failure (tls_ctx_t *ctx) { @@ -345,6 +353,22 @@ openssl_ctx_handshake_rx (tls_ctx_t *ctx, session_t *tls_session) /* * Handshake complete */ + if (ctx->alpn_list) + { + const unsigned char *proto = 0; + unsigned int proto_len; + SSL_get0_alpn_selected (oc->ssl, &proto, &proto_len); + if (proto_len) + { + TLS_DBG (1, "Selected ALPN protocol: %U", format_openssl_alpn_proto, + proto, proto_len); + tls_alpn_proto_id_t id = { .len = (u8) proto_len, + .base = (u8 *) proto }; + ctx->alpn_selected = tls_alpn_proto_by_str (&id); + } + else + TLS_DBG (1, "No ALPN negotiated"); + } if (!SSL_is_server (oc->ssl)) { /* @@ -775,6 +799,18 @@ openssl_ctx_init_client (tls_ctx_t * ctx) SSL_CTX_set_options (oc->client_ssl_ctx, flags); SSL_CTX_set1_cert_store (oc->client_ssl_ctx, om->cert_store); + if (ctx->alpn_list) + { + rv = SSL_CTX_set_alpn_protos (oc->client_ssl_ctx, + (const unsigned char *) ctx->alpn_list, + (unsigned int) vec_len (ctx->alpn_list)); + if (rv != 0) + { + TLS_DBG (1, "Couldn't set alpn protos"); + return -1; + } + } + oc->ssl = SSL_new (oc->client_ssl_ctx); if (oc->ssl == NULL) { @@ -846,6 +882,22 @@ openssl_ctx_init_client (tls_ctx_t * ctx) return 0; } +static int +openssl_alpn_select_cb (SSL *ssl, const unsigned char **out, + unsigned char *outlen, const unsigned char *in, + unsigned int inlen, void *arg) +{ + u8 *proto_list = arg; + if (SSL_select_next_proto ( + (unsigned char **) out, outlen, (const unsigned char *) proto_list, + vec_len (proto_list), in, inlen) != OPENSSL_NPN_NEGOTIATED) + { + TLS_DBG (1, "server support no alpn proto advertised by client"); + return SSL_TLSEXT_ERR_ALERT_FATAL; + } + return SSL_TLSEXT_ERR_OK; +} + static int openssl_start_listen (tls_ctx_t * lctx) { @@ -988,6 +1040,10 @@ openssl_start_listen (tls_ctx_t * lctx) BIO_free (cert_bio); + if (lctx->alpn_list) + SSL_CTX_set_alpn_select_cb (ssl_ctx, openssl_alpn_select_cb, + (void *) lctx->alpn_list); + olc_index = openssl_listen_ctx_alloc (); olc = openssl_lctx_get (olc_index); olc->ssl_ctx = ssl_ctx; diff --git a/src/vnet/CMakeLists.txt b/src/vnet/CMakeLists.txt index 14c91100eb6..65d612c0f3a 100644 --- a/src/vnet/CMakeLists.txt +++ b/src/vnet/CMakeLists.txt @@ -1020,6 +1020,7 @@ list(APPEND VNET_HEADERS tls/tls_inlines.h tls/tls_record.h tls/tls_test.h + tls/tls_types.h ) diff --git a/src/vnet/session/transport_types.h b/src/vnet/session/transport_types.h index 55cb1206e6b..0e412939438 100644 --- a/src/vnet/session/transport_types.h +++ b/src/vnet/session/transport_types.h @@ -291,6 +291,7 @@ typedef enum transport_endpt_ext_cfg_type_ typedef struct transport_endpt_crypto_cfg_ { u32 ckpair_index; + u8 alpn_protos[4]; /**< ordered by preference for server */ u8 crypto_engine; u8 hostname[256]; /**< full domain len is 255 as per rfc 3986 */ } transport_endpt_crypto_cfg_t; diff --git a/src/vnet/tls/tls.c b/src/vnet/tls/tls.c index d409ee5f126..1b07352db26 100644 --- a/src/vnet/tls/tls.c +++ b/src/vnet/tls/tls.c @@ -23,6 +23,12 @@ tls_engine_vft_t *tls_vfts; void tls_disconnect (u32 ctx_handle, clib_thread_index_t thread_index); +static const tls_alpn_proto_id_t tls_alpn_proto_ids[] = { +#define _(sym, str) { (u8) (sizeof (str) - 1), (u8 *) str }, + foreach_tls_alpn_protos +#undef _ +}; + void tls_disconnect_transport (tls_ctx_t * ctx) { @@ -92,6 +98,46 @@ tls_add_app_q_evt (app_worker_t *app_wrk, session_t *app_session) return 0; } +tls_alpn_proto_t +tls_alpn_proto_by_str (tls_alpn_proto_id_t *alpn_id) +{ + tls_main_t *tm = &tls_main; + uword *p; + + p = hash_get_mem (tm->alpn_proto_by_str, alpn_id); + if (p) + return p[0]; + + return TLS_ALPN_PROTO_NONE; +} + +tls_alpn_proto_t +tls_get_alpn_selected (u32 ctx_handle) +{ + tls_ctx_t *ctx; + ctx = tls_ctx_get (ctx_handle); + return ctx->alpn_selected; +} + +u8 * +format_tls_alpn_proto (u8 *s, va_list *args) +{ + tls_alpn_proto_t alpn_proto = va_arg (*args, int); + u8 *t = 0; + + switch (alpn_proto) + { +#define _(sym, str) \ + case TLS_ALPN_PROTO_##sym: \ + t = (u8 *) str; \ + break; + foreach_tls_alpn_protos +#undef _ + default : return format (s, "BUG: unknown"); + } + return format (s, "%s", t); +} + u32 tls_listener_ctx_alloc (void) { @@ -629,7 +675,9 @@ tls_connect (transport_endpoint_cfg_t * tep) tls_ctx_t *ctx; u32 ctx_index; transport_endpt_ext_cfg_t *ext_cfg; - int rv; + int rv, i; + u8 *p; + const tls_alpn_proto_id_t *alpn_proto; sep = (session_endpoint_cfg_t *) tep; ext_cfg = session_endpoint_get_ext_cfg (sep, TRANSPORT_ENDPT_EXT_CFG_CRYPTO); @@ -662,6 +710,13 @@ tls_connect (transport_endpoint_cfg_t * tep) ctx->srv_hostname = format (0, "%s", ccfg->hostname); vec_terminate_c_string (ctx->srv_hostname); } + for (i = 0; i < sizeof (ccfg->alpn_protos) && ccfg->alpn_protos[i]; i++) + { + alpn_proto = &tls_alpn_proto_ids[ccfg->alpn_protos[i]]; + vec_add2 (ctx->alpn_list, p, alpn_proto->len + 1); + *p++ = alpn_proto->len; + clib_memcpy_fast (p, alpn_proto->base, alpn_proto->len); + } ctx->tls_ctx_engine = engine_type; @@ -712,7 +767,9 @@ tls_start_listen (u32 app_listener_index, transport_endpoint_cfg_t *tep) tls_ctx_t *lctx; u32 lctx_index; transport_endpt_ext_cfg_t *ext_cfg; - int rv; + int rv, i; + u8 *p; + const tls_alpn_proto_id_t *alpn_proto; sep = (session_endpoint_cfg_t *) tep; ext_cfg = session_endpoint_get_ext_cfg (sep, TRANSPORT_ENDPT_EXT_CFG_CRYPTO); @@ -762,6 +819,13 @@ tls_start_listen (u32 app_listener_index, transport_endpoint_cfg_t *tep) lctx->ckpair_index = ccfg->ckpair_index; lctx->c_s_index = app_listener_index; lctx->c_flags |= TRANSPORT_CONNECTION_F_NO_LOOKUP; + for (i = 0; i < sizeof (ccfg->alpn_protos) && ccfg->alpn_protos[i]; i++) + { + alpn_proto = &tls_alpn_proto_ids[ccfg->alpn_protos[i]]; + vec_add2 (lctx->alpn_list, p, alpn_proto->len + 1); + *p++ = alpn_proto->len; + clib_memcpy_fast (p, alpn_proto->base, alpn_proto->len); + } if (tls_vfts[engine_type].ctx_start_listen (lctx)) { @@ -1243,12 +1307,28 @@ tls_register_engine (const tls_engine_vft_t * vft, crypto_engine_type_t type) tls_vfts[type] = *vft; } +static uword +tls_alpn_proto_hash_key_sum (hash_t *h, uword key) +{ + tls_alpn_proto_id_t *id = uword_to_pointer (key, tls_alpn_proto_id_t *); + return hash_memory (id->base, id->len, 0); +} + +static uword +tls_alpn_proto_hash_key_equal (hash_t *h, uword key1, uword key2) +{ + tls_alpn_proto_id_t *id1 = uword_to_pointer (key1, tls_alpn_proto_id_t *); + tls_alpn_proto_id_t *id2 = uword_to_pointer (key2, tls_alpn_proto_id_t *); + return id1 && id2 && tls_alpn_proto_id_eq (id1, id2); +} + static clib_error_t * tls_init (vlib_main_t * vm) { vlib_thread_main_t *vtm = vlib_get_thread_main (); tls_main_t *tm = &tls_main; u32 num_threads; + const tls_alpn_proto_id_t *alpn_proto; num_threads = 1 /* main thread */ + vtm->n_threads; @@ -1274,7 +1354,18 @@ tls_init (vlib_main_t * vm) FIB_PROTOCOL_IP4, ~0); transport_register_protocol (TRANSPORT_PROTO_DTLS, &dtls_proto, FIB_PROTOCOL_IP6, ~0); - return 0; + + tm->alpn_proto_by_str = hash_create2 ( + 0, sizeof (tls_alpn_proto_id_t), sizeof (uword), + tls_alpn_proto_hash_key_sum, tls_alpn_proto_hash_key_equal, 0, 0); + +#define _(sym, str) \ + alpn_proto = &tls_alpn_proto_ids[TLS_ALPN_PROTO_##sym]; \ + hash_set_mem (tm->alpn_proto_by_str, alpn_proto, TLS_ALPN_PROTO_##sym); + foreach_tls_alpn_protos +#undef _ + + return 0; } VLIB_INIT_FUNCTION (tls_init); diff --git a/src/vnet/tls/tls.h b/src/vnet/tls/tls.h index 7e69432512e..3c38aaf8c79 100644 --- a/src/vnet/tls/tls.h +++ b/src/vnet/tls/tls.h @@ -16,6 +16,7 @@ #include #include #include +#include #include #ifndef SRC_VNET_TLS_TLS_H_ @@ -121,6 +122,8 @@ typedef struct tls_ctx_ u8 *srv_hostname; u32 ckpair_index; transport_proto_t tls_type; + u8 *alpn_list; + tls_alpn_proto_t alpn_selected; } tls_ctx_t; typedef struct tls_main_ @@ -133,6 +136,7 @@ typedef struct tls_main_ u8 **rx_bufs; u8 **tx_bufs; + uword *alpn_proto_by_str; /* * Config */ diff --git a/src/vnet/tls/tls_types.h b/src/vnet/tls/tls_types.h new file mode 100644 index 00000000000..deb79239eb3 --- /dev/null +++ b/src/vnet/tls/tls_types.h @@ -0,0 +1,72 @@ +/* SPDX-License-Identifier: Apache-2.0 + * Copyright(c) 2025 Cisco Systems, Inc. + */ + +#ifndef SRC_VNET_TLS_TLS_TYPES_H_ +#define SRC_VNET_TLS_TLS_TYPES_H_ + +#include + +#define foreach_tls_alpn_protos \ + _ (NONE, "none") \ + _ (HTTP_1_1, "http/1.1") \ + _ (HTTP_2, "h2") \ + _ (HTTP_3, "h3") \ + _ (IMAP, "imap") \ + _ (POP3, "pop3") \ + _ (SMB2, "smb") \ + _ (TURN, "stun.turn") \ + _ (STUN, "stun.nat-discovery") \ + _ (WEBRTC, "webrtc") \ + _ (CWEBRTC, "c-webrtc") \ + _ (FTP, "ftp") \ + _ (MANAGE_SIEVE, "managesieve") \ + _ (COAP_TLS, "coap") \ + _ (COAP_DSTL, "co") \ + _ (XMPP_CLIENT, "xmpp-client") \ + _ (XMPP_SERVER, "xmpp-server") \ + _ (ACME_TLS_1, "acme-tls/1") \ + _ (MQTT, "mqtt") \ + _ (DNS_OVER_TLS, "dot") \ + _ (NTSKE_1, "ntske/1") \ + _ (SUN_RPC, "sunrpc") \ + _ (IRC, "irc") \ + _ (NNTP, "nntp") \ + _ (NNSP, "nnsp") \ + _ (DOQ, "doq") \ + _ (SIP_2, "sip/2") \ + _ (TDS_8_0, "tds/8.0") \ + _ (DICOM, "dicom") \ + _ (POSTGRESQL, "postgresql") \ + _ (RADIUS_1_0, "radius/1.0") \ + _ (RADIUS_1_1, "radius/1.1") + +typedef enum tls_alpn_proto_ +{ +#define _(sym, str) TLS_ALPN_PROTO_##sym, + foreach_tls_alpn_protos +#undef _ +} __clib_packed tls_alpn_proto_t; + +typedef struct tls_alpn_proto_id_ +{ + u8 len; + u8 *base; +} tls_alpn_proto_id_t; + +static inline u8 +tls_alpn_proto_id_eq (tls_alpn_proto_id_t *actual, + tls_alpn_proto_id_t *expected) +{ + if (actual->len != expected->len) + return 0; + return memcmp (actual->base, expected->base, expected->len) == 0 ? 1 : 0; +} + +tls_alpn_proto_t tls_alpn_proto_by_str (tls_alpn_proto_id_t *alpn_id); + +tls_alpn_proto_t tls_get_alpn_selected (u32 ctx_handle); + +format_function_t format_tls_alpn_proto; + +#endif /* SRC_VNET_TLS_TLS_TYPES_H_ */ -- 2.16.6