/* Find base session to which the user want to attach a stream */
quic_session_handle = session_handle (quic_session);
- QUIC_DBG (2, "Connect stream: quic_session_handle 0x%lx",
- quic_session_handle);
+ QUIC_DBG (2, "Connect stream: session 0x%lx", quic_session_handle);
if (session_type_transport_proto (quic_session->session_type) !=
TRANSPORT_PROTO_QUIC)
return 0;
}
+static_always_inline void
+quic_ctx_set_alpn_protos (quic_ctx_t *ctx, transport_endpt_crypto_cfg_t *ccfg)
+{
+ ctx->alpn_protos[0] = ccfg->alpn_protos[0];
+ ctx->alpn_protos[1] = ccfg->alpn_protos[1];
+ ctx->alpn_protos[2] = ccfg->alpn_protos[2];
+ ctx->alpn_protos[3] = ccfg->alpn_protos[3];
+}
+
static int
quic_connect_connection (session_endpoint_cfg_t * sep)
{
cargs->sep_ext.ns_index = app->ns_index;
cargs->sep_ext.transport_flags = TRANSPORT_CFG_F_CONNECTED;
+ quic_ctx_set_alpn_protos (ctx, ccfg);
ctx->crypto_engine = ccfg->crypto_engine;
ctx->ckpair_index = ccfg->ckpair_index;
error = quic_eng_crypto_context_acquire (ctx);
lctx->parent_app_id = app_wrk->app_index;
lctx->udp_session_handle = udp_handle;
lctx->c_s_index = quic_listen_session_index;
+ lctx->listener_ctx_id = lctx_index;
+ quic_ctx_set_alpn_protos (lctx, ccfg);
lctx->crypto_engine = ccfg->crypto_engine;
lctx->ckpair_index = ccfg->ckpair_index;
if ((rv = quic_eng_crypto_context_acquire (lctx)))
quic_common_get_transport_endpoint (ctx, tep, is_lcl);
}
+static tls_alpn_proto_t
+quic_get_alpn_selected (u32 ctx_index, clib_thread_index_t thread_index)
+{
+ quic_ctx_t *ctx;
+ ctx = quic_ctx_get (ctx_index, thread_index);
+ return ctx->alpn_selected;
+}
+
static session_cb_vft_t quic_app_cb_vft = {
.session_accept_callback = quic_udp_session_accepted_callback,
.session_disconnect_callback = quic_udp_session_disconnect_callback,
.format_listener = format_quic_listener,
.get_transport_endpoint = quic_get_transport_endpoint,
.get_transport_listener_endpoint = quic_get_transport_listener_endpoint,
+ .get_alpn_selected = quic_get_alpn_selected,
.transport_options = {
.name = "quic",
.short_name = "Q",
u32 ckpair_index;
u32 crypto_engine;
u32 crypto_context_index;
+ u8 alpn_protos[4];
+ tls_alpn_proto_t alpn_selected;
u8 flags;
struct
quic_session = session_alloc (ctx->c_thread_index);
QUIC_DBG (2,
- "Accept connection (new quic_session): session_handle 0x%lx, "
+ "Accept connection (new quic_session): session 0x%lx, "
"session_index %u, ctx_index %u, thread %u",
session_handle (quic_session), quic_session->session_index,
ctx->c_c_index, ctx->c_thread_index);
2, "Accept connection: conn key value 0x%llx, ctx_index %u, thread %u",
kv.value, pctx->ctx_index, pctx->thread_index);
+ if (lctx->alpn_protos[0])
+ {
+ const char *proto = ptls_get_negotiated_protocol (quicly_get_tls (conn));
+ if (proto)
+ {
+ tls_alpn_proto_id_t id = { .base = (u8 *) proto,
+ .len = strlen (proto) };
+ ctx->alpn_selected = tls_alpn_proto_by_str (&id);
+ }
+ }
+
/* If notify fails, reset connection immediatly */
rv = app_worker_init_accepted (quic_session);
if (rv)
{
clib_bihash_kv_16_8_t kv;
quicly_context_t *quicly_ctx;
+ ptls_iovec_t alpn_list[4];
+ ptls_handshake_properties_t hs_properties = {
+ .client.negotiated_protocols.list = alpn_list
+ };
+ const tls_alpn_proto_id_t *alpn_proto;
quic_quicly_main_t *qqm = &quic_quicly_main;
- int ret;
+ int ret, i;
+ /* build alpn list if app provided something */
+ for (i = 0; i < sizeof (ctx->alpn_protos) && ctx->alpn_protos[i]; i++)
+ {
+ alpn_proto = &tls_alpn_proto_ids[ctx->alpn_protos[i]];
+ alpn_list[i].base = alpn_proto->base;
+ alpn_list[i].len = (size_t) alpn_proto->len;
+ hs_properties.client.negotiated_protocols.count++;
+ }
quicly_ctx = quic_quicly_get_quicly_ctx_from_ctx (ctx);
- ret = quicly_connect (
- (quicly_conn_t **) &ctx->conn, quicly_ctx, (char *) ctx->srv_hostname, sa,
- NULL, &qqm->next_cid[thread_index], ptls_iovec_init (NULL, 0),
- &qqm->hs_properties, NULL, NULL);
+ ret = quicly_connect ((quicly_conn_t **) &ctx->conn, quicly_ctx,
+ (char *) ctx->srv_hostname, sa, NULL,
+ &qqm->next_cid[thread_index],
+ ptls_iovec_init (NULL, 0), &hs_properties, NULL, NULL);
++qqm->next_cid[thread_index].master_id;
/* save context handle in quicly connection */
quic_quicly_store_conn_ctx (ctx->conn, ctx);
quic_session->session_type =
session_type_from_proto_and_ip (TRANSPORT_PROTO_QUIC, ctx->udp_is_ip4);
+ if (ctx->alpn_protos[0])
+ {
+ const char *proto =
+ ptls_get_negotiated_protocol (quicly_get_tls (ctx->conn));
+ if (proto)
+ {
+ QUIC_DBG (2, "alpn proto selected %s", proto);
+ tls_alpn_proto_id_t id = { .base = (u8 *) proto,
+ .len = strlen (proto) };
+ ctx->alpn_selected = tls_alpn_proto_by_str (&id);
+ }
+ }
+
/* If quic session connected fails, immediatly close connection */
app_wrk = app_worker_get (ctx->parent_app_wrk_id);
if ((rv = app_worker_init_connected (app_wrk, quic_session)))
quic_main_t *qm;
ptls_cipher_suite_t ***quic_ciphers;
u32 *per_thread_crypto_key_indices;
- ptls_handshake_properties_t hs_properties;
clib_bihash_16_8_t connection_hash; /**< quic connection id -> conn handle */
quic_quicly_session_cache_t session_cache;
quicly_cid_plaintext_t *next_cid;
#if QUIC_DEBUG >= 2
if (ctx->c_s_index == QUIC_SESSION_INVALID)
{
- clib_warning ("Unopened Session closed by peer (%U) %.*S ",
- quic_quicly_format_err, code, reason_len, reason);
+ clib_warning ("Unopened Session closed by peer: error %U, reason %U, "
+ "ctx_index %u, thread %u",
+ quic_quicly_format_err, code, format_ascii_bytes, reason,
+ reason_len, ctx->c_c_index, ctx->c_thread_index);
}
else
{
session_t *quic_session =
session_get (ctx->c_s_index, ctx->c_thread_index);
- clib_warning ("Session 0x%lx closed by peer (%U) %.*s ",
+ clib_warning ("Session closed by peer: session 0x%lx, error %U, reason "
+ "%U, ctx_index %u, thread %u",
session_handle (quic_session), quic_quicly_format_err,
- code, reason_len, reason);
+ code, format_ascii_bytes, reason, reason_len,
+ ctx->c_c_index, ctx->c_thread_index);
}
#endif
ctx->conn_state = QUIC_CONN_STATE_PASSIVE_CLOSING;
};
static quicly_now_t quicly_vpp_now_cb = { quic_quicly_get_time };
+static int
+quic_quicly_on_client_hello_ptls (ptls_on_client_hello_t *self, ptls_t *tls,
+ ptls_on_client_hello_parameters_t *params)
+{
+ quic_quicly_on_client_hello_t *ch_ctx =
+ (quic_quicly_on_client_hello_t *) self;
+ quic_ctx_t *lctx;
+ const tls_alpn_proto_id_t *alpn_proto;
+ int i, j, ret;
+
+ lctx = quic_quicly_get_quic_ctx (ch_ctx->lctx_index, 0);
+
+ /* handle ALPN, both sides need to offer something */
+ if (params->negotiated_protocols.count && lctx->alpn_protos[0])
+ {
+ for (i = 0; i < sizeof (lctx->alpn_protos) && lctx->alpn_protos[i]; i++)
+ {
+ alpn_proto = &tls_alpn_proto_ids[lctx->alpn_protos[i]];
+ for (j = 0; j < params->negotiated_protocols.count; j++)
+ {
+ if (alpn_proto->len != params->negotiated_protocols.list[j].len)
+ continue;
+ if (!memcmp (alpn_proto->base,
+ params->negotiated_protocols.list[j].base,
+ alpn_proto->len))
+ goto alpn_proto_match;
+ }
+ }
+#if QUIC_DEBUG >= 2
+ u8 *client_alpn_list = 0;
+ for (j = 0; j < params->negotiated_protocols.count; j++)
+ {
+ if (j > 0)
+ vec_add (client_alpn_list, ", ", 2);
+ vec_add (client_alpn_list, params->negotiated_protocols.list[j].base,
+ params->negotiated_protocols.list[j].len);
+ }
+ clib_warning (
+ "unsupported alpn proto(s) requested by client: proto [%U], "
+ "ctx_index %u, thread %u",
+ format_ascii_bytes, client_alpn_list,
+ (uword) vec_len (client_alpn_list), lctx->c_c_index,
+ lctx->c_thread_index);
+#endif
+ return PTLS_ALERT_NO_APPLICATION_PROTOCOL;
+ alpn_proto_match:
+ if ((ret = ptls_set_negotiated_protocol (tls, (char *) alpn_proto->base,
+ alpn_proto->len)) != 0)
+ {
+ QUIC_ERR ("ptls_set_negotiated_protocol failed: error %d, ctx_index "
+ "%u, thread %u",
+ ret, lctx->c_c_index, lctx->c_thread_index);
+ return ret;
+ }
+ QUIC_DBG (2, "alpn proto selected %U, ctx_index %u, thread %u",
+ format_ascii_bytes, alpn_proto->base, (uword) alpn_proto->len,
+ lctx->c_c_index, lctx->c_thread_index);
+ }
+ return 0;
+}
+
static int
quic_quicly_init_crypto_context (crypto_context_t *crctx, quic_ctx_t *ctx)
{
quicly_ctx = &data->quicly_ctx;
ptls_ctx = &data->ptls_ctx;
+ data->client_hello_ctx.super.cb = quic_quicly_on_client_hello_ptls;
+ data->client_hello_ctx.lctx_index = ctx->listener_ctx_id;
+
ptls_ctx->random_bytes = ptls_openssl_random_bytes;
ptls_ctx->get_time = &ptls_get_time;
ptls_ctx->key_exchanges = ptls_openssl_key_exchanges;
ptls_ctx->cipher_suites);
ptls_ctx->certificates.list = NULL;
ptls_ctx->certificates.count = 0;
- ptls_ctx->on_client_hello = NULL;
+ ptls_ctx->on_client_hello = &data->client_hello_ctx.super;
ptls_ctx->emit_certificate = NULL;
ptls_ctx->sign_certificate = NULL;
ptls_ctx->verify_certificate = NULL;
crypto_key_t key;
};
+typedef struct quic_quicly_on_client_hello_
+{
+ ptls_on_client_hello_t super;
+ u32 lctx_index;
+} quic_quicly_on_client_hello_t;
+
typedef struct quic_quicly_crypto_context_data_
{
quicly_context_t quicly_ctx;
char cid_key[QUIC_IV_LEN];
ptls_context_t ptls_ctx;
+ quic_quicly_on_client_hello_t client_hello_ctx;
} quic_quicly_crypto_context_data_t;
static_always_inline u8
--- /dev/null
+package main
+
+import (
+ . "fd.io/hs-test/infra"
+)
+
+func init() {
+ RegisterVethTests(QuicAlpMatchTest, QuicAlpnOverlapMatchTest, QuicAlpnServerPriorityMatchTest, QuicAlpnMismatchTest, QuicAlpnEmptyServerListTest, QuicAlpnEmptyClientListTest)
+}
+
+func QuicAlpMatchTest(s *VethsSuite) {
+ serverAddress := s.Interfaces.Server.Ip4AddressString() + ":" + s.Ports.Port1
+ s.Log(s.Containers.ServerVpp.VppInstance.Vppctl("test alpn server alpn-proto1 3 uri quic://" + serverAddress))
+
+ uri := "quic://" + serverAddress
+ o := s.Containers.ClientVpp.VppInstance.Vppctl("test alpn client alpn-proto1 3 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: h3")
+}
+
+func QuicAlpnOverlapMatchTest(s *VethsSuite) {
+ serverAddress := s.Interfaces.Server.Ip4AddressString() + ":" + s.Ports.Port1
+ s.Log(s.Containers.ServerVpp.VppInstance.Vppctl("test alpn server alpn-proto1 3 alpn-proto2 1 uri quic://" + serverAddress))
+
+ uri := "quic://" + serverAddress
+ o := s.Containers.ClientVpp.VppInstance.Vppctl("test alpn client alpn-proto1 2 alpn-proto2 3 uri " + uri)
+ s.Log(o)
+ s.AssertNotContains(o, "connect failed")
+ s.AssertNotContains(o, "timeout")
+ // selected based on overlap
+ s.AssertContains(o, "ALPN selected: h3")
+}
+
+func QuicAlpnServerPriorityMatchTest(s *VethsSuite) {
+ serverAddress := s.Interfaces.Server.Ip4AddressString() + ":" + s.Ports.Port1
+ s.Log(s.Containers.ServerVpp.VppInstance.Vppctl("test alpn server alpn-proto1 3 alpn-proto2 1 uri quic://" + serverAddress))
+
+ uri := "quic://" + serverAddress
+ o := s.Containers.ClientVpp.VppInstance.Vppctl("test alpn client alpn-proto1 1 alpn-proto2 3 uri " + uri)
+ s.Log(o)
+ s.AssertNotContains(o, "connect failed")
+ s.AssertNotContains(o, "timeout")
+ // selected based on server priority
+ s.AssertContains(o, "ALPN selected: h3")
+}
+
+func QuicAlpnMismatchTest(s *VethsSuite) {
+ s.Skip("QUIC bug: handshake failure not reported to client app as connect error, skipping...")
+ serverAddress := s.Interfaces.Server.Ip4AddressString() + ":" + s.Ports.Port1
+ s.Log(s.Containers.ServerVpp.VppInstance.Vppctl("test alpn server alpn-proto1 2 alpn-proto2 1 uri quic://" + serverAddress))
+
+ uri := "quic://" + serverAddress
+ 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 quic handshake")
+}
+
+func QuicAlpnEmptyServerListTest(s *VethsSuite) {
+ serverAddress := s.Interfaces.Server.Ip4AddressString() + ":" + s.Ports.Port1
+ s.Log(s.Containers.ServerVpp.VppInstance.Vppctl("test alpn server uri quic://" + serverAddress))
+
+ uri := "quic://" + serverAddress
+ 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")
+ // no alpn negotiation
+ s.AssertContains(o, "ALPN selected: none")
+}
+
+func QuicAlpnEmptyClientListTest(s *VethsSuite) {
+ serverAddress := s.Interfaces.Server.Ip4AddressString() + ":" + s.Ports.Port1
+ s.Log(s.Containers.ServerVpp.VppInstance.Vppctl("test alpn server alpn-proto1 3 alpn-proto2 1 uri quic://" + serverAddress))
+
+ uri := "quic://" + serverAddress
+ 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")
+}