diff --git a/net/quic/Makefile b/net/quic/Makefile
index 094e9da5d739..eee7501588d3 100644
--- a/net/quic/Makefile
+++ b/net/quic/Makefile
@@ -5,4 +5,4 @@
obj-$(CONFIG_IP_QUIC) += quic.o
-quic-y := common.o family.o protocol.o socket.o stream.o
+quic-y := common.o family.o protocol.o socket.o stream.o connid.o
diff --git a/net/quic/connid.c b/net/quic/connid.c
new file mode 100644
index 000000000000..63bf39e71b15
--- /dev/null
+++ b/net/quic/connid.c
@@ -0,0 +1,311 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/* QUIC kernel implementation
+ * (C) Copyright Red Hat Corp. 2023
+ *
+ * This file is part of the QUIC kernel implementation
+ *
+ * Connection ID management.
+ *
+ * Written or modified by:
+ * Xin Long <lucien.xin@gmail.com>
+ */
+
+#include <crypto/utils.h>
+#include <linux/quic.h>
+#include <net/sock.h>
+
+#include "common.h"
+#include "connid.h"
+
+/* Look up a source connection ID (SCID) in the global source connection ID hash
+ * table. Must be called with RCU read-side protection held. On success, returns
+ * the connection ID with a reference held on its socket.
+ */
+struct quic_conn_id *quic_conn_id_lookup(struct net *net, u8 *scid, u32 len)
+{
+ struct quic_shash_head *head = quic_source_conn_id_head(net, scid, len);
+ struct quic_source_conn_id *s_conn_id;
+ struct quic_conn_id *conn_id = NULL;
+ struct hlist_nulls_node *node;
+
+ WARN_ON_ONCE(!rcu_read_lock_held());
+
+ hlist_nulls_for_each_entry_rcu(s_conn_id, node, &head->head, node) {
+ if (net != sock_net(s_conn_id->sk))
+ continue;
+ if (s_conn_id->common.id.len != len ||
+ memcmp(scid, &s_conn_id->common.id.data, len))
+ continue;
+ if (likely(refcount_inc_not_zero(&s_conn_id->sk->sk_refcnt)))
+ conn_id = &s_conn_id->common.id;
+ break;
+ }
+ /* No need to check get_nulls_value(node) != hash for !conn_id, as
+ * hashtable size is fixed and a conn_id can not rehashed.
+ */
+ return conn_id;
+}
+
+/* Check if a given stateless reset token exists in the used connection IDs
+ * (up to and including the furthest used one). Skips all-zero tokens.
+ */
+bool quic_conn_id_token_exists(struct quic_conn_id_set *id_set, u8 *token)
+{
+ struct quic_common_conn_id *common, *last_used;
+ struct quic_dest_conn_id *dcid;
+
+ /* Determine the furthest used CID by sequence number. */
+ last_used = id_set->active;
+ if (id_set->alt && id_set->alt->number > last_used->number)
+ last_used = id_set->alt;
+
+ /* Fast path: check the furthest used CID first. */
+ dcid = (struct quic_dest_conn_id *)last_used;
+ if (memchr_inv(dcid->token, 0, QUIC_CONN_ID_TOKEN_LEN) &&
+ !crypto_memneq(dcid->token, token, QUIC_CONN_ID_TOKEN_LEN))
+ return true;
+
+ /* Check all CIDs up to (but not including) the last used. */
+ list_for_each_entry(common, &id_set->head, list) {
+ if (common == last_used)
+ break;
+ dcid = (struct quic_dest_conn_id *)common;
+ if (memchr_inv(dcid->token, 0, QUIC_CONN_ID_TOKEN_LEN) &&
+ !crypto_memneq(dcid->token, token, QUIC_CONN_ID_TOKEN_LEN))
+ return true;
+ }
+ return false;
+}
+
+static void quic_source_conn_id_free_rcu(struct rcu_head *head)
+{
+ struct quic_source_conn_id *s_conn_id;
+
+ s_conn_id = container_of(head, struct quic_source_conn_id, rcu);
+ kfree(s_conn_id);
+}
+
+static void quic_source_conn_id_free(struct quic_source_conn_id *s_conn_id)
+{
+ u8 *data = s_conn_id->common.id.data;
+ u32 len = s_conn_id->common.id.len;
+ struct quic_shash_head *head;
+
+ head = quic_source_conn_id_head(sock_net(s_conn_id->sk), data, len);
+ spin_lock_bh(&head->lock);
+ hlist_nulls_del_init_rcu(&s_conn_id->node);
+ spin_unlock_bh(&head->lock);
+
+ /* Freeing is deferred via RCU to avoid use-after-free during
+ * concurrent lookups.
+ */
+ call_rcu(&s_conn_id->rcu, quic_source_conn_id_free_rcu);
+}
+
+static void quic_conn_id_del(struct quic_common_conn_id *common)
+{
+ list_del(&common->list);
+ if (!common->hashed) {
+ kfree(common);
+ return;
+ }
+ quic_source_conn_id_free((struct quic_source_conn_id *)common);
+}
+
+/* Add a connection ID with sequence number and associated private data to the
+ * connection ID set.
+ */
+int quic_conn_id_add(struct quic_conn_id_set *id_set,
+ struct quic_conn_id *conn_id, u32 number, void *data,
+ gfp_t gfp)
+{
+ struct quic_source_conn_id *s_conn_id, *pos;
+ struct quic_dest_conn_id *d_conn_id;
+ struct quic_common_conn_id *common;
+ struct hlist_nulls_node *node;
+ struct quic_shash_head *head;
+ struct list_head *list;
+ struct net *net;
+
+ /* Locate insertion point to keep list ordered by number. */
+ list = &id_set->head;
+ list_for_each_entry(common, list, list) {
+ if (number == common->number) {
+ if (quic_conn_id_cmp(&common->id, conn_id))
+ return -EINVAL;
+ if (!id_set->source && data) {
+ d_conn_id = (struct quic_dest_conn_id *)common;
+ if (crypto_memneq(d_conn_id->token, data,
+ QUIC_CONN_ID_TOKEN_LEN))
+ return -EINVAL;
+ }
+ return 0; /* Ignore if it already exists on the list. */
+ }
+ if (number < common->number) {
+ list = &common->list;
+ break;
+ }
+ }
+
+ if (conn_id->len > QUIC_CONN_ID_MAX_LEN)
+ return -EINVAL;
+ common = kzalloc(id_set->entry_size, gfp);
+ if (!common)
+ return -ENOMEM;
+ common->id = *conn_id;
+ common->number = number;
+ if (!id_set->source) {
+ /* For destination connection IDs, copy the stateless reset
+ * token if available.
+ */
+ if (data) {
+ d_conn_id = (struct quic_dest_conn_id *)common;
+ memcpy(d_conn_id->token, data, QUIC_CONN_ID_TOKEN_LEN);
+ }
+ } else {
+ /* For source connection IDs, mark as hashed and insert into
+ * the global source connection ID hashtable.
+ */
+ common->hashed = true;
+ s_conn_id = (struct quic_source_conn_id *)common;
+ s_conn_id->sk = data;
+ net = sock_net(s_conn_id->sk);
+
+ head = quic_source_conn_id_head(net, common->id.data,
+ common->id.len);
+ spin_lock_bh(&head->lock);
+
+ /* Check for collision before inserting */
+ hlist_nulls_for_each_entry(pos, node, &head->head, node) {
+ if (net != sock_net(pos->sk))
+ continue;
+ if (quic_conn_id_cmp(&pos->common.id, &common->id))
+ continue;
+ spin_unlock_bh(&head->lock);
+ kfree(common);
+ WARN_ON_ONCE(1);
+ return -EEXIST;
+ }
+
+ hlist_nulls_add_head_rcu(&s_conn_id->node, &head->head);
+ spin_unlock_bh(&head->lock);
+ }
+ list_add_tail(&common->list, list);
+
+ if (number == quic_conn_id_last_number(id_set) + 1) {
+ if (!id_set->active)
+ id_set->active = common;
+ id_set->count++;
+ id_set->use_count++;
+
+ /* Increment count for consecutive following IDs. */
+ list_for_each_entry_continue(common, &id_set->head, list) {
+ if (common->number != ++number)
+ break;
+ id_set->count++;
+ id_set->use_count++;
+ }
+ }
+ return 0;
+}
+
+/* Retire a connection ID by marking it dead, then remove consecutive dead
+ * entries from the head of the list. The caller must ensure the number fall
+ * within the consecutive range [first_number, last_number].
+ */
+void quic_conn_id_retire(struct quic_conn_id_set *id_set, u32 number)
+{
+ struct quic_common_conn_id *common, *tmp, *retired = NULL;
+ struct list_head *list;
+
+ if (WARN_ON_ONCE(id_set->use_count <= 1))
+ return;
+
+ list = &id_set->head;
+ list_for_each_entry(common, list, list) {
+ if (common->number > number)
+ break;
+ if (common->number == number) {
+ if (common->dead)
+ return;
+ retired = common;
+ retired->dead = true;
+ id_set->use_count--;
+ break;
+ }
+ }
+
+ if (!retired)
+ return;
+
+ list_for_each_entry_safe(common, tmp, list, list) {
+ if (!common->dead)
+ break;
+ quic_conn_id_del(common);
+ id_set->count--;
+ }
+
+ /* Update the active and alternate Connection IDs. */
+ if (id_set->active == retired)
+ id_set->active = common;
+ if (id_set->alt == retired)
+ id_set->alt = common;
+}
+
+struct quic_conn_id *quic_conn_id_find(struct quic_conn_id_set *id_set,
+ u32 number)
+{
+ struct quic_common_conn_id *common;
+
+ list_for_each_entry(common, &id_set->head, list) {
+ if (common->number > number)
+ break;
+ if (common->number == number)
+ return &common->id;
+ }
+ return NULL;
+}
+
+void quic_conn_id_update_active(struct quic_conn_id_set *id_set, u32 number)
+{
+ struct quic_conn_id *conn_id;
+
+ if (number == id_set->active->number)
+ return;
+ conn_id = quic_conn_id_find(id_set, number);
+ if (!conn_id || quic_conn_id_dead(conn_id))
+ return;
+ quic_conn_id_set_active(id_set, conn_id);
+}
+
+void quic_conn_id_set_init(struct quic_conn_id_set *id_set, bool source)
+{
+ id_set->entry_size = source ? sizeof(struct quic_source_conn_id) :
+ sizeof(struct quic_dest_conn_id);
+ id_set->source = source;
+ INIT_LIST_HEAD(&id_set->head);
+}
+
+void quic_conn_id_set_free(struct quic_conn_id_set *id_set)
+{
+ struct quic_common_conn_id *common, *tmp;
+
+ list_for_each_entry_safe(common, tmp, &id_set->head, list)
+ quic_conn_id_del(common);
+ id_set->count = 0;
+ id_set->use_count = 0;
+ id_set->alt = NULL;
+ id_set->active = NULL;
+}
+
+void quic_conn_id_get_param(struct quic_conn_id_set *id_set,
+ struct quic_transport_param *p)
+{
+ p->active_connection_id_limit = id_set->max_count;
+}
+
+void quic_conn_id_set_param(struct quic_conn_id_set *id_set,
+ struct quic_transport_param *p)
+{
+ id_set->max_count = p->active_connection_id_limit;
+}diff --git a/net/quic/connid.h b/net/quic/connid.h
new file mode 100644
index 000000000000..39aa8b5c06fc
--- /dev/null
+++ b/net/quic/connid.h
@@ -0,0 +1,191 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/* QUIC kernel implementation
+ * (C) Copyright Red Hat Corp. 2023
+ *
+ * This file is part of the QUIC kernel implementation
+ *
+ * Written or modified by:
+ * Xin Long <lucien.xin@gmail.com>
+ */
+
+#define QUIC_CONN_ID_LIMIT 8
+#define QUIC_CONN_ID_DEF 7
+#define QUIC_CONN_ID_LEAST 2
+
+#define QUIC_CONN_ID_TOKEN_LEN 16
+
+/* Common fields shared by both source and destination Connection IDs */
+struct quic_common_conn_id {
+ struct quic_conn_id id; /* Connection ID value and its length */
+ struct list_head list; /* List node for connection ID management */
+ u32 number; /* Sequence number assigned to this Connection ID */
+ bool hashed; /* true if stored in source_conn_id hash table */
+ bool dead; /* true if the Connection ID has been retired */
+};
+
+struct quic_source_conn_id {
+ struct quic_common_conn_id common;
+ struct hlist_nulls_node node; /* Hash table node for fast lookup */
+ struct rcu_head rcu; /* RCU header for deferred destruction */
+ struct sock *sk; /* Socket associated with this Connection ID */
+};
+
+struct quic_dest_conn_id {
+ struct quic_common_conn_id common;
+ /* Stateless reset token in rfc9000#section-10.3 */
+ u8 token[QUIC_CONN_ID_TOKEN_LEN];
+};
+
+struct quic_conn_id_set {
+ /* Connection ID in use on the current path */
+ struct quic_common_conn_id *active;
+ /* Connection ID to use for a new path (e.g., after migration) */
+ struct quic_common_conn_id *alt;
+ struct list_head head; /* List head of available connection IDs */
+ u8 entry_size; /* Size of each connection ID entry in the list */
+ u8 max_count; /* active_connection_id_limit in rfc9000#section-18.2 */
+ u8 use_count; /* Number of non-retired consecutive connection IDs */
+ u8 count; /* Number of consecutive connection IDs */
+ bool source; /* true if this is a source conn_id set, false if dest */
+};
+
+static inline u32 quic_conn_id_first_number(struct quic_conn_id_set *id_set)
+{
+ struct quic_common_conn_id *common;
+
+ /* The id_set is guaranteed to be non-empty when called (sk is not in
+ * CLOSE state).
+ */
+ common = list_first_entry(&id_set->head, struct quic_common_conn_id,
+ list);
+ return common->number;
+}
+
+static inline u32 quic_conn_id_last_number(struct quic_conn_id_set *id_set)
+{
+ return quic_conn_id_first_number(id_set) + id_set->count - 1;
+}
+
+static inline void quic_conn_id_generate(struct quic_conn_id *conn_id)
+{
+ get_random_bytes(conn_id->data, QUIC_CONN_ID_DEF_LEN);
+ conn_id->len = QUIC_CONN_ID_DEF_LEN;
+}
+
+/* Select an alternate destination Connection ID for a new path (e.g., after
+ * migration).
+ */
+static inline bool quic_conn_id_select_alt(struct quic_conn_id_set *id_set,
+ bool active)
+{
+ if (id_set->alt)
+ return true;
+ /* NAT rebinding: peer keeps using the current source conn_id.
+ * In this case, continue using the same dest conn_id for the new path.
+ */
+ if (active) {
+ id_set->alt = id_set->active;
+ return true;
+ }
+ /* Treat the prev conn_ids as used.
+ * Try selecting the next conn_id in the list, unless at the end.
+ */
+ if (id_set->active->number != quic_conn_id_last_number(id_set)) {
+ id_set->alt = list_next_entry(id_set->active, list);
+ return true;
+ }
+ /* If there's only one conn_id in the list, reuse the active one. */
+ if (id_set->active->number == quic_conn_id_first_number(id_set)) {
+ id_set->alt = id_set->active;
+ return true;
+ }
+ /* No alternate conn_id could be selected. Caller should send a
+ * QUIC_FRAME_RETIRE_CONNECTION_ID frame to request new connection IDs
+ * from the peer.
+ */
+ return false;
+}
+
+static inline void quic_conn_id_set_alt(struct quic_conn_id_set *id_set,
+ struct quic_conn_id *alt)
+{
+ id_set->alt = (struct quic_common_conn_id *)alt;
+}
+
+/* Swap the active and alternate destination Connection IDs after path
+ * migration completes, since the path has already been switched accordingly.
+ */
+static inline void quic_conn_id_swap_active(struct quic_conn_id_set *id_set)
+{
+ void *active = id_set->active;
+
+ id_set->active = id_set->alt;
+ id_set->alt = active;
+}
+
+/* Choose which destination Connection ID to use for a new path migration if
+ * alt is true.
+ */
+static inline struct quic_conn_id *
+quic_conn_id_choose(struct quic_conn_id_set *id_set, u8 alt)
+{
+ return (alt && id_set->alt) ? &id_set->alt->id : &id_set->active->id;
+}
+
+static inline struct quic_conn_id *
+quic_conn_id_active(struct quic_conn_id_set *id_set)
+{
+ return &id_set->active->id;
+}
+
+static inline bool quic_conn_id_dead(struct quic_conn_id *conn_id)
+{
+ return ((struct quic_common_conn_id *)conn_id)->dead;
+}
+
+static inline void quic_conn_id_set_active(struct quic_conn_id_set *id_set,
+ struct quic_conn_id *active)
+{
+ id_set->active = (struct quic_common_conn_id *)active;
+}
+
+static inline u32 quic_conn_id_number(struct quic_conn_id *conn_id)
+{
+ return ((struct quic_common_conn_id *)conn_id)->number;
+}
+
+static inline struct sock *quic_conn_id_sk(struct quic_conn_id *conn_id)
+{
+ return ((struct quic_source_conn_id *)conn_id)->sk;
+}
+
+static inline void quic_conn_id_set_token(struct quic_conn_id *conn_id,
+ u8 *token)
+{
+ memcpy(((struct quic_dest_conn_id *)conn_id)->token, token,
+ QUIC_CONN_ID_TOKEN_LEN);
+}
+
+static inline int quic_conn_id_cmp(struct quic_conn_id *a,
+ struct quic_conn_id *b)
+{
+ return a->len != b->len || memcmp(a->data, b->data, a->len);
+}
+
+int quic_conn_id_add(struct quic_conn_id_set *id_set,
+ struct quic_conn_id *conn_id, u32 number, void *data,
+ gfp_t gfp);
+bool quic_conn_id_token_exists(struct quic_conn_id_set *id_set, u8 *token);
+void quic_conn_id_retire(struct quic_conn_id_set *id_set, u32 number);
+
+struct quic_conn_id *quic_conn_id_find(struct quic_conn_id_set *id_set,
+ u32 number);
+struct quic_conn_id *quic_conn_id_lookup(struct net *net, u8 *scid, u32 len);
+void quic_conn_id_update_active(struct quic_conn_id_set *id_set, u32 number);
+
+void quic_conn_id_get_param(struct quic_conn_id_set *id_set,
+ struct quic_transport_param *p);
+void quic_conn_id_set_param(struct quic_conn_id_set *id_set,
+ struct quic_transport_param *p);
+void quic_conn_id_set_init(struct quic_conn_id_set *id_set, bool source);
+void quic_conn_id_set_free(struct quic_conn_id_set *id_set);diff --git a/net/quic/protocol.c b/net/quic/protocol.c
index c17119227c26..d6ea3c8708b8 100644
--- a/net/quic/protocol.c
+++ b/net/quic/protocol.c
@@ -375,6 +375,7 @@ static __exit void quic_exit(void)
unregister_pernet_subsys(&quic_net_ops);
quic_hash_tables_destroy();
percpu_counter_destroy(&quic_sockets_allocated);
+ rcu_barrier();
pr_info("quic: exit\n");
}
diff --git a/net/quic/socket.c b/net/quic/socket.c
index 37660ff7528b..d4fed1782529 100644
--- a/net/quic/socket.c
+++ b/net/quic/socket.c
@@ -50,6 +50,9 @@ static int quic_init_sock(struct sock *sk)
sock_prot_inuse_add(sock_net(sk), sk->sk_prot, 1);
INIT_LIST_HEAD(quic_reqs(sk));
+ quic_conn_id_set_init(quic_source(sk), true);
+ quic_conn_id_set_init(quic_dest(sk), false);
+
if (quic_stream_init(quic_streams(sk)))
return -ENOMEM;
@@ -58,6 +61,9 @@ static int quic_init_sock(struct sock *sk)
static void quic_destroy_sock(struct sock *sk)
{
+ quic_conn_id_set_free(quic_source(sk));
+ quic_conn_id_set_free(quic_dest(sk));
+
quic_stream_free(quic_streams(sk));
quic_data_free(quic_ticket(sk));diff --git a/net/quic/socket.h b/net/quic/socket.h
index e76737b9b74b..68a58f0016cc 100644
--- a/net/quic/socket.h
+++ b/net/quic/socket.h
@@ -14,6 +14,7 @@
#include "common.h"
#include "family.h"
#include "stream.h"
+#include "connid.h"
#include "protocol.h"
@@ -36,6 +37,8 @@ struct quic_sock {
struct quic_data alpn;
struct quic_stream_table streams;
+ struct quic_conn_id_set source;
+ struct quic_conn_id_set dest;
};
struct quic6_sock {@@ -73,6 +76,16 @@ static inline struct quic_stream_table *quic_streams(const struct sock *sk)
return &quic_sk(sk)->streams;
}
+static inline struct quic_conn_id_set *quic_source(const struct sock *sk)
+{
+ return &quic_sk(sk)->source;
+}
+
+static inline struct quic_conn_id_set *quic_dest(const struct sock *sk)
+{
+ return &quic_sk(sk)->dest;
+}
+
static inline bool quic_is_serv(const struct sock *sk)
{
return !!sk->sk_max_ack_backlog;diff --git a/net/quic/stream.h b/net/quic/stream.h
index 48b27aa33967..c96d1516ecbe 100644
--- a/net/quic/stream.h
+++ b/net/quic/stream.h
@@ -84,8 +84,8 @@ struct quic_stream_limits {
s64 max_uni_stream_id; /* Highest allowed uni stream ID */
s64 active_stream_id; /* Most recently opened stream ID */
- u8 bidi_blocked; /* STREAMS_BLOCKED_BIDI sent, awaiting ACK */
- u8 uni_blocked; /* STREAMS_BLOCKED_UNI sent, awaiting ACK */
+ u8 bidi_blocked; /* STREAMS_BLOCKED/MAX_STREAMS_BIDI sent */
+ u8 uni_blocked; /* STREAMS_BLOCKED/MAX_STREAMS_UNI sent */
u8 bidi_pending; /* MAX_STREAMS_BIDI needs to be sent */
u8 uni_pending; /* MAX_STREAMS_UNI needs to be sent */
--
2.47.1