QUIC PORT, CHANNEL: Move DEMUX and default packet handling out of CHANNEL
authorHugo Landau <hlandau@openssl.org>
Thu, 9 Nov 2023 10:27:13 +0000 (10:27 +0000)
committerHugo Landau <hlandau@openssl.org>
Thu, 21 Dec 2023 08:11:59 +0000 (08:11 +0000)
Reviewed-by: Tomas Mraz <tomas@openssl.org>
Reviewed-by: Matt Caswell <matt@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/22674)

include/internal/quic_channel.h
include/internal/quic_port.h
ssl/quic/quic_channel.c
ssl/quic/quic_channel_local.h
ssl/quic/quic_port.c
ssl/quic/quic_port_local.h
ssl/quic/quic_tserver.c

index 59caf58b30c45a8fa00d731aae0c61cb2dbbbc65..b8d3edf23f8b16ce59188fe7a2147f6a88d83680 100644 (file)
@@ -262,6 +262,11 @@ void ossl_quic_channel_on_remote_conn_close(QUIC_CHANNEL *ch,
 void ossl_quic_channel_on_new_conn_id(QUIC_CHANNEL *ch,
                                       OSSL_QUIC_FRAME_NEW_CONN_ID *f);
 
+/* Temporarily exposed during QUIC_PORT transition. */
+int ossl_quic_channel_on_new_conn(QUIC_CHANNEL *ch, const BIO_ADDR *peer,
+                                  const QUIC_CONN_ID *peer_scid,
+                                  const QUIC_CONN_ID *peer_dcid);
+
 /*
  * Queries and Accessors
  * =====================
index 08740f5afa76bb28820836b8ec04692d12a8a1d6..75fcaad87fdedec2439bab6b54e37be2b1ab2ea4 100644 (file)
@@ -57,6 +57,13 @@ typedef struct quic_port_args_st {
      * inside newly created channels.
      */
     SSL_CTX         *channel_ctx;
+
+    /*
+     * If 1, this port is to be used for multiple connections, so
+     * non-zero-length CIDs should be used. If 0, this port will only be used
+     * for a single connection, so a zero-length local CID can be used.
+     */
+    int             is_multi_conn;
 } QUIC_PORT_ARGS;
 
 typedef struct quic_port_st QUIC_PORT;
@@ -104,6 +111,9 @@ CRYPTO_MUTEX *ossl_quic_port_get0_mutex(QUIC_PORT *port);
 /* Gets the current time. */
 OSSL_TIME ossl_quic_port_get_time(QUIC_PORT *port);
 
+int ossl_quic_port_get_rx_short_dcid_len(const QUIC_PORT *port);
+int ossl_quic_port_get_tx_init_dcid_len(const QUIC_PORT *port);
+
 # endif
 
 #endif
index 1ea69584c5197ccf5fadc68b46cd1aaeb7d1bb78..2d7784ff04aa159ec3f3faf75dfcd2eb824d0ac4 100644 (file)
@@ -26,7 +26,6 @@
  * TODO(QUIC SERVER): Implement retry logic
  */
 
-#define INIT_DCID_LEN                   8
 #define INIT_CRYPTO_RECV_BUF_LEN    16384
 #define INIT_CRYPTO_SEND_BUF_LEN    16384
 #define INIT_APP_BUF_LEN             8192
@@ -99,10 +98,6 @@ static void ch_start_terminating(QUIC_CHANNEL *ch,
                                  const QUIC_TERMINATE_CAUSE *tcause,
                                  int force_immediate);
 static int ch_stateless_reset_token_handler(const unsigned char *data, size_t datalen, void *arg);
-static void ch_default_packet_handler(QUIC_URXE *e, void *arg);
-static int ch_server_on_new_conn(QUIC_CHANNEL *ch, const BIO_ADDR *peer,
-                                 const QUIC_CONN_ID *peer_scid,
-                                 const QUIC_CONN_ID *peer_dcid);
 static void ch_on_txp_ack_tx(const OSSL_QUIC_FRAME_ACK *ack, uint32_t pn_space,
                              void *arg);
 static void ch_rx_handle_version_neg(QUIC_CHANNEL *ch, OSSL_QRX_PKT *pkt);
@@ -220,7 +215,7 @@ static void chan_remove_reset_token(QUIC_CHANNEL *ch, uint64_t seq_num)
  *
  * TODO(QUIC FUTURE): optimise this to only be called for unparsable packets
  */
-static int ch_stateless_reset_token_handler(const unsigned char *data,
+static int ossl_unused ch_stateless_reset_token_handler(const unsigned char *data,
                                             size_t datalen, void *arg)
 {
     QUIC_SRT_ELEM srte;
@@ -275,7 +270,8 @@ static int ch_init(QUIC_CHANNEL *ch)
     OSSL_QRX_ARGS qrx_args = {0};
     QUIC_TLS_ARGS tls_args = {0};
     uint32_t pn_space;
-    size_t rx_short_cid_len = ch->is_server ? INIT_DCID_LEN : 0;
+    size_t rx_short_dcid_len = ossl_quic_port_get_rx_short_dcid_len(ch->port);
+    size_t tx_init_dcid_len = ossl_quic_port_get_tx_init_dcid_len(ch->port);
 
     if (ch->port == NULL)
         goto err;
@@ -288,7 +284,7 @@ static int ch_init(QUIC_CHANNEL *ch)
 
     /* For clients, generate our initial DCID. */
     if (!ch->is_server
-        && !gen_rand_conn_id(ch->port->libctx, INIT_DCID_LEN, &ch->init_dcid))
+        && !gen_rand_conn_id(ch->port->libctx, tx_init_dcid_len, &ch->init_dcid))
         goto err;
 
     /* We plug in a network write BIO to the QTX later when we get one. */
@@ -395,31 +391,16 @@ static int ch_init(QUIC_CHANNEL *ch)
 
     ossl_quic_tx_packetiser_set_ack_tx_cb(ch->txp, ch_on_txp_ack_tx, ch);
 
-    if ((ch->demux = ossl_quic_demux_new(/*BIO=*/NULL,
-                                         /*Short CID Len=*/rx_short_cid_len,
-                                         get_time, ch)) == NULL)
-        goto err;
-
     /*
      * Setup a handler to detect stateless reset tokens.
      */
-    ossl_quic_demux_set_stateless_reset_handler(ch->demux,
-                                                &ch_stateless_reset_token_handler,
-                                                ch);
-
-    /*
-     * If we are a server, setup our handler for packets not corresponding to
-     * any known DCID on our end. This is for handling clients establishing new
-     * connections.
-     */
-    if (ch->is_server)
-        ossl_quic_demux_set_default_handler(ch->demux,
-                                            ch_default_packet_handler,
-                                            ch);
+    //ossl_quic_demux_set_stateless_reset_handler(ch->demux,
+    //                                            &ch_stateless_reset_token_handler,
+    //                                            ch);
 
     qrx_args.libctx             = ch->port->libctx;
-    qrx_args.demux              = ch->demux;
-    qrx_args.short_conn_id_len  = rx_short_cid_len;
+    qrx_args.demux              = ch->port->demux;
+    qrx_args.short_conn_id_len  = rx_short_dcid_len;
     qrx_args.max_deferred       = 32;
 
     if ((ch->qrx = ossl_qrx_new(&qrx_args)) == NULL)
@@ -531,7 +512,6 @@ static void ch_cleanup(QUIC_CHANNEL *ch)
 
     ossl_quic_tls_free(ch->qtls);
     ossl_qrx_free(ch->qrx);
-    ossl_quic_demux_free(ch->demux);
     OPENSSL_free(ch->local_transport_params);
     OPENSSL_free((char *)ch->terminate_cause.reason);
     OSSL_ERR_STATE_free(ch->err_state);
@@ -690,7 +670,7 @@ int ossl_quic_channel_is_handshake_confirmed(const QUIC_CHANNEL *ch)
 
 QUIC_DEMUX *ossl_quic_channel_get0_demux(QUIC_CHANNEL *ch)
 {
-    return ch->demux;
+    return ch->port->demux;
 }
 
 QUIC_PORT *ossl_quic_channel_get0_port(QUIC_CHANNEL *ch)
@@ -705,7 +685,7 @@ CRYPTO_MUTEX *ossl_quic_channel_get_mutex(QUIC_CHANNEL *ch)
 
 int ossl_quic_channel_has_pending(const QUIC_CHANNEL *ch)
 {
-    return ossl_quic_demux_has_pending(ch->demux)
+    return ossl_quic_demux_has_pending(ch->port->demux)
         || ossl_qrx_processed_read_pending(ch->qrx);
 }
 
@@ -2032,7 +2012,7 @@ static void ch_rx_pre(QUIC_CHANNEL *ch)
      * Get DEMUX to BIO_recvmmsg from the network and queue incoming datagrams
      * to the appropriate QRX instance.
      */
-    ret = ossl_quic_demux_pump(ch->demux);
+    ret = ossl_quic_demux_pump(ch->port->demux);
     if (ret == QUIC_DEMUX_PUMP_RES_STATELESS_RESET)
         ch_stateless_reset(ch);
     else if (ret == QUIC_DEMUX_PUMP_RES_PERMANENT_FAIL)
@@ -2423,87 +2403,6 @@ static void ch_raise_version_neg_failure(QUIC_CHANNEL *ch)
     ch_start_terminating(ch, &tcause, 1);
 }
 
-/*
- * This is called by the demux when we get a packet not destined for any known
- * DCID.
- */
-static void ch_default_packet_handler(QUIC_URXE *e, void *arg)
-{
-    QUIC_CHANNEL *ch = arg;
-    PACKET pkt;
-    QUIC_PKT_HDR hdr;
-
-    if (!ossl_assert(ch->is_server))
-        goto undesirable;
-
-    /*
-     * We only support one connection to our server currently, so if we already
-     * started one, ignore any new connection attempts.
-     */
-    if (ch->state != QUIC_CHANNEL_STATE_IDLE)
-        goto undesirable;
-
-    /*
-     * We have got a packet for an unknown DCID. This might be an attempt to
-     * open a new connection.
-     */
-    if (e->data_len < QUIC_MIN_INITIAL_DGRAM_LEN)
-        goto undesirable;
-
-    if (!PACKET_buf_init(&pkt, ossl_quic_urxe_data(e), e->data_len))
-        goto err;
-
-    /*
-     * We set short_conn_id_len to SIZE_MAX here which will cause the decode
-     * operation to fail if we get a 1-RTT packet. This is fine since we only
-     * care about Initial packets.
-     */
-    if (!ossl_quic_wire_decode_pkt_hdr(&pkt, SIZE_MAX, 1, 0, &hdr, NULL))
-        goto undesirable;
-
-    switch (hdr.version) {
-        case QUIC_VERSION_1:
-            break;
-
-        case QUIC_VERSION_NONE:
-        default:
-            /* Unknown version or proactive version negotiation request, bail. */
-            /* TODO(QUIC SERVER): Handle version negotiation on server side */
-            goto undesirable;
-    }
-
-    /*
-     * We only care about Initial packets which might be trying to establish a
-     * connection.
-     */
-    if (hdr.type != QUIC_PKT_TYPE_INITIAL)
-        goto undesirable;
-
-    /*
-     * Assume this is a valid attempt to initiate a connection.
-     *
-     * We do not register the DCID in the initial packet we received and that
-     * DCID is not actually used again, thus after provisioning the correct
-     * Initial keys derived from it (which is done in the call below) we pass
-     * the received packet directly to the QRX so that it can process it as a
-     * one-time thing, instead of going through the usual DEMUX DCID-based
-     * routing.
-     */
-    if (!ch_server_on_new_conn(ch, &e->peer,
-                               &hdr.src_conn_id,
-                               &hdr.dst_conn_id))
-        goto err;
-
-    ossl_qrx_inject_urxe(ch->qrx, e);
-    return;
-
-err:
-    ossl_quic_channel_raise_protocol_error(ch, QUIC_ERR_INTERNAL_ERROR, 0,
-                                           "internal error");
-undesirable:
-    ossl_quic_demux_release_urxe(ch->demux, e);
-}
-
 /* Try to generate packets and if possible, flush them to the network. */
 static int ch_tx(QUIC_CHANNEL *ch)
 {
@@ -2741,7 +2640,7 @@ int ossl_quic_channel_set_net_rbio(QUIC_CHANNEL *ch, BIO *net_rbio)
     if (!ch_update_poll_desc(ch, net_rbio, /*for_write=*/0))
         return 0;
 
-    ossl_quic_demux_set_bio(ch->demux, net_rbio);
+    ossl_quic_demux_set_bio(ch->port->demux, net_rbio);
     ch->net_rbio = net_rbio;
     return 1;
 }
@@ -3497,15 +3396,17 @@ static void ch_on_idle_timeout(QUIC_CHANNEL *ch)
 }
 
 /* Called when we, as a server, get a new incoming connection. */
-static int ch_server_on_new_conn(QUIC_CHANNEL *ch, const BIO_ADDR *peer,
-                                 const QUIC_CONN_ID *peer_scid,
-                                 const QUIC_CONN_ID *peer_dcid)
+int ossl_quic_channel_on_new_conn(QUIC_CHANNEL *ch, const BIO_ADDR *peer,
+                                  const QUIC_CONN_ID *peer_scid,
+                                  const QUIC_CONN_ID *peer_dcid)
 {
     if (!ossl_assert(ch->state == QUIC_CHANNEL_STATE_IDLE && ch->is_server))
         return 0;
 
+
     /* Generate a SCID we will use for the connection. */
-    if (!gen_rand_conn_id(ch->port->libctx, INIT_DCID_LEN,
+    if (!gen_rand_conn_id(ch->port->libctx,
+                          ossl_quic_port_get_tx_init_dcid_len(ch->port),
                           &ch->cur_local_cid))
         return 0;
 
index 37cf73c67a148ffa1b3f8b1bc9214d69bd69eb8c..e2aea16f685a8eb067fb81b73025ef9572efcf24 100644 (file)
@@ -87,14 +87,7 @@ struct quic_channel_st {
     const OSSL_CC_METHOD            *cc_method;
     OSSL_ACKM                       *ackm;
 
-    /*
-     * RX demuxer. We register incoming DCIDs with this. Since we currently only
-     * support client operation and use one L4 port per connection, we own the
-     * demuxer and register a single zero-length DCID with it.
-     */
-    QUIC_DEMUX                      *demux;
-
-    /* Record layers in the TX and RX directions, plus the RX demuxer. */
+    /* Record layers in the TX and RX directions. */
     OSSL_QTX                        *qtx;
     OSSL_QRX                        *qrx;
 
index 66e0d3b0d533b8a8bb51c233cac1c95fb5cf7c08..5c5cde8ecd8b9dc9f5945a80d405c87704a9593b 100644 (file)
  * QUIC Port Structure
  * ===================
  */
+#define INIT_DCID_LEN                   8
+
 static int port_init(QUIC_PORT *port);
 static void port_cleanup(QUIC_PORT *port);
 static OSSL_TIME get_time(void *arg);
 static void port_tick(QUIC_TICK_RESULT *res, void *arg, uint32_t flags);
-//static void port_default_packet_handler(QUIC_URXE *e, void *arg);
+static void port_default_packet_handler(QUIC_URXE *e, void *arg);
 
 DEFINE_LIST_OF_IMPL(ch, QUIC_CHANNEL);
 
@@ -32,12 +34,13 @@ QUIC_PORT *ossl_quic_port_new(const QUIC_PORT_ARGS *args)
     if ((port = OPENSSL_zalloc(sizeof(QUIC_PORT))) == NULL)
         return NULL;
 
-    port->libctx      = args->libctx;
-    port->propq       = args->propq;
-    port->mutex       = args->mutex;
-    port->now_cb      = args->now_cb;
-    port->now_cb_arg  = args->now_cb_arg;
-    port->channel_ctx = args->channel_ctx;
+    port->libctx        = args->libctx;
+    port->propq         = args->propq;
+    port->mutex         = args->mutex;
+    port->now_cb        = args->now_cb;
+    port->now_cb_arg    = args->now_cb_arg;
+    port->channel_ctx   = args->channel_ctx;
+    port->is_multi_conn = args->is_multi_conn;
 
     if (!port_init(port)) {
         OPENSSL_free(port);
@@ -58,13 +61,13 @@ void ossl_quic_port_free(QUIC_PORT *port)
 
 static int port_init(QUIC_PORT *port)
 {
-    size_t rx_short_cid_len = 8;
+    size_t rx_short_dcid_len = (port->is_multi_conn ? INIT_DCID_LEN : 0);
 
     if (port->channel_ctx == NULL)
         goto err;
 
     if ((port->demux = ossl_quic_demux_new(/*BIO=*/NULL,
-                                           /*Short CID Len=*/rx_short_cid_len,
+                                           /*Short CID Len=*/rx_short_dcid_len,
                                            get_time, port)) == NULL)
         goto err;
 
@@ -74,11 +77,13 @@ static int port_init(QUIC_PORT *port)
      * connections.
      */
     // if (is_server)
-    //ossl_quic_demux_set_default_handler(port->demux,
-    //                                    port_default_packet_handler,
-    //                                    port);
+    ossl_quic_demux_set_default_handler(port->demux,
+                                        port_default_packet_handler,
+                                        port);
 
     ossl_quic_reactor_init(&port->rtor, port_tick, port, ossl_time_zero());
+    port->rx_short_dcid_len = (unsigned char)rx_short_dcid_len;
+    port->tx_init_dcid_len  = INIT_DCID_LEN;
     return 1;
 
 err:
@@ -121,6 +126,15 @@ static OSSL_TIME get_time(void *port)
     return ossl_quic_port_get_time(port);
 }
 
+int ossl_quic_port_get_rx_short_dcid_len(const QUIC_PORT *port)
+{
+    return port->rx_short_dcid_len;
+}
+
+int ossl_quic_port_get_tx_init_dcid_len(const QUIC_PORT *port)
+{
+    return port->tx_init_dcid_len;
+}
 
 /*
  * QUIC Port: Network BIO Configuration
@@ -266,7 +280,13 @@ QUIC_CHANNEL *ossl_quic_port_create_outgoing(QUIC_PORT *port, SSL *tls)
 
 QUIC_CHANNEL *ossl_quic_port_create_incoming(QUIC_PORT *port, SSL *tls)
 {
-    return port_make_channel(port, tls, /*is_server=*/1);
+    QUIC_CHANNEL *ch;
+
+    assert(port->tserver_ch == NULL);
+
+    ch = port_make_channel(port, tls, /*is_server=*/1);
+    port->tserver_ch = ch;
+    return ch;
 }
 
 /*
@@ -283,3 +303,95 @@ static void port_tick(QUIC_TICK_RESULT *res, void *arg, uint32_t flags)
 {
     /* TODO */
 }
+
+/*
+ * Handles an incoming connection request and potentially decides to make a
+ * connection from it. If a new connection is made, the new channel is written
+ * to *new_ch.
+ */
+static void port_on_new_conn(QUIC_PORT *port, const BIO_ADDR *peer,
+                             const QUIC_CONN_ID *scid,
+                             const QUIC_CONN_ID *dcid,
+                             QUIC_CHANNEL **new_ch)
+{
+    if (port->tserver_ch != NULL) {
+        /* Specially assign to existing channel */
+        if (!ossl_quic_channel_on_new_conn(port->tserver_ch, peer, scid, dcid))
+            return;
+
+        *new_ch = port->tserver_ch;
+        port->tserver_ch = NULL;
+        return;
+    }
+}
+
+/*
+ * This is called by the demux when we get a packet not destined for any known
+ * DCID.
+ */
+static void port_default_packet_handler(QUIC_URXE *e, void *arg)
+{
+    QUIC_PORT *port = arg;
+    PACKET pkt;
+    QUIC_PKT_HDR hdr;
+    QUIC_CHANNEL *new_ch = NULL;
+
+    if (port->tserver_ch == NULL)
+        goto undesirable;
+
+    /*
+     * We have got a packet for an unknown DCID. This might be an attempt to
+     * open a new connection.
+     */
+    if (e->data_len < QUIC_MIN_INITIAL_DGRAM_LEN)
+        goto undesirable;
+
+    if (!PACKET_buf_init(&pkt, ossl_quic_urxe_data(e), e->data_len))
+        goto undesirable;
+
+    /*
+     * We set short_conn_id_len to SIZE_MAX here which will cause the decode
+     * operation to fail if we get a 1-RTT packet. This is fine since we only
+     * care about Initial packets.
+     */
+    if (!ossl_quic_wire_decode_pkt_hdr(&pkt, SIZE_MAX, 1, 0, &hdr, NULL))
+        goto undesirable;
+
+    switch (hdr.version) {
+        case QUIC_VERSION_1:
+            break;
+
+        case QUIC_VERSION_NONE:
+        default:
+            /* Unknown version or proactive version negotiation request, bail. */
+            /* TODO(QUIC SERVER): Handle version negotiation on server side */
+            goto undesirable;
+    }
+
+    /*
+     * We only care about Initial packets which might be trying to establish a
+     * connection.
+     */
+    if (hdr.type != QUIC_PKT_TYPE_INITIAL)
+        goto undesirable;
+
+    /*
+     * Try to process this as a valid attempt to initiate a connection.
+     *
+     * We do not register the DCID in the Initial packet we received as
+     * that DCID is not actually used again, thus after provisioning
+     * the new connection and associated Initial keys, we inject the
+     * received packet directly to the new channel's QRX so that it can
+     * process it as a one-time thing, instead of going through the usual
+     * DEMUX DCID-based routing.
+     */
+    port_on_new_conn(port, &e->peer, &hdr.src_conn_id, &hdr.dst_conn_id,
+                     &new_ch);
+    if (new_ch != NULL)
+        ossl_qrx_inject_urxe(new_ch->qrx, e);
+
+    return;
+
+undesirable:
+    ossl_quic_demux_release_urxe(port->demux, e);
+}
index fc0521d02bc0135ca75f6374d2a526e07b00cdcd..ff109365aaf8b6b8423eeea9af9848148218393a 100644 (file)
@@ -48,6 +48,17 @@ struct quic_port_st {
 
     /* List of all child channels. */
     OSSL_LIST(ch)                   channel_list;
+
+    /* Special TSERVER channel. To be removed in the future. */
+    QUIC_CHANNEL                    *tserver_ch;
+
+    /* DCID length used for incoming short header packets. */
+    unsigned char                   rx_short_dcid_len;
+    /* For clients, CID length used for outgoing Initial packets. */
+    unsigned char                   tx_init_dcid_len;
+
+    /* Is this port created to support multiple connections? */
+    unsigned int                    is_multi_conn                   : 1;
 };
 
 # endif
index 72469632535980af2a392900d350daba97e5a88f..bc6668ef90c3e95f69db82f369bfef7898d156ae 100644 (file)
@@ -122,6 +122,7 @@ QUIC_TSERVER *ossl_quic_tserver_new(const QUIC_TSERVER_ARGS *args,
     port_args.channel_ctx   = srv->ctx;
     port_args.now_cb        = srv->args.now_cb;
     port_args.now_cb_arg    = srv->args.now_cb_arg;
+    port_args.is_multi_conn = 1;
 
     if ((srv->port = ossl_quic_port_new(&port_args)) == NULL)
         goto err;