tls: add ALPN support 60/42960/7
authorMatus Fabian <[email protected]>
Wed, 14 May 2025 16:33:43 +0000 (12:33 -0400)
committerFlorin Coras <[email protected]>
Tue, 20 May 2025 18:19:43 +0000 (18:19 +0000)
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 <[email protected]>
docs/spelling_wordlist.txt
extras/hs-test/tls_test.go [new file with mode: 0644]
src/plugins/hs_apps/CMakeLists.txt
src/plugins/hs_apps/alpn_client.c [new file with mode: 0644]
src/plugins/hs_apps/alpn_server.c [new file with mode: 0644]
src/plugins/tlsopenssl/tls_openssl.c
src/vnet/CMakeLists.txt
src/vnet/session/transport_types.h
src/vnet/tls/tls.c
src/vnet/tls/tls.h
src/vnet/tls/tls_types.h [new file with mode: 0644]

index ecfebfb..b407b39 100644 (file)
@@ -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 (file)
index 0000000..4265f36
--- /dev/null
@@ -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")
+}
index 3e80a84..1d83175 100644 (file)
@@ -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 (file)
index 0000000..8f744e1
--- /dev/null
@@ -0,0 +1,356 @@
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright(c) 2025 Cisco Systems, Inc.
+ */
+
+#include <vnet/session/application_interface.h>
+#include <vnet/session/application.h>
+#include <vnet/session/session.h>
+#include <vnet/tls/tls_types.h>
+
+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://ip/port>] [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 (file)
index 0000000..ba0cc14
--- /dev/null
@@ -0,0 +1,261 @@
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright(c) 2025 Cisco Systems, Inc.
+ */
+
+#include <vnet/session/application_interface.h>
+#include <vnet/session/session.h>
+#include <vnet/tls/tls_types.h>
+
+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://ip/port> [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);
index 1337919..825aa91 100644 (file)
@@ -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;
index 14c9110..65d612c 100644 (file)
@@ -1020,6 +1020,7 @@ list(APPEND VNET_HEADERS
   tls/tls_inlines.h
   tls/tls_record.h
   tls/tls_test.h
+  tls/tls_types.h
 )
 
 
index 55cb120..0e41293 100644 (file)
@@ -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;
index d409ee5..1b07352 100644 (file)
@@ -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);
index 7e69432..3c38aaf 100644 (file)
@@ -16,6 +16,7 @@
 #include <vnet/session/application_interface.h>
 #include <vnet/session/application.h>
 #include <vnet/session/session.h>
+#include <vnet/tls/tls_types.h>
 #include <vppinfra/lock.h>
 
 #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 (file)
index 0000000..deb7923
--- /dev/null
@@ -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 <vppinfra/types.h>
+
+#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_ */