@@ -0,0 +1,596 @@
+// 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
+ *
+ * Path management.
+ *
+ * Written or modified by:
+ * Xin Long <lucien.xin@gmail.com>
+ */
+
+#include <net/udp_tunnel.h>
+#include <linux/quic.h>
+
+#include "common.h"
+#include "family.h"
+#include "path.h"
+
+static int quic_udp_rcv(struct sock *sk, struct sk_buff *skb)
+{
+ quic_set_skb_iif(skb);
+
+ memset(skb->cb, 0, sizeof(skb->cb));
+ QUIC_SKB_CB(skb)->seqno = -1;
+ QUIC_SKB_CB(skb)->time = quic_ktime_get_us();
+
+ skb_pull(skb, sizeof(struct udphdr));
+ skb_dst_force(skb);
+ kfree_skb(skb);
+ /* .encap_rcv must return 0 if skb was either consumed or dropped. */
+ return 0;
+}
+
+static int quic_udp_err(struct sock *sk, struct sk_buff *skb)
+{
+ return 0;
+}
+
+static void quic_udp_sock_put_work(struct work_struct *work)
+{
+ struct quic_udp_sock *us = container_of(work, struct quic_udp_sock,
+ work);
+ struct quic_uhash_head *head;
+ struct sock *sk = us->sk;
+
+ /* Hold the sock to safely access it in quic_udp_sock_lookup() even
+ * after udp_tunnel_sock_release(). The release must occur before
+ * __hlist_del() so a new UDP tunnel socket can be created for the same
+ * address and port if quic_udp_sock_lookup() fails to find one.
+ *
+ * Note: udp_tunnel_sock_release() cannot be called under the mutex due
+ * to some lockdep warnings.
+ */
+ sock_hold(sk);
+ udp_tunnel_sock_release(sk);
+
+ head = quic_udp_sock_head(sock_net(sk), ntohs(us->addr.v4.sin_port));
+ mutex_lock(&head->lock);
+ __hlist_del(&us->node);
+ mutex_unlock(&head->lock);
+
+ put_net_track(sock_net(sk), &us->ns_tracker);
+ sock_put(sk);
+ kfree(us);
+}
+
+static struct quic_udp_sock *quic_udp_sock_create(struct sock *sk,
+ union quic_addr *a)
+{
+ struct udp_tunnel_sock_cfg tuncfg = {};
+ struct udp_port_cfg udp_conf = {};
+ struct net *net = sock_net(sk);
+ struct quic_uhash_head *head;
+ struct quic_udp_sock *us;
+ struct socket *sock;
+ int err;
+
+ us = kmalloc_obj(*us);
+ if (!us)
+ return ERR_PTR(-ENOMEM);
+
+ quic_udp_conf_init(sk, &udp_conf, a);
+ err = udp_sock_create(net, &udp_conf, &sock);
+ if (err) {
+ pr_debug("%s: failed to create udp sock\n", __func__);
+ kfree(us);
+ return ERR_PTR(err);
+ }
+
+ tuncfg.encap_type = 1;
+ tuncfg.encap_rcv = quic_udp_rcv;
+ tuncfg.encap_err_lookup = quic_udp_err;
+ setup_udp_tunnel_sock(net, sock->sk, &tuncfg);
+
+ refcount_set(&us->refcnt, 1);
+ us->sk = sock->sk;
+ memcpy(&us->addr, a, sizeof(*a));
+ us->bind_ifindex = udp_conf.bind_ifindex;
+ get_net_track(net, &us->ns_tracker, GFP_KERNEL);
+
+ INIT_WORK(&us->work, quic_udp_sock_put_work);
+ head = quic_udp_sock_head(net, ntohs(a->v4.sin_port));
+ hlist_add_head(&us->node, &head->head);
+
+ return us;
+}
+
+static bool quic_udp_sock_get(struct quic_udp_sock *us)
+{
+ return refcount_inc_not_zero(&us->refcnt);
+}
+
+static void quic_udp_sock_put(struct quic_udp_sock *us)
+{
+ /* The UDP socket may be freed in atomic RX context during connection
+ * migration; defer the release to a workqueue.
+ */
+ if (refcount_dec_and_test(&us->refcnt))
+ queue_work(quic_wq, &us->work);
+}
+
+/* Lookup a quic_udp_sock in the global hash table by port or address. If 'a'
+ * is provided, it searches for a socket whose local address matches 'a' and,
+ * if applicable, matches the device binding. If 'a' is NULL, it searches only
+ * by port.
+ */
+static struct quic_udp_sock *quic_udp_sock_lookup(struct sock *sk,
+ union quic_addr *a, u16 port)
+{
+ struct net *net = sock_net(sk);
+ struct quic_uhash_head *head;
+ struct quic_udp_sock *us;
+
+ head = quic_udp_sock_head(net, port);
+ hlist_for_each_entry(us, &head->head, node) {
+ if (net != sock_net(us->sk))
+ continue;
+ if (a) {
+ if (quic_cmp_sk_addr(us->sk, &us->addr, a) &&
+ us->bind_ifindex == quic_get_dev_if(sk, a))
+ return us;
+ continue;
+ }
+ if (ntohs(us->addr.v4.sin_port) == port)
+ return us;
+ }
+ return NULL;
+}
+
+static void quic_path_set_udp_sk(struct quic_path *path,
+ struct quic_udp_sock *us)
+{
+ if (path->udp_sk)
+ quic_udp_sock_put(path->udp_sk);
+
+ path->udp_sk = us;
+ if (!us) {
+ path->usk = NULL;
+ memset(&path->uaddr, 0, sizeof(path->uaddr));
+ return;
+ }
+ path->usk = us->sk;
+ memcpy(&path->uaddr, &us->addr, sizeof(us->addr));
+}
+
+/* Binds a QUIC path to a local port and sets up a UDP socket. */
+int quic_path_bind(struct sock *sk, struct quic_path_group *paths, u8 path)
+{
+ union quic_addr *a = quic_path_saddr(paths, path);
+ int rover, low, high, remaining;
+ struct net *net = sock_net(sk);
+ struct quic_uhash_head *head;
+ struct quic_udp_sock *us;
+ u16 port;
+
+ port = ntohs(a->v4.sin_port);
+ if (port) {
+ if (inet_port_requires_bind_service(net, port) &&
+ !ns_capable(net->user_ns, CAP_NET_BIND_SERVICE))
+ return -EACCES;
+ head = quic_udp_sock_head(net, port);
+ mutex_lock(&head->lock);
+ us = quic_udp_sock_lookup(sk, a, port);
+ if (us) {
+ if (!uid_eq(current_fsuid(), sk_uid(us->sk))) {
+ mutex_unlock(&head->lock);
+ return -EADDRINUSE;
+ }
+ /* Allow reuse of an existing UDP tunnel socket.
+ * However, if it is in the middle of asynchronous
+ * teardown (via workqueue), it is temporarily unusable.
+ * Return -EAGAIN (not -EADDRINUSE) to signal the caller
+ * to retry soon.
+ */
+ if (!quic_udp_sock_get(us)) {
+ mutex_unlock(&head->lock);
+ return -EAGAIN;
+ }
+ } else {
+ us = quic_udp_sock_create(sk, a);
+ if (IS_ERR(us)) {
+ mutex_unlock(&head->lock);
+ return PTR_ERR(us);
+ }
+ }
+ mutex_unlock(&head->lock);
+ quic_path_set_udp_sk(&paths->path[path], us);
+ return 0;
+ }
+
+ inet_sk_get_local_port_range(sk, &low, &high);
+ remaining = (high - low) + 1;
+ rover = get_random_u32_below(remaining) + low;
+ do {
+ rover++;
+ if (rover < low || rover > high)
+ rover = low;
+ port = (u16)rover;
+ if (inet_is_local_reserved_port(net, port))
+ continue;
+
+ head = quic_udp_sock_head(net, port);
+ mutex_lock(&head->lock);
+ if (quic_udp_sock_lookup(sk, NULL, port)) {
+ mutex_unlock(&head->lock);
+ cond_resched();
+ continue;
+ }
+ a->v4.sin_port = htons(port);
+ us = quic_udp_sock_create(sk, a);
+ if (IS_ERR(us)) {
+ a->v4.sin_port = 0;
+ mutex_unlock(&head->lock);
+ if (PTR_ERR(us) == -EADDRINUSE) {
+ cond_resched();
+ continue;
+ }
+ return PTR_ERR(us);
+ }
+ mutex_unlock(&head->lock);
+
+ quic_path_set_udp_sk(&paths->path[path], us);
+ __sk_dst_reset(sk);
+ return 0;
+ } while (--remaining > 0);
+
+ return -EADDRINUSE;
+}
+
+/* Swaps the active and alternate QUIC paths.
+ *
+ * Promotes the alternate path (path[1]) to become the new active path
+ * (path[0]). If the alternate path has a valid UDP socket, the entire path is
+ * swapped. Otherwise, only the destination address is exchanged, assuming the
+ * source address is the same and no rebind is needed.
+ *
+ * This is typically used during path migration or alternate path promotion.
+ */
+void quic_path_swap(struct quic_path_group *paths)
+{
+ struct quic_path path = paths->path[0];
+
+ paths->alt_probes = 0;
+ paths->alt_state = QUIC_PATH_ALT_SWAPPED;
+
+ /* Protect path[0] modifications with seqcount for RCU readers in
+ * quic_sock_lookup(). The seqcount allows readers to detect torn reads
+ * during the non-atomic structure assignment.
+ */
+ local_bh_disable();
+ preempt_disable_nested();
+ write_seqcount_begin(&paths->path_seq);
+ if (paths->path[1].udp_sk) {
+ paths->path[0] = paths->path[1];
+ paths->path[1] = path;
+ goto out;
+ }
+
+ paths->path[0].daddr = paths->path[1].daddr;
+ paths->path[1].daddr = path.daddr;
+out:
+ write_seqcount_end(&paths->path_seq);
+ preempt_enable_nested();
+ local_bh_enable();
+}
+
+/* Frees resources associated with a QUIC path.
+ *
+ * This is used for cleanup during error handling or when the path is no longer
+ * needed.
+ */
+void quic_path_unbind(struct sock *sk, struct quic_path_group *paths, u8 path)
+{
+ paths->alt_probes = 0;
+ paths->alt_state = QUIC_PATH_ALT_NONE;
+
+ quic_path_set_udp_sk(&paths->path[path], NULL);
+
+ memset(quic_path_daddr(paths, path), 0, sizeof(union quic_addr));
+ memset(quic_path_saddr(paths, path), 0, sizeof(union quic_addr));
+}
+
+/* Detects and records a potential alternate path.
+ *
+ * If the new source or destination address differs from the active path, and
+ * alternate path detection is not disabled, the function updates the alternate
+ * path slot (path[1]) with the new addresses.
+ *
+ * This is typically called on packet receive to detect new possible network
+ * paths (e.g., NAT rebinding, mobility).
+ *
+ * Returns true if the alternate path (path[1]) matches (sa, da), regardless
+ * of whether it was newly detected or already present. Returns false if the
+ * addresses don't match the alternate path.
+ */
+bool quic_path_detect_alt(struct quic_path_group *paths, union quic_addr *sa,
+ union quic_addr *da, struct sock *sk)
+{
+ bool remote = !quic_cmp_sk_addr(sk, quic_path_daddr(paths, 0), da);
+ bool local = !quic_cmp_sk_addr(sk, quic_path_saddr(paths, 0), sa);
+
+ if (!local && !remote)
+ return false;
+
+ if ((local && paths->disable_saddr_alt) ||
+ (remote && paths->disable_daddr_alt))
+ return false;
+
+ if (!quic_path_saddr(paths, 1)->v4.sin_port)
+ quic_path_set_saddr(paths, 1, sa);
+
+ if (!quic_cmp_sk_addr(sk, quic_path_saddr(paths, 1), sa))
+ return false;
+
+ if (!quic_path_daddr(paths, 1)->v4.sin_port)
+ quic_path_set_daddr(paths, 1, da);
+
+ return quic_cmp_sk_addr(sk, quic_path_daddr(paths, 1), da);
+}
+
+void quic_path_get_param(struct quic_path_group *paths,
+ struct quic_transport_param *p)
+{
+ if (p->remote) {
+ p->disable_active_migration = paths->disable_saddr_alt;
+ return;
+ }
+ p->disable_active_migration = paths->disable_daddr_alt;
+}
+
+void quic_path_set_param(struct quic_path_group *paths,
+ struct quic_transport_param *p)
+{
+ if (p->remote) {
+ paths->disable_saddr_alt = !!p->disable_active_migration;
+ return;
+ }
+ paths->disable_daddr_alt = !!p->disable_active_migration;
+}
+
+/* State Machine defined in rfc8899#section-5.2 */
+enum quic_plpmtud_state {
+ QUIC_PL_DISABLED,
+ QUIC_PL_BASE,
+ QUIC_PL_SEARCH,
+ QUIC_PL_COMPLETE,
+ QUIC_PL_ERROR,
+};
+
+#define QUIC_BASE_PLPMTU 1200
+#define QUIC_MAX_PLPMTU 9000
+#define QUIC_MIN_PLPMTU 512
+
+#define QUIC_MAX_PROBES 3
+
+#define QUIC_PL_BIG_STEP 32
+#define QUIC_PL_MIN_STEP 4
+
+/* Track PLPMTUD probe sends and handle probe exhaustion.
+ *
+ * Called right before sending a probe packet in QUIC Path MTU Discovery.
+ * Tracks probe count and manages state transitions based on the number of
+ * probes sent and current PLPMTUD state (BASE, SEARCH, COMPLETE, ERROR).
+ * Detects probe failures and black holes, adjusting PMTU and probe sizes
+ * accordingly.
+ *
+ * Return: New PMTU value if updated, else 0.
+ */
+u32 quic_path_pl_send(struct quic_path_group *paths, s64 number)
+{
+ u32 pathmtu = 0;
+
+ paths->pl.number = number;
+ if (paths->pl.probe_count < QUIC_MAX_PROBES)
+ goto out;
+
+ paths->pl.probe_count = 0;
+ if (paths->pl.state == QUIC_PL_BASE) {
+ if (paths->pl.probe_size == QUIC_BASE_PLPMTU) {
+ /* BASE_PLPMTU Confirming Failed: Base -> Error. */
+ paths->pl.state = QUIC_PL_ERROR;
+
+ paths->pl.pmtu = QUIC_BASE_PLPMTU;
+ pathmtu = QUIC_BASE_PLPMTU;
+ }
+ } else if (paths->pl.state == QUIC_PL_SEARCH) {
+ if (paths->pl.pmtu == paths->pl.probe_size) {
+ /* Black Hole Detected: Search -> Base. */
+ paths->pl.state = QUIC_PL_BASE;
+ paths->pl.probe_size = QUIC_BASE_PLPMTU;
+ paths->pl.probe_high = 0;
+
+ paths->pl.pmtu = QUIC_BASE_PLPMTU;
+ pathmtu = QUIC_BASE_PLPMTU;
+ } else { /* Normal probe failure. */
+ paths->pl.probe_high = paths->pl.probe_size;
+ paths->pl.probe_size = paths->pl.pmtu;
+ }
+ } else if (paths->pl.state == QUIC_PL_COMPLETE) {
+ if (paths->pl.pmtu == paths->pl.probe_size) {
+ /* Black Hole Detected: Search Complete -> Base. */
+ paths->pl.state = QUIC_PL_BASE;
+ paths->pl.probe_size = QUIC_BASE_PLPMTU;
+
+ /* probe_high already reset when entering COMPLETE. */
+ paths->pl.pmtu = QUIC_BASE_PLPMTU;
+ pathmtu = QUIC_BASE_PLPMTU;
+ }
+ }
+
+out:
+ pr_debug("%s: paths: %p, state: %d, pmtu: %d, size: %d, high: %d\n",
+ __func__, paths, paths->pl.state, paths->pl.pmtu,
+ paths->pl.probe_size, paths->pl.probe_high);
+ paths->pl.probe_count++;
+ return pathmtu;
+}
+
+/* Handle successful reception of a PMTU probe.
+ *
+ * Called when a probe packet is acknowledged. Updates probe size and
+ * transitions state if needed (e.g., from SEARCH to COMPLETE). Expands PMTU
+ * using a two-phase linear search: coarse stepping (QUIC_PL_BIG_STEP) to
+ * quickly find the upper bound, then fine stepping (QUIC_PL_MIN_STEP) to
+ * refine.
+ *
+ * Return: New PMTU to apply, or 0 if unchanged.
+ */
+u32 quic_path_pl_recv(struct quic_path_group *paths, bool *raise_timer,
+ bool *complete)
+{
+ u32 pathmtu = 0;
+ u16 next;
+
+ pr_debug("%s: paths: %p, state: %d, pmtu: %d, size: %d, high: %d\n",
+ __func__, paths, paths->pl.state, paths->pl.pmtu,
+ paths->pl.probe_size, paths->pl.probe_high);
+
+ *raise_timer = false;
+ paths->pl.number = 0;
+ paths->pl.pmtu = paths->pl.probe_size;
+ paths->pl.probe_count = 0;
+ if (paths->pl.state == QUIC_PL_BASE) {
+ paths->pl.state = QUIC_PL_SEARCH; /* Base -> Search */
+ paths->pl.probe_size += QUIC_PL_BIG_STEP;
+ } else if (paths->pl.state == QUIC_PL_ERROR) {
+ paths->pl.state = QUIC_PL_SEARCH; /* Error -> Search */
+
+ paths->pl.pmtu = paths->pl.probe_size;
+ pathmtu = (u32)paths->pl.pmtu;
+ paths->pl.probe_size += QUIC_PL_BIG_STEP;
+ } else if (paths->pl.state == QUIC_PL_SEARCH) {
+ if (!paths->pl.probe_high) {
+ if (paths->pl.probe_size < QUIC_MAX_PLPMTU) {
+ next = paths->pl.probe_size + QUIC_PL_BIG_STEP;
+ paths->pl.probe_size =
+ min_t(u16, next, QUIC_MAX_PLPMTU);
+ *complete = false;
+ return 0;
+ }
+ paths->pl.probe_high = QUIC_MAX_PLPMTU;
+ }
+ paths->pl.probe_size += QUIC_PL_MIN_STEP;
+ if (paths->pl.probe_size >= paths->pl.probe_high) {
+ paths->pl.probe_high = 0;
+ /* Search -> Search Complete */
+ paths->pl.state = QUIC_PL_COMPLETE;
+
+ paths->pl.probe_size = paths->pl.pmtu;
+ pathmtu = (u32)paths->pl.pmtu;
+ *raise_timer = true;
+ }
+ } else if (paths->pl.state == QUIC_PL_COMPLETE) {
+ /* Raise probe_size after 30 * interval in Search Complete;
+ * Search Complete -> Search.
+ */
+ paths->pl.state = QUIC_PL_SEARCH;
+ next = paths->pl.probe_size + QUIC_PL_MIN_STEP;
+ paths->pl.probe_size = min_t(u16, next, QUIC_MAX_PLPMTU);
+ }
+
+ *complete = (paths->pl.state == QUIC_PL_COMPLETE);
+ return pathmtu;
+}
+
+/* Handle ICMP "Packet Too Big" messages.
+ *
+ * Responds to an incoming ICMP error by reducing the probe size or falling
+ * back to a safe baseline PMTU depending on current state. Also handles cases
+ * where the PMTU hint lies between probe and current PMTU.
+ *
+ * Return: New PMTU to apply if state changes, or 0 if no change.
+ */
+u32 quic_path_pl_toobig(struct quic_path_group *paths, u32 pmtu,
+ bool *reset_timer)
+{
+ u32 pathmtu = 0;
+
+ pr_debug("%s: paths: %p, state: %d, pmtu: %d, size: %d, ptb: %u\n",
+ __func__, paths, paths->pl.state, paths->pl.pmtu,
+ paths->pl.probe_size, pmtu);
+
+ *reset_timer = false;
+ if (pmtu < QUIC_MIN_PLPMTU || pmtu >= (u32)paths->pl.probe_size)
+ return pathmtu;
+
+ if (paths->pl.state == QUIC_PL_BASE) {
+ if (pmtu < QUIC_BASE_PLPMTU) {
+ paths->pl.state = QUIC_PL_ERROR; /* Base -> Error */
+
+ paths->pl.pmtu = QUIC_BASE_PLPMTU;
+ pathmtu = QUIC_BASE_PLPMTU;
+ }
+ } else if (paths->pl.state == QUIC_PL_SEARCH) {
+ if (pmtu >= QUIC_BASE_PLPMTU && pmtu < (u32)paths->pl.pmtu) {
+ paths->pl.state = QUIC_PL_BASE; /* Search -> Base */
+ paths->pl.probe_size = QUIC_BASE_PLPMTU;
+ paths->pl.probe_count = 0;
+
+ paths->pl.probe_high = 0;
+ paths->pl.pmtu = QUIC_BASE_PLPMTU;
+ pathmtu = QUIC_BASE_PLPMTU;
+ } else if (pmtu > (u32)paths->pl.pmtu &&
+ pmtu < (u32)paths->pl.probe_size) {
+ paths->pl.probe_size = (u16)pmtu;
+ paths->pl.probe_count = 0;
+ }
+ } else if (paths->pl.state == QUIC_PL_COMPLETE) {
+ if (pmtu >= QUIC_BASE_PLPMTU && pmtu < (u32)paths->pl.pmtu) {
+ paths->pl.state = QUIC_PL_BASE; /* Complete -> Base */
+ paths->pl.probe_size = QUIC_BASE_PLPMTU;
+ paths->pl.probe_count = 0;
+
+ paths->pl.probe_high = 0;
+ paths->pl.pmtu = QUIC_BASE_PLPMTU;
+ pathmtu = QUIC_BASE_PLPMTU;
+ *reset_timer = true;
+ }
+ }
+ return pathmtu;
+}
+
+/* Reset PLPMTUD state for a path.
+ *
+ * Resets all PLPMTUD-related state to its initial configuration. Called when
+ * a new path is initialized or when recovering from errors.
+ */
+void quic_path_pl_reset(struct quic_path_group *paths)
+{
+ paths->pl.number = 0;
+ paths->pl.probe_high = 0;
+ paths->pl.probe_count = 0;
+ paths->pl.state = QUIC_PL_BASE;
+ paths->pl.pmtu = QUIC_BASE_PLPMTU;
+ paths->pl.probe_size = QUIC_BASE_PLPMTU;
+}
+
+/* Check if a packet number confirms PLPMTUD probe.
+ *
+ * Checks whether the last probe (tracked by .number) has been acknowledged.
+ * If the probe number lies within the ACK range, confirmation is successful.
+ *
+ * Return: true if probe is confirmed, false otherwise.
+ */
+bool quic_path_pl_confirm(struct quic_path_group *paths, s64 largest,
+ s64 smallest)
+{
+ return paths->pl.number && paths->pl.number >= smallest &&
+ paths->pl.number <= largest;
+}
+
+void quic_path_init(struct quic_path_group *paths)
+{
+ seqcount_init(&paths->path_seq);
+}@@ -0,0 +1,192 @@
+/* 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_PATH_MIN_PMTU 1200U
+#define QUIC_PATH_MAX_PMTU 65535U
+
+#define QUIC_MIN_UDP_PAYLOAD 1200
+#define QUIC_MAX_UDP_PAYLOAD 65527
+
+#define QUIC_PATH_ENTROPY_LEN 8
+
+#define QUIC_PMTUD_RAISE_TIMER_FACTOR 30
+
+extern struct workqueue_struct *quic_wq;
+
+/* Connection Migration State Machine:
+ *
+ * +--------+ recv non-probing, free old path +----------+
+ * | NONE | <-------------------------------------- | SWAPPED |
+ * +--------+ +----------+
+ * | ^ \ ^
+ * | \ \ |
+ * | \ \ new path detected, | recv
+ * | \ \ has another DCID, | Path
+ * | \ \ snd Path Challenge | Response
+ * | \ ------------------------------- |
+ * | ------------------------------- \ |
+ * | new path detected, Path \ \ |
+ * | has no other DCID, Challenge \ \ |
+ * | request a new DCID failed \ \ |
+ * v \ v |
+ * +----------+ +----------+
+ * | PENDING | ------------------------------------> | PROBING |
+ * +----------+ recv a new DCID, snd Path Challenge +----------+
+ */
+enum {
+ QUIC_PATH_ALT_NONE,
+ QUIC_PATH_ALT_PENDING, /* Waiting for new dest conn ID for migration */
+ QUIC_PATH_ALT_PROBING, /* Validating alternate path (PATH_CHALLENGE) */
+ QUIC_PATH_ALT_SWAPPED, /* Alternate path is now active; roles swapped */
+};
+
+struct quic_udp_sock {
+ struct work_struct work; /* Workqueue to destroy UDP tunnel socket */
+ struct hlist_node node; /* Node in addr-based UDP socket hash table */
+ union quic_addr addr; /* Source addr of underlying UDP tunnel socket */
+ int bind_ifindex;
+ refcount_t refcnt;
+ struct sock *sk; /* Underlying UDP tunnel socket */
+ netns_tracker ns_tracker;
+};
+
+struct quic_path {
+ union quic_addr daddr; /* Destination address */
+ union quic_addr saddr; /* Source address */
+
+ /* Wrapped UDP socket for receiving QUIC */
+ struct quic_udp_sock *udp_sk;
+ /* Cached UDP tunnel socket and source addr for RCU access */
+ union quic_addr uaddr;
+ struct sock *usk;
+};
+
+struct quic_path_group {
+ /* Connection ID validation during handshake (rfc9000#section-7.3) */
+ struct quic_conn_id retry_dcid; /* Source CID from Retry packet */
+ struct quic_conn_id orig_dcid; /* Destination CID from first Initial */
+
+ /* Path validation (rfc9000#section-8.2) */
+ u8 entropy[QUIC_PATH_ENTROPY_LEN]; /* Entropy for PATH_CHALLENGE */
+ struct quic_path path[2]; /* Active path (0) and alternate path (1) */
+ seqcount_t path_seq; /* Protects path[0] during swap */
+ struct flowi fl; /* Flow info from routing decisions */
+
+ /* Anti-amplification limit (rfc9000#section-8) */
+ u32 ampl_sndlen; /* Bytes sent before address is validated */
+ u32 ampl_rcvlen; /* Bytes received to lift amplification limit */
+
+ /* MTU discovery handling */
+ struct { /* PLPMTUD probing (rfc8899) */
+ s64 number; /* Packet number used for current probe */
+ u16 pmtu; /* Confirmed path MTU */
+
+ u16 probe_size; /* Current probe packet size */
+ u16 probe_high; /* Highest failed probe size */
+ u8 probe_count; /* Retry count for current probe_size */
+ u8 state; /* Probe state machine (rfc8899#section-5.2) */
+ } pl;
+ u32 mtu_info; /* PMTU value from received ICMP, pending apply */
+
+ u32 plpmtud_interval; /* Time interval for the PLPMTUD probe timer */
+ u32 keepalive_interval; /* Time interval to maintain path liveness */
+
+ u8 ecn_probes; /* ECN probe counter */
+ u8 validated:1; /* Path validated with PATH_RESPONSE */
+ u8 blocked:1; /* Blocked by anti-amplification limit */
+ u8 version:1; /* Version negotiation performed */
+ u8 retry:1; /* Retry used in initial packet */
+
+ /* Connection Migration (rfc9000#section-9) */
+ u8 disable_saddr_alt:1; /* Remote disable_active_migration parameter */
+ u8 disable_daddr_alt:1; /* Local disable_active_migration parameter */
+ u8 pref_addr:1; /* Preferred address offered (rfc9000#section-18.2) */
+ u8 alt_probes; /* Number of PATH_CHALLENGE probes sent */
+ u8 alt_state; /* Connection migration state (see above) */
+};
+
+static inline union quic_addr *quic_path_saddr(struct quic_path_group *paths,
+ u8 path)
+{
+ return &paths->path[path].saddr;
+}
+
+static inline void quic_path_set_saddr(struct quic_path_group *paths, u8 path,
+ union quic_addr *addr)
+{
+ memcpy(quic_path_saddr(paths, path), addr, sizeof(*addr));
+}
+
+static inline union quic_addr *quic_path_daddr(struct quic_path_group *paths,
+ u8 path)
+{
+ return &paths->path[path].daddr;
+}
+
+static inline void quic_path_set_daddr(struct quic_path_group *paths, u8 path,
+ union quic_addr *addr)
+{
+ memcpy(quic_path_daddr(paths, path), addr, sizeof(*addr));
+}
+
+static inline union quic_addr *quic_path_uaddr(struct quic_path_group *paths,
+ u8 path)
+{
+ return &paths->path[path].uaddr;
+}
+
+static inline struct sock *quic_path_usock(struct quic_path_group *paths,
+ u8 path)
+{
+ return paths->path[path].usk;
+}
+
+static inline bool quic_path_alt_state(struct quic_path_group *paths, u8 state)
+{
+ return paths->alt_state == state;
+}
+
+static inline void quic_path_set_alt_state(struct quic_path_group *paths,
+ u8 state)
+{
+ paths->alt_state = state;
+}
+
+/* Returns the destination Connection ID (DCID) used for identifying the
+ * connection. Per rfc9000#section-7.3, handshake packets are considered part
+ * of the same connection if their DCID matches the one returned here.
+ */
+static inline struct quic_conn_id *
+quic_path_orig_dcid(struct quic_path_group *paths)
+{
+ return paths->retry ? &paths->retry_dcid : &paths->orig_dcid;
+}
+
+void quic_path_init(struct quic_path_group *paths);
+
+bool quic_path_detect_alt(struct quic_path_group *paths, union quic_addr *sa,
+ union quic_addr *da, struct sock *sk);
+int quic_path_bind(struct sock *sk, struct quic_path_group *paths, u8 path);
+void quic_path_unbind(struct sock *sk, struct quic_path_group *paths, u8 path);
+void quic_path_swap(struct quic_path_group *paths);
+
+u32 quic_path_pl_recv(struct quic_path_group *paths, bool *raise_timer,
+ bool *complete);
+u32 quic_path_pl_toobig(struct quic_path_group *paths, u32 pmtu,
+ bool *reset_timer);
+u32 quic_path_pl_send(struct quic_path_group *paths, s64 number);
+
+void quic_path_get_param(struct quic_path_group *paths,
+ struct quic_transport_param *p);
+void quic_path_set_param(struct quic_path_group *paths,
+ struct quic_transport_param *p);
+bool quic_path_pl_confirm(struct quic_path_group *paths,
+ s64 largest, s64 smallest);
+void quic_path_pl_reset(struct quic_path_group *paths);