AI Trajectory


Trajectory: (Expand/Collapse All)
Seq Timestamp Type Name Duration
0/0 2026/07/16 03:53 flow patch-triage
1m Args:
null

Results:
{
  "EnableConfigs": null,
  "FocusSymbols": [],
  "Reasoning": "The patch introduces the core structures and functions for the QUIC protocol, but it is a skeleton/preliminary patch. The socket operations (bind, connect, sendmsg, recvmsg, setsockopt) all return -EOPNOTSUPP. Furthermore, the UDP tunnel socket required to receive packets is never created because `quic_path_bind` is not called anywhere, making the packet reception and processing logic completely unreachable from user space or the network.",
  "WorthFuzzing": false
}

1/1 2026/07/16 03:53 action read-patch-diff
0m Args:
null

Results:
{
  "PatchDiff": "commit f275446826c8678c88010162b6126dc1f6d31c5f\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate:   Thu Jul 16 03:53:04 2026 +0000\n\n    syz-cluster: applied patch under review\n\ndiff --git a/Documentation/networking/ip-sysctl.rst b/Documentation/networking/ip-sysctl.rst\nindex 208f46967ee59b..6dd9d6970cec45 100644\n--- a/Documentation/networking/ip-sysctl.rst\n+++ b/Documentation/networking/ip-sysctl.rst\n@@ -3809,6 +3809,45 @@ l3mdev_accept - BOOLEAN\n \tDefault: 1 (enabled)\n \n \n+``/proc/sys/net/quic/*`` Variables\n+===================================\n+\n+quic_mem - vector of 3 LONGs: min, pressure, max\n+\tNumber of pages allowed for queueing by all QUIC sockets.\n+\n+\tmin: below this number of pages QUIC is not bothered about its\n+\tmemory appetite.\n+\n+\tpressure: when amount of memory allocated by QUIC exceeds this number\n+\tof pages, QUIC moderates its memory consumption and enters memory\n+\tpressure mode, which is exited when memory consumption falls\n+\tunder \"min\".\n+\n+\tmax: number of pages allowed for queueing by all QUIC sockets.\n+\n+\tDefaults are calculated at boot time from amount of available\n+\tmemory.\n+\n+quic_rmem - vector of 3 INTEGERs: min, default, max\n+\tOnly the first value (\"min\") is used, \"default\" and \"max\" are\n+\tignored.\n+\n+\tmin: Minimal size of receive buffer used by QUIC sockets.\n+\tIt is guaranteed to each QUIC socket, even under moderate memory\n+\tpressure.\n+\n+\tDefault: 4K\n+\n+quic_wmem - vector of 3 INTEGERs: min, default, max\n+\tOnly the first value (\"min\") is used, \"default\" and \"max\" are\n+\tignored.\n+\n+\tmin: Amount of memory reserved for send buffers for QUIC sockets.\n+\tEach QUIC socket has rights to use it due to fact of its birth.\n+\n+\tDefault: 4K\n+\n+\n ``/proc/sys/net/core/*``\n ========================\n \ndiff --git a/MAINTAINERS b/MAINTAINERS\nindex f3218abefd0c6b..5ce461662a0c9c 100644\n--- a/MAINTAINERS\n+++ b/MAINTAINERS\n@@ -22404,6 +22404,15 @@ L:\tlinux-wireless@vger.kernel.org\n S:\tMaintained\n F:\tdrivers/net/wireless/quantenna/\n \n+QUIC PROTOCOL\n+M:\tXin Long \u003clucien.xin@gmail.com\u003e\n+L:\tquic@lists.linux.dev\n+S:\tMaintained\n+W:\thttps://github.com/lxin/quic\n+F:\tinclude/linux/quic.h\n+F:\tinclude/uapi/linux/quic.h\n+F:\tnet/quic/\n+\n RADEON and AMDGPU DRM DRIVERS\n M:\tAlex Deucher \u003calexander.deucher@amd.com\u003e\n M:\tChristian König \u003cchristian.koenig@amd.com\u003e\ndiff --git a/include/linux/quic.h b/include/linux/quic.h\nnew file mode 100644\nindex 00000000000000..908308a6bb8320\n--- /dev/null\n+++ b/include/linux/quic.h\n@@ -0,0 +1,24 @@\n+/* SPDX-License-Identifier: GPL-2.0-or-later */\n+/* QUIC kernel implementation\n+ * (C) Copyright Red Hat Corp. 2023\n+ *\n+ * This file is part of the QUIC kernel implementation\n+ *\n+ * Written or modified by:\n+ *    Xin Long \u003clucien.xin@gmail.com\u003e\n+ */\n+\n+#ifndef _LINUX_QUIC_H\n+#define _LINUX_QUIC_H\n+\n+#include \u003clinux/sockptr.h\u003e\n+#include \u003cuapi/linux/quic.h\u003e\n+\n+struct sock;\n+\n+int quic_do_setsockopt(struct sock *sk, int optname, sockptr_t optval,\n+\t\t       unsigned int optlen);\n+int quic_do_getsockopt(struct sock *sk, int optname, sockptr_t optval,\n+\t\t       sockptr_t optlen);\n+\n+#endif\ndiff --git a/include/linux/socket.h b/include/linux/socket.h\nindex 2a8d7b14f1d119..9d6ec1b2c50ba7 100644\n--- a/include/linux/socket.h\n+++ b/include/linux/socket.h\n@@ -400,6 +400,7 @@ struct ucred {\n #define SOL_MCTP\t285\n #define SOL_SMC\t\t286\n #define SOL_VSOCK\t287\n+#define SOL_QUIC\t288\n \n /* IPX options */\n #define IPX_TYPE\t1\ndiff --git a/include/uapi/linux/in.h b/include/uapi/linux/in.h\nindex ced0fc3c3aa534..e4072152f2e62f 100644\n--- a/include/uapi/linux/in.h\n+++ b/include/uapi/linux/in.h\n@@ -85,6 +85,8 @@ enum {\n #define IPPROTO_RAW\t\tIPPROTO_RAW\n   IPPROTO_SMC = 256,\t\t/* Shared Memory Communications\t\t*/\n #define IPPROTO_SMC\t\tIPPROTO_SMC\n+  IPPROTO_QUIC = 261,\t\t/* A UDP-Based Multiplexed Secure Transport */\n+#define IPPROTO_QUIC\t\tIPPROTO_QUIC\n   IPPROTO_MPTCP = 262,\t\t/* Multipath TCP connection\t\t*/\n #define IPPROTO_MPTCP\t\tIPPROTO_MPTCP\n   IPPROTO_MAX\ndiff --git a/include/uapi/linux/quic.h b/include/uapi/linux/quic.h\nnew file mode 100644\nindex 00000000000000..d29b21f7b77cb9\n--- /dev/null\n+++ b/include/uapi/linux/quic.h\n@@ -0,0 +1,241 @@\n+/* SPDX-License-Identifier: GPL-2.0+ WITH Linux-syscall-note */\n+/* QUIC kernel implementation\n+ * (C) Copyright Red Hat Corp. 2023\n+ *\n+ * This file is part of the QUIC kernel implementation\n+ *\n+ * Written or modified by:\n+ *    Xin Long \u003clucien.xin@gmail.com\u003e\n+ */\n+\n+#ifndef _UAPI_LINUX_QUIC_H\n+#define _UAPI_LINUX_QUIC_H\n+\n+#include \u003clinux/types.h\u003e\n+#ifdef __KERNEL__\n+#include \u003clinux/socket.h\u003e\n+#else\n+#include \u003csys/socket.h\u003e /* for MSG_* flags */\n+#endif\n+\n+/* NOTE: Structure descriptions are specified in:\n+ * https://datatracker.ietf.org/doc/html/draft-lxin-quic-socket-apis\n+ */\n+\n+/* Send or Receive Options APIs */\n+enum quic_cmsg_type {\n+\tQUIC_STREAM_INFO,\n+\tQUIC_HANDSHAKE_INFO,\n+};\n+\n+#define QUIC_STREAM_TYPE_SERVER_MASK\t0x01\n+#define QUIC_STREAM_TYPE_UNI_MASK\t0x02\n+#define QUIC_STREAM_TYPE_MASK\t\t0x03\n+\n+enum quic_msg_flags {\n+\t/* flags for stream_flags */\n+\tMSG_QUIC_STREAM_NEW\t\t= MSG_SYN,\n+\tMSG_QUIC_STREAM_FIN\t\t= MSG_FIN,\n+\tMSG_QUIC_STREAM_UNI\t\t= MSG_CONFIRM,\n+\tMSG_QUIC_STREAM_DONTWAIT\t= MSG_WAITFORONE,\n+\tMSG_QUIC_STREAM_SNDBLOCK\t= MSG_ERRQUEUE,\n+\n+\t/* extended flags for msg_flags */\n+\tMSG_QUIC_DATAGRAM\t\t= MSG_RST,\n+\tMSG_QUIC_NOTIFICATION\t\t= MSG_MORE,\n+};\n+\n+enum quic_crypto_level {\n+\tQUIC_CRYPTO_APP,\n+\tQUIC_CRYPTO_INITIAL,\n+\tQUIC_CRYPTO_HANDSHAKE,\n+\tQUIC_CRYPTO_EARLY,\n+\tQUIC_CRYPTO_MAX,\n+};\n+\n+struct quic_handshake_info {\n+\t__u8\tcrypto_level;\n+};\n+\n+struct quic_stream_info {\n+\t__s64\tstream_id;\n+\t__u32\tstream_flags;\n+};\n+\n+/* Socket Options APIs */\n+#define QUIC_SOCKOPT_EVENT\t\t\t\t0\n+#define QUIC_SOCKOPT_STREAM_OPEN\t\t\t1\n+#define QUIC_SOCKOPT_STREAM_RESET\t\t\t2\n+#define QUIC_SOCKOPT_STREAM_STOP_SENDING\t\t3\n+#define QUIC_SOCKOPT_CONNECTION_ID\t\t\t4\n+#define QUIC_SOCKOPT_CONNECTION_CLOSE\t\t\t5\n+#define QUIC_SOCKOPT_CONNECTION_MIGRATION\t\t6\n+#define QUIC_SOCKOPT_KEY_UPDATE\t\t\t\t7\n+#define QUIC_SOCKOPT_TRANSPORT_PARAM\t\t\t8\n+#define QUIC_SOCKOPT_CONFIG\t\t\t\t9\n+#define QUIC_SOCKOPT_TOKEN\t\t\t\t10\n+#define QUIC_SOCKOPT_ALPN\t\t\t\t11\n+#define QUIC_SOCKOPT_SESSION_TICKET\t\t\t12\n+#define QUIC_SOCKOPT_CRYPTO_SECRET\t\t\t13\n+#define QUIC_SOCKOPT_TRANSPORT_PARAM_EXT\t\t14\n+\n+#define QUIC_VERSION_V1\t\t\t0x1\n+#define QUIC_VERSION_V2\t\t\t0x6b3343cf\n+\n+struct quic_transport_param {\n+\t__u8\tremote;\n+\t__u8\tdisable_active_migration;\n+\t__u8\tgrease_quic_bit;\n+\t__u8\tstateless_reset;\n+\t__u8\tdisable_1rtt_encryption;\n+\t__u8\tdisable_compatible_version;\n+\t__u8\tactive_connection_id_limit;\n+\t__u8\tack_delay_exponent;\n+\t__u16\tmax_datagram_frame_size;\n+\t__u16\tmax_udp_payload_size;\n+\t__u32\tmax_idle_timeout;\n+\t__u32\tmax_ack_delay;\n+\t__u16\tmax_streams_bidi;\n+\t__u16\tmax_streams_uni;\n+\t__u64\tmax_data;\n+\t__u64\tmax_stream_data_bidi_local;\n+\t__u64\tmax_stream_data_bidi_remote;\n+\t__u64\tmax_stream_data_uni;\n+};\n+\n+struct quic_config {\n+\t__u32\tversion;\n+\t__u32\tplpmtud_probe_interval;\n+\t__u32\tinitial_smoothed_rtt;\n+\t__u32\tpayload_cipher_type;\n+\t__u8\tcongestion_control_algo;\n+\t__u8\tvalidate_peer_address;\n+\t__u8\tstream_data_nodelay;\n+\t__u8\treceive_session_ticket;\n+\t__u8\tcertificate_request;\n+\t__u8\treserved[3];\n+\t__u32\tkeepalive_probe_interval;\n+};\n+\n+struct quic_crypto_secret {\n+\t__u8\tsend;  /* send or recv */\n+\t__u8\tlevel; /* crypto level */\n+\t__u16\treserved;\n+\t__u32\ttype; /* TLS_CIPHER_* */\n+#define QUIC_CRYPTO_SECRET_BUFFER_SIZE 48\n+\t__u8\tsecret[QUIC_CRYPTO_SECRET_BUFFER_SIZE];\n+};\n+\n+enum quic_cong_algo {\n+\tQUIC_CONG_ALG_RENO,\n+\tQUIC_CONG_ALG_MAX,\n+};\n+\n+struct quic_errinfo {\n+\t__s64\tstream_id;\n+\t__u32\terrcode;\n+};\n+\n+struct quic_connection_id_info {\n+\t__u8\tdest;\n+\t__u8\treserved[3];\n+\t__u32\tactive;\n+\t__u32\tprior_to;\n+};\n+\n+struct quic_event_option {\n+\t__u8\ttype;\n+\t__u8\ton;\n+};\n+\n+/* Event APIs */\n+enum quic_event_type {\n+\tQUIC_EVENT_NONE,\n+\tQUIC_EVENT_STREAM_UPDATE,\n+\tQUIC_EVENT_STREAM_MAX_DATA,\n+\tQUIC_EVENT_STREAM_MAX_STREAM,\n+\tQUIC_EVENT_CONNECTION_ID,\n+\tQUIC_EVENT_CONNECTION_CLOSE,\n+\tQUIC_EVENT_CONNECTION_MIGRATION,\n+\tQUIC_EVENT_KEY_UPDATE,\n+\tQUIC_EVENT_NEW_TOKEN,\n+\tQUIC_EVENT_NEW_SESSION_TICKET,\n+\tQUIC_EVENT_MAX,\n+};\n+\n+enum {\n+\tQUIC_STREAM_SEND_STATE_READY,\n+\tQUIC_STREAM_SEND_STATE_SEND,\n+\tQUIC_STREAM_SEND_STATE_SENT,\n+\tQUIC_STREAM_SEND_STATE_RECVD,\n+\tQUIC_STREAM_SEND_STATE_RESET_SENT,\n+\tQUIC_STREAM_SEND_STATE_RESET_RECVD,\n+\n+\tQUIC_STREAM_RECV_STATE_RECV,\n+\tQUIC_STREAM_RECV_STATE_SIZE_KNOWN,\n+\tQUIC_STREAM_RECV_STATE_RECVD,\n+\tQUIC_STREAM_RECV_STATE_READ,\n+\tQUIC_STREAM_RECV_STATE_RESET_RECVD,\n+\tQUIC_STREAM_RECV_STATE_RESET_READ,\n+};\n+\n+struct quic_stream_update {\n+\t__s64\tid;\n+\t__u8\tstate;\n+\t__u8\treserved[3];\n+\t__u32\terrcode;\n+\t__u64\tfinalsz;\n+};\n+\n+struct quic_stream_max_data {\n+\t__s64\tid;\n+\t__u64\tmax_data;\n+};\n+\n+struct quic_connection_close {\n+\t__u32\terrcode;\n+\t__u8\tframe;\n+\t__u8\treserved[3];\n+#define QUIC_CLOSE_PHRASE_BUFFER_SIZE 64\n+\t__u8\tphrase[QUIC_CLOSE_PHRASE_BUFFER_SIZE];\n+};\n+\n+union quic_event {\n+\tstruct quic_stream_update\tupdate;\n+\tstruct quic_stream_max_data\tmax_data;\n+\tstruct quic_connection_close\tclose;\n+\tstruct quic_connection_id_info\tinfo;\n+\t__u64\tmax_stream;\n+\t__u8\tlocal_migration;\n+\t__u8\tkey_update_phase;\n+};\n+\n+enum {\n+\tQUIC_TRANSPORT_ERROR_NONE\t\t\t= 0x00,\n+\tQUIC_TRANSPORT_ERROR_INTERNAL\t\t\t= 0x01,\n+\tQUIC_TRANSPORT_ERROR_CONNECTION_REFUSED\t\t= 0x02,\n+\tQUIC_TRANSPORT_ERROR_FLOW_CONTROL\t\t= 0x03,\n+\tQUIC_TRANSPORT_ERROR_STREAM_LIMIT\t\t= 0x04,\n+\tQUIC_TRANSPORT_ERROR_STREAM_STATE\t\t= 0x05,\n+\tQUIC_TRANSPORT_ERROR_FINAL_SIZE\t\t\t= 0x06,\n+\tQUIC_TRANSPORT_ERROR_FRAME_ENCODING\t\t= 0x07,\n+\tQUIC_TRANSPORT_ERROR_TRANSPORT_PARAM\t\t= 0x08,\n+\tQUIC_TRANSPORT_ERROR_CONNECTION_ID_LIMIT\t= 0x09,\n+\tQUIC_TRANSPORT_ERROR_PROTOCOL_VIOLATION\t\t= 0x0a,\n+\tQUIC_TRANSPORT_ERROR_INVALID_TOKEN\t\t= 0x0b,\n+\tQUIC_TRANSPORT_ERROR_APPLICATION\t\t= 0x0c,\n+\tQUIC_TRANSPORT_ERROR_CRYPTO_BUF_EXCEEDED\t= 0x0d,\n+\tQUIC_TRANSPORT_ERROR_KEY_UPDATE\t\t\t= 0x0e,\n+\tQUIC_TRANSPORT_ERROR_AEAD_LIMIT_REACHED\t\t= 0x0f,\n+\tQUIC_TRANSPORT_ERROR_NO_VIABLE_PATH\t\t= 0x10,\n+\tQUIC_TRANSPORT_ERROR_VERSION_NEGOTIATION\t= 0x11,\n+\n+\t/* The cryptographic handshake failed. A range of 256 values is reserved\n+\t * for carrying error codes specific to the cryptographic handshake that\n+\t * is used. Codes for errors occurring when TLS is used for the\n+\t * cryptographic handshake are described in Section 4.8 of [QUIC-TLS].\n+\t */\n+\tQUIC_TRANSPORT_ERROR_CRYPTO\t\t\t= 0x0100,\n+};\n+\n+#endif /* _UAPI_LINUX_QUIC_H */\ndiff --git a/net/Kconfig b/net/Kconfig\nindex e384773935515d..ab0d82e108ea96 100644\n--- a/net/Kconfig\n+++ b/net/Kconfig\n@@ -251,6 +251,7 @@ source \"net/bridge/netfilter/Kconfig\"\n \n endif # if NETFILTER\n \n+source \"net/quic/Kconfig\"\n source \"net/sctp/Kconfig\"\n source \"net/rds/Kconfig\"\n source \"net/tipc/Kconfig\"\ndiff --git a/net/Makefile b/net/Makefile\nindex 5b2dd7f07a85e9..e2b25ede017d7c 100644\n--- a/net/Makefile\n+++ b/net/Makefile\n@@ -39,6 +39,7 @@ obj-$(CONFIG_PHONET)\t\t+= phonet/\n ifneq ($(CONFIG_VLAN_8021Q),)\n obj-y\t\t\t\t+= 8021q/\n endif\n+obj-$(CONFIG_IP_QUIC)\t\t+= quic/\n obj-$(CONFIG_IP_SCTP)\t\t+= sctp/\n obj-$(CONFIG_RDS)\t\t+= rds/\n obj-$(CONFIG_WIRELESS)\t\t+= wireless/\ndiff --git a/net/quic/Kconfig b/net/quic/Kconfig\nnew file mode 100644\nindex 00000000000000..602e6d89eafdd2\n--- /dev/null\n+++ b/net/quic/Kconfig\n@@ -0,0 +1,35 @@\n+# SPDX-License-Identifier: GPL-2.0-or-later\n+#\n+# QUIC configuration\n+#\n+\n+menuconfig IP_QUIC\n+\ttristate \"QUIC: A UDP-Based Multiplexed Secure Transport (Experimental)\"\n+\tdepends on INET\n+\tdepends on IPV6\n+\tselect CRYPTO\n+\tselect CRYPTO_HMAC\n+\tselect CRYPTO_AES\n+\tselect CRYPTO_GCM\n+\tselect CRYPTO_CCM\n+\tselect CRYPTO_CHACHA20POLY1305\n+\tselect NET_UDP_TUNNEL\n+\tdefault n\n+\thelp\n+\t  QUIC: A UDP-Based Multiplexed and Secure Transport\n+\n+\t  From rfc9000 \u003chttps://www.rfc-editor.org/rfc/rfc9000.html\u003e.\n+\n+\t  QUIC provides applications with flow-controlled streams for structured\n+\t  communication, low-latency connection establishment, and network path\n+\t  migration.  QUIC includes security measures that ensure\n+\t  confidentiality, integrity, and availability in a range of deployment\n+\t  circumstances.  Accompanying documents describe the integration of\n+\t  TLS for key negotiation, loss detection, and an exemplary congestion\n+\t  control algorithm.\n+\n+\t  To compile this protocol support as a module, choose M here: the\n+\t  module will be called quic. Debug messages are handled by the\n+\t  kernel's dynamic debugging framework.\n+\n+\t  If in doubt, say N.\ndiff --git a/net/quic/Makefile b/net/quic/Makefile\nnew file mode 100644\nindex 00000000000000..0f903f4a7ff1fc\n--- /dev/null\n+++ b/net/quic/Makefile\n@@ -0,0 +1,9 @@\n+# SPDX-License-Identifier: GPL-2.0-or-later\n+#\n+# Makefile for QUIC support code.\n+#\n+\n+obj-$(CONFIG_IP_QUIC) += quic.o\n+\n+quic-y := common.o family.o protocol.o socket.o stream.o connid.o path.o \\\n+\t  cong.o pnspace.o crypto.o timer.o packet.o\ndiff --git a/net/quic/common.c b/net/quic/common.c\nnew file mode 100644\nindex 00000000000000..51d71b6733c7d0\n--- /dev/null\n+++ b/net/quic/common.c\n@@ -0,0 +1,565 @@\n+// SPDX-License-Identifier: GPL-2.0-or-later\n+/* QUIC kernel implementation\n+ * (C) Copyright Red Hat Corp. 2023\n+ *\n+ * This file is part of the QUIC kernel implementation\n+ *\n+ * Initialization/cleanup for QUIC protocol support.\n+ *\n+ * Written or modified by:\n+ *    Xin Long \u003clucien.xin@gmail.com\u003e\n+ */\n+\n+#include \u003clinux/unaligned.h\u003e\n+#include \u003cnet/netns/hash.h\u003e\n+#include \u003clinux/vmalloc.h\u003e\n+#include \u003clinux/jhash.h\u003e\n+\n+#include \"common.h\"\n+\n+#define QUIC_VARINT_2BYTE_PREFIX\t0x40\n+#define QUIC_VARINT_4BYTE_PREFIX\t0x80\n+#define QUIC_VARINT_8BYTE_PREFIX\t0xc0\n+\n+#define QUIC_VARINT_LENGTH(p)\t\tBIT((*(p)) \u003e\u003e 6)\n+\n+struct quic_hashinfo {\n+\tstruct quic_shash_table shash; /* Source connection ID hashtable */\n+\tstruct quic_shash_table lhash; /* Listening sock hashtable */\n+\tstruct quic_shash_table chash; /* Connection sock hashtable */\n+\tstruct quic_uhash_table uhash; /* UDP sock hashtable */\n+};\n+\n+static struct quic_hashinfo quic_hashinfo;\n+\n+u32 quic_sock_hash_size(void)\n+{\n+\treturn quic_hashinfo.chash.size;\n+}\n+\n+u32 quic_sock_hash(struct net *net, union quic_addr *s, union quic_addr *d)\n+{\n+\tu32 ports = ((__force u32)s-\u003ev4.sin_port) \u003c\u003c 16 |\n+\t\t    (__force u32)d-\u003ev4.sin_port;\n+\tu32 saddr = (s-\u003esa.sa_family == AF_INET6) ?\n+\t\t    jhash(\u0026s-\u003ev6.sin6_addr, 16, 0) :\n+\t\t    (__force u32)s-\u003ev4.sin_addr.s_addr;\n+\tu32 daddr = (d-\u003esa.sa_family == AF_INET6) ?\n+\t\t    jhash(\u0026d-\u003ev6.sin6_addr, 16, 0) :\n+\t\t    (__force u32)d-\u003ev4.sin_addr.s_addr;\n+\tu32 hash = jhash_3words(saddr, daddr, ports, net_hash_mix(net));\n+\n+\treturn hash \u0026 (quic_sock_hash_size() - 1);\n+}\n+\n+struct quic_shash_head *quic_sock_head(u32 hash)\n+{\n+\treturn \u0026quic_hashinfo.chash.hash[hash];\n+}\n+\n+u32 quic_listen_sock_hash_size(void)\n+{\n+\treturn quic_hashinfo.lhash.size;\n+}\n+\n+u32 quic_listen_sock_hash(struct net *net, u16 port)\n+{\n+\tu32 hash = jhash_1word((__force u32)port, net_hash_mix(net));\n+\n+\treturn hash \u0026 (quic_listen_sock_hash_size() - 1);\n+}\n+\n+struct quic_shash_head *quic_listen_sock_head(u32 hash)\n+{\n+\treturn \u0026quic_hashinfo.lhash.hash[hash];\n+}\n+\n+struct quic_shash_head *quic_source_conn_id_head(struct net *net, u8 *scid,\n+\t\t\t\t\t\t u32 len)\n+{\n+\tu32 hash = jhash_1word(jhash(scid, len, 0), net_hash_mix(net));\n+\tstruct quic_shash_table *ht = \u0026quic_hashinfo.shash;\n+\n+\treturn \u0026ht-\u003ehash[hash \u0026 (ht-\u003esize - 1)];\n+}\n+\n+struct quic_uhash_head *quic_udp_sock_head(struct net *net, u16 port)\n+{\n+\tu32 hash = jhash_1word((__force u32)port, net_hash_mix(net));\n+\tstruct quic_uhash_table *ht = \u0026quic_hashinfo.uhash;\n+\n+\treturn \u0026ht-\u003ehash[hash \u0026 (ht-\u003esize - 1)];\n+}\n+\n+u32 quic_addr_hash(struct net *net, union quic_addr *a)\n+{\n+\tu32 addr = (a-\u003esa.sa_family == AF_INET6) ?\n+\t\t   jhash(\u0026a-\u003ev6.sin6_addr, 16, 0) :\n+\t\t   (__force u32)a-\u003ev4.sin_addr.s_addr;\n+\n+\treturn jhash_2words(addr, (__force u32)a-\u003ev4.sin_port,\n+\t\t\t    net_hash_mix(net));\n+}\n+\n+void quic_hash_tables_destroy(void)\n+{\n+\tvfree(quic_hashinfo.shash.hash);\n+\tvfree(quic_hashinfo.lhash.hash);\n+\tvfree(quic_hashinfo.chash.hash);\n+\tvfree(quic_hashinfo.uhash.hash);\n+}\n+\n+static int quic_shash_table_init(struct quic_shash_table *ht, u32 size)\n+{\n+\tint i;\n+\n+\tht-\u003ehash = vmalloc_array(size, sizeof(struct quic_shash_head));\n+\tif (!ht-\u003ehash)\n+\t\treturn -ENOMEM;\n+\n+\tht-\u003esize = size;\n+\tfor (i = 0; i \u003c ht-\u003esize; i++) {\n+\t\tspin_lock_init(\u0026ht-\u003ehash[i].lock);\n+\t\tINIT_HLIST_NULLS_HEAD(\u0026ht-\u003ehash[i].head, i);\n+\t}\n+\treturn 0;\n+}\n+\n+static int quic_uhash_table_init(struct quic_uhash_table *ht, u32 size)\n+{\n+\tint i;\n+\n+\tht-\u003ehash = vmalloc_array(size, sizeof(struct quic_uhash_head));\n+\tif (!ht-\u003ehash)\n+\t\treturn -ENOMEM;\n+\n+\tht-\u003esize = size;\n+\tfor (i = 0; i \u003c ht-\u003esize; i++) {\n+\t\tmutex_init(\u0026ht-\u003ehash[i].lock);\n+\t\tINIT_HLIST_HEAD(\u0026ht-\u003ehash[i].head);\n+\t}\n+\treturn 0;\n+}\n+\n+int quic_hash_tables_init(void)\n+{\n+\tunsigned long nr_pages = totalram_pages();\n+\tunsigned long limit;\n+\tu32 size;\n+\tint err;\n+\n+\t/* Scale hash table size based on system memory, similar to SCTP. */\n+\tif (nr_pages \u003e= (128 * 1024))\n+\t\tlimit = nr_pages \u003e\u003e (22 - PAGE_SHIFT);\n+\telse\n+\t\tlimit = nr_pages \u003e\u003e (24 - PAGE_SHIFT);\n+\n+\tlimit = roundup_pow_of_two(limit ?: 1);\n+\n+\t/* Source connection ID table (fast lookup, larger size) */\n+\tsize = min_t(unsigned long, limit, 64 * 1024UL);\n+\terr = quic_shash_table_init(\u0026quic_hashinfo.shash, size);\n+\tif (err)\n+\t\tgoto err;\n+\tsize = min_t(unsigned long, limit, 16 * 1024UL);\n+\terr = quic_shash_table_init(\u0026quic_hashinfo.lhash, size);\n+\tif (err)\n+\t\tgoto err;\n+\terr = quic_shash_table_init(\u0026quic_hashinfo.chash, size);\n+\tif (err)\n+\t\tgoto err;\n+\terr = quic_uhash_table_init(\u0026quic_hashinfo.uhash, size);\n+\tif (err)\n+\t\tgoto err;\n+\treturn 0;\n+err:\n+\tquic_hash_tables_destroy();\n+\treturn err;\n+}\n+\n+/* Returns the number of bytes required to encode a QUIC variable-length\n+ * integer.\n+ */\n+u8 quic_var_len(u64 n)\n+{\n+\tif (n \u003c= QUIC_VARINT_1BYTE_MAX)\n+\t\treturn 1;\n+\tif (n \u003c= QUIC_VARINT_2BYTE_MAX)\n+\t\treturn 2;\n+\tif (n \u003c= QUIC_VARINT_4BYTE_MAX)\n+\t\treturn 4;\n+\treturn 8;\n+}\n+\n+/* Decodes a QUIC variable-length integer from a buffer. */\n+u8 quic_get_var(u8 **pp, u32 *plen, u64 *val)\n+{\n+\tu8 *p = *pp, len;\n+\tu64 v = 0;\n+\n+\tif (!*plen)\n+\t\treturn 0;\n+\n+\tlen = QUIC_VARINT_LENGTH(p);\n+\tif (*plen \u003c len)\n+\t\treturn 0;\n+\n+\tswitch (len) {\n+\tcase 1:\n+\t\tv = *p;\n+\t\tbreak;\n+\tcase 2:\n+\t\tv = get_unaligned_be16(p) \u0026 QUIC_VARINT_2BYTE_MAX;\n+\t\tbreak;\n+\tcase 4:\n+\t\tv = get_unaligned_be32(p) \u0026 QUIC_VARINT_4BYTE_MAX;\n+\t\tbreak;\n+\tcase 8:\n+\t\tv = get_unaligned_be64(p) \u0026 QUIC_VARINT_8BYTE_MAX;\n+\t\tbreak;\n+\tdefault:\n+\t\treturn 0;\n+\t}\n+\n+\t*plen -= len;\n+\t*pp = p + len;\n+\t*val = v;\n+\treturn len;\n+}\n+\n+/* Reads a fixed-length integer from the buffer. */\n+u32 quic_get_int(u8 **pp, u32 *plen, u64 *val, u32 len)\n+{\n+\tu8 *p = *pp;\n+\tu64 v = 0;\n+\n+\tif (*plen \u003c len)\n+\t\treturn 0;\n+\n+\tswitch (len) {\n+\tcase 1:\n+\t\tv = *p;\n+\t\tbreak;\n+\tcase 2:\n+\t\tv = get_unaligned_be16(p);\n+\t\tbreak;\n+\tcase 3:\n+\t\tv = get_unaligned_be24(p);\n+\t\tbreak;\n+\tcase 4:\n+\t\tv = get_unaligned_be32(p);\n+\t\tbreak;\n+\tcase 8:\n+\t\tv = get_unaligned_be64(p);\n+\t\tbreak;\n+\tdefault:\n+\t\treturn 0;\n+\t}\n+\t*plen -= len;\n+\t*pp = p + len;\n+\t*val = v;\n+\treturn len;\n+}\n+\n+u32 quic_get_data(u8 **pp, u32 *plen, u8 *data, u32 len)\n+{\n+\tif (*plen \u003c len)\n+\t\treturn 0;\n+\n+\tmemcpy(data, *pp, len);\n+\t*pp += len;\n+\t*plen -= len;\n+\n+\treturn len;\n+}\n+\n+/* Encodes a value into the QUIC variable-length integer format. */\n+u8 *quic_put_var(u8 *p, u64 num)\n+{\n+\tif (num \u003c= QUIC_VARINT_1BYTE_MAX) {\n+\t\t*p++ = (u8)num;\n+\t\treturn p;\n+\t}\n+\tif (num \u003c= QUIC_VARINT_2BYTE_MAX) {\n+\t\tput_unaligned_be16((u16)num, p);\n+\t\t*p |= QUIC_VARINT_2BYTE_PREFIX;\n+\t\treturn p + 2;\n+\t}\n+\tif (num \u003c= QUIC_VARINT_4BYTE_MAX) {\n+\t\tput_unaligned_be32((u32)num, p);\n+\t\t*p |= QUIC_VARINT_4BYTE_PREFIX;\n+\t\treturn p + 4;\n+\t}\n+\tput_unaligned_be64(num, p);\n+\t*p |= QUIC_VARINT_8BYTE_PREFIX;\n+\treturn p + 8;\n+}\n+\n+/* Writes a fixed-length integer to the buffer in network byte order. */\n+u8 *quic_put_int(u8 *p, u64 num, u8 len)\n+{\n+\tswitch (len) {\n+\tcase 1:\n+\t\t*p++ = (u8)num;\n+\t\treturn p;\n+\tcase 2:\n+\t\tput_unaligned_be16((u16)num, p);\n+\t\treturn p + 2;\n+\tcase 4:\n+\t\tput_unaligned_be32((u32)num, p);\n+\t\treturn p + 4;\n+\tcase 8:\n+\t\tput_unaligned_be64(num, p);\n+\t\treturn p + 8;\n+\tdefault:\n+\t\treturn NULL;\n+\t}\n+}\n+\n+/* Encodes a value as a variable-length integer with explicit length. */\n+u8 *quic_put_varint(u8 *p, u64 num, u8 len)\n+{\n+\tswitch (len) {\n+\tcase 1:\n+\t\t*p++ = (u8)num;\n+\t\treturn p;\n+\tcase 2:\n+\t\tput_unaligned_be16((u16)num, p);\n+\t\t*p |= QUIC_VARINT_2BYTE_PREFIX;\n+\t\treturn p + 2;\n+\tcase 4:\n+\t\tput_unaligned_be32((u32)num, p);\n+\t\t*p |= QUIC_VARINT_4BYTE_PREFIX;\n+\t\treturn p + 4;\n+\tdefault:\n+\t\treturn NULL;\n+\t}\n+}\n+\n+u8 *quic_put_data(u8 *p, u8 *data, u32 len)\n+{\n+\tif (!len)\n+\t\treturn p;\n+\n+\tmemcpy(p, data, len);\n+\treturn p + len;\n+}\n+\n+/* Writes a transport parameter as two varints: ID and value length, followed\n+ * by value.\n+ */\n+u8 *quic_put_param(u8 *p, u16 id, u64 value)\n+{\n+\tp = quic_put_var(p, id);\n+\tp = quic_put_var(p, quic_var_len(value));\n+\treturn quic_put_var(p, value);\n+}\n+\n+/* Reads a QUIC transport parameter value. */\n+u8 quic_get_param(u64 *pdest, u8 **pp, u32 *plen)\n+{\n+\tu64 valuelen;\n+\n+\tif (!quic_get_var(pp, plen, \u0026valuelen))\n+\t\treturn 0;\n+\n+\tif (*plen \u003c valuelen)\n+\t\treturn 0;\n+\n+\tif (quic_get_var(pp, plen, pdest) != valuelen)\n+\t\treturn 0;\n+\n+\treturn (u8)valuelen;\n+}\n+\n+/* rfc9000#section-a.3: DecodePacketNumber()\n+ *\n+ * Reconstructs the full packet number from a truncated one.\n+ */\n+s64 quic_get_num(s64 max_pkt_num, s64 pkt_num, u32 n)\n+{\n+\ts64 expected = max_pkt_num + 1;\n+\ts64 win = BIT_ULL(n * 8);\n+\ts64 hwin = win / 2;\n+\ts64 mask = win - 1;\n+\ts64 cand;\n+\n+\tcand = (expected \u0026 ~mask) | pkt_num;\n+\tif (cand \u003c= expected - hwin \u0026\u0026 cand \u003c BIT_ULL(QUIC_PN_BITS) - win)\n+\t\treturn cand + win;\n+\tif (cand \u003e expected + hwin \u0026\u0026 cand \u003e= win)\n+\t\treturn cand - win;\n+\treturn cand;\n+}\n+\n+int quic_data_dup(struct quic_data *to, u8 *data, u32 len, gfp_t gfp)\n+{\n+\tif (!len) {\n+\t\tquic_data_free(to);\n+\t\treturn 0;\n+\t}\n+\n+\tdata = kmemdup(data, len, gfp);\n+\tif (!data)\n+\t\treturn -ENOMEM;\n+\n+\tkfree(to-\u003edata);\n+\tto-\u003edata = data;\n+\tto-\u003elen = len;\n+\treturn 0;\n+}\n+\n+int quic_data_append(struct quic_data *to, u8 *data, u32 len, gfp_t gfp)\n+{\n+\tu8 *p;\n+\n+\tif (!len)\n+\t\treturn 0;\n+\n+\tif (to-\u003elen \u003e U32_MAX - len)\n+\t\treturn -EOVERFLOW;\n+\n+\tp = kmalloc(to-\u003elen + len, gfp);\n+\tif (!p)\n+\t\treturn -ENOMEM;\n+\tp = quic_put_data(p, to-\u003edata, to-\u003elen);\n+\tp = quic_put_data(p, data, len);\n+\n+\tkfree(to-\u003edata);\n+\tto-\u003elen = to-\u003elen + len;\n+\tto-\u003edata = p - to-\u003elen;\n+\treturn 0;\n+}\n+\n+/* Check whether 'd2' is equal to any element inside the list 'd1'.\n+ *\n+ * 'd1' is assumed to be a sequence of length-prefixed elements. Each element\n+ * is compared to 'd2' using 'quic_data_cmp()'.\n+ *\n+ * Returns true if a match is found, false otherwise.\n+ */\n+bool quic_data_has(struct quic_data *d1, struct quic_data *d2)\n+{\n+\tstruct quic_data d;\n+\tu64 length;\n+\tu32 len;\n+\tu8 *p;\n+\n+\tfor (p = d1-\u003edata, len = d1-\u003elen; len; len -= length, p += length) {\n+\t\tif (!quic_get_int(\u0026p, \u0026len, \u0026length, 1) || len \u003c length)\n+\t\t\treturn false;\n+\t\tquic_data(\u0026d, p, length);\n+\t\tif (!quic_data_cmp(\u0026d, d2))\n+\t\t\treturn true;\n+\t}\n+\treturn false;\n+}\n+\n+/* Check if any element of 'd1' is present in the list 'd2'.\n+ *\n+ * Iterates through each element in 'd1', and uses 'quic_data_has()' to check\n+ * for its presence in 'd2'.\n+ *\n+ * Returns true if any match is found, false otherwise.\n+ */\n+bool quic_data_match(struct quic_data *d1, struct quic_data *d2)\n+{\n+\tstruct quic_data d;\n+\tu64 length;\n+\tu32 len;\n+\tu8 *p;\n+\n+\tfor (p = d1-\u003edata, len = d1-\u003elen; len; len -= length, p += length) {\n+\t\tif (!quic_get_int(\u0026p, \u0026len, \u0026length, 1) || len \u003c length)\n+\t\t\treturn false;\n+\t\tquic_data(\u0026d, p, length);\n+\t\tif (quic_data_has(d2, \u0026d))\n+\t\t\treturn true;\n+\t}\n+\treturn false;\n+}\n+\n+/* Serialize a list of 'quic_data' elements into a comma-separated string.\n+ *\n+ * Each element in 'from' is length-prefixed. This function copies their raw\n+ * content into the output buffer 'to', inserting commas in between. The\n+ * resulting string length is written to '*plen'.\n+ */\n+int quic_data_to_string(u8 *to, u32 *plen, struct quic_data *from)\n+{\n+\tu32 remlen = *plen;\n+\tstruct quic_data d;\n+\tu8 *data = to, *p;\n+\tu64 length;\n+\tu32 len;\n+\n+\tp = from-\u003edata;\n+\tlen = from-\u003elen;\n+\twhile (len) {\n+\t\tif (!quic_get_int(\u0026p, \u0026len, \u0026length, 1) || len \u003c length)\n+\t\t\treturn -EINVAL;\n+\n+\t\tquic_data(\u0026d, p, length);\n+\t\tif (d.len \u003e remlen)\n+\t\t\treturn -EOVERFLOW;\n+\n+\t\tdata = quic_put_data(data, d.data, d.len);\n+\t\tremlen -= d.len;\n+\t\tp += d.len;\n+\t\tlen -= d.len;\n+\t\tif (len) {\n+\t\t\tif (!remlen)\n+\t\t\t\treturn -EOVERFLOW;\n+\t\t\tdata = quic_put_int(data, ',', 1);\n+\t\t\tremlen--;\n+\t\t}\n+\t}\n+\t*plen = data - to;\n+\treturn 0;\n+}\n+\n+/* Parse a comma-separated string into a 'quic_data' list format.\n+ *\n+ * Each comma-separated token is turned into a length-prefixed element. The\n+ * first byte of each element stores the length. Elements are stored in\n+ * 'to-\u003edata', and 'to-\u003elen' is updated.\n+ */\n+int quic_data_from_string(struct quic_data *to, u8 *from, u32 len)\n+{\n+\tu32 remlen = to-\u003elen;\n+\tstruct quic_data d;\n+\tu8 *p = to-\u003edata;\n+\n+\tto-\u003elen = 0;\n+\twhile (len) {\n+\t\twhile (len \u0026\u0026 *from == ' ') {\n+\t\t\tfrom++;\n+\t\t\tlen--;\n+\t\t}\n+\t\tif (!len)\n+\t\t\tbreak;\n+\t\tif (!remlen)\n+\t\t\treturn -EOVERFLOW;\n+\t\td.data = p++;\n+\t\td.len  = 0;\n+\t\tremlen--;\n+\t\twhile (len) {\n+\t\t\tif (*from == ',') {\n+\t\t\t\tfrom++;\n+\t\t\t\tlen--;\n+\t\t\t\tbreak;\n+\t\t\t}\n+\t\t\tif (!remlen)\n+\t\t\t\treturn -EOVERFLOW;\n+\t\t\t*p++ = *from++;\n+\t\t\tlen--;\n+\t\t\td.len++;\n+\t\t\tremlen--;\n+\t\t}\n+\t\tif (d.len \u003e U8_MAX)\n+\t\t\treturn -EINVAL;\n+\t\t*d.data = (u8)(d.len);\n+\t\tto-\u003elen += d.len + 1;\n+\t}\n+\treturn 0;\n+}\ndiff --git a/net/quic/common.h b/net/quic/common.h\nnew file mode 100644\nindex 00000000000000..6cb2b1b89cfb17\n--- /dev/null\n+++ b/net/quic/common.h\n@@ -0,0 +1,220 @@\n+/* SPDX-License-Identifier: GPL-2.0-or-later */\n+/* QUIC kernel implementation\n+ * (C) Copyright Red Hat Corp. 2023\n+ *\n+ * This file is part of the QUIC kernel implementation\n+ *\n+ * Written or modified by:\n+ *    Xin Long \u003clucien.xin@gmail.com\u003e\n+ */\n+\n+#include \u003cnet/net_namespace.h\u003e\n+\n+#define QUIC_MAX_ACK_DELAY\t(16384 * 1000)\n+#define QUIC_DEF_ACK_DELAY\t25000\n+\n+#define QUIC_STREAM_BIT_FIN\t0x01\n+#define QUIC_STREAM_BIT_LEN\t0x02\n+#define QUIC_STREAM_BIT_OFF\t0x04\n+#define QUIC_STREAM_BIT_MASK\t0x08\n+\n+#define QUIC_CONN_ID_MAX_LEN\t20\n+#define QUIC_CONN_ID_DEF_LEN\t8\n+\n+#define QUIC_PN_MAX_LEN\t\t4 /* For encoded packet number */\n+#define QUIC_PN_BITS\t\t62\n+#define QUIC_PN_MAX\t\t(BIT_ULL(QUIC_PN_BITS) - 1)\n+\n+#define QUIC_VARINT_1BYTE_MAX\t\t0x3fULL\n+#define QUIC_VARINT_2BYTE_MAX\t\t0x3fffULL\n+#define QUIC_VARINT_4BYTE_MAX\t\t0x3fffffffULL\n+#define QUIC_VARINT_8BYTE_MAX\t\t0x3fffffffffffffffULL\n+\n+struct quic_conn_id {\n+\tu8 data[QUIC_CONN_ID_MAX_LEN];\n+\tu8 len;\n+};\n+\n+static inline void quic_conn_id_update(struct quic_conn_id *conn_id, u8 *data,\n+\t\t\t\t       u32 len)\n+{\n+\t/* The caller must ensure len does not exceed QUIC_CONN_ID_MAX_LEN. */\n+\tif (WARN_ON_ONCE(len \u003e QUIC_CONN_ID_MAX_LEN))\n+\t\treturn;\n+\tmemcpy(conn_id-\u003edata, data, len);\n+\tconn_id-\u003elen = (u8)len;\n+}\n+\n+struct quic_skb_cb {\n+\t/* Callback and temporary context when encryption/decryption completes\n+\t * in async mode\n+\t */\n+\tvoid (*crypto_done)(struct sk_buff *skb, int err);\n+\tvoid *crypto_ctx;\n+\tunion {\n+\t\tstruct sk_buff *last; /* Last packet in bundle on TX */\n+\t\tu64 time; /* Arrival timestamp in UDP tunnel on RX */\n+\t};\n+\ts64 number;  /* Parsed packet number, or the largest previously seen */\n+\tu32 seqno;   /* Dest connection ID number on RX */\n+\tu16 length;  /* Payload length + packet number length */\n+\n+\tu16 number_offset; /* Offset of packet number field */\n+\tu8 number_len;     /* Length of the packet number field */\n+\tu8 level; /* Encryption level: Initial, Handshake, App, or Early */\n+\n+\tu16 errcode;     /* Error code on packet processing failure */\n+\tu8 errframe;     /* Frame type causing packet processing failure */\n+\n+\tu8 key_update:1; /* Key update triggered by this packet */\n+\tu8 key_phase:1;  /* Key phase used (0 or 1) */\n+\tu8 backlog:1;    /* Enqueued into backlog list */\n+\tu8 resume:1;     /* Crypto already processed (encrypted or decrypted) */\n+\tu8 path:1;       /* Packet arrived from a new or migrating path */\n+\tu8 sync:1;       /* Force synchronous crypto (process context only) */\n+\tu8 ecn:2;        /* ECN marking used on TX */\n+};\n+\n+#define QUIC_SKB_CB(skb)\t((struct quic_skb_cb *)\u0026((skb)-\u003ecb[0]))\n+\n+struct quichdr {\n+#if defined(__LITTLE_ENDIAN_BITFIELD)\n+\t__u8 pnl:2,\n+\t     key:1,\n+\t     reserved:2,\n+\t     spin:1,\n+\t     fixed:1,\n+\t     form:1;\n+#elif defined(__BIG_ENDIAN_BITFIELD)\n+\t__u8 form:1,\n+\t     fixed:1,\n+\t     spin:1,\n+\t     reserved:2,\n+\t     key:1,\n+\t     pnl:2;\n+#endif\n+};\n+\n+static inline struct quichdr *quic_hdr(struct sk_buff *skb)\n+{\n+\treturn (struct quichdr *)skb-\u003edata;\n+}\n+\n+struct quichshdr {\n+#if defined(__LITTLE_ENDIAN_BITFIELD)\n+\t__u8 pnl:2,\n+\t     reserved:2,\n+\t     type:2,\n+\t     fixed:1,\n+\t     form:1;\n+#elif defined(__BIG_ENDIAN_BITFIELD)\n+\t__u8 form:1,\n+\t     fixed:1,\n+\t     type:2,\n+\t     reserved:2,\n+\t     pnl:2;\n+#endif\n+};\n+\n+static inline struct quichshdr *quic_hshdr(struct sk_buff *skb)\n+{\n+\treturn (struct quichshdr *)skb-\u003edata;\n+}\n+\n+union quic_addr {\n+\tstruct sockaddr_in6 v6;\n+\tstruct sockaddr_in v4;\n+\tstruct sockaddr sa;\n+};\n+\n+static inline union quic_addr *quic_addr(const void *addr)\n+{\n+\treturn (union quic_addr *)addr;\n+}\n+\n+struct quic_shash_head {\n+\tstruct hlist_nulls_head\thead;\n+\tspinlock_t\t\tlock; /* Protects 'head' in atomic context */\n+};\n+\n+struct quic_shash_table {\n+\tstruct quic_shash_head *hash;\n+\tu32 size;\n+};\n+\n+struct quic_uhash_head {\n+\tstruct hlist_head\thead;\n+\tstruct mutex\t\tlock; /* Protects 'head' in process context */\n+};\n+\n+struct quic_uhash_table {\n+\tstruct quic_uhash_head *hash;\n+\tu32 size;\n+};\n+\n+struct quic_data {\n+\tu8 *data;\n+\tu32 len;\n+};\n+\n+static inline struct quic_data *quic_data(struct quic_data *d, u8 *data,\n+\t\t\t\t\t  u32 len)\n+{\n+\td-\u003edata = data;\n+\td-\u003elen  = len;\n+\treturn d;\n+}\n+\n+static inline int quic_data_cmp(struct quic_data *d1, struct quic_data *d2)\n+{\n+\treturn d1-\u003elen != d2-\u003elen || memcmp(d1-\u003edata, d2-\u003edata, d1-\u003elen);\n+}\n+\n+static inline void quic_data_free(struct quic_data *d)\n+{\n+\tkfree(d-\u003edata);\n+\td-\u003edata = NULL;\n+\td-\u003elen = 0;\n+}\n+\n+static inline u64 quic_ktime_get_us(void)\n+{\n+\treturn ktime_to_us(ktime_get());\n+}\n+\n+u32 quic_sock_hash(struct net *net, union quic_addr *s, union quic_addr *d);\n+struct quic_shash_head *quic_sock_head(u32 hash);\n+u32 quic_sock_hash_size(void);\n+\n+u32 quic_listen_sock_hash(struct net *net, u16 port);\n+struct quic_shash_head *quic_listen_sock_head(u32 hash);\n+u32 quic_listen_sock_hash_size(void);\n+\n+struct quic_shash_head *quic_source_conn_id_head(struct net *net, u8 *scid,\n+\t\t\t\t\t\t u32 len);\n+struct quic_uhash_head *quic_udp_sock_head(struct net *net, u16 port);\n+u32 quic_addr_hash(struct net *net, union quic_addr *a);\n+\n+void quic_hash_tables_destroy(void);\n+int quic_hash_tables_init(void);\n+\n+u32 quic_get_data(u8 **pp, u32 *plen, u8 *data, u32 len);\n+u32 quic_get_int(u8 **pp, u32 *plen, u64 *val, u32 len);\n+s64 quic_get_num(s64 max_pkt_num, s64 pkt_num, u32 n);\n+u8 quic_get_param(u64 *pdest, u8 **pp, u32 *plen);\n+u8 quic_get_var(u8 **pp, u32 *plen, u64 *val);\n+u8 quic_var_len(u64 n);\n+\n+u8 *quic_put_param(u8 *p, u16 id, u64 value);\n+u8 *quic_put_data(u8 *p, u8 *data, u32 len);\n+u8 *quic_put_varint(u8 *p, u64 num, u8 len);\n+u8 *quic_put_int(u8 *p, u64 num, u8 len);\n+u8 *quic_put_var(u8 *p, u64 num);\n+\n+int quic_data_from_string(struct quic_data *to, u8 *from, u32 len);\n+int quic_data_to_string(u8 *to, u32 *plen, struct quic_data *from);\n+\n+int quic_data_append(struct quic_data *to, u8 *data, u32 len, gfp_t gfp);\n+int quic_data_dup(struct quic_data *to, u8 *data, u32 len, gfp_t gfp);\n+bool quic_data_match(struct quic_data *d1, struct quic_data *d2);\n+bool quic_data_has(struct quic_data *d1, struct quic_data *d2);\ndiff --git a/net/quic/cong.c b/net/quic/cong.c\nnew file mode 100644\nindex 00000000000000..dc04bde55c6022\n--- /dev/null\n+++ b/net/quic/cong.c\n@@ -0,0 +1,338 @@\n+// SPDX-License-Identifier: GPL-2.0-or-later\n+/* QUIC kernel implementation\n+ * (C) Copyright Red Hat Corp. 2023\n+ *\n+ * This file is part of the QUIC kernel implementation\n+ *\n+ * Initialization/cleanup for QUIC protocol support.\n+ *\n+ * Written or modified by:\n+ *    Xin Long \u003clucien.xin@gmail.com\u003e\n+ */\n+\n+#include \u003clinux/quic.h\u003e\n+\n+#include \"common.h\"\n+#include \"cong.h\"\n+\n+/* NEW RENO APIs */\n+static void quic_reno_handle_packet_lost(struct quic_cong *cong)\n+{\n+\tswitch (cong-\u003estate) {\n+\tcase QUIC_CONG_SLOW_START:\n+\t\tpr_debug(\"%s: slow_start -\u003e recovery, cwnd: %u, ssth: %u\\n\",\n+\t\t\t __func__, cong-\u003ewindow, cong-\u003essthresh);\n+\t\tbreak;\n+\tcase QUIC_CONG_RECOVERY_PERIOD:\n+\t\treturn;\n+\tcase QUIC_CONG_CONGESTION_AVOIDANCE:\n+\t\tpr_debug(\"%s: cong_avoid -\u003e recovery, cwnd: %u, ssth: %u\\n\",\n+\t\t\t __func__, cong-\u003ewindow, cong-\u003essthresh);\n+\t\tbreak;\n+\tdefault:\n+\t\tpr_debug(\"%s: wrong congestion state: %d\\n\", __func__,\n+\t\t\t cong-\u003estate);\n+\t\treturn;\n+\t}\n+\n+\tcong-\u003erecovery_time = cong-\u003etime;\n+\tcong-\u003estate = QUIC_CONG_RECOVERY_PERIOD;\n+\tcong-\u003essthresh = max(cong-\u003ewindow \u003e\u003e 1U, cong-\u003emin_window);\n+\tcong-\u003ewindow = cong-\u003essthresh;\n+}\n+\n+static void quic_reno_on_packet_lost(struct quic_cong *cong, u64 time,\n+\t\t\t\t     u32 bytes, s64 number)\n+{\n+\tquic_reno_handle_packet_lost(cong);\n+}\n+\n+static void quic_reno_on_packet_acked(struct quic_cong *cong, u64 time,\n+\t\t\t\t      u32 bytes, s64 number)\n+{\n+\tu64 new_window;\n+\n+\tswitch (cong-\u003estate) {\n+\tcase QUIC_CONG_SLOW_START:\n+\t\tnew_window = (u64)cong-\u003ewindow + bytes;\n+\t\tcong-\u003ewindow = min_t(u64, new_window, cong-\u003emax_window);\n+\t\tif (cong-\u003ewindow \u003c cong-\u003essthresh)\n+\t\t\tbreak;\n+\t\tcong-\u003estate = QUIC_CONG_CONGESTION_AVOIDANCE;\n+\t\tpr_debug(\"%s: slow_start -\u003e cong_avoid, cwnd: %u, ssth: %u\\n\",\n+\t\t\t __func__, cong-\u003ewindow, cong-\u003essthresh);\n+\t\tbreak;\n+\tcase QUIC_CONG_RECOVERY_PERIOD:\n+\t\tif (cong-\u003erecovery_time \u003e= time)\n+\t\t\tbreak;\n+\t\tcong-\u003estate = QUIC_CONG_CONGESTION_AVOIDANCE;\n+\t\tpr_debug(\"%s: recovery -\u003e cong_avoid, cwnd: %u, ssth: %u\\n\",\n+\t\t\t __func__, cong-\u003ewindow, cong-\u003essthresh);\n+\t\tbreak;\n+\tcase QUIC_CONG_CONGESTION_AVOIDANCE:\n+\t\t/* cong-\u003ewindow is never zero; it is initialized by\n+\t\t * quic_packet_route() during connect/accept.\n+\t\t */\n+\t\tnew_window = div64_ul((u64)cong-\u003emss * bytes, cong-\u003ewindow) +\n+\t\t\t     cong-\u003ewindow;\n+\t\tcong-\u003ewindow = min_t(u64, new_window, cong-\u003emax_window);\n+\t\tbreak;\n+\tdefault:\n+\t\tpr_debug(\"%s: wrong congestion state: %d\\n\", __func__,\n+\t\t\t cong-\u003estate);\n+\t\treturn;\n+\t}\n+}\n+\n+static void quic_reno_on_process_ecn(struct quic_cong *cong)\n+{\n+\tquic_reno_handle_packet_lost(cong);\n+}\n+\n+static void quic_reno_on_init(struct quic_cong *cong)\n+{\n+}\n+\n+static const struct quic_cong_ops quic_congs[] = {\n+\t{ /* QUIC_CONG_ALG_RENO */\n+\t\t.on_packet_acked = quic_reno_on_packet_acked,\n+\t\t.on_packet_lost = quic_reno_on_packet_lost,\n+\t\t.on_process_ecn = quic_reno_on_process_ecn,\n+\t\t.on_init = quic_reno_on_init,\n+\t},\n+};\n+\n+static bool quic_cong_check_persistent_congestion(struct quic_cong *cong,\n+\t\t\t\t\t\t  u64 time)\n+{\n+\tu32 ssthresh;\n+\n+\ttime -= cong-\u003epc_start_time;\n+\n+\t/* rfc9002#section-7.6.1:\n+\t *   (smoothed_rtt + max(4*rttvar, kGranularity) + max_ack_delay) *\n+\t *      kPersistentCongestionThreshold\n+\t */\n+\tssthresh = cong-\u003esmoothed_rtt +\n+\t\t   max(4 * cong-\u003erttvar, QUIC_KGRANULARITY);\n+\tssthresh = (ssthresh + cong-\u003emax_ack_delay) *\n+\t\t   QUIC_KPERSISTENT_CONGESTION_THRESHOLD;\n+\n+\treturn time \u003e ssthresh;\n+}\n+\n+/* COMMON APIs */\n+void quic_cong_on_packet_lost(struct quic_cong *cong, u64 time, u32 bytes,\n+\t\t\t      s64 number)\n+{\n+\tif (cong-\u003epc_start_time \u0026\u0026 time \u003e cong-\u003epc_start_time \u0026\u0026\n+\t    quic_cong_check_persistent_congestion(cong, time)) {\n+\t\tcong-\u003epc_start_time = 0;\n+\t\tcong-\u003emin_rtt_valid = 0;\n+\t\tcong-\u003ewindow = cong-\u003emin_window;\n+\t\tcong-\u003estate = QUIC_CONG_SLOW_START;\n+\t\treturn;\n+\t}\n+\n+\tif (!cong-\u003epc_start_time \u0026\u0026 cong-\u003eis_rtt_set)\n+\t\tcong-\u003epc_start_time = time;\n+\n+\tcong-\u003eops-\u003eon_packet_lost(cong, time, bytes, number);\n+}\n+\n+void quic_cong_on_packet_acked(struct quic_cong *cong, u64 time, u32 bytes,\n+\t\t\t       s64 number)\n+{\n+\t/* When a packet is acked, if time - cong-\u003epc_start_time \u003c= duration\n+\t * threshold, it means the acked packet was sent within the persistent\n+\t * congestion window.\n+\t *\n+\t * This breaks the condition in rfc9002#section-7.6.2:\n+\t *\n+\t * - across all packet number spaces, none of the packets sent between\n+\t *   the send times of these two packets are acknowledged;\n+\t *\n+\t * so pc_start_time is reset to 0.\n+\t */\n+\tif (cong-\u003epc_start_time \u0026\u0026 time \u003e cong-\u003epc_start_time \u0026\u0026\n+\t    !quic_cong_check_persistent_congestion(cong, time))\n+\t\tcong-\u003epc_start_time = 0;\n+\n+\tcong-\u003eops-\u003eon_packet_acked(cong, time, bytes, number);\n+}\n+\n+void quic_cong_on_process_ecn(struct quic_cong *cong)\n+{\n+\tcong-\u003eops-\u003eon_process_ecn(cong);\n+}\n+\n+/* Update Probe Timeout (PTO) and loss detection delay based on RTT stats. */\n+static void quic_cong_pto_update(struct quic_cong *cong)\n+{\n+\tu32 pto, loss_delay;\n+\n+\t/* rfc9002#section-6.2.1:\n+\t *   PTO = smoothed_rtt + max(4*rttvar, kGranularity) + max_ack_delay\n+\t */\n+\tpto = cong-\u003esmoothed_rtt + max(4 * cong-\u003erttvar, QUIC_KGRANULARITY);\n+\tcong-\u003epto = pto + cong-\u003emax_ack_delay;\n+\n+\t/* rfc9002#section-6.1.2:\n+\t *   max(kTimeThreshold * max(smoothed_rtt, latest_rtt), kGranularity)\n+\t */\n+\tloss_delay = QUIC_KTIME_THRESHOLD(max(cong-\u003esmoothed_rtt,\n+\t\t\t\t\t      cong-\u003elatest_rtt));\n+\tcong-\u003eloss_delay = max(loss_delay, QUIC_KGRANULARITY);\n+\n+\tpr_debug(\"%s: update pto: %u\\n\", __func__, pto);\n+}\n+\n+/* Update pacing timestamp after sending 'bytes' bytes.\n+ *\n+ * This function tracks when the next packet is allowed to be sent based on\n+ * pacing rate.\n+ */\n+static void quic_cong_update_pacing_time(struct quic_cong *cong, u32 bytes)\n+{\n+\tu64 prior_time, credit, len_ns, rate = cong-\u003epacing_rate;\n+\n+\tif (!rate)\n+\t\treturn;\n+\n+\tprior_time = cong-\u003epacing_time;\n+\tcong-\u003epacing_time = max(cong-\u003epacing_time, ktime_get_ns());\n+\tcredit = cong-\u003epacing_time - prior_time;\n+\n+\t/* take into account OS jitter */\n+\tlen_ns = div64_ul((u64)bytes * NSEC_PER_SEC, rate);\n+\tlen_ns -= min_t(u64, len_ns / 2, credit);\n+\tcong-\u003epacing_time += len_ns;\n+}\n+\n+/* Compute and update the pacing rate based on congestion window and smoothed\n+ * RTT.\n+ */\n+static void quic_cong_pace_update(struct quic_cong *cong, u32 bytes,\n+\t\t\t\t  u64 max_rate)\n+{\n+\tu64 rate;\n+\n+\tif (unlikely(!cong-\u003esmoothed_rtt))\n+\t\treturn;\n+\n+\t/* rate = N * congestion_window / smoothed_rtt */\n+\trate = div64_ul((u64)cong-\u003ewindow * USEC_PER_SEC * 2,\n+\t\t\tcong-\u003esmoothed_rtt);\n+\n+\tcong-\u003epacing_rate = min_t(u64, rate, max_rate);\n+\tpr_debug(\"%s: update pacing rate: %llu, max rate: %llu, srtt: %u\\n\",\n+\t\t __func__, cong-\u003epacing_rate, max_rate, cong-\u003esmoothed_rtt);\n+}\n+\n+void quic_cong_on_packet_sent(struct quic_cong *cong, u64 time, u32 bytes,\n+\t\t\t      s64 number)\n+{\n+\tif (!bytes)\n+\t\treturn;\n+\tif (cong-\u003eops-\u003eon_packet_sent)\n+\t\tcong-\u003eops-\u003eon_packet_sent(cong, time, bytes, number);\n+\tquic_cong_update_pacing_time(cong, bytes);\n+}\n+\n+void quic_cong_on_ack_recv(struct quic_cong *cong, u32 bytes, u64 max_rate)\n+{\n+\tif (!bytes)\n+\t\treturn;\n+\tif (cong-\u003eops-\u003eon_ack_recv)\n+\t\tcong-\u003eops-\u003eon_ack_recv(cong, bytes, max_rate);\n+\tquic_cong_pace_update(cong, bytes, max_rate);\n+}\n+\n+/* rfc9002#section-5: Estimating the Round-Trip Time */\n+void quic_cong_rtt_update(struct quic_cong *cong, u64 time, u32 ack_delay)\n+{\n+\tu32 adjusted_rtt, rttvar_sample;\n+\n+\t/* Ignore RTT sample if ACK delay is suspiciously large. */\n+\tif (ack_delay \u003e cong-\u003emax_ack_delay * 2 ||\n+\t    cong-\u003etime - time \u003e QUIC_RTT_MAX)\n+\t\treturn;\n+\n+\t/* rfc9002#section-5.1:\n+\t *   latest_rtt = ack_time - send_time_of_largest_acked\n+\t */\n+\tcong-\u003elatest_rtt = cong-\u003etime - time;\n+\n+\t/* rfc9002#section-5.2: Estimating min_rtt */\n+\tif (!cong-\u003emin_rtt_valid) {\n+\t\tcong-\u003emin_rtt = cong-\u003elatest_rtt;\n+\t\tcong-\u003emin_rtt_valid = 1;\n+\t}\n+\tif (cong-\u003emin_rtt \u003e cong-\u003elatest_rtt)\n+\t\tcong-\u003emin_rtt = cong-\u003elatest_rtt;\n+\n+\tif (!cong-\u003eis_rtt_set) {\n+\t\t/* rfc9002#section-5.3:\n+\t\t *   smoothed_rtt = latest_rtt\n+\t\t *   rttvar = latest_rtt / 2\n+\t\t */\n+\t\tcong-\u003esmoothed_rtt = cong-\u003elatest_rtt;\n+\t\tcong-\u003erttvar = cong-\u003esmoothed_rtt / 2;\n+\t\tquic_cong_pto_update(cong);\n+\t\tcong-\u003eis_rtt_set = 1;\n+\t\treturn;\n+\t}\n+\n+\t/* rfc9002#section-5.3:\n+\t *   adjusted_rtt = latest_rtt\n+\t *   if (latest_rtt \u003e= min_rtt + ack_delay):\n+\t *     adjusted_rtt = latest_rtt - ack_delay\n+\t *   smoothed_rtt = 7/8 * smoothed_rtt + 1/8 * adjusted_rtt\n+\t *   rttvar_sample = abs(smoothed_rtt - adjusted_rtt)\n+\t *   rttvar = 3/4 * rttvar + 1/4 * rttvar_sample\n+\t */\n+\tadjusted_rtt = cong-\u003elatest_rtt;\n+\tif (cong-\u003elatest_rtt \u003e= cong-\u003emin_rtt + ack_delay)\n+\t\tadjusted_rtt = cong-\u003elatest_rtt - ack_delay;\n+\n+\tcong-\u003esmoothed_rtt = (cong-\u003esmoothed_rtt * 7 + adjusted_rtt) / 8;\n+\trttvar_sample = abs_diff(cong-\u003esmoothed_rtt, adjusted_rtt);\n+\tcong-\u003erttvar = (cong-\u003erttvar * 3 + rttvar_sample) / 4;\n+\tquic_cong_pto_update(cong);\n+\n+\tif (cong-\u003eops-\u003eon_rtt_update)\n+\t\tcong-\u003eops-\u003eon_rtt_update(cong);\n+}\n+\n+void quic_cong_set_algo(struct quic_cong *cong, u8 algo)\n+{\n+\t/* The caller must ensure algo \u003c QUIC_CONG_ALG_MAX. */\n+\tif (WARN_ON_ONCE(algo \u003e= QUIC_CONG_ALG_MAX))\n+\t\treturn;\n+\tcong-\u003ealgo = algo;\n+\tcong-\u003estate = QUIC_CONG_SLOW_START;\n+\tcong-\u003essthresh = U32_MAX;\n+\tcong-\u003eops = \u0026quic_congs[algo];\n+\tcong-\u003eops-\u003eon_init(cong);\n+}\n+\n+void quic_cong_set_srtt(struct quic_cong *cong, u32 srtt)\n+{\n+\t/* rfc9002#section-5.3:\n+\t *   smoothed_rtt = kInitialRtt\n+\t *   rttvar = kInitialRtt / 2\n+\t */\n+\tcong-\u003einitial_srtt = srtt;\n+\tcong-\u003elatest_rtt = srtt;\n+\tcong-\u003esmoothed_rtt = cong-\u003elatest_rtt;\n+\tcong-\u003erttvar = cong-\u003esmoothed_rtt / 2;\n+\tquic_cong_pto_update(cong);\n+}\n+\n+void quic_cong_init(struct quic_cong *cong)\n+{\n+\tcong-\u003emax_ack_delay = QUIC_DEF_ACK_DELAY;\n+\tcong-\u003emax_window = S32_MAX / 4;\n+\tquic_cong_set_algo(cong, QUIC_CONG_ALG_RENO);\n+\tquic_cong_set_srtt(cong, QUIC_RTT_INIT);\n+}\ndiff --git a/net/quic/cong.h b/net/quic/cong.h\nnew file mode 100644\nindex 00000000000000..a9cd5e93ec000f\n--- /dev/null\n+++ b/net/quic/cong.h\n@@ -0,0 +1,130 @@\n+/* SPDX-License-Identifier: GPL-2.0-or-later */\n+/* QUIC kernel implementation\n+ * (C) Copyright Red Hat Corp. 2023\n+ *\n+ * This file is part of the QUIC kernel implementation\n+ *\n+ * Written or modified by:\n+ *    Xin Long \u003clucien.xin@gmail.com\u003e\n+ */\n+\n+#define QUIC_KPERSISTENT_CONGESTION_THRESHOLD\t3\n+#define QUIC_KPACKET_THRESHOLD\t\t\t3\n+#define QUIC_KTIME_THRESHOLD(rtt)\t\t((rtt) * 9 / 8)\n+#define QUIC_KGRANULARITY\t\t\t1000U\n+\n+#define QUIC_RTT_INIT\t\t333000U\n+#define QUIC_RTT_MAX\t\t6000000U\n+#define QUIC_RTT_MIN\t\tQUIC_KGRANULARITY\n+\n+/* rfc9002#section-7.3: Congestion Control States\n+ *\n+ *                  New path or      +------------+\n+ *             persistent congestion |   Slow     |\n+ *         (O)----------------------\u003e|   Start    |\n+ *                                   +------------+\n+ *                                         |\n+ *                                 Loss or |\n+ *                         ECN-CE increase |\n+ *                                         v\n+ *  +------------+     Loss or       +------------+\n+ *  | Congestion |  ECN-CE increase  |  Recovery  |\n+ *  | Avoidance  |------------------\u003e|   Period   |\n+ *  +------------+                   +------------+\n+ *            ^                            |\n+ *            |                            |\n+ *            +----------------------------+\n+ *               Acknowledgment of packet\n+ *                 sent during recovery\n+ */\n+enum quic_cong_state {\n+\tQUIC_CONG_SLOW_START,\n+\tQUIC_CONG_RECOVERY_PERIOD,\n+\tQUIC_CONG_CONGESTION_AVOIDANCE,\n+};\n+\n+struct quic_cong {\n+\t/* RTT tracking */\n+\tu32 max_ack_delay;\t/* max_ack_delay from rfc9000#section-18.2 */\n+\tu32 smoothed_rtt;\t/* Smoothed RTT */\n+\tu32 latest_rtt;\t\t/* Latest RTT sample */\n+\tu32 min_rtt;\t\t/* Lowest observed RTT */\n+\tu32 rttvar;\t\t/* RTT variation */\n+\tu32 pto;\t\t/* Probe timeout */\n+\n+\t/* Timing \u0026 pacing */\n+\tu64 pc_start_time;      /* Persistent congestion tracking timestamp */\n+\tu64 recovery_time;\t/* Recovery period start timestamp */\n+\tu64 pacing_rate;\t/* Packet sending speed Bytes/sec */\n+\tu64 pacing_time;\t/* Next scheduled send timestamp (ns) */\n+\tu64 time;\t\t/* Cached current timestamp */\n+\n+\t/* Congestion window */\n+\tu32 max_window;\t\t/* Max growth cap */\n+\tu32 min_window;\t\t/* Min window limit */\n+\tu32 loss_delay;\t\t/* Time before marking loss */\n+\tu32 ssthresh;\t\t/* Slow start threshold */\n+\tu32 window;\t\t/* Bytes in flight allowed */\n+\tu32 mss;\t\t/* QUIC MSS (excl. UDP) */\n+\n+\t/* Algorithm-specific */\n+\tconst struct quic_cong_ops *ops;\n+\tu64 priv[8];\t\t/* Algo private data */\n+\n+\tu32 initial_srtt;\t/* Initial smoothed RTT */\n+\tu8 algo;\t\t/* Congestion control algorithm */\n+\n+\t/* Flags \u0026 state */\n+\tu8 min_rtt_valid;\t/* min_rtt initialized */\n+\tu8 is_rtt_set;\t\t/* RTT samples exist */\n+\tu8 state;\t\t/* State machine in rfc9002#section-7.3 */\n+};\n+\n+/* Hooks for congestion control algorithms */\n+struct quic_cong_ops {\n+\tvoid (*on_packet_acked)(struct quic_cong *cong, u64 time, u32 bytes,\n+\t\t\t\ts64 number);\n+\tvoid (*on_packet_lost)(struct quic_cong *cong, u64 time, u32 bytes,\n+\t\t\t       s64 number);\n+\tvoid (*on_process_ecn)(struct quic_cong *cong);\n+\tvoid (*on_init)(struct quic_cong *cong);\n+\n+\t/* Optional callbacks */\n+\tvoid (*on_packet_sent)(struct quic_cong *cong, u64 time, u32 bytes,\n+\t\t\t       s64 number);\n+\tvoid (*on_ack_recv)(struct quic_cong *cong, u32 bytes, u64 max_rate);\n+\tvoid (*on_rtt_update)(struct quic_cong *cong);\n+};\n+\n+static inline void quic_cong_set_mss(struct quic_cong *cong, u32 mss)\n+{\n+\tif (cong-\u003emss == mss)\n+\t\treturn;\n+\n+\t/* rfc9002#section-7.2: Initial and Minimum Congestion Window */\n+\tcong-\u003emss = mss;\n+\tcong-\u003emin_window = max(min(mss * 10, 14720U), mss * 2);\n+\n+\tif (cong-\u003ewindow \u003c cong-\u003emin_window)\n+\t\tcong-\u003ewindow = cong-\u003emin_window;\n+}\n+\n+static inline void *quic_cong_priv(struct quic_cong *cong)\n+{\n+\treturn (void *)cong-\u003epriv;\n+}\n+\n+void quic_cong_on_packet_acked(struct quic_cong *cong, u64 time, u32 bytes,\n+\t\t\t       s64 number);\n+void quic_cong_on_packet_lost(struct quic_cong *cong, u64 time, u32 bytes,\n+\t\t\t      s64 number);\n+void quic_cong_on_process_ecn(struct quic_cong *cong);\n+\n+void quic_cong_on_packet_sent(struct quic_cong *cong, u64 time, u32 bytes,\n+\t\t\t      s64 number);\n+void quic_cong_on_ack_recv(struct quic_cong *cong, u32 bytes, u64 max_rate);\n+void quic_cong_rtt_update(struct quic_cong *cong, u64 time, u32 ack_delay);\n+\n+void quic_cong_set_srtt(struct quic_cong *cong, u32 srtt);\n+void quic_cong_set_algo(struct quic_cong *cong, u8 algo);\n+void quic_cong_init(struct quic_cong *cong);\ndiff --git a/net/quic/connid.c b/net/quic/connid.c\nnew file mode 100644\nindex 00000000000000..a6d42679ee97a6\n--- /dev/null\n+++ b/net/quic/connid.c\n@@ -0,0 +1,271 @@\n+// SPDX-License-Identifier: GPL-2.0-or-later\n+/* QUIC kernel implementation\n+ * (C) Copyright Red Hat Corp. 2023\n+ *\n+ * This file is part of the QUIC kernel implementation\n+ *\n+ * Initialization/cleanup for QUIC protocol support.\n+ *\n+ * Written or modified by:\n+ *    Xin Long \u003clucien.xin@gmail.com\u003e\n+ */\n+\n+#include \u003ccrypto/utils.h\u003e\n+#include \u003clinux/quic.h\u003e\n+#include \u003cnet/sock.h\u003e\n+\n+#include \"common.h\"\n+#include \"connid.h\"\n+\n+/* Lookup a source connection ID (scid) in the global source connection ID hash\n+ * table.\n+ */\n+struct quic_conn_id *quic_conn_id_lookup(struct net *net, u8 *scid, u32 len)\n+{\n+\tstruct quic_shash_head *head = quic_source_conn_id_head(net, scid, len);\n+\tstruct quic_source_conn_id *s_conn_id;\n+\tstruct quic_conn_id *conn_id = NULL;\n+\tstruct hlist_nulls_node *node;\n+\n+\tWARN_ON_ONCE(!rcu_read_lock_held());\n+\n+\thlist_nulls_for_each_entry_rcu(s_conn_id, node, \u0026head-\u003ehead, node) {\n+\t\tif (net != sock_net(s_conn_id-\u003esk))\n+\t\t\tcontinue;\n+\t\tif (s_conn_id-\u003ecommon.id.len != len ||\n+\t\t    memcmp(scid, \u0026s_conn_id-\u003ecommon.id.data, len))\n+\t\t\tcontinue;\n+\t\tif (likely(refcount_inc_not_zero(\u0026s_conn_id-\u003esk-\u003esk_refcnt)))\n+\t\t\tconn_id = \u0026s_conn_id-\u003ecommon.id;\n+\t\tbreak;\n+\t}\n+\t/* No need to check get_nulls_value(node) != hash for !conn_id, as\n+\t * hashtable size is fixed and a conn_id can not rehashed.\n+\t */\n+\treturn conn_id;\n+}\n+\n+/* Check if a given stateless reset token exists in any connection ID in the\n+ * connection ID set.\n+ */\n+bool quic_conn_id_token_exists(struct quic_conn_id_set *id_set, u8 *token)\n+{\n+\tstruct quic_common_conn_id *common;\n+\tstruct quic_dest_conn_id *dcid;\n+\n+\tdcid = (struct quic_dest_conn_id *)id_set-\u003eactive;\n+\tif (memchr_inv(dcid-\u003etoken, 0, QUIC_CONN_ID_TOKEN_LEN) \u0026\u0026\n+\t    !crypto_memneq(dcid-\u003etoken, token, QUIC_CONN_ID_TOKEN_LEN))\n+\t\treturn true; /* Fast path. */\n+\n+\tlist_for_each_entry(common, \u0026id_set-\u003ehead, list) {\n+\t\tdcid = (struct quic_dest_conn_id *)common;\n+\t\tif (common == id_set-\u003eactive)\n+\t\t\tcontinue;\n+\t\tif (memchr_inv(dcid-\u003etoken, 0, QUIC_CONN_ID_TOKEN_LEN) \u0026\u0026\n+\t\t    !crypto_memneq(dcid-\u003etoken, token, QUIC_CONN_ID_TOKEN_LEN))\n+\t\t\treturn true;\n+\t}\n+\treturn false;\n+}\n+\n+static void quic_source_conn_id_free_rcu(struct rcu_head *head)\n+{\n+\tstruct quic_source_conn_id *s_conn_id;\n+\n+\ts_conn_id = container_of(head, struct quic_source_conn_id, rcu);\n+\tkfree(s_conn_id);\n+}\n+\n+static void quic_source_conn_id_free(struct quic_source_conn_id *s_conn_id)\n+{\n+\tu8 *data = s_conn_id-\u003ecommon.id.data;\n+\tu32 len = s_conn_id-\u003ecommon.id.len;\n+\tstruct quic_shash_head *head;\n+\n+\tif (!hlist_nulls_unhashed(\u0026s_conn_id-\u003enode)) {\n+\t\thead = quic_source_conn_id_head(sock_net(s_conn_id-\u003esk), data,\n+\t\t\t\t\t\tlen);\n+\t\tspin_lock_bh(\u0026head-\u003elock);\n+\t\thlist_nulls_del_init_rcu(\u0026s_conn_id-\u003enode);\n+\t\tspin_unlock_bh(\u0026head-\u003elock);\n+\t}\n+\n+\t/* Freeing is deferred via RCU to avoid use-after-free during\n+\t * concurrent lookups.\n+\t */\n+\tcall_rcu(\u0026s_conn_id-\u003ercu, quic_source_conn_id_free_rcu);\n+}\n+\n+static void quic_conn_id_del(struct quic_common_conn_id *common)\n+{\n+\tlist_del(\u0026common-\u003elist);\n+\tif (!common-\u003ehashed) {\n+\t\tkfree(common);\n+\t\treturn;\n+\t}\n+\tquic_source_conn_id_free((struct quic_source_conn_id *)common);\n+}\n+\n+/* Add a connection ID with sequence number and associated private data to the\n+ * connection ID set.\n+ */\n+int quic_conn_id_add(struct quic_conn_id_set *id_set,\n+\t\t     struct quic_conn_id *conn_id, u32 number, void *data,\n+\t\t     gfp_t gfp)\n+{\n+\tbool dest = id_set-\u003eentry_size == sizeof(struct quic_dest_conn_id);\n+\tstruct quic_source_conn_id *s_conn_id;\n+\tstruct quic_dest_conn_id *d_conn_id;\n+\tstruct quic_common_conn_id *common;\n+\tstruct quic_shash_head *head;\n+\tstruct list_head *list;\n+\n+\t/* Locate insertion point to keep list ordered by number. */\n+\tlist = \u0026id_set-\u003ehead;\n+\tlist_for_each_entry(common, list, list) {\n+\t\tif (number == common-\u003enumber) {\n+\t\t\tif (quic_conn_id_cmp(\u0026common-\u003eid, conn_id))\n+\t\t\t\treturn -EINVAL;\n+\t\t\tif (dest \u0026\u0026 data) {\n+\t\t\t\td_conn_id = (struct quic_dest_conn_id *)common;\n+\t\t\t\tif (crypto_memneq(d_conn_id-\u003etoken, data,\n+\t\t\t\t\t\t  QUIC_CONN_ID_TOKEN_LEN))\n+\t\t\t\t\treturn -EINVAL;\n+\t\t\t}\n+\t\t\treturn 0; /* Ignore if it already exists on the list. */\n+\t\t}\n+\t\tif (number \u003c common-\u003enumber) {\n+\t\t\tlist = \u0026common-\u003elist;\n+\t\t\tbreak;\n+\t\t}\n+\t}\n+\n+\tif (conn_id-\u003elen \u003e QUIC_CONN_ID_MAX_LEN)\n+\t\treturn -EINVAL;\n+\tcommon = kzalloc(id_set-\u003eentry_size, gfp);\n+\tif (!common)\n+\t\treturn -ENOMEM;\n+\tcommon-\u003eid = *conn_id;\n+\tcommon-\u003enumber = number;\n+\tif (dest) {\n+\t\t/* For destination connection IDs, copy the stateless reset\n+\t\t * token if available.\n+\t\t */\n+\t\tif (data) {\n+\t\t\td_conn_id = (struct quic_dest_conn_id *)common;\n+\t\t\tmemcpy(d_conn_id-\u003etoken, data, QUIC_CONN_ID_TOKEN_LEN);\n+\t\t}\n+\t} else {\n+\t\t/* For source connection IDs, mark as hashed and insert into\n+\t\t * the global source connection ID hashtable.\n+\t\t */\n+\t\tcommon-\u003ehashed = 1;\n+\t\ts_conn_id = (struct quic_source_conn_id *)common;\n+\t\ts_conn_id-\u003esk = data;\n+\n+\t\thead = quic_source_conn_id_head(sock_net(s_conn_id-\u003esk),\n+\t\t\t\t\t\tcommon-\u003eid.data,\n+\t\t\t\t\t\tcommon-\u003eid.len);\n+\t\tspin_lock_bh(\u0026head-\u003elock);\n+\t\thlist_nulls_add_head_rcu(\u0026s_conn_id-\u003enode, \u0026head-\u003ehead);\n+\t\tspin_unlock_bh(\u0026head-\u003elock);\n+\t}\n+\tlist_add_tail(\u0026common-\u003elist, list);\n+\n+\tif (number == quic_conn_id_last_number(id_set) + 1) {\n+\t\tif (!id_set-\u003eactive)\n+\t\t\tid_set-\u003eactive = common;\n+\t\tid_set-\u003ecount++;\n+\n+\t\t/* Increment count for consecutive following IDs. */\n+\t\tlist_for_each_entry_continue(common, \u0026id_set-\u003ehead, list) {\n+\t\t\tif (common-\u003enumber != ++number)\n+\t\t\t\tbreak;\n+\t\t\tid_set-\u003ecount++;\n+\t\t}\n+\t}\n+\treturn 0;\n+}\n+\n+/* Remove consecutive connection IDs from the set with sequence numbers less\n+ * than or equal to a number.\n+ */\n+void quic_conn_id_remove(struct quic_conn_id_set *id_set, u32 number)\n+{\n+\tstruct quic_common_conn_id *common, *tmp;\n+\tstruct list_head *list;\n+\n+\t/* The number must be less than the sequence number of the last\n+\t * consecutive connection ID in the set.\n+\t */\n+\tif (WARN_ON_ONCE(number \u003e= quic_conn_id_last_number(id_set)))\n+\t\treturn;\n+\tlist = \u0026id_set-\u003ehead;\n+\tlist_for_each_entry_safe(common, tmp, list, list) {\n+\t\tif (common-\u003enumber \u003e number)\n+\t\t\tbreak;\n+\t\tif (id_set-\u003eactive == common)\n+\t\t\tid_set-\u003eactive = tmp;\n+\t\tif (id_set-\u003ealt == common)\n+\t\t\tid_set-\u003ealt = tmp;\n+\t\tquic_conn_id_del(common);\n+\t\tid_set-\u003ecount--;\n+\t}\n+}\n+\n+struct quic_conn_id *quic_conn_id_find(struct quic_conn_id_set *id_set,\n+\t\t\t\t       u32 number)\n+{\n+\tstruct quic_common_conn_id *common;\n+\n+\tlist_for_each_entry(common, \u0026id_set-\u003ehead, list) {\n+\t\tif (common-\u003enumber \u003e number)\n+\t\t\tbreak;\n+\t\tif (common-\u003enumber == number)\n+\t\t\treturn \u0026common-\u003eid;\n+\t}\n+\treturn NULL;\n+}\n+\n+void quic_conn_id_update_active(struct quic_conn_id_set *id_set, u32 number)\n+{\n+\tstruct quic_conn_id *conn_id;\n+\n+\tif (number == id_set-\u003eactive-\u003enumber)\n+\t\treturn;\n+\tconn_id = quic_conn_id_find(id_set, number);\n+\tif (!conn_id)\n+\t\treturn;\n+\tquic_conn_id_set_active(id_set, conn_id);\n+}\n+\n+void quic_conn_id_set_init(struct quic_conn_id_set *id_set, bool source)\n+{\n+\tid_set-\u003eentry_size = source ? sizeof(struct quic_source_conn_id) :\n+\t\t\t\t      sizeof(struct quic_dest_conn_id);\n+\tINIT_LIST_HEAD(\u0026id_set-\u003ehead);\n+}\n+\n+void quic_conn_id_set_free(struct quic_conn_id_set *id_set)\n+{\n+\tstruct quic_common_conn_id *common, *tmp;\n+\n+\tlist_for_each_entry_safe(common, tmp, \u0026id_set-\u003ehead, list)\n+\t\tquic_conn_id_del(common);\n+\tid_set-\u003ecount = 0;\n+\tid_set-\u003ealt = NULL;\n+\tid_set-\u003eactive = NULL;\n+}\n+\n+void quic_conn_id_get_param(struct quic_conn_id_set *id_set,\n+\t\t\t    struct quic_transport_param *p)\n+{\n+\tp-\u003eactive_connection_id_limit = id_set-\u003emax_count;\n+}\n+\n+void quic_conn_id_set_param(struct quic_conn_id_set *id_set,\n+\t\t\t    struct quic_transport_param *p)\n+{\n+\tid_set-\u003emax_count = p-\u003eactive_connection_id_limit;\n+}\ndiff --git a/net/quic/connid.h b/net/quic/connid.h\nnew file mode 100644\nindex 00000000000000..abad396a6ad6a7\n--- /dev/null\n+++ b/net/quic/connid.h\n@@ -0,0 +1,183 @@\n+/* SPDX-License-Identifier: GPL-2.0-or-later */\n+/* QUIC kernel implementation\n+ * (C) Copyright Red Hat Corp. 2023\n+ *\n+ * This file is part of the QUIC kernel implementation\n+ *\n+ * Written or modified by:\n+ *    Xin Long \u003clucien.xin@gmail.com\u003e\n+ */\n+\n+#define QUIC_CONN_ID_LIMIT\t8\n+#define QUIC_CONN_ID_DEF\t7\n+#define QUIC_CONN_ID_LEAST\t2\n+\n+#define QUIC_CONN_ID_TOKEN_LEN\t16\n+\n+/* Common fields shared by both source and destination Connection IDs */\n+struct quic_common_conn_id {\n+\tstruct quic_conn_id id; /* Connection ID value and its length */\n+\tstruct list_head list;  /* List node for connection ID management */\n+\tu32 number; /* Sequence number assigned to this Connection ID */\n+\tu8 hashed;  /* Non-zero if stored in source_conn_id hash table */\n+};\n+\n+struct quic_source_conn_id {\n+\tstruct quic_common_conn_id common;\n+\tstruct hlist_nulls_node node; /* Hash table node for fast lookup */\n+\tstruct rcu_head rcu; /* RCU header for deferred destruction */\n+\tstruct sock *sk;      /* Socket associated with this Connection ID */\n+};\n+\n+struct quic_dest_conn_id {\n+\tstruct quic_common_conn_id common;\n+\t/* Stateless reset token in rfc9000#section-10.3 */\n+\tu8 token[QUIC_CONN_ID_TOKEN_LEN];\n+};\n+\n+struct quic_conn_id_set {\n+\t/* Connection ID in use on the current path */\n+\tstruct quic_common_conn_id *active;\n+\t/* Connection ID to use for a new path (e.g., after migration) */\n+\tstruct quic_common_conn_id *alt;\n+\tstruct list_head head; /* List head of available connection IDs */\n+\tu8 entry_size; /* Size of each connection ID entry in the list */\n+\tu8 max_count;  /* active_connection_id_limit in rfc9000#section-18.2 */\n+\tu8 count;      /* Current number of connection IDs in the list */\n+};\n+\n+static inline u32 quic_conn_id_first_number(struct quic_conn_id_set *id_set)\n+{\n+\tstruct quic_common_conn_id *common;\n+\n+\t/* The id_set is guaranteed to be non-empty when called (sk is not in\n+\t * CLOSE state).\n+\t */\n+\tcommon = list_first_entry(\u0026id_set-\u003ehead, struct quic_common_conn_id,\n+\t\t\t\t  list);\n+\treturn common-\u003enumber;\n+}\n+\n+static inline u32 quic_conn_id_last_number(struct quic_conn_id_set *id_set)\n+{\n+\treturn quic_conn_id_first_number(id_set) + id_set-\u003ecount - 1;\n+}\n+\n+static inline void quic_conn_id_generate(struct quic_conn_id *conn_id)\n+{\n+\tget_random_bytes(conn_id-\u003edata, QUIC_CONN_ID_DEF_LEN);\n+\tconn_id-\u003elen = QUIC_CONN_ID_DEF_LEN;\n+}\n+\n+/* Select an alternate destination Connection ID for a new path (e.g., after\n+ * migration).\n+ */\n+static inline bool quic_conn_id_select_alt(struct quic_conn_id_set *id_set,\n+\t\t\t\t\t   bool active)\n+{\n+\tif (id_set-\u003ealt)\n+\t\treturn true;\n+\t/* NAT rebinding: peer keeps using the current source conn_id.\n+\t * In this case, continue using the same dest conn_id for the new path.\n+\t */\n+\tif (active) {\n+\t\tid_set-\u003ealt = id_set-\u003eactive;\n+\t\treturn true;\n+\t}\n+\t/* Treat the prev conn_ids as used.\n+\t * Try selecting the next conn_id in the list, unless at the end.\n+\t */\n+\tif (id_set-\u003eactive-\u003enumber != quic_conn_id_last_number(id_set)) {\n+\t\tid_set-\u003ealt = list_next_entry(id_set-\u003eactive, list);\n+\t\treturn true;\n+\t}\n+\t/* If there's only one conn_id in the list, reuse the active one. */\n+\tif (id_set-\u003eactive-\u003enumber == quic_conn_id_first_number(id_set)) {\n+\t\tid_set-\u003ealt = id_set-\u003eactive;\n+\t\treturn true;\n+\t}\n+\t/* No alternate conn_id could be selected.  Caller should send a\n+\t * QUIC_FRAME_RETIRE_CONNECTION_ID frame to request new connection IDs\n+\t * from the peer.\n+\t */\n+\treturn false;\n+}\n+\n+static inline void quic_conn_id_set_alt(struct quic_conn_id_set *id_set,\n+\t\t\t\t\tstruct quic_conn_id *alt)\n+{\n+\tid_set-\u003ealt = (struct quic_common_conn_id *)alt;\n+}\n+\n+/* Swap the active and alternate destination Connection IDs after path\n+ * migration completes, since the path has already been switched accordingly.\n+ */\n+static inline void quic_conn_id_swap_active(struct quic_conn_id_set *id_set)\n+{\n+\tvoid *active = id_set-\u003eactive;\n+\n+\tid_set-\u003eactive = id_set-\u003ealt;\n+\tid_set-\u003ealt = active;\n+}\n+\n+/* Choose which destination Connection ID to use for a new path migration if\n+ * alt is true.\n+ */\n+static inline struct quic_conn_id *\n+quic_conn_id_choose(struct quic_conn_id_set *id_set, u8 alt)\n+{\n+\treturn (alt \u0026\u0026 id_set-\u003ealt) ? \u0026id_set-\u003ealt-\u003eid : \u0026id_set-\u003eactive-\u003eid;\n+}\n+\n+static inline struct quic_conn_id *\n+quic_conn_id_active(struct quic_conn_id_set *id_set)\n+{\n+\treturn \u0026id_set-\u003eactive-\u003eid;\n+}\n+\n+static inline void quic_conn_id_set_active(struct quic_conn_id_set *id_set,\n+\t\t\t\t\t   struct quic_conn_id *active)\n+{\n+\tid_set-\u003eactive = (struct quic_common_conn_id *)active;\n+}\n+\n+static inline u32 quic_conn_id_number(struct quic_conn_id *conn_id)\n+{\n+\treturn ((struct quic_common_conn_id *)conn_id)-\u003enumber;\n+}\n+\n+static inline struct sock *quic_conn_id_sk(struct quic_conn_id *conn_id)\n+{\n+\treturn ((struct quic_source_conn_id *)conn_id)-\u003esk;\n+}\n+\n+static inline void quic_conn_id_set_token(struct quic_conn_id *conn_id,\n+\t\t\t\t\t  u8 *token)\n+{\n+\tmemcpy(((struct quic_dest_conn_id *)conn_id)-\u003etoken, token,\n+\t       QUIC_CONN_ID_TOKEN_LEN);\n+}\n+\n+static inline int quic_conn_id_cmp(struct quic_conn_id *a,\n+\t\t\t\t   struct quic_conn_id *b)\n+{\n+\treturn a-\u003elen != b-\u003elen || memcmp(a-\u003edata, b-\u003edata, a-\u003elen);\n+}\n+\n+int quic_conn_id_add(struct quic_conn_id_set *id_set,\n+\t\t     struct quic_conn_id *conn_id, u32 number, void *data,\n+\t\t     gfp_t gfp);\n+bool quic_conn_id_token_exists(struct quic_conn_id_set *id_set, u8 *token);\n+void quic_conn_id_remove(struct quic_conn_id_set *id_set, u32 number);\n+\n+struct quic_conn_id *quic_conn_id_find(struct quic_conn_id_set *id_set,\n+\t\t\t\t       u32 number);\n+struct quic_conn_id *quic_conn_id_lookup(struct net *net, u8 *scid, u32 len);\n+void quic_conn_id_update_active(struct quic_conn_id_set *id_set, u32 number);\n+\n+void quic_conn_id_get_param(struct quic_conn_id_set *id_set,\n+\t\t\t    struct quic_transport_param *p);\n+void quic_conn_id_set_param(struct quic_conn_id_set *id_set,\n+\t\t\t    struct quic_transport_param *p);\n+void quic_conn_id_set_init(struct quic_conn_id_set *id_set, bool source);\n+void quic_conn_id_set_free(struct quic_conn_id_set *id_set);\ndiff --git a/net/quic/crypto.c b/net/quic/crypto.c\nnew file mode 100644\nindex 00000000000000..8b254bb0556cf8\n--- /dev/null\n+++ b/net/quic/crypto.c\n@@ -0,0 +1,1249 @@\n+// SPDX-License-Identifier: GPL-2.0-or-later\n+/* QUIC kernel implementation\n+ * (C) Copyright Red Hat Corp. 2023\n+ *\n+ * This file is part of the QUIC kernel implementation\n+ *\n+ * Initialization/cleanup for QUIC protocol support.\n+ *\n+ * Written or modified by:\n+ *    Xin Long \u003clucien.xin@gmail.com\u003e\n+ */\n+\n+#include \u003ccrypto/skcipher.h\u003e\n+#include \u003clinux/skbuff.h\u003e\n+#include \u003ccrypto/utils.h\u003e\n+#include \u003ccrypto/aead.h\u003e\n+#include \u003ccrypto/hash.h\u003e\n+#include \u003clinux/quic.h\u003e\n+#include \u003cnet/tls.h\u003e\n+\n+#include \"common.h\"\n+#include \"crypto.h\"\n+\n+/* HKDF-Extract. */\n+static int quic_crypto_hkdf_extract(struct crypto_shash *tfm,\n+\t\t\t\t    struct quic_data *srt,\n+\t\t\t\t    struct quic_data *hash,\n+\t\t\t\t    struct quic_data *key)\n+{\n+\tint err;\n+\n+\terr = crypto_shash_setkey(tfm, srt-\u003edata, srt-\u003elen);\n+\tif (err)\n+\t\treturn err;\n+\n+\treturn crypto_shash_tfm_digest(tfm, hash-\u003edata, hash-\u003elen, key-\u003edata);\n+}\n+\n+#define QUIC_MAX_INFO_LEN\t256\n+\n+/* HKDF-Expand-Label. */\n+static int quic_crypto_hkdf_expand(struct crypto_shash *tfm,\n+\t\t\t\t   struct quic_data *srt,\n+\t\t\t\t   struct quic_data *label,\n+\t\t\t\t   struct quic_data *key)\n+{\n+\tu8 info[QUIC_MAX_INFO_LEN], *p = info, tmp[QUIC_SECRET_LEN];\n+\tunsigned int i, infolen, hashlen = srt-\u003elen;\n+\tSHASH_DESC_ON_STACK(desc, tfm);\n+\tu8 LABEL[] = \"tls13 \", cnt = 1;\n+\tconst u8 *prev = NULL;\n+\tint err;\n+\n+\t/* rfc8446#section-7.1:\n+\t *\n+\t *  HKDF-Expand-Label(Secret, Label, Context, Length) =\n+\t *       HKDF-Expand(Secret, HkdfLabel, Length)\n+\t *\n+\t *  Where HkdfLabel is specified as:\n+\t *\n+\t *  struct {\n+\t *      uint16 length = Length;\n+\t *      opaque label\u003c7..255\u003e = \"tls13 \" + Label;\n+\t *      opaque context\u003c0..255\u003e = Context;\n+\t *  } HkdfLabel;\n+\t */\n+\tput_unaligned_be16(key-\u003elen, p);\n+\tp += 2;\n+\t*p++ = (u8)(sizeof(LABEL) - 1 + label-\u003elen);\n+\tp = quic_put_data(p, LABEL, sizeof(LABEL) - 1);\n+\tp = quic_put_data(p, label-\u003edata, label-\u003elen);\n+\t*p++ = 0;\n+\tinfolen = (unsigned int)(p - info);\n+\n+\terr = crypto_shash_setkey(tfm, srt-\u003edata, srt-\u003elen);\n+\tif (err)\n+\t\treturn err;\n+\tdesc-\u003etfm = tfm;\n+\n+\tfor (i = 0; i \u003c key-\u003elen; i += hashlen) {\n+\t\terr = crypto_shash_init(desc);\n+\t\tif (err)\n+\t\t\tgoto out;\n+\n+\t\tif (prev) {\n+\t\t\terr = crypto_shash_update(desc, prev, hashlen);\n+\t\t\tif (err)\n+\t\t\t\tgoto out;\n+\t\t}\n+\n+\t\terr = crypto_shash_update(desc, info, infolen);\n+\t\tif (err)\n+\t\t\tgoto out;\n+\n+\t\tBUILD_BUG_ON(sizeof(cnt) != 1);\n+\t\tif (key-\u003elen - i \u003c hashlen) {\n+\t\t\terr = crypto_shash_finup(desc, \u0026cnt, 1, tmp);\n+\t\t\tif (err)\n+\t\t\t\tgoto out;\n+\t\t\tmemcpy(\u0026key-\u003edata[i], tmp, key-\u003elen - i);\n+\t\t\tmemzero_explicit(tmp, sizeof(tmp));\n+\t\t} else {\n+\t\t\terr = crypto_shash_finup(desc, \u0026cnt, 1, \u0026key-\u003edata[i]);\n+\t\t\tif (err)\n+\t\t\t\tgoto out;\n+\t\t}\n+\t\tcnt++;\n+\t\tprev = \u0026key-\u003edata[i];\n+\t}\n+out:\n+\tshash_desc_zero(desc);\n+\tmemzero_explicit(tmp, sizeof(tmp));\n+\treturn err;\n+}\n+\n+#define KEY_LABEL_V1\t\t\"quic key\"\n+#define IV_LABEL_V1\t\t\"quic iv\"\n+#define HP_KEY_LABEL_V1\t\t\"quic hp\"\n+\n+#define KU_LABEL_V1\t\t\"quic ku\"\n+\n+/* rfc9369#section-3.3.2:\n+ *\n+ * The labels used in rfc9001 to derive packet protection keys, header\n+ * protection keys, Retry Integrity Tag keys, and key updates change from \"quic\n+ * key\" to \"quicv2 key\", from \"quic iv\" to \"quicv2 iv\", from \"quic hp\" to\n+ * \"quicv2 hp\", and from \"quic ku\" to \"quicv2 ku\".\n+ */\n+#define KEY_LABEL_V2\t\t\"quicv2 key\"\n+#define IV_LABEL_V2\t\t\"quicv2 iv\"\n+#define HP_KEY_LABEL_V2\t\t\"quicv2 hp\"\n+\n+#define KU_LABEL_V2\t\t\"quicv2 ku\"\n+\n+/* Packet Protection Keys. */\n+static int quic_crypto_keys_derive(struct crypto_shash *tfm,\n+\t\t\t\t   struct quic_data *s, struct quic_data *k,\n+\t\t\t\t   struct quic_data *i, struct quic_data *hp_k,\n+\t\t\t\t   u32 version)\n+{\n+\tstruct quic_data hp_k_l = {HP_KEY_LABEL_V1, strlen(HP_KEY_LABEL_V1)};\n+\tstruct quic_data k_l = {KEY_LABEL_V1, strlen(KEY_LABEL_V1)};\n+\tstruct quic_data i_l = {IV_LABEL_V1, strlen(IV_LABEL_V1)};\n+\tint err;\n+\n+\t/* rfc9001#section-5.1:\n+\t *\n+\t * The current encryption level secret and the label \"quic key\" are\n+\t * input to the KDF to produce the AEAD key; the label \"quic iv\" is\n+\t * used to derive the Initialization Vector (IV). The header protection\n+\t * key uses the \"quic hp\" label.  Using these labels provides key\n+\t * separation between QUIC and TLS.\n+\t */\n+\tif (version == QUIC_VERSION_V2) {\n+\t\tquic_data(\u0026hp_k_l, HP_KEY_LABEL_V2, strlen(HP_KEY_LABEL_V2));\n+\t\tquic_data(\u0026k_l, KEY_LABEL_V2, strlen(KEY_LABEL_V2));\n+\t\tquic_data(\u0026i_l, IV_LABEL_V2, strlen(IV_LABEL_V2));\n+\t}\n+\n+\terr = quic_crypto_hkdf_expand(tfm, s, \u0026k_l, k);\n+\tif (err)\n+\t\treturn err;\n+\terr = quic_crypto_hkdf_expand(tfm, s, \u0026i_l, i);\n+\tif (err)\n+\t\treturn err;\n+\t/* Don't change hp key for key update. */\n+\tif (!hp_k)\n+\t\treturn 0;\n+\n+\treturn quic_crypto_hkdf_expand(tfm, s, \u0026hp_k_l, hp_k);\n+}\n+\n+/* Derive and install reception (RX) or transmission (TX) packet protection\n+ * keys for the current key phase.  This installs AEAD protection key, IV, and\n+ * optionally header protection key.\n+ */\n+static int quic_crypto_keys_derive_and_install(struct quic_crypto *crypto,\n+\t\t\t\t\t       bool rx, u8 phase)\n+{\n+\tstruct quic_data srt = {}, k, iv, hp_k = {}, *hp = NULL;\n+\tu8 key[QUIC_KEY_LEN], hp_key[QUIC_KEY_LEN] = {};\n+\tu32 keylen, ivlen = QUIC_IV_LEN;\n+\tstruct crypto_skcipher *hp_tfm;\n+\tstruct crypto_aead *tfm;\n+\tint err;\n+\n+\tkeylen = crypto-\u003ecipher-\u003ekeylen;\n+\tquic_data(\u0026k, key, keylen);\n+\n+\tif (rx) {\n+\t\tquic_data(\u0026srt, crypto-\u003erx_secret[phase],\n+\t\t\t  crypto-\u003ecipher-\u003esecretlen);\n+\t\tquic_data(\u0026iv, crypto-\u003erx_iv[phase], ivlen);\n+\t\ttfm = crypto-\u003erx_tfm[phase];\n+\t\thp_tfm = crypto-\u003erx_hp_tfm;\n+\t} else {\n+\t\tquic_data(\u0026srt, crypto-\u003etx_secret[phase],\n+\t\t\t  crypto-\u003ecipher-\u003esecretlen);\n+\t\tquic_data(\u0026iv, crypto-\u003etx_iv[phase], ivlen);\n+\t\ttfm = crypto-\u003etx_tfm[phase];\n+\t\thp_tfm = crypto-\u003etx_hp_tfm;\n+\t}\n+\n+\t/* Only derive header protection key when not in key update. */\n+\tif (crypto-\u003ekey_phase == phase)\n+\t\thp = quic_data(\u0026hp_k, hp_key, keylen);\n+\terr = quic_crypto_keys_derive(crypto-\u003esecret_tfm, \u0026srt, \u0026k, \u0026iv, hp,\n+\t\t\t\t      crypto-\u003eversion);\n+\tif (err)\n+\t\tgoto out;\n+\terr = crypto_aead_setauthsize(tfm, QUIC_TAG_LEN);\n+\tif (err)\n+\t\tgoto out;\n+\terr = crypto_aead_setkey(tfm, key, keylen);\n+\tif (err)\n+\t\tgoto out;\n+\tif (hp) {\n+\t\terr = crypto_skcipher_setkey(hp_tfm, hp_key, keylen);\n+\t\tif (err)\n+\t\t\tgoto out;\n+\t}\n+out:\n+\tmemzero_explicit(key, sizeof(key));\n+\tmemzero_explicit(hp_key, sizeof(hp_key));\n+\treturn err;\n+}\n+\n+#define QUIC_CIPHER_MIN TLS_CIPHER_AES_GCM_128\n+#define QUIC_CIPHER_MAX TLS_CIPHER_CHACHA20_POLY1305\n+\n+#define TLS_CIPHER_AES_GCM_128_SECRET_SIZE\t\t32\n+#define TLS_CIPHER_AES_GCM_256_SECRET_SIZE\t\t48\n+#define TLS_CIPHER_AES_CCM_128_SECRET_SIZE\t\t32\n+#define TLS_CIPHER_CHACHA20_POLY1305_SECRET_SIZE\t32\n+\n+#define CIPHER_DESC(type, aead_n, skc_n, sha_n)[type - QUIC_CIPHER_MIN] = { \\\n+\t.secretlen = type ## _SECRET_SIZE, \\\n+\t.keylen = type ## _KEY_SIZE, \\\n+\t.aead = aead_n, \\\n+\t.skc = skc_n, \\\n+\t.shash = sha_n, \\\n+}\n+\n+static const struct quic_cipher\n+ciphers[QUIC_CIPHER_MAX + 1 - QUIC_CIPHER_MIN] = {\n+\tCIPHER_DESC(TLS_CIPHER_AES_GCM_128,\n+\t\t    \"gcm(aes)\", \"ecb(aes)\", \"hmac(sha256)\"),\n+\tCIPHER_DESC(TLS_CIPHER_AES_GCM_256,\n+\t\t    \"gcm(aes)\", \"ecb(aes)\", \"hmac(sha384)\"),\n+\tCIPHER_DESC(TLS_CIPHER_AES_CCM_128,\n+\t\t    \"ccm(aes)\", \"ecb(aes)\", \"hmac(sha256)\"),\n+\tCIPHER_DESC(TLS_CIPHER_CHACHA20_POLY1305,\n+\t\t    \"rfc7539(chacha20,poly1305)\", \"chacha20\", \"hmac(sha256)\"),\n+};\n+\n+static bool quic_crypto_is_cipher_ccm(struct quic_crypto *crypto)\n+{\n+\treturn crypto-\u003ecipher_type == TLS_CIPHER_AES_CCM_128;\n+}\n+\n+static bool quic_crypto_is_cipher_chacha(struct quic_crypto *crypto)\n+{\n+\treturn crypto-\u003ecipher_type == TLS_CIPHER_CHACHA20_POLY1305;\n+}\n+\n+static void *quic_crypto_skcipher_mem_alloc(struct crypto_skcipher *tfm,\n+\t\t\t\t\t    u32 mask_size, u8 **iv,\n+\t\t\t\t\t    struct skcipher_request **req,\n+\t\t\t\t\t    gfp_t gfp)\n+{\n+\tunsigned int iv_size, req_size;\n+\tunsigned int len;\n+\tu8 *mem;\n+\n+\tiv_size = crypto_skcipher_ivsize(tfm);\n+\treq_size = sizeof(**req) + crypto_skcipher_reqsize(tfm);\n+\n+\tlen = mask_size;\n+\tlen += iv_size;\n+\tlen += crypto_skcipher_alignmask(tfm) \u0026\n+\t       ~(crypto_tfm_ctx_alignment() - 1);\n+\tlen = ALIGN(len, crypto_tfm_ctx_alignment());\n+\tlen += req_size;\n+\n+\tmem = kzalloc(len, gfp);\n+\tif (!mem)\n+\t\treturn NULL;\n+\n+\t*iv = (u8 *)PTR_ALIGN(mem + mask_size,\n+\t\t\t      crypto_skcipher_alignmask(tfm) + 1);\n+\t*req = (struct skcipher_request *)PTR_ALIGN(*iv + iv_size,\n+\t\t\t\t\t\t    crypto_tfm_ctx_alignment());\n+\n+\treturn (void *)mem;\n+}\n+\n+/* Extracts and reconstructs the packet number from an incoming QUIC packet. */\n+static int quic_crypto_get_number(struct sk_buff *skb)\n+{\n+\tstruct quic_skb_cb *cb = QUIC_SKB_CB(skb);\n+\ts64 number_max = cb-\u003enumber;\n+\tu32 len = cb-\u003elength;\n+\tu8 *p;\n+\n+\t/* rfc9000#section-17.1:\n+\t *\n+\t * Once header protection is removed, the packet number is decoded by\n+\t * finding the packet number value that is closest to the next expected\n+\t * packet. The next expected packet is the highest received packet\n+\t * number plus one.\n+\t */\n+\tp = (u8 *)quic_hdr(skb) + cb-\u003enumber_offset;\n+\tif (!quic_get_int(\u0026p, \u0026len, \u0026cb-\u003enumber, cb-\u003enumber_len))\n+\t\treturn -EINVAL;\n+\tcb-\u003enumber = quic_get_num(number_max, cb-\u003enumber, cb-\u003enumber_len);\n+\treturn 0;\n+}\n+\n+#define QUIC_SAMPLE_LEN\t\t16\n+\n+#define QUIC_HEADER_FORM_BIT\t0x80\n+#define QUIC_LONG_HEADER_MASK\t0x0f\n+#define QUIC_SHORT_HEADER_MASK\t0x1f\n+\n+/* Header Protection. */\n+static int quic_crypto_header_protect(struct quic_crypto *crypto,\n+\t\t\t\t      struct sk_buff *skb, bool enc, gfp_t gfp)\n+{\n+\tstruct quic_skb_cb *cb = QUIC_SKB_CB(skb);\n+\tu8 *mask, *iv, *p, h_mask, chacha;\n+\tstruct skcipher_request *req;\n+\tstruct crypto_skcipher *tfm;\n+\tstruct sk_buff *trailer;\n+\tstruct scatterlist sg;\n+\tint err, i;\n+\n+\tchacha = quic_crypto_is_cipher_chacha(crypto);\n+\tif (!enc) {\n+\t\ttfm = crypto-\u003erx_hp_tfm;\n+\t\tif (cb-\u003elength \u003c QUIC_PN_MAX_LEN + QUIC_SAMPLE_LEN)\n+\t\t\treturn -EINVAL;\n+\n+\t\terr = skb_cow_data(skb, 0, \u0026trailer);\n+\t\tif (err \u003c 0)\n+\t\t\treturn err;\n+\t} else {\n+\t\ttfm = crypto-\u003etx_hp_tfm;\n+\t}\n+\n+\tmask = quic_crypto_skcipher_mem_alloc(tfm, QUIC_SAMPLE_LEN, \u0026iv, \u0026req,\n+\t\t\t\t\t      gfp);\n+\tif (!mask)\n+\t\treturn -ENOMEM;\n+\n+\t/* rfc9001#section-5.4.2: Header Protection Sample:\n+\t *\n+\t *   # pn_offset is the start of the Packet Number field.\n+\t *   sample_offset = pn_offset + 4\n+\t *\n+\t *   sample = packet[sample_offset..sample_offset+sample_length]\n+\t *\n+\t * rfc9001#section-5.4.3: AES-Based Header Protection:\n+\t *\n+\t *   header_protection(hp_key, sample):\n+\t *     mask = AES-ECB(hp_key, sample)\n+\t *\n+\t * rfc9001#section-5.4.4: ChaCha20-Based Header Protection:\n+\t *\n+\t *   header_protection(hp_key, sample):\n+\t *     counter = sample[0..3]\n+\t *     nonce = sample[4..15]\n+\t *     mask = ChaCha20(hp_key, counter, nonce, {0,0,0,0,0})\n+\t */\n+\tp = skb-\u003edata + cb-\u003enumber_offset + QUIC_PN_MAX_LEN;\n+\tmemcpy((chacha ? iv : mask), p, QUIC_SAMPLE_LEN);\n+\tsg_init_one(\u0026sg, mask, QUIC_SAMPLE_LEN);\n+\tskcipher_request_set_tfm(req, tfm);\n+\tskcipher_request_set_crypt(req, \u0026sg, \u0026sg, QUIC_SAMPLE_LEN, iv);\n+\terr = crypto_skcipher_encrypt(req);\n+\tif (err)\n+\t\tgoto out;\n+\n+\t/* rfc9001#section-5.4.1:\n+\t *\n+\t * mask = header_protection(hp_key, sample)\n+\t *\n+\t * pn_length = (packet[0] \u0026 0x03) + 1\n+\t * if (packet[0] \u0026 0x80) == 0x80:\n+\t *    # Long header: 4 bits masked\n+\t *    packet[0] ^= mask[0] \u0026 0x0f\n+\t * else:\n+\t *    # Short header: 5 bits masked\n+\t *    packet[0] ^= mask[0] \u0026 0x1f\n+\t *\n+\t * # pn_offset is the start of the Packet Number field.\n+\t * packet[pn_offset:pn_offset+pn_length] ^= mask[1:1+pn_length]\n+\t */\n+\tp = skb-\u003edata;\n+\th_mask = ((*p \u0026 QUIC_HEADER_FORM_BIT) == QUIC_HEADER_FORM_BIT) ?\n+\t\t QUIC_LONG_HEADER_MASK : QUIC_SHORT_HEADER_MASK;\n+\t*p = (u8)(*p ^ (mask[0] \u0026 h_mask));\n+\tif (!enc) {\n+\t\tif (!quic_hdr(skb)-\u003eform)\n+\t\t\tcb-\u003ekey_phase = quic_hdr(skb)-\u003ekey;\n+\t\tcb-\u003enumber_len = quic_hdr(skb)-\u003epnl + 1;\n+\t}\n+\tp += cb-\u003enumber_offset;\n+\tfor (i = 1; i \u003c= cb-\u003enumber_len; i++)\n+\t\t*p++ ^= mask[i];\n+\n+\tif (!enc)\n+\t\terr = quic_crypto_get_number(skb);\n+out:\n+\tkfree_sensitive(mask);\n+\treturn err;\n+}\n+\n+static void *quic_crypto_aead_mem_alloc(struct crypto_aead *tfm, u32 ctx_size,\n+\t\t\t\t\tu8 **iv, struct aead_request **req,\n+\t\t\t\t\tstruct scatterlist **sg, u32 nsg,\n+\t\t\t\t\tgfp_t gfp)\n+{\n+\tunsigned int iv_size, req_size;\n+\tunsigned int len;\n+\tu8 *mem;\n+\n+\tiv_size = crypto_aead_ivsize(tfm);\n+\treq_size = sizeof(**req) + crypto_aead_reqsize(tfm);\n+\n+\tlen = ctx_size;\n+\tlen += iv_size;\n+\tlen += crypto_aead_alignmask(tfm) \u0026 ~(crypto_tfm_ctx_alignment() - 1);\n+\tlen = ALIGN(len, crypto_tfm_ctx_alignment());\n+\tlen += req_size;\n+\tlen = ALIGN(len, __alignof__(struct scatterlist));\n+\tlen += nsg * sizeof(**sg);\n+\n+\tmem = kzalloc(len, gfp);\n+\tif (!mem)\n+\t\treturn NULL;\n+\n+\t*iv = (u8 *)PTR_ALIGN(mem + ctx_size, crypto_aead_alignmask(tfm) + 1);\n+\t*req = (struct aead_request *)PTR_ALIGN(*iv + iv_size,\n+\t\t\t\t\t\tcrypto_tfm_ctx_alignment());\n+\t*sg = (struct scatterlist *)PTR_ALIGN((u8 *)*req + req_size,\n+\t\t\t\t\t      __alignof__(struct scatterlist));\n+\n+\treturn (void *)mem;\n+}\n+\n+static void quic_crypto_done(void *data, int err)\n+{\n+\tstruct sk_buff *skb = data;\n+\tstruct quic_crypto *crypto;\n+\tstruct quic_skb_cb *cb;\n+\n+\tif (err == -EINPROGRESS)\n+\t\treturn;\n+\n+\tcb = QUIC_SKB_CB(skb);\n+\tcrypto = *(struct quic_crypto **)cb-\u003ecrypto_ctx;\n+\tatomic_dec(\u0026crypto-\u003easync_pending[cb-\u003ekey_phase]);\n+\n+\tkfree_sensitive(cb-\u003ecrypto_ctx);\n+\tcb-\u003ecrypto_done(skb, err);\n+}\n+\n+/* AEAD Usage. */\n+static int quic_crypto_payload_protect(struct quic_crypto *crypto,\n+\t\t\t\t       struct sk_buff *skb, bool enc, gfp_t gfp)\n+{\n+\tu8 *base_iv, *iv, i, nonce[QUIC_IV_LEN], ccm, phase;\n+\tstruct quic_skb_cb *cb = QUIC_SKB_CB(skb);\n+\tu32 len, hlen, sglen, nsg;\n+\tstruct aead_request *req;\n+\tstruct crypto_aead *tfm;\n+\tstruct sk_buff *trailer;\n+\tstruct scatterlist *sg;\n+\tvoid *ctx;\n+\t__be64 n;\n+\tint err;\n+\n+\tccm = quic_crypto_is_cipher_ccm(crypto);\n+\tphase = cb-\u003ekey_phase;\n+\thlen = cb-\u003enumber_offset + cb-\u003enumber_len;\n+\tif (enc) {\n+\t\ttfm = crypto-\u003etx_tfm[phase];\n+\t\tbase_iv = crypto-\u003etx_iv[phase];\n+\t\tlen = skb-\u003elen;\n+\t\terr = skb_cow_data(skb, QUIC_TAG_LEN, \u0026trailer);\n+\t\tif (err \u003c 0)\n+\t\t\treturn err;\n+\t\tpskb_put(skb, trailer, QUIC_TAG_LEN);\n+\t\tif (!quic_hdr(skb)-\u003eform)\n+\t\t\tquic_hdr(skb)-\u003ekey = phase;\n+\t\tsglen = skb-\u003elen;\n+\t\tnsg = (u32)err;\n+\t} else {\n+\t\ttfm = crypto-\u003erx_tfm[phase];\n+\t\tbase_iv = crypto-\u003erx_iv[phase];\n+\t\tlen = cb-\u003elength + cb-\u003enumber_offset;\n+\t\tif (len - hlen \u003c QUIC_TAG_LEN)\n+\t\t\treturn -EINVAL;\n+\t\tsglen = len;\n+\t\tnsg = 1;\n+\t}\n+\n+\tctx = quic_crypto_aead_mem_alloc(tfm, sizeof(void *), \u0026iv, \u0026req, \u0026sg,\n+\t\t\t\t\t nsg, gfp);\n+\tif (!ctx)\n+\t\treturn -ENOMEM;\n+\n+\tsg_init_table(sg, nsg);\n+\terr = skb_to_sgvec(skb, sg, 0, sglen);\n+\tif (err \u003c 0)\n+\t\tgoto out;\n+\n+\t/* rfc9001#section-5.3:\n+\t *\n+\t * The associated data, A, for the AEAD is the contents of the QUIC\n+\t * header, starting from the first byte of either the short or long\n+\t * header, up to and including the unprotected packet number.\n+\t *\n+\t * The nonce, N, is formed by combining the packet protection IV with\n+\t * the packet number.  The 62 bits of the reconstructed QUIC packet\n+\t * number in network byte order are left-padded with zeros to the size\n+\t * of the IV. The exclusive OR of the padded packet number and the IV\n+\t * forms the AEAD nonce.\n+\t */\n+\tmemcpy(nonce, base_iv, QUIC_IV_LEN);\n+\tn = cpu_to_be64(cb-\u003enumber);\n+\tfor (i = 0; i \u003c sizeof(n); i++)\n+\t\tnonce[QUIC_IV_LEN - sizeof(n) + i] ^= ((u8 *)\u0026n)[i];\n+\n+\t/* For CCM based ciphers, first byte of IV is a constant. */\n+\tiv[0] = TLS_AES_CCM_IV_B0_BYTE;\n+\tmemcpy(\u0026iv[ccm], nonce, QUIC_IV_LEN);\n+\taead_request_set_tfm(req, tfm);\n+\taead_request_set_ad(req, hlen);\n+\taead_request_set_crypt(req, sg, sg, len - hlen, iv);\n+\tif (cb-\u003esync) {\n+\t\tDECLARE_CRYPTO_WAIT(wait);\n+\n+\t\taead_request_set_callback(req, CRYPTO_TFM_REQ_MAY_BACKLOG,\n+\t\t\t\t\t  crypto_req_done, \u0026wait);\n+\t\terr = enc ? crypto_aead_encrypt(req) : crypto_aead_decrypt(req);\n+\t\tif (err == -EINPROGRESS || err == -EBUSY)\n+\t\t\terr = crypto_wait_req(err, \u0026wait);\n+\t\tgoto out;\n+\t}\n+\n+\taead_request_set_callback(req, CRYPTO_TFM_REQ_MAY_BACKLOG,\n+\t\t\t\t  quic_crypto_done, skb);\n+\t*(struct quic_crypto **)ctx = crypto;\n+\tcb-\u003ecrypto_ctx = ctx; /* Async free context for quic_crypto_done() */\n+\terr = enc ? crypto_aead_encrypt(req) : crypto_aead_decrypt(req);\n+\tif (err == -EINPROGRESS || err == -EBUSY) {\n+\t\tatomic_inc(\u0026crypto-\u003easync_pending[phase]);\n+\t\tmemzero_explicit(nonce, sizeof(nonce));\n+\t\treturn -EINPROGRESS;\n+\t}\n+\n+out:\n+\tkfree_sensitive(ctx);\n+\tmemzero_explicit(nonce, sizeof(nonce));\n+\treturn err;\n+}\n+\n+/* Encrypts a QUIC packet before transmission.  This function performs AEAD\n+ * encryption of the packet payload and applies header protection. It handles\n+ * key phase tracking and key update timing.\n+ *\n+ * Return: 0 on success, or a negative error code.\n+ */\n+int quic_crypto_encrypt(struct quic_crypto *crypto, struct sk_buff *skb,\n+\t\t\tgfp_t gfp)\n+{\n+\tstruct quic_skb_cb *cb = QUIC_SKB_CB(skb);\n+\tint err;\n+\n+\tcb-\u003ekey_phase = crypto-\u003ekey_phase;\n+\t/* Packet payload is already encrypted (e.g., resumed from async),\n+\t * proceed to header protection only.\n+\t */\n+\tif (cb-\u003eresume)\n+\t\tgoto out;\n+\n+\t/* If a key update is pending and this is the first packet using the\n+\t * new key, save the current time. Later used to clear old keys after\n+\t * some time has passed (see quic_crypto_decrypt()).\n+\t */\n+\tif (crypto-\u003ekey_pending \u0026\u0026 !crypto-\u003ekey_update_send_time)\n+\t\tcrypto-\u003ekey_update_send_time = quic_ktime_get_us();\n+\n+\terr = quic_crypto_payload_protect(crypto, skb, true, gfp);\n+\tif (err)\n+\t\treturn err;\n+out:\n+\treturn quic_crypto_header_protect(crypto, skb, true, gfp);\n+}\n+\n+/* Decrypts a QUIC packet after reception.  This function removes header\n+ * protection, decrypts the payload, and processes any key updates if the key\n+ * phase bit changes.\n+ *\n+ * Return: 0 on success, or a negative error code.\n+ */\n+int quic_crypto_decrypt(struct quic_crypto *crypto, struct sk_buff *skb,\n+\t\t\tgfp_t gfp)\n+{\n+\tstruct quic_skb_cb *cb = QUIC_SKB_CB(skb);\n+\tint err = 0;\n+\tu64 time;\n+\tu8 phase;\n+\n+\t/* Payload was decrypted asynchronously.  Proceed with parsing packet\n+\t * number and key phase.\n+\t */\n+\tif (cb-\u003eresume) {\n+\t\terr = quic_crypto_get_number(skb);\n+\t\tif (err)\n+\t\t\treturn err;\n+\t\tgoto out;\n+\t}\n+\tif (!cb-\u003enumber_len) { /* Packet header not yet decrypted. */\n+\t\terr = quic_crypto_header_protect(crypto, skb, false, gfp);\n+\t\tif (err) {\n+\t\t\tpr_debug(\"%s: hd decrypt err %d\\n\", __func__, err);\n+\t\t\treturn err;\n+\t\t}\n+\t}\n+\n+\t/* rfc9001#section-6:\n+\t *\n+\t * The Key Phase bit allows a recipient to detect a change in keying\n+\t * material without needing to receive the first packet that triggered\n+\t * the change. An endpoint that notices a changed Key Phase bit updates\n+\t * keys and decrypts the packet that contains the changed value.\n+\t */\n+\tphase = cb-\u003ekey_phase;\n+\tif (phase != crypto-\u003ekey_phase \u0026\u0026 !crypto-\u003ekey_pending) {\n+\t\tif (!crypto-\u003esend_ready) /* Not ready for key update. */\n+\t\t\treturn -EINVAL;\n+\t\tif (!cb-\u003ebacklog) /* Key update requires process context. */\n+\t\t\treturn -EKEYREVOKED;\n+\t\t/* Cannot do key update while async crypto is in progress. */\n+\t\tif (unlikely(atomic_read(\u0026crypto-\u003easync_pending[phase])))\n+\t\t\treturn -EBUSY;\n+\t\terr = quic_crypto_key_update(crypto); /* Perform key update. */\n+\t\tif (err) {\n+\t\t\tcb-\u003eerrcode = QUIC_TRANSPORT_ERROR_KEY_UPDATE;\n+\t\t\treturn err;\n+\t\t}\n+\t\tcb-\u003esync = 1;\n+\t\tcb-\u003ekey_update = 1; /* Mark packet as triggering key update. */\n+\t}\n+\n+\terr = quic_crypto_payload_protect(crypto, skb, false, gfp);\n+\tif (err) {\n+\t\tif (err == -EINPROGRESS)\n+\t\t\treturn err;\n+\t\t/* When using the old keys can not decrypt the packets, the\n+\t\t * peer might start another key_update. Thus, clear the last\n+\t\t * key_pending so that next packets will trigger the new\n+\t\t * key-update.\n+\t\t */\n+\t\tif (crypto-\u003ekey_pending \u0026\u0026 phase != crypto-\u003ekey_phase) {\n+\t\t\tcrypto-\u003ekey_pending = 0;\n+\t\t\tcrypto-\u003ekey_update_time = 0;\n+\t\t\tcrypto-\u003ekey_update_send_time = 0;\n+\t\t}\n+\t\treturn err;\n+\t}\n+\n+out:\n+\t/* rfc9001#section-6.2:\n+\t *\n+\t * If a packet is successfully processed using the next key and IV,\n+\t * then the peer has initiated a key update.\n+\t */\n+\tif (cb-\u003ekey_update) {\n+\t\tcrypto-\u003ekey_pending = 1;\n+\t\tcrypto-\u003ekey_derived = 0;\n+\t\tcrypto-\u003ekey_phase = !crypto-\u003ekey_phase;\n+\t}\n+\t/* rfc9001#section-6.1:\n+\t *\n+\t * An endpoint MUST retain old keys until it has successfully\n+\t * unprotected a packet sent using the new keys. An endpoint SHOULD\n+\t * retain old keys for some time after unprotecting a packet sent using\n+\t * the new keys.\n+\t */\n+\tif (crypto-\u003ekey_pending \u0026\u0026 cb-\u003ekey_phase == crypto-\u003ekey_phase) {\n+\t\ttime = crypto-\u003ekey_update_send_time;\n+\t\tif (time \u0026\u0026\n+\t\t    quic_ktime_get_us() - time \u003e= crypto-\u003ekey_update_time) {\n+\t\t\tcrypto-\u003ekey_pending = 0;\n+\t\t\tcrypto-\u003ekey_update_time = 0;\n+\t\t\tcrypto-\u003ekey_update_send_time = 0;\n+\t\t}\n+\t}\n+\treturn err;\n+}\n+\n+int quic_crypto_set_cipher(struct quic_crypto *crypto, u32 type)\n+{\n+\tconst struct quic_cipher *cipher;\n+\tvoid *tfm;\n+\tint err;\n+\n+\tif (type \u003c QUIC_CIPHER_MIN || type \u003e QUIC_CIPHER_MAX)\n+\t\treturn -EINVAL;\n+\n+\tcipher = \u0026ciphers[type - QUIC_CIPHER_MIN];\n+\ttfm = crypto_alloc_shash(cipher-\u003eshash, 0, 0);\n+\tif (IS_ERR(tfm))\n+\t\treturn PTR_ERR(tfm);\n+\tcrypto-\u003esecret_tfm = tfm;\n+\n+\t/* Allocate AEAD and HP transform for each RX key phase. */\n+\ttfm = crypto_alloc_aead(cipher-\u003eaead, 0, 0);\n+\tif (IS_ERR(tfm)) {\n+\t\terr = PTR_ERR(tfm);\n+\t\tgoto err;\n+\t}\n+\tcrypto-\u003erx_tfm[0] = tfm;\n+\ttfm = crypto_alloc_aead(cipher-\u003eaead, 0, 0);\n+\tif (IS_ERR(tfm)) {\n+\t\terr = PTR_ERR(tfm);\n+\t\tgoto err;\n+\t}\n+\tcrypto-\u003erx_tfm[1] = tfm;\n+\ttfm = crypto_alloc_sync_skcipher(cipher-\u003eskc, 0, 0);\n+\tif (IS_ERR(tfm)) {\n+\t\terr = PTR_ERR(tfm);\n+\t\tgoto err;\n+\t}\n+\tcrypto-\u003erx_hp_tfm = tfm;\n+\n+\t/* Allocate AEAD and HP transform for each TX key phase. */\n+\ttfm = crypto_alloc_aead(cipher-\u003eaead, 0, 0);\n+\tif (IS_ERR(tfm)) {\n+\t\terr = PTR_ERR(tfm);\n+\t\tgoto err;\n+\t}\n+\tcrypto-\u003etx_tfm[0] = tfm;\n+\ttfm = crypto_alloc_aead(cipher-\u003eaead, 0, 0);\n+\tif (IS_ERR(tfm)) {\n+\t\terr = PTR_ERR(tfm);\n+\t\tgoto err;\n+\t}\n+\tcrypto-\u003etx_tfm[1] = tfm;\n+\ttfm = crypto_alloc_sync_skcipher(cipher-\u003eskc, 0, 0);\n+\tif (IS_ERR(tfm)) {\n+\t\terr = PTR_ERR(tfm);\n+\t\tgoto err;\n+\t}\n+\tcrypto-\u003etx_hp_tfm = tfm;\n+\n+\tcrypto-\u003ecipher = cipher;\n+\tcrypto-\u003ecipher_type = type;\n+\treturn 0;\n+err:\n+\tquic_crypto_free(crypto);\n+\treturn err;\n+}\n+\n+int quic_crypto_set_secret(struct quic_crypto *crypto,\n+\t\t\t   struct quic_crypto_secret *srt, u32 version)\n+{\n+\tconst struct quic_cipher *cipher;\n+\tu8 phase = crypto-\u003ekey_phase;\n+\tint err;\n+\n+\t/* If no cipher has been initialized yet, set it up. */\n+\tif (!crypto-\u003ecipher) {\n+\t\terr = quic_crypto_set_cipher(crypto, srt-\u003etype);\n+\t\tif (err)\n+\t\t\treturn err;\n+\t}\n+\tcipher = crypto-\u003ecipher;\n+\n+\t/* Handle RX path setup. */\n+\tif (!srt-\u003esend) {\n+\t\tcrypto-\u003eversion = version;\n+\t\tmemcpy(crypto-\u003erx_secret[phase], srt-\u003esecret,\n+\t\t       cipher-\u003esecretlen);\n+\t\terr = quic_crypto_keys_derive_and_install(crypto, true, phase);\n+\t\tif (err)\n+\t\t\treturn err;\n+\t\tcrypto-\u003erecv_ready = 1;\n+\t\treturn 0;\n+\t}\n+\n+\t/* Handle TX path setup. */\n+\tcrypto-\u003eversion = version;\n+\tmemcpy(crypto-\u003etx_secret[phase], srt-\u003esecret, cipher-\u003esecretlen);\n+\terr = quic_crypto_keys_derive_and_install(crypto, false, phase);\n+\tif (err)\n+\t\treturn err;\n+\tcrypto-\u003esend_ready = 1;\n+\treturn 0;\n+}\n+\n+/* Save token secret in Initial TX secret (phase 1) for token generation. */\n+int quic_crypto_set_token_secret(struct quic_crypto *crypto)\n+{\n+\t/* Reuse TX AEAD (phase 1) in Initial crypto. */\n+\tu8 key[TLS_CIPHER_AES_GCM_128_KEY_SIZE], *srt = crypto-\u003etx_secret[1];\n+\tstruct crypto_aead *tfm = crypto-\u003etx_tfm[1];\n+\tstruct quic_data s = {}, k, i;\n+\tint err;\n+\n+\tif (!memchr_inv(srt, 0, TLS_CIPHER_AES_GCM_128_SECRET_SIZE))\n+\t\tget_random_bytes(srt, TLS_CIPHER_AES_GCM_128_SECRET_SIZE);\n+\n+\tquic_data(\u0026s, srt, TLS_CIPHER_AES_GCM_128_SECRET_SIZE);\n+\tquic_data(\u0026k, key, TLS_CIPHER_AES_GCM_128_KEY_SIZE);\n+\tquic_data(\u0026i, crypto-\u003etx_iv[1], QUIC_IV_LEN);\n+\terr = quic_crypto_keys_derive(crypto-\u003esecret_tfm, \u0026s, \u0026k, \u0026i, NULL,\n+\t\t\t\t      QUIC_VERSION_V1);\n+\tif (err)\n+\t\tgoto out;\n+\terr = crypto_aead_setauthsize(tfm, QUIC_TAG_LEN);\n+\tif (err)\n+\t\tgoto out;\n+\terr = crypto_aead_setkey(tfm, key, TLS_CIPHER_AES_GCM_128_KEY_SIZE);\n+out:\n+\tmemzero_explicit(key, sizeof(key));\n+\treturn err;\n+}\n+\n+/* Initiating a Key Update. */\n+int quic_crypto_key_update(struct quic_crypto *crypto)\n+{\n+\tstruct quic_data l = {KU_LABEL_V1, strlen(KU_LABEL_V1)};\n+\tu8 phase = crypto-\u003ekey_phase;\n+\tstruct quic_data k, srt;\n+\tu32 secret_len;\n+\tint err;\n+\n+\tif (crypto-\u003ekey_pending || !crypto-\u003erecv_ready)\n+\t\treturn -EINVAL;\n+\tif (crypto-\u003ekey_derived)\n+\t\treturn 0;\n+\n+\t/* rfc9001#section-6.1:\n+\t *\n+\t * Endpoints maintain separate read and write secrets for packet\n+\t * protection. An endpoint initiates a key update by updating its\n+\t * packet protection write secret and using that to protect new\n+\t * packets. The endpoint creates a new write secret from the existing\n+\t * write secret. This uses the KDF function provided by TLS with a\n+\t * label of \"quic ku\". The corresponding key and IV are created from\n+\t * that secret. The header protection key is not updated.\n+\t *\n+\t * For example, to update write keys with TLS 1.3, HKDF-Expand-Label is\n+\t * used as:\n+\t *   secret_\u003cn+1\u003e = HKDF-Expand-Label(secret_\u003cn\u003e, \"quic ku\",\n+\t *                                    \"\", Hash.length)\n+\t */\n+\tsecret_len = crypto-\u003ecipher-\u003esecretlen;\n+\tif (crypto-\u003eversion == QUIC_VERSION_V2)\n+\t\tquic_data(\u0026l, KU_LABEL_V2, strlen(KU_LABEL_V2));\n+\n+\tquic_data(\u0026srt, crypto-\u003etx_secret[phase], secret_len);\n+\tquic_data(\u0026k, crypto-\u003etx_secret[!phase], secret_len);\n+\terr = quic_crypto_hkdf_expand(crypto-\u003esecret_tfm, \u0026srt, \u0026l, \u0026k);\n+\tif (err)\n+\t\treturn err;\n+\terr = quic_crypto_keys_derive_and_install(crypto, false, !phase);\n+\tif (err)\n+\t\treturn err;\n+\n+\tquic_data(\u0026srt, crypto-\u003erx_secret[phase], secret_len);\n+\tquic_data(\u0026k, crypto-\u003erx_secret[!phase], secret_len);\n+\terr = quic_crypto_hkdf_expand(crypto-\u003esecret_tfm, \u0026srt, \u0026l, \u0026k);\n+\tif (err)\n+\t\treturn err;\n+\terr = quic_crypto_keys_derive_and_install(crypto, true, !phase);\n+\tif (err)\n+\t\treturn err;\n+\n+\tcrypto-\u003ekey_derived = 1;\n+\treturn 0;\n+}\n+\n+void quic_crypto_free(struct quic_crypto *crypto)\n+{\n+\tif (crypto-\u003erx_tfm[0])\n+\t\tcrypto_free_aead(crypto-\u003erx_tfm[0]);\n+\tif (crypto-\u003erx_tfm[1])\n+\t\tcrypto_free_aead(crypto-\u003erx_tfm[1]);\n+\tif (crypto-\u003etx_tfm[0])\n+\t\tcrypto_free_aead(crypto-\u003etx_tfm[0]);\n+\tif (crypto-\u003etx_tfm[1])\n+\t\tcrypto_free_aead(crypto-\u003etx_tfm[1]);\n+\tif (crypto-\u003esecret_tfm)\n+\t\tcrypto_free_shash(crypto-\u003esecret_tfm);\n+\tif (crypto-\u003erx_hp_tfm)\n+\t\tcrypto_free_skcipher(crypto-\u003erx_hp_tfm);\n+\tif (crypto-\u003etx_hp_tfm)\n+\t\tcrypto_free_skcipher(crypto-\u003etx_hp_tfm);\n+\n+\tmemzero_explicit(crypto, offsetof(struct quic_crypto, send_offset));\n+}\n+\n+#define QUIC_INITIAL_SALT_V1 \\\n+\t\"\\x38\\x76\\x2c\\xf7\\xf5\\x59\\x34\\xb3\\x4d\\x17\" \\\n+\t\"\\x9a\\xe6\\xa4\\xc8\\x0c\\xad\\xcc\\xbb\\x7f\\x0a\"\n+\n+#define QUIC_INITIAL_SALT_V2 \\\n+\t\"\\x0d\\xed\\xe3\\xde\\xf7\\x00\\xa6\\xdb\\x81\\x93\" \\\n+\t\"\\x81\\xbe\\x6e\\x26\\x9d\\xcb\\xf9\\xbd\\x2e\\xd9\"\n+\n+#define QUIC_INITIAL_SALT_LEN\t20\n+\n+/* Initial Secrets. */\n+int quic_crypto_initial_keys_install(struct quic_crypto *crypto,\n+\t\t\t\t     struct quic_conn_id *conn_id,\n+\t\t\t\t     u32 version, bool is_serv)\n+{\n+\tu8 secret[TLS_CIPHER_AES_GCM_128_SECRET_SIZE];\n+\tstruct quic_data salt, s, k, l, dcid;\n+\tstruct quic_crypto_secret srt = {};\n+\tchar *tl, *rl, *sal;\n+\tint err;\n+\n+\t/* rfc9001#section-5.2:\n+\t *\n+\t * The secret used by clients to construct Initial packets uses the PRK\n+\t * and the label \"client in\" as input to the HKDF-Expand-Label function\n+\t * from TLS [TLS13] to produce a 32-byte secret. Packets constructed by\n+\t * the server use the same process with the label \"server in\". The hash\n+\t * function for HKDF when deriving initial secrets and keys is SHA-256\n+\t * [SHA].\n+\t *\n+\t * This process in pseudocode is:\n+\t *\n+\t *   initial_salt = 0x38762cf7f55934b34d179ae6a4c80cadccbb7f0a\n+\t *   initial_secret = HKDF-Extract(initial_salt,\n+\t *                                 client_dst_connection_id)\n+\t *\n+\t *   client_initial_secret = HKDF-Expand-Label(initial_secret,\n+\t *                                             \"client in\", \"\",\n+\t *                                             Hash.length)\n+\t *   server_initial_secret = HKDF-Expand-Label(initial_secret,\n+\t *                                             \"server in\", \"\",\n+\t *                                             Hash.length)\n+\t */\n+\tif (is_serv) {\n+\t\trl = \"client in\";\n+\t\ttl = \"server in\";\n+\t} else {\n+\t\ttl = \"client in\";\n+\t\trl = \"server in\";\n+\t}\n+\tsal = QUIC_INITIAL_SALT_V1;\n+\tif (version == QUIC_VERSION_V2)\n+\t\tsal = QUIC_INITIAL_SALT_V2;\n+\tquic_data(\u0026salt, sal, QUIC_INITIAL_SALT_LEN);\n+\tquic_data(\u0026dcid, conn_id-\u003edata, conn_id-\u003elen);\n+\tquic_data(\u0026s, secret, TLS_CIPHER_AES_GCM_128_SECRET_SIZE);\n+\terr = quic_crypto_hkdf_extract(crypto-\u003esecret_tfm, \u0026salt, \u0026dcid, \u0026s);\n+\tif (err)\n+\t\tgoto out;\n+\n+\tquic_data(\u0026l, tl, strlen(tl));\n+\tquic_data(\u0026k, srt.secret, TLS_CIPHER_AES_GCM_128_SECRET_SIZE);\n+\tsrt.type = TLS_CIPHER_AES_GCM_128;\n+\tsrt.send = 1;\n+\terr = quic_crypto_hkdf_expand(crypto-\u003esecret_tfm, \u0026s, \u0026l, \u0026k);\n+\tif (err)\n+\t\tgoto out;\n+\terr = quic_crypto_set_secret(crypto, \u0026srt, version);\n+\tif (err)\n+\t\tgoto out;\n+\n+\tquic_data(\u0026l, rl, strlen(rl));\n+\tquic_data(\u0026k, srt.secret, TLS_CIPHER_AES_GCM_128_SECRET_SIZE);\n+\tsrt.type = TLS_CIPHER_AES_GCM_128;\n+\tsrt.send = 0;\n+\terr = quic_crypto_hkdf_expand(crypto-\u003esecret_tfm, \u0026s, \u0026l, \u0026k);\n+\tif (err)\n+\t\tgoto out;\n+\terr = quic_crypto_set_secret(crypto, \u0026srt, version);\n+out:\n+\tmemzero_explicit(secret, sizeof(secret));\n+\tmemzero_explicit(\u0026srt, sizeof(srt));\n+\treturn err;\n+}\n+\n+#define QUIC_RETRY_KEY_V1 \\\n+\t\"\\xbe\\x0c\\x69\\x0b\\x9f\\x66\\x57\\x5a\\x1d\\x76\\x6b\\x54\\xe3\\x68\\xc8\\x4e\"\n+#define QUIC_RETRY_KEY_V2 \\\n+\t\"\\x8f\\xb4\\xb0\\x1b\\x56\\xac\\x48\\xe2\\x60\\xfb\\xcb\\xce\\xad\\x7c\\xcc\\x92\"\n+\n+#define QUIC_RETRY_NONCE_V1 \"\\x46\\x15\\x99\\xd3\\x5d\\x63\\x2b\\xf2\\x23\\x98\\x25\\xbb\"\n+#define QUIC_RETRY_NONCE_V2 \"\\xd8\\x69\\x69\\xbc\\x2d\\x7c\\x6d\\x99\\x90\\xef\\xb0\\x4a\"\n+\n+/* Retry Packet Integrity. */\n+int quic_crypto_get_retry_tag(struct quic_crypto *crypto, struct sk_buff *skb,\n+\t\t\t      struct quic_conn_id *odcid, u32 version, u8 *tag)\n+{\n+\t/* Reuse RX AEAD (phase 1) in Initial crypto. */\n+\tstruct crypto_aead *tfm = crypto-\u003erx_tfm[1];\n+\tu8 *pseudo_retry, *p, *iv, *key;\n+\tDECLARE_CRYPTO_WAIT(wait);\n+\tstruct aead_request *req;\n+\tstruct scatterlist *sg;\n+\tu32 plen;\n+\tint err;\n+\n+\t/* The caller must ensure skb-\u003elen \u003e QUIC_TAG_LEN. */\n+\tif (skb-\u003elen \u003c= QUIC_TAG_LEN)\n+\t\treturn -EINVAL;\n+\n+\t/* rfc9001#section-5.8:\n+\t *\n+\t * The Retry Integrity Tag is a 128-bit field that is computed as the\n+\t * output of AEAD_AES_128_GCM used with the following inputs:\n+\t *\n+\t * - The secret key, K, is 128 bits equal to\n+\t *   0xbe0c690b9f66575a1d766b54e368c84e.\n+\t * - The nonce, N, is 96 bits equal to 0x461599d35d632bf2239825bb.\n+\t * - The plaintext, P, is empty.\n+\t * - The associated data, A, is the contents of the Retry\n+\t *   Pseudo-Packet,\n+\t *\n+\t * The Retry Pseudo-Packet is not sent over the wire. It is computed by\n+\t * taking the transmitted Retry packet, removing the Retry Integrity\n+\t * Tag, and prepending the two following fields: ODCID Length +\n+\t * Original Destination Connection ID (ODCID).\n+\t */\n+\terr = crypto_aead_setauthsize(tfm, QUIC_TAG_LEN);\n+\tif (err)\n+\t\treturn err;\n+\tkey = QUIC_RETRY_KEY_V1;\n+\tif (version == QUIC_VERSION_V2)\n+\t\tkey = QUIC_RETRY_KEY_V2;\n+\terr = crypto_aead_setkey(tfm, key, TLS_CIPHER_AES_GCM_128_KEY_SIZE);\n+\tif (err)\n+\t\treturn err;\n+\n+\tplen = 1 + odcid-\u003elen + skb-\u003elen - QUIC_TAG_LEN;\n+\tpseudo_retry = quic_crypto_aead_mem_alloc(tfm, plen + QUIC_TAG_LEN, \u0026iv,\n+\t\t\t\t\t\t  \u0026req, \u0026sg, 1, GFP_KERNEL);\n+\tif (!pseudo_retry)\n+\t\treturn -ENOMEM;\n+\n+\tp = pseudo_retry;\n+\tp = quic_put_int(p, odcid-\u003elen, 1);\n+\tp = quic_put_data(p, odcid-\u003edata, odcid-\u003elen);\n+\tp = quic_put_data(p, skb-\u003edata, skb-\u003elen - QUIC_TAG_LEN);\n+\tsg_init_one(sg, pseudo_retry, plen + QUIC_TAG_LEN);\n+\n+\tmemcpy(iv, QUIC_RETRY_NONCE_V1, QUIC_IV_LEN);\n+\tif (version == QUIC_VERSION_V2)\n+\t\tmemcpy(iv, QUIC_RETRY_NONCE_V2, QUIC_IV_LEN);\n+\taead_request_set_tfm(req, tfm);\n+\taead_request_set_ad(req, plen);\n+\taead_request_set_crypt(req, sg, sg, 0, iv);\n+\taead_request_set_callback(req, CRYPTO_TFM_REQ_MAY_BACKLOG,\n+\t\t\t\t  crypto_req_done, \u0026wait);\n+\terr = crypto_aead_encrypt(req);\n+\tif (err == -EINPROGRESS || err == -EBUSY)\n+\t\terr = crypto_wait_req(err, \u0026wait);\n+\tif (!err)\n+\t\tmemcpy(tag, p, QUIC_TAG_LEN);\n+\n+\tkfree_sensitive(pseudo_retry);\n+\treturn err;\n+}\n+\n+/* Derives a key and IV using HKDF, configures the AEAD transform and performs\n+ * AEAD encryption/decryption for the provided token.\n+ */\n+static int quic_crypto_token_protect(struct quic_crypto *crypto, u8 *token,\n+\t\t\t\t     u32 len, u32 adlen, bool enc)\n+{\n+\t/* Reuse TX AEAD (phase 1) in Initial crypto. */\n+\tstruct crypto_aead *tfm = crypto-\u003etx_tfm[1];\n+\tu32 extra = enc ? QUIC_TAG_LEN : 0, tslen;\n+\tDECLARE_CRYPTO_WAIT(wait);\n+\tstruct aead_request *req;\n+\tstruct scatterlist *sg;\n+\tvoid *ctx = NULL;\n+\tu8 *nonce, *p, i;\n+\t__be64 n;\n+\tint err;\n+\tu64 ts;\n+\n+\tctx = quic_crypto_aead_mem_alloc(tfm, 0, \u0026nonce, \u0026req, \u0026sg, 1,\n+\t\t\t\t\t GFP_KERNEL);\n+\tif (!ctx) {\n+\t\terr = -ENOMEM;\n+\t\tgoto out;\n+\t}\n+\tmemcpy(nonce, crypto-\u003etx_iv[1], QUIC_IV_LEN);\n+\n+\ttslen = sizeof(ts);\n+\tp = token + adlen - tslen;\n+\tquic_get_int(\u0026p, \u0026tslen, \u0026ts, tslen);\n+\n+\tn = cpu_to_be64(ts);\n+\tfor (i = 0; i \u003c sizeof(n); i++)\n+\t\tnonce[QUIC_IV_LEN - sizeof(n) + i] ^= ((u8 *)\u0026n)[i];\n+\n+\tsg_init_one(sg, token, len);\n+\taead_request_set_tfm(req, tfm);\n+\taead_request_set_ad(req, adlen);\n+\taead_request_set_crypt(req, sg, sg, len - adlen - extra, nonce);\n+\taead_request_set_callback(req, CRYPTO_TFM_REQ_MAY_BACKLOG,\n+\t\t\t\t  crypto_req_done, \u0026wait);\n+\terr = enc ? crypto_aead_encrypt(req) : crypto_aead_decrypt(req);\n+\tif (err == -EINPROGRESS || err == -EBUSY)\n+\t\terr = crypto_wait_req(err, \u0026wait);\n+\n+out:\n+\tkfree_sensitive(ctx);\n+\treturn err;\n+}\n+\n+/* Generate a token for Retry or address validation.\n+ *\n+ * Builds a token with the format: [flag][client address][timestamp][original\n+ * DCID][auth tag]\n+ *\n+ * Encrypts the token (excluding the first flag byte) using AES-GCM with a key\n+ * and IV derived via HKDF. The original DCID is stored to be recovered later\n+ * from a Client Initial packet.  Ensures the token is bound to the client\n+ * address and time, preventing reuse or tampering.\n+ *\n+ * Returns 0 on success or a negative error code on failure.\n+ */\n+int quic_crypto_generate_token(struct quic_crypto *crypto, void *addr,\n+\t\t\t       u32 addrlen, struct quic_conn_id *conn_id,\n+\t\t\t       u8 *token, u32 *tlen)\n+{\n+\tu8 *token_buf, *p, flag = *token;\n+\tu64 ts = quic_ktime_get_us();\n+\tu32 tslen = sizeof(ts);\n+\tint err, len;\n+\n+\tlen = sizeof(flag) + addrlen + tslen + conn_id-\u003elen + QUIC_TAG_LEN;\n+\ttoken_buf = kmalloc(len, GFP_KERNEL);\n+\tif (!token_buf)\n+\t\treturn -ENOMEM;\n+\n+\tp = token_buf;\n+\tp = quic_put_int(p, flag, sizeof(flag));\n+\tp = quic_put_data(p, addr, addrlen);\n+\tp = quic_put_int(p, ts, tslen);\n+\tquic_put_data(p, conn_id-\u003edata, conn_id-\u003elen);\n+\n+\terr = quic_crypto_token_protect(crypto, token_buf, len,\n+\t\t\t\t\tsizeof(flag) + addrlen + tslen, true);\n+\tif (err)\n+\t\tgoto out;\n+\n+\tmemcpy(token, token_buf, len);\n+\t*tlen = len;\n+out:\n+\tkfree(token_buf);\n+\treturn err;\n+}\n+\n+/* Validate a Retry or address validation token.\n+ *\n+ * Decrypts the token using derived key and IV. Checks that the decrypted\n+ * address matches the provided address, validates the embedded timestamp\n+ * against current time with a version-specific timeout. If applicable, it\n+ * extracts and returns the original destination connection ID (ODCID) for\n+ * Retry packets.\n+ *\n+ * Returns 0 if the token is valid, -EINVAL if invalid, or another negative\n+ * error code.\n+ */\n+int quic_crypto_verify_token(struct quic_crypto *crypto, void *addr,\n+\t\t\t     u32 addrlen, struct quic_conn_id *conn_id,\n+\t\t\t     u8 *token, u32 len)\n+{\n+\tu64 t, ts = quic_ktime_get_us(), timeout = QUIC_TOKEN_TIMEOUT_RETRY;\n+\tu8 *token_buf, *p, flag;\n+\tu32 tslen = sizeof(ts);\n+\tint err;\n+\n+\tif (len \u003c sizeof(flag) + addrlen + tslen + QUIC_TAG_LEN)\n+\t\treturn -EINVAL;\n+\ttoken_buf = kmemdup(token, len, GFP_KERNEL);\n+\tif (!token_buf)\n+\t\treturn -ENOMEM;\n+\n+\terr = quic_crypto_token_protect(crypto, token_buf, len,\n+\t\t\t\t\tsizeof(flag) + addrlen + tslen, false);\n+\tif (err)\n+\t\tgoto out;\n+\n+\terr = -EINVAL;\n+\tp = token_buf;\n+\tflag = *p++;\n+\tlen -= sizeof(flag);\n+\tif (crypto_memneq(p, addr, addrlen))\n+\t\tgoto out;\n+\n+\tp += addrlen;\n+\tlen -= addrlen;\n+\tif (flag == QUIC_TOKEN_FLAG_REGULAR)\n+\t\ttimeout = QUIC_TOKEN_TIMEOUT_REGULAR;\n+\tif (!quic_get_int(\u0026p, \u0026len, \u0026t, tslen) || t \u003e ts || ts - t \u003e timeout)\n+\t\tgoto out;\n+\n+\tlen -= QUIC_TAG_LEN;\n+\tif (len \u003e QUIC_CONN_ID_MAX_LEN)\n+\t\tgoto out;\n+\n+\tif (flag == QUIC_TOKEN_FLAG_RETRY)\n+\t\tquic_conn_id_update(conn_id, p, len);\n+\terr = 0;\n+out:\n+\tkfree(token_buf);\n+\treturn err;\n+}\n+\n+/* Derive a secret using HKDF-Extract and HKDF-Expand with the given label.\n+ * Used to generate a stateless reset token or session resumption master key.\n+ */\n+int quic_crypto_derive_secret(struct quic_crypto *crypto, void *data, u32 len,\n+\t\t\t      char *label, u8 *srt, u32 srt_len)\n+{\n+\tstruct crypto_shash *tfm = crypto-\u003esecret_tfm;\n+\tu8 secret[TLS_CIPHER_AES_GCM_128_SECRET_SIZE];\n+\tstruct quic_data salt, s, l, k;\n+\tint err;\n+\n+\tquic_data(\u0026salt, data, len);\n+\tquic_data(\u0026k, crypto-\u003etx_secret[1], TLS_CIPHER_AES_GCM_128_SECRET_SIZE);\n+\tquic_data(\u0026s, secret, TLS_CIPHER_AES_GCM_128_SECRET_SIZE);\n+\terr = quic_crypto_hkdf_extract(tfm, \u0026salt, \u0026k, \u0026s);\n+\tif (err)\n+\t\tgoto out;\n+\n+\tquic_data(\u0026l, label, strlen(label));\n+\tquic_data(\u0026k, srt, srt_len);\n+\terr = quic_crypto_hkdf_expand(tfm, \u0026s, \u0026l, \u0026k);\n+out:\n+\tmemzero_explicit(secret, sizeof(secret));\n+\treturn err;\n+}\ndiff --git a/net/quic/crypto.h b/net/quic/crypto.h\nnew file mode 100644\nindex 00000000000000..77281a824f721c\n--- /dev/null\n+++ b/net/quic/crypto.h\n@@ -0,0 +1,88 @@\n+/* SPDX-License-Identifier: GPL-2.0-or-later */\n+/* QUIC kernel implementation\n+ * (C) Copyright Red Hat Corp. 2023\n+ *\n+ * This file is part of the QUIC kernel implementation\n+ *\n+ * Written or modified by:\n+ *    Xin Long \u003clucien.xin@gmail.com\u003e\n+ */\n+\n+#define QUIC_TAG_LEN\t16\n+#define QUIC_IV_LEN\t12\n+#define QUIC_KEY_LEN\t32\n+#define QUIC_SECRET_LEN\t48\n+\n+#define QUIC_TOKEN_FLAG_REGULAR\t\t0\n+#define QUIC_TOKEN_FLAG_RETRY\t\t1\n+#define QUIC_TOKEN_TIMEOUT_RETRY\t3000000\n+#define QUIC_TOKEN_TIMEOUT_REGULAR\t600000000\n+\n+struct quic_cipher {\n+\tu32 secretlen; /* Length of the traffic secret */\n+\tu32 keylen;    /* Length of the AEAD key */\n+\n+\tchar *shash; /* Name of hash algorithm used for key derivation */\n+\tchar *aead;  /* Name of AEAD algorithm used for payload en/decryption */\n+\tchar *skc;   /* Name of cipher algorithm used for header protection */\n+};\n+\n+struct quic_crypto {\n+\tstruct crypto_skcipher *tx_hp_tfm; /* TX header protection tfm */\n+\tstruct crypto_skcipher *rx_hp_tfm; /* RX header protection tfm */\n+\tstruct crypto_shash *secret_tfm;   /* Key derivation (HKDF) tfm */\n+\tstruct crypto_aead *tx_tfm[2]; /* AEAD tfm for TX (key phase 0 and 1) */\n+\tstruct crypto_aead *rx_tfm[2]; /* AEAD tfm for RX (key phase 0 and 1) */\n+\n+\tconst struct quic_cipher *cipher;  /* Cipher info (selected cipher) */\n+\tu32 cipher_type; /* Cipher suite (e.g., AES_GCM_128, etc.) */\n+\n+\tu8 tx_secret[2][QUIC_SECRET_LEN]; /* TX secret (key phase 0 and 1) */\n+\tu8 rx_secret[2][QUIC_SECRET_LEN]; /* RX secret (key phase 0 and 1) */\n+\tu8 tx_iv[2][QUIC_IV_LEN];      /* IVs for TX (key phase 0 and 1) */\n+\tu8 rx_iv[2][QUIC_IV_LEN];      /* IVs for RX (key phase 0 and 1) */\n+\tatomic_t async_pending[2]; /* Async pending count (key phase 0 and 1) */\n+\n+\t/* Timestamp 1st packet sent after key update */\n+\tu64 key_update_send_time;\n+\tu64 key_update_time; /* Timestamp old keys retained after key update */\n+\tu32 version;         /* QUIC version in use */\n+\n+\tu8 ticket_ready:1; /* True if a session ticket is ready to read */\n+\tu8 key_pending:1;  /* A key update is in progress */\n+\tu8 key_derived:1;  /* Key derived for the key update */\n+\tu8 send_ready:1;   /* TX encryption context is initialized */\n+\tu8 recv_ready:1;   /* RX decryption context is initialized */\n+\tu8 key_phase:1;    /* Current key phase being used (0 or 1) */\n+\n+\tu64 send_offset; /* Number of handshake bytes sent by user */\n+\tu64 recv_offset; /* Number of handshake bytes read by user */\n+};\n+\n+int quic_crypto_set_secret(struct quic_crypto *crypto,\n+\t\t\t   struct quic_crypto_secret *srt, u32 version);\n+int quic_crypto_set_cipher(struct quic_crypto *crypto, u32 type);\n+int quic_crypto_key_update(struct quic_crypto *crypto);\n+\n+int quic_crypto_encrypt(struct quic_crypto *crypto, struct sk_buff *skb,\n+\t\t\tgfp_t gfp);\n+int quic_crypto_decrypt(struct quic_crypto *crypto, struct sk_buff *skb,\n+\t\t\tgfp_t gfp);\n+\n+int quic_crypto_derive_secret(struct quic_crypto *crypto, void *data, u32 len,\n+\t\t\t      char *label, u8 *srt, u32 srt_len);\n+int quic_crypto_initial_keys_install(struct quic_crypto *crypto,\n+\t\t\t\t     struct quic_conn_id *conn_id,\n+\t\t\t\t     u32 version, bool is_serv);\n+int quic_crypto_set_token_secret(struct quic_crypto *crypto);\n+\n+int quic_crypto_generate_token(struct quic_crypto *crypto, void *addr,\n+\t\t\t       u32 addrlen, struct quic_conn_id *conn_id,\n+\t\t\t       u8 *token, u32 *tlen);\n+int quic_crypto_get_retry_tag(struct quic_crypto *crypto, struct sk_buff *skb,\n+\t\t\t      struct quic_conn_id *odcid, u32 version, u8 *tag);\n+int quic_crypto_verify_token(struct quic_crypto *crypto, void *addr,\n+\t\t\t     u32 addrlen, struct quic_conn_id *conn_id,\n+\t\t\t     u8 *token, u32 len);\n+\n+void quic_crypto_free(struct quic_crypto *crypto);\ndiff --git a/net/quic/family.c b/net/quic/family.c\nnew file mode 100644\nindex 00000000000000..be792cfadabc9e\n--- /dev/null\n+++ b/net/quic/family.c\n@@ -0,0 +1,439 @@\n+// SPDX-License-Identifier: GPL-2.0-or-later\n+/* QUIC kernel implementation\n+ * (C) Copyright Red Hat Corp. 2023\n+ *\n+ * This file is part of the QUIC kernel implementation\n+ *\n+ * Initialization/cleanup for QUIC protocol support.\n+ *\n+ * Written or modified by:\n+ *    Xin Long \u003clucien.xin@gmail.com\u003e\n+ */\n+\n+#include \u003cnet/inet_common.h\u003e\n+#include \u003cnet/udp_tunnel.h\u003e\n+#include \u003clinux/icmp.h\u003e\n+\n+#include \"common.h\"\n+#include \"family.h\"\n+\n+static bool quic_v4_is_any_addr(union quic_addr *addr)\n+{\n+\treturn addr-\u003ev4.sin_addr.s_addr == htonl(INADDR_ANY);\n+}\n+\n+static bool quic_v6_is_any_addr(union quic_addr *addr)\n+{\n+\treturn ipv6_addr_any(\u0026addr-\u003ev6.sin6_addr);\n+}\n+\n+static void quic_v4_udp_conf_init(struct sock *sk, struct udp_port_cfg *conf,\n+\t\t\t\t  union quic_addr *a)\n+{\n+\tconf-\u003efamily = AF_INET;\n+\tconf-\u003elocal_ip.s_addr = a-\u003ev4.sin_addr.s_addr;\n+\tconf-\u003elocal_udp_port = a-\u003ev4.sin_port;\n+\tconf-\u003ebind_ifindex = sk-\u003esk_bound_dev_if;\n+}\n+\n+static void quic_v6_udp_conf_init(struct sock *sk, struct udp_port_cfg *conf,\n+\t\t\t\t  union quic_addr *a)\n+{\n+\tconf-\u003efamily = AF_INET6;\n+\tconf-\u003elocal_ip6 = a-\u003ev6.sin6_addr;\n+\tconf-\u003elocal_udp_port = a-\u003ev6.sin6_port;\n+\tconf-\u003euse_udp6_rx_checksums = true;\n+\tconf-\u003euse_udp6_tx_checksums = true;\n+\tconf-\u003eipv6_v6only = ipv6_only_sock(sk);\n+\tconf-\u003ebind_ifindex = quic_get_dev_if(sk, a);\n+}\n+\n+static int quic_v4_flow_route(struct sock *sk, union quic_addr *da,\n+\t\t\t      union quic_addr *sa, struct flowi *fl)\n+{\n+\tstruct flowi4 *fl4;\n+\tstruct rtable *rt;\n+\n+\tif (__sk_dst_check(sk, 0))\n+\t\treturn 1;\n+\n+\tmemset(fl, 0x00, sizeof(*fl));\n+\tfl4 = \u0026fl-\u003eu.ip4;\n+\tfl4-\u003esaddr = sa-\u003ev4.sin_addr.s_addr;\n+\tfl4-\u003efl4_sport = sa-\u003ev4.sin_port;\n+\tfl4-\u003edaddr = da-\u003ev4.sin_addr.s_addr;\n+\tfl4-\u003efl4_dport = da-\u003ev4.sin_port;\n+\tfl4-\u003eflowi4_proto = IPPROTO_UDP;\n+\tfl4-\u003eflowi4_oif = quic_get_dev_if(sk, da);\n+\n+\tfl4-\u003eflowi4_scope = ip_sock_rt_scope(sk);\n+\tfl4-\u003eflowi4_dscp = inet_sk_dscp(inet_sk(sk));\n+\n+\tfl4-\u003eflowi4_uid = sk_uid(sk);\n+\tfl4-\u003eflowi4_mark = sk-\u003esk_mark;\n+\n+\trt = ip_route_output_flow(sock_net(sk), fl4, sk);\n+\tif (IS_ERR(rt))\n+\t\treturn PTR_ERR(rt);\n+\n+\tif (quic_v4_is_any_addr(sa)) {\n+\t\tsa-\u003ev4.sin_family = AF_INET;\n+\t\tsa-\u003ev4.sin_addr.s_addr = fl4-\u003esaddr;\n+\t}\n+\tsk_setup_caps(sk, \u0026rt-\u003edst);\n+\treturn 0;\n+}\n+\n+static int quic_v6_flow_route(struct sock *sk, union quic_addr *da,\n+\t\t\t      union quic_addr *sa, struct flowi *fl)\n+{\n+\tstruct ipv6_pinfo *np = inet6_sk(sk);\n+\tstruct in6_addr *final_p, final;\n+\tstruct ip6_flowlabel *flowlabel;\n+\tstruct dst_entry *dst;\n+\tstruct flowi6 *fl6;\n+\n+\tif (__sk_dst_check(sk, np-\u003edst_cookie))\n+\t\treturn 1;\n+\n+\tmemset(fl, 0x00, sizeof(*fl));\n+\tfl6 = \u0026fl-\u003eu.ip6;\n+\tfl6-\u003esaddr = sa-\u003ev6.sin6_addr;\n+\tfl6-\u003efl6_sport = sa-\u003ev6.sin6_port;\n+\tfl6-\u003edaddr = da-\u003ev6.sin6_addr;\n+\tfl6-\u003efl6_dport = da-\u003ev6.sin6_port;\n+\tfl6-\u003eflowi6_proto = IPPROTO_UDP;\n+\tfl6-\u003eflowi6_oif = quic_get_dev_if(sk, da);\n+\n+\tif (inet6_test_bit(SNDFLOW, sk)) {\n+\t\tfl6-\u003eflowlabel = (da-\u003ev6.sin6_flowinfo \u0026 IPV6_FLOWINFO_MASK);\n+\t\tif (fl6-\u003eflowlabel \u0026 IPV6_FLOWLABEL_MASK) {\n+\t\t\tflowlabel = fl6_sock_lookup(sk, fl6-\u003eflowlabel);\n+\t\t\tif (IS_ERR(flowlabel))\n+\t\t\t\treturn -EINVAL;\n+\t\t\tfl6_sock_release(flowlabel);\n+\t\t}\n+\t}\n+\tfl6-\u003eflowlabel = ip6_make_flowinfo(np-\u003etclass, fl6-\u003eflowlabel);\n+\n+\tfl6-\u003eflowi6_uid = sk_uid(sk);\n+\tfl6-\u003eflowi6_mark = sk-\u003esk_mark;\n+\n+\trcu_read_lock();\n+\tfinal_p = fl6_update_dst(fl6, rcu_dereference(np-\u003eopt), \u0026final);\n+\trcu_read_unlock();\n+\n+\tdst = ip6_dst_lookup_flow(sock_net(sk), sk, fl6, final_p);\n+\tif (IS_ERR(dst))\n+\t\treturn PTR_ERR(dst);\n+\n+\tif (quic_v6_is_any_addr(sa)) {\n+\t\tsa-\u003ev6.sin6_family = AF_INET6;\n+\t\tsa-\u003ev6.sin6_addr = fl6-\u003esaddr;\n+\t\tif ((ipv6_addr_type(\u0026fl6-\u003esaddr) \u0026 IPV6_ADDR_LINKLOCAL))\n+\t\t\tsa-\u003ev6.sin6_scope_id = fl6-\u003eflowi6_oif;\n+\t}\n+\tip6_dst_store(sk, dst, false, false);\n+\treturn 0;\n+}\n+\n+static void quic_v4_lower_xmit(struct sock *sk, struct sk_buff *skb,\n+\t\t\t       struct flowi *fl)\n+{\n+\tstruct quic_skb_cb *cb = QUIC_SKB_CB(skb);\n+\tu8 tos = (inet_sk(sk)-\u003etos | cb-\u003eecn), ttl;\n+\tstruct flowi4 *fl4 = \u0026fl-\u003eu.ip4;\n+\tstruct dst_entry *dst;\n+\t__be16 df = 0;\n+\n+\tpr_debug(\"%s: skb: %p, len: %d, num: %lld, %pI4:%d -\u003e %pI4:%d\\n\",\n+\t\t __func__, skb, skb-\u003elen, cb-\u003enumber, \u0026fl4-\u003esaddr,\n+\t\t ntohs(fl4-\u003efl4_sport), \u0026fl4-\u003edaddr, ntohs(fl4-\u003efl4_dport));\n+\n+\tdst = sk_dst_get(sk);\n+\tif (!dst) {\n+\t\tkfree_skb(skb);\n+\t\treturn;\n+\t}\n+\tif (ip_dont_fragment(sk, dst) \u0026\u0026 !skb-\u003eignore_df)\n+\t\tdf = htons(IP_DF);\n+\n+\tttl = (u8)ip4_dst_hoplimit(dst);\n+\tudp_tunnel_xmit_skb((struct rtable *)dst, sk, skb, fl4-\u003esaddr,\n+\t\t\t    fl4-\u003edaddr, tos, ttl, df, fl4-\u003efl4_sport,\n+\t\t\t    fl4-\u003efl4_dport, false, false, 0);\n+}\n+\n+static void quic_v6_lower_xmit(struct sock *sk, struct sk_buff *skb,\n+\t\t\t       struct flowi *fl)\n+{\n+\tstruct quic_skb_cb *cb = QUIC_SKB_CB(skb);\n+\tu8 tc = (inet6_sk(sk)-\u003etclass | cb-\u003eecn), ttl;\n+\tstruct flowi6 *fl6 = \u0026fl-\u003eu.ip6;\n+\tstruct dst_entry *dst;\n+\t__be32 label;\n+\n+\tpr_debug(\"%s: skb: %p, len: %d, num: %lld, %pI6c:%d -\u003e %pI6c:%d\\n\",\n+\t\t __func__, skb, skb-\u003elen, cb-\u003enumber, \u0026fl6-\u003esaddr,\n+\t\t ntohs(fl6-\u003efl6_sport), \u0026fl6-\u003edaddr, ntohs(fl6-\u003efl6_dport));\n+\n+\tdst = sk_dst_get(sk);\n+\tif (!dst) {\n+\t\tkfree_skb(skb);\n+\t\treturn;\n+\t}\n+\n+\tttl = (u8)ip6_dst_hoplimit(dst);\n+\tlabel = ip6_make_flowlabel(sock_net(sk), skb, fl6-\u003eflowlabel, true,\n+\t\t\t\t   fl6);\n+\tudp_tunnel6_xmit_skb(dst, sk, skb, NULL, \u0026fl6-\u003esaddr, \u0026fl6-\u003edaddr, tc,\n+\t\t\t     ttl, label, fl6-\u003efl6_sport, fl6-\u003efl6_dport, false,\n+\t\t\t     0);\n+}\n+\n+static void quic_v4_get_msg_addrs(struct sk_buff *skb, union quic_addr *da,\n+\t\t\t\t  union quic_addr *sa)\n+{\n+\tstruct udphdr *uh = udp_hdr(skb);\n+\n+\tsa-\u003ev4.sin_family = AF_INET;\n+\tsa-\u003ev4.sin_port = uh-\u003esource;\n+\tsa-\u003ev4.sin_addr.s_addr = ip_hdr(skb)-\u003esaddr;\n+\n+\tda-\u003ev4.sin_family = AF_INET;\n+\tda-\u003ev4.sin_port = uh-\u003edest;\n+\tda-\u003ev4.sin_addr.s_addr = ip_hdr(skb)-\u003edaddr;\n+}\n+\n+static void quic_v6_get_msg_addrs(struct sk_buff *skb, union quic_addr *da,\n+\t\t\t\t  union quic_addr *sa)\n+{\n+\tstruct udphdr *uh = udp_hdr(skb);\n+\n+\tsa-\u003ev6.sin6_family = AF_INET6;\n+\tsa-\u003ev6.sin6_port = uh-\u003esource;\n+\tsa-\u003ev6.sin6_addr = ipv6_hdr(skb)-\u003esaddr;\n+\tsa-\u003ev6.sin6_scope_id = skb-\u003eskb_iif;\n+\n+\tda-\u003ev6.sin6_family = AF_INET6;\n+\tda-\u003ev6.sin6_port = uh-\u003edest;\n+\tda-\u003ev6.sin6_addr = ipv6_hdr(skb)-\u003edaddr;\n+\tda-\u003ev6.sin6_scope_id = skb-\u003eskb_iif;\n+}\n+\n+static int quic_v4_get_mtu_info(struct sk_buff *skb, u32 *info)\n+{\n+\tstruct icmphdr *hdr;\n+\n+\thdr = (struct icmphdr *)(skb_network_header(skb) -\n+\t\t\t\t sizeof(struct icmphdr));\n+\tif (hdr-\u003etype == ICMP_DEST_UNREACH \u0026\u0026 hdr-\u003ecode == ICMP_FRAG_NEEDED) {\n+\t\t*info = ntohs(hdr-\u003eun.frag.mtu);\n+\t\treturn 0;\n+\t}\n+\n+\t/* Defer other types' processing to UDP error handler. */\n+\treturn -EINVAL;\n+}\n+\n+static int quic_v6_get_mtu_info(struct sk_buff *skb, u32 *info)\n+{\n+\tstruct icmp6hdr *hdr;\n+\n+\thdr = (struct icmp6hdr *)(skb_network_header(skb) -\n+\t\t\t\t  sizeof(struct icmp6hdr));\n+\tif (hdr-\u003eicmp6_type == ICMPV6_PKT_TOOBIG) {\n+\t\t*info = ntohl(hdr-\u003eicmp6_mtu);\n+\t\treturn 0;\n+\t}\n+\n+\t/* Defer other types' processing to UDP error handler. */\n+\treturn -EINVAL;\n+}\n+\n+static bool quic_v4_cmp_sk_addr(struct sock *sk, union quic_addr *a,\n+\t\t\t\tunion quic_addr *addr)\n+{\n+\tif (a-\u003ev4.sin_port != addr-\u003ev4.sin_port)\n+\t\treturn false;\n+\tif (a-\u003ev4.sin_family != addr-\u003ev4.sin_family)\n+\t\treturn false;\n+\t/* No match: specific socket vs ANY lookup. */\n+\tif (addr-\u003ev4.sin_addr.s_addr == htonl(INADDR_ANY))\n+\t\treturn a-\u003ev4.sin_addr.s_addr == htonl(INADDR_ANY);\n+\tif (a-\u003ev4.sin_addr.s_addr == htonl(INADDR_ANY))\n+\t\treturn true;\n+\treturn a-\u003ev4.sin_addr.s_addr == addr-\u003ev4.sin_addr.s_addr;\n+}\n+\n+static bool quic_v6_cmp_sk_addr(struct sock *sk, union quic_addr *a,\n+\t\t\t\tunion quic_addr *addr)\n+{\n+\tif (a-\u003esa.sa_family == AF_INET \u0026\u0026 addr-\u003esa.sa_family == AF_INET)\n+\t\treturn quic_v4_cmp_sk_addr(sk, a, addr);\n+\n+\tif (a-\u003ev4.sin_port != addr-\u003ev4.sin_port)\n+\t\treturn false;\n+\n+\tif (a-\u003esa.sa_family != addr-\u003esa.sa_family) {\n+\t\tif (ipv6_only_sock(sk))\n+\t\t\treturn false;\n+\t\treturn quic_is_any_addr(a);\n+\t}\n+\n+\t/* No match: specific socket vs ANY lookup. */\n+\tif (ipv6_addr_any(\u0026addr-\u003ev6.sin6_addr))\n+\t\treturn ipv6_addr_any(\u0026a-\u003ev6.sin6_addr);\n+\tif (ipv6_addr_any(\u0026a-\u003ev6.sin6_addr))\n+\t\treturn true;\n+\tif (!ipv6_addr_equal(\u0026a-\u003ev6.sin6_addr, \u0026addr-\u003ev6.sin6_addr))\n+\t\treturn false;\n+\tif ((ipv6_addr_type(\u0026a-\u003ev6.sin6_addr) \u0026 IPV6_ADDR_LINKLOCAL) \u0026\u0026\n+\t    a-\u003ev6.sin6_scope_id \u0026\u0026 addr-\u003ev6.sin6_scope_id \u0026\u0026\n+\t    a-\u003ev6.sin6_scope_id != addr-\u003ev6.sin6_scope_id)\n+\t\treturn false;\n+\treturn true;\n+}\n+\n+static int quic_v4_get_sk_addr(struct socket *sock, struct sockaddr *uaddr,\n+\t\t\t       int peer)\n+{\n+\treturn inet_getname(sock, uaddr, peer);\n+}\n+\n+static int quic_v6_get_sk_addr(struct socket *sock, struct sockaddr *uaddr,\n+\t\t\t       int peer)\n+{\n+\tunion quic_addr *a = quic_addr(uaddr);\n+\tint ret;\n+\n+\tret = inet6_getname(sock, uaddr, peer);\n+\tif (ret \u003c 0)\n+\t\treturn ret;\n+\n+\tif (a-\u003esa.sa_family == AF_INET6 \u0026\u0026\n+\t    ipv6_addr_v4mapped(\u0026a-\u003ev6.sin6_addr)) {\n+\t\ta-\u003ev4.sin_family = AF_INET;\n+\t\ta-\u003ev4.sin_port = a-\u003ev6.sin6_port;\n+\t\ta-\u003ev4.sin_addr.s_addr = a-\u003ev6.sin6_addr.s6_addr32[3];\n+\t}\n+\n+\tif (a-\u003esa.sa_family == AF_INET) {\n+\t\tmemset(a-\u003ev4.sin_zero, 0, sizeof(a-\u003ev4.sin_zero));\n+\t\treturn sizeof(struct sockaddr_in);\n+\t}\n+\treturn sizeof(struct sockaddr_in6);\n+}\n+\n+#define quic_af_ipv4(a)\t\t((a)-\u003esa.sa_family == AF_INET)\n+\n+u32 quic_encap_len(union quic_addr *a)\n+{\n+\treturn (quic_af_ipv4(a) ? sizeof(struct iphdr) :\n+\t\t\t\t  sizeof(struct ipv6hdr)) +\n+\t       sizeof(struct udphdr);\n+}\n+\n+bool quic_is_any_addr(union quic_addr *a)\n+{\n+\treturn quic_af_ipv4(a) ? quic_v4_is_any_addr(a) :\n+\t\t\t\t quic_v6_is_any_addr(a);\n+}\n+\n+void quic_udp_conf_init(struct sock *sk, struct udp_port_cfg *conf,\n+\t\t\tunion quic_addr *a)\n+{\n+\tquic_af_ipv4(a) ? quic_v4_udp_conf_init(sk, conf, a) :\n+\t\t\t  quic_v6_udp_conf_init(sk, conf, a);\n+}\n+\n+int quic_flow_route(struct sock *sk, union quic_addr *da, union quic_addr *sa,\n+\t\t    struct flowi *fl)\n+{\n+\treturn quic_af_ipv4(da) ? quic_v4_flow_route(sk, da, sa, fl) :\n+\t\t\t\t  quic_v6_flow_route(sk, da, sa, fl);\n+}\n+\n+void quic_lower_xmit(struct sock *sk, struct sk_buff *skb, union quic_addr *da,\n+\t\t     struct flowi *fl)\n+{\n+\tlocal_bh_disable();\n+\tquic_af_ipv4(da) ? quic_v4_lower_xmit(sk, skb, fl) :\n+\t\t\t   quic_v6_lower_xmit(sk, skb, fl);\n+\tlocal_bh_enable();\n+}\n+\n+#define quic_skb_ipv4(skb)\t(ip_hdr(skb)-\u003eversion == 4)\n+\n+void quic_get_msg_addrs(struct sk_buff *skb, union quic_addr *da,\n+\t\t\tunion quic_addr *sa)\n+{\n+\tmemset(sa, 0, sizeof(*sa));\n+\tmemset(da, 0, sizeof(*da));\n+\tquic_skb_ipv4(skb) ? quic_v4_get_msg_addrs(skb, da, sa) :\n+\t\t\t     quic_v6_get_msg_addrs(skb, da, sa);\n+}\n+\n+int quic_get_mtu_info(struct sk_buff *skb, u32 *info)\n+{\n+\treturn quic_skb_ipv4(skb) ? quic_v4_get_mtu_info(skb, info) :\n+\t\t\t\t    quic_v6_get_mtu_info(skb, info);\n+}\n+\n+#define quic_pf_ipv4(sk)\t((sk)-\u003esk_family == PF_INET)\n+\n+bool quic_cmp_sk_addr(struct sock *sk, union quic_addr *a,\n+\t\t      union quic_addr *addr)\n+{\n+\treturn quic_pf_ipv4(sk) ? quic_v4_cmp_sk_addr(sk, a, addr) :\n+\t\t\t\t  quic_v6_cmp_sk_addr(sk, a, addr);\n+}\n+\n+int quic_get_sk_addr(struct socket *sock, struct sockaddr *a, int peer)\n+{\n+\treturn quic_pf_ipv4(sock-\u003esk) ? quic_v4_get_sk_addr(sock, a, peer) :\n+\t\t\t\t\tquic_v6_get_sk_addr(sock, a, peer);\n+}\n+\n+int quic_get_dev_if(struct sock *sk, union quic_addr *a)\n+{\n+\tif (!quic_af_ipv4(a) \u0026\u0026\n+\t    ipv6_addr_type(\u0026a-\u003ev6.sin6_addr) \u0026 IPV6_ADDR_LINKLOCAL \u0026\u0026\n+\t    a-\u003ev6.sin6_scope_id)\n+\t\treturn a-\u003ev6.sin6_scope_id;\n+\n+\treturn sk-\u003esk_bound_dev_if;\n+}\n+\n+void quic_set_skb_iif(struct sk_buff *skb)\n+{\n+\t/* Save the inet/inet6 iif before skb dst/cb are cleared. */\n+\tskb-\u003eskb_iif = quic_skb_ipv4(skb) ? inet_iif(skb) : inet6_iif(skb);\n+}\n+\n+int quic_common_setsockopt(struct sock *sk, int level, int optname,\n+\t\t\t   sockptr_t optval, unsigned int optlen)\n+{\n+\treturn quic_pf_ipv4(sk) ?\n+\t       ip_setsockopt(sk, level, optname, optval, optlen) :\n+\t       ipv6_setsockopt(sk, level, optname, optval, optlen);\n+}\n+\n+int quic_common_getsockopt(struct sock *sk, int level, int optname,\n+\t\t\t   char __user *optval, int __user *optlen)\n+{\n+\treturn quic_pf_ipv4(sk) ?\n+\t       ip_getsockopt(sk, level, optname, optval, optlen) :\n+\t       ipv6_getsockopt(sk, level, optname, optval, optlen);\n+}\n+\n+bool quic_sk_accept_pmtu(struct sock *sk, struct sk_buff *skb)\n+{\n+\treturn quic_skb_ipv4(skb) ? ip_sk_accept_pmtu(sk) :\n+\t\t\t\t    ip6_sk_accept_pmtu(sk);\n+}\n+\n+void quic_sk_destruct(struct sock *sk)\n+{\n+\tquic_pf_ipv4(sk) ? inet_sock_destruct(sk) : inet6_sock_destruct(sk);\n+}\ndiff --git a/net/quic/family.h b/net/quic/family.h\nnew file mode 100644\nindex 00000000000000..02342c4b7c99e2\n--- /dev/null\n+++ b/net/quic/family.h\n@@ -0,0 +1,44 @@\n+/* SPDX-License-Identifier: GPL-2.0-or-later */\n+/* QUIC kernel implementation\n+ * (C) Copyright Red Hat Corp. 2023\n+ *\n+ * This file is part of the QUIC kernel implementation\n+ *\n+ * Written or modified by:\n+ *    Xin Long \u003clucien.xin@gmail.com\u003e\n+ */\n+\n+#define QUIC_PORT_LEN\t\t2\n+#define QUIC_ADDR4_LEN\t\t4\n+#define QUIC_ADDR6_LEN\t\t16\n+\n+#define QUIC_PREF_ADDR_LEN \\\n+\t(QUIC_ADDR4_LEN + QUIC_PORT_LEN + QUIC_ADDR6_LEN + QUIC_PORT_LEN)\n+\n+bool quic_is_any_addr(union quic_addr *a);\n+u32 quic_encap_len(union quic_addr *a);\n+\n+void quic_lower_xmit(struct sock *sk, struct sk_buff *skb, union quic_addr *da,\n+\t\t     struct flowi *fl);\n+int quic_flow_route(struct sock *sk, union quic_addr *da, union quic_addr *sa,\n+\t\t    struct flowi *fl);\n+void quic_udp_conf_init(struct sock *sk, struct udp_port_cfg *conf,\n+\t\t\tunion quic_addr *a);\n+\n+void quic_get_msg_addrs(struct sk_buff *skb, union quic_addr *da,\n+\t\t\tunion quic_addr *sa);\n+int quic_get_mtu_info(struct sk_buff *skb, u32 *info);\n+\n+bool quic_cmp_sk_addr(struct sock *sk, union quic_addr *a,\n+\t\t      union quic_addr *addr);\n+int quic_get_sk_addr(struct socket *sock, struct sockaddr *a, int peer);\n+\n+int quic_get_dev_if(struct sock *sk, union quic_addr *a);\n+void quic_set_skb_iif(struct sk_buff *skb);\n+\n+int quic_common_setsockopt(struct sock *sk, int level, int optname,\n+\t\t\t   sockptr_t optval, unsigned int optlen);\n+int quic_common_getsockopt(struct sock *sk, int level, int optname,\n+\t\t\t   char __user *optval, int __user *optlen);\n+bool quic_sk_accept_pmtu(struct sock *sk, struct sk_buff *skb);\n+void quic_sk_destruct(struct sock *sk);\ndiff --git a/net/quic/packet.c b/net/quic/packet.c\nnew file mode 100644\nindex 00000000000000..57808cc5d95d3c\n--- /dev/null\n+++ b/net/quic/packet.c\n@@ -0,0 +1,890 @@\n+// SPDX-License-Identifier: GPL-2.0-or-later\n+/* QUIC kernel implementation\n+ * (C) Copyright Red Hat Corp. 2023\n+ *\n+ * This file is part of the QUIC kernel implementation\n+ *\n+ * Initialization/cleanup for QUIC protocol support.\n+ *\n+ * Written or modified by:\n+ *    Xin Long \u003clucien.xin@gmail.com\u003e\n+ */\n+\n+#include \"socket.h\"\n+\n+#define QUIC_HLEN\t\t1\n+\n+#define QUIC_LONG_HLEN(dcid, scid) \\\n+\t(QUIC_HLEN + QUIC_VERSION_LEN + 1 + (dcid)-\u003elen + 1 + (scid)-\u003elen)\n+\n+#define QUIC_VERSION_NUM\t2\n+\n+/* Supported QUIC versions and their compatible versions. Used for Compatible\n+ * Version Negotiation in rfc9368#section-2.3.\n+ */\n+static u32 quic_versions[QUIC_VERSION_NUM][4] = {\n+\t/* Version,\tCompatible Versions */\n+\t{ QUIC_VERSION_V1, QUIC_VERSION_V2, QUIC_VERSION_V1, 0 },\n+\t{ QUIC_VERSION_V2, QUIC_VERSION_V2, QUIC_VERSION_V1, 0 },\n+};\n+\n+/* Get the compatible version list for a given QUIC version. */\n+u32 *quic_packet_compatible_versions(u32 version)\n+{\n+\tu8 i;\n+\n+\tfor (i = 0; i \u003c QUIC_VERSION_NUM; i++)\n+\t\tif (version == quic_versions[i][0])\n+\t\t\treturn quic_versions[i];\n+\treturn NULL;\n+}\n+\n+/* Convert version-specific type to internal standard packet type. */\n+static u8 quic_packet_version_get_type(u32 version, u8 type)\n+{\n+\tif (version == QUIC_VERSION_V1)\n+\t\treturn type;\n+\n+\tswitch (type) {\n+\tcase QUIC_PACKET_INITIAL_V2:\n+\t\treturn QUIC_PACKET_INITIAL;\n+\tcase QUIC_PACKET_0RTT_V2:\n+\t\treturn QUIC_PACKET_0RTT;\n+\tcase QUIC_PACKET_HANDSHAKE_V2:\n+\t\treturn QUIC_PACKET_HANDSHAKE;\n+\tcase QUIC_PACKET_RETRY_V2:\n+\t\treturn QUIC_PACKET_RETRY;\n+\tdefault:\n+\t\treturn QUIC_PACKET_INVALID;\n+\t}\n+}\n+\n+/* Extracts a QUIC Connection ID from a buffer in the long header packet. */\n+static int quic_packet_get_connid(struct quic_conn_id *connid, u8 **pp,\n+\t\t\t\t  u32 *plen)\n+{\n+\tu64 len;\n+\n+\tif (!quic_get_int(pp, plen, \u0026len, 1) ||\n+\t    len \u003e *plen || len \u003e QUIC_CONN_ID_MAX_LEN)\n+\t\treturn -EINVAL;\n+\n+\tquic_conn_id_update(connid, *pp, len);\n+\t*plen -= len;\n+\t*pp += len;\n+\treturn 0;\n+}\n+\n+/* Parse QUIC version and connection IDs (DCID and SCID) from a Long header\n+ * packet buffer.\n+ */\n+static int quic_packet_get_long_header(struct quic_conn_id *dcid,\n+\t\t\t\t       struct quic_conn_id *scid, u32 *version,\n+\t\t\t\t       u8 **pp, u32 *plen)\n+{\n+\tint err;\n+\tu64 v;\n+\n+\t*pp += QUIC_HLEN;\n+\t*plen -= QUIC_HLEN;\n+\n+\tif (!quic_get_int(pp, plen, \u0026v, QUIC_VERSION_LEN))\n+\t\treturn -EINVAL;\n+\tif (version)\n+\t\t*version = v;\n+\n+\terr = quic_packet_get_connid(dcid, pp, plen);\n+\tif (err)\n+\t\treturn err;\n+\tif (!scid)\n+\t\treturn 0;\n+\treturn quic_packet_get_connid(scid, pp, plen);\n+}\n+\n+/* Extracts a QUIC token from a buffer in the Client Initial packet. */\n+static int quic_packet_get_token(struct quic_data *token, u8 **pp, u32 *plen)\n+{\n+\tu64 len;\n+\n+\tif (!quic_get_var(pp, plen, \u0026len) || len \u003e *plen)\n+\t\treturn -EINVAL;\n+\tquic_data(token, *pp, len);\n+\t*plen -= len;\n+\t*pp += len;\n+\treturn 0;\n+}\n+\n+/* Process PMTU reduction event on a QUIC socket. */\n+void quic_packet_rcv_err_pmtu(struct sock *sk)\n+{\n+\tstruct quic_path_group *paths = quic_paths(sk);\n+\tstruct quic_packet *packet = quic_packet(sk);\n+\tu32 pathmtu, info, taglen;\n+\tstruct dst_entry *dst;\n+\tbool reset_timer;\n+\n+\tif (quic_is_closed(sk))\n+\t\treturn;\n+\n+\tinfo = clamp(paths-\u003emtu_info, QUIC_PATH_MIN_PMTU, QUIC_PATH_MAX_PMTU);\n+\t/* If PLPMTUD is not enabled, update MSS using route and ICMP info. */\n+\tif (!paths-\u003eplpmtud_interval) {\n+\t\tif (quic_packet_route(sk))\n+\t\t\treturn;\n+\n+\t\tdst = __sk_dst_get(sk);\n+\t\tif (dst)\n+\t\t\tdst-\u003eops-\u003eupdate_pmtu(dst, sk, NULL, info, true);\n+\t\tquic_packet_mss_update(sk, info - packet-\u003ehlen);\n+\t\treturn;\n+\t}\n+\t/* PLPMTUD is enabled: adjust to smaller PMTU, subtract headers and\n+\t * AEAD tag.  Also notify the QUIC path layer for possible state\n+\t * changes and probing.\n+\t */\n+\tpacket-\u003elevel = QUIC_CRYPTO_APP;\n+\ttaglen = quic_packet_taglen(packet);\n+\tinfo = info - packet-\u003ehlen - taglen;\n+\tpathmtu = quic_path_pl_toobig(paths, info, \u0026reset_timer);\n+\tif (reset_timer)\n+\t\tquic_timer_reset(sk, QUIC_TIMER_PMTU, paths-\u003eplpmtud_interval);\n+\tif (pathmtu)\n+\t\tquic_packet_mss_update(sk, pathmtu + taglen);\n+}\n+\n+/* Handle ICMP Toobig packet and update QUIC socket path MTU. */\n+static int quic_packet_rcv_err(struct sock *sk, struct sk_buff *skb)\n+{\n+\tunion quic_addr daddr, saddr;\n+\tu32 info;\n+\n+\t/* ICMP embeds the original outgoing QUIC packet, so saddr/daddr are\n+\t * reversed when parsed. Only address-based socket lookup is possible\n+\t * in this case.\n+\t */\n+\tquic_get_msg_addrs(skb, \u0026saddr, \u0026daddr);\n+\tsk = quic_sock_lookup(skb, \u0026daddr, \u0026saddr, sk, NULL);\n+\tif (!sk)\n+\t\treturn -ENOENT;\n+\n+\tif (quic_get_mtu_info(skb, \u0026info) || !quic_sk_accept_pmtu(sk, skb)) {\n+\t\tsock_put(sk);\n+\t\treturn 0;\n+\t}\n+\n+\t/* Success: update socket path MTU info. */\n+\tbh_lock_sock(sk);\n+\tquic_paths(sk)-\u003emtu_info = info;\n+\tif (sock_owned_by_user(sk)) {\n+\t\t/* Socket locked by userspace. Defer MTU processing via\n+\t\t * release_cb. Hold socket reference to prevent it being\n+\t\t * freed before deferral.\n+\t\t */\n+\t\tif (!test_and_set_bit(QUIC_MTU_REDUCED_DEFERRED,\n+\t\t\t\t      \u0026sk-\u003esk_tsq_flags))\n+\t\t\tsock_hold(sk);\n+\t\tgoto out;\n+\t}\n+\t/* Otherwise, process the MTU reduction now. */\n+\tquic_packet_rcv_err_pmtu(sk);\n+out:\n+\tbh_unlock_sock(sk);\n+\tsock_put(sk);\n+\treturn 1;\n+}\n+\n+#define QUIC_PACKET_BACKLOG_MAX\t\t4096\n+\n+/* Queue a packet for later processing when sleeping is allowed. */\n+static int quic_packet_backlog_schedule(struct net *net, struct sk_buff *skb)\n+{\n+\tstruct quic_skb_cb *cb = QUIC_SKB_CB(skb);\n+\tstruct quic_net *qn = quic_net(net);\n+\tstruct sk_buff_head *head;\n+\n+\tif (cb-\u003ebacklog)\n+\t\treturn 0;\n+\n+\thead = \u0026qn-\u003ebacklog_list;\n+\tspin_lock_bh(\u0026head-\u003elock);\n+\tif (head-\u003eqlen \u003e= QUIC_PACKET_BACKLOG_MAX) {\n+\t\tspin_unlock_bh(\u0026head-\u003elock);\n+\t\tQUIC_INC_STATS(net, QUIC_MIB_PKT_RCVDROP);\n+\t\tkfree_skb(skb);\n+\t\treturn -ENOBUFS;\n+\t}\n+\tcb-\u003ebacklog = 1;\n+\t__skb_queue_tail(head, skb);\n+\tspin_unlock_bh(\u0026head-\u003elock);\n+\n+\tqueue_work(quic_wq, \u0026qn-\u003ework);\n+\treturn 1;\n+}\n+\n+#define TLS_MT_CLIENT_HELLO\t1\n+#define TLS_EXT_alpn\t\t16\n+\n+/*  TLS Client Hello Msg:\n+ *\n+ *    uint16 ProtocolVersion;\n+ *    opaque Random[32];\n+ *    uint8 CipherSuite[2];\n+ *\n+ *    struct {\n+ *        ExtensionType extension_type;\n+ *        opaque extension_data\u003c0..2^16-1\u003e;\n+ *    } Extension;\n+ *\n+ *    struct {\n+ *        ProtocolVersion legacy_version = 0x0303;\n+ *        Random rand;\n+ *        opaque legacy_session_id\u003c0..32\u003e;\n+ *        CipherSuite cipher_suites\u003c2..2^16-2\u003e;\n+ *        opaque legacy_compression_methods\u003c1..2^8-1\u003e;\n+ *        Extension extensions\u003c8..2^16-1\u003e;\n+ *    } ClientHello;\n+ */\n+\n+#define TLS_CH_RANDOM_LEN\t32\n+#define TLS_CH_VERSION_LEN\t2\n+#define TLS_MAX_EXTENSIONS\t128\n+\n+/* Extract ALPN data from a TLS ClientHello message.\n+ *\n+ * Parses the TLS ClientHello handshake message to find the ALPN (Application\n+ * Layer Protocol Negotiation) TLS extension. It validates the TLS ClientHello\n+ * structure, including version, random, session ID, cipher suites, compression\n+ * methods, and extensions. Once the ALPN extension is found, the ALPN\n+ * protocols list is extracted and stored in @alpn.\n+ *\n+ * Return: 0 on success or no ALPN found, a negative error code on failed\n+ * parsing.\n+ */\n+static int quic_packet_get_alpn(struct quic_data *alpn, u8 *p, u32 len)\n+{\n+\tint err = -EINVAL, found = 0, exts = 0;\n+\tu64 length, type;\n+\n+\t/* Verify handshake message type (ClientHello) and its length. */\n+\tif (!quic_get_int(\u0026p, \u0026len, \u0026type, 1) || type != TLS_MT_CLIENT_HELLO)\n+\t\treturn err;\n+\tif (!quic_get_int(\u0026p, \u0026len, \u0026length, 3) ||\n+\t    len \u003c TLS_CH_RANDOM_LEN + TLS_CH_VERSION_LEN ||\n+\t    length \u003c TLS_CH_RANDOM_LEN + TLS_CH_VERSION_LEN)\n+\t\treturn err;\n+\tif (len \u003e (u32)length) /* Cap len to handshake msg length. */\n+\t\tlen = length;\n+\t/* Skip legacy_version (2 bytes) + random (32 bytes). */\n+\tp += TLS_CH_RANDOM_LEN + TLS_CH_VERSION_LEN;\n+\tlen -= TLS_CH_RANDOM_LEN + TLS_CH_VERSION_LEN;\n+\t/* legacy_session_id_len must be zero (QUIC requirement). */\n+\tif (!quic_get_int(\u0026p, \u0026len, \u0026length, 1) || length)\n+\t\treturn err;\n+\n+\t/* Skip cipher_suites (2 bytes length + variable data). */\n+\tif (!quic_get_int(\u0026p, \u0026len, \u0026length, 2) || length \u003e (u64)len)\n+\t\treturn err;\n+\tlen -= length;\n+\tp += length;\n+\n+\t/* Skip legacy_compression_methods (1 byte length + variable data). */\n+\tif (!quic_get_int(\u0026p, \u0026len, \u0026length, 1) || length \u003e (u64)len)\n+\t\treturn err;\n+\tlen -= length;\n+\tp += length;\n+\n+\t/* Read TLS extensions length (2 bytes). */\n+\tif (!quic_get_int(\u0026p, \u0026len, \u0026length, 2))\n+\t\treturn err;\n+\tif (len \u003e (u32)length) /* Limit len to extensions length if larger. */\n+\t\tlen = length;\n+\twhile (len \u003e= 4) { /* Scan extensions for ALPN (TLS_EXT_alpn). */\n+\t\tif (exts++ \u003e= TLS_MAX_EXTENSIONS)\n+\t\t\treturn err;\n+\t\tif (!quic_get_int(\u0026p, \u0026len, \u0026type, 2))\n+\t\t\tbreak;\n+\t\tif (!quic_get_int(\u0026p, \u0026len, \u0026length, 2))\n+\t\t\tbreak;\n+\t\tif (len \u003c (u32)length) /* Incomplete TLS extensions. */\n+\t\t\treturn 0;\n+\t\tif (type == TLS_EXT_alpn) { /* Found ALPN extension. */\n+\t\t\tif (length \u003e QUIC_ALPN_MAX_LEN)\n+\t\t\t\treturn err;\n+\t\t\tlen = length;\n+\t\t\tfound = 1;\n+\t\t\tbreak;\n+\t\t}\n+\t\t/* Skip non-ALPN extensions. */\n+\t\tp += length;\n+\t\tlen -= length;\n+\t}\n+\tif (!found) { /* No ALPN ext: set alpn-\u003elen = 0 and alpn-\u003edata = p. */\n+\t\tquic_data(alpn, p, 0);\n+\t\treturn 0;\n+\t}\n+\n+\t/* Parse ALPN protocols list length (2 bytes). */\n+\tif (!quic_get_int(\u0026p, \u0026len, \u0026length, 2) || length \u003e (u64)len)\n+\t\treturn err;\n+\tquic_data(alpn, p, length); /* Store ALPN list in alpn-\u003edata. */\n+\tlen = length;\n+\twhile (len) { /* Validate ALPN protocols list format. */\n+\t\tif (!quic_get_int(\u0026p, \u0026len, \u0026length, 1) || length \u003e (u64)len) {\n+\t\t\t/* Bad ALPN: set alpn-\u003elen = 0, alpn-\u003edata = NULL. */\n+\t\t\tquic_data(alpn, NULL, 0);\n+\t\t\treturn err;\n+\t\t}\n+\t\tlen -= length;\n+\t\tp += length;\n+\t}\n+\tpr_debug(\"%s: alpn_len: %d\\n\", __func__, alpn-\u003elen);\n+\treturn 0;\n+}\n+\n+#define QUIC_FRAME_CRYPTO\t0x06\n+\n+/* Parse ALPN from a QUIC Initial packet.\n+ *\n+ * This function processes a QUIC Initial packet to extract the ALPN from the\n+ * TLS ClientHello message inside the QUIC CRYPTO frame. It verifies packet\n+ * type, version compatibility, decrypts the packet payload, and locates the\n+ * CRYPTO frame to parse the TLS ClientHello.  Finally, it calls\n+ * quic_packet_get_alpn() to extract the ALPN extension data.\n+ *\n+ * Return: 0 on success or no ALPN found, a negative error code on failed\n+ * parsing.\n+ */\n+static int quic_packet_parse_alpn(struct sk_buff *skb, struct quic_data *alpn)\n+{\n+\tstruct quic_skb_cb *cb = QUIC_SKB_CB(skb);\n+\tstruct quic_conn_id dcid = {}, scid = {};\n+\tstruct net *net = sock_net(skb-\u003esk);\n+\tu32 len = skb-\u003elen, version;\n+\tstruct quic_crypto *crypto;\n+\tu8 *p = skb-\u003edata, type;\n+\tstruct quic_data token;\n+\tu64 offset, length;\n+\tint err;\n+\n+\tif (!static_branch_unlikely(\u0026quic_alpn_demux_key))\n+\t\treturn 0;\n+\terr = quic_packet_get_long_header(\u0026dcid, \u0026scid, \u0026version, \u0026p, \u0026len);\n+\tif (err)\n+\t\treturn err;\n+\tif (!quic_packet_compatible_versions(version))\n+\t\treturn 0;\n+\t/* Only parse Initial packets. */\n+\ttype = quic_packet_version_get_type(version, quic_hshdr(skb)-\u003etype);\n+\tif (type != QUIC_PACKET_INITIAL)\n+\t\treturn 0;\n+\terr = quic_packet_get_token(\u0026token, \u0026p, \u0026len);\n+\tif (err)\n+\t\treturn err;\n+\tif (!quic_get_var(\u0026p, \u0026len, \u0026length) || length \u003e (u64)len)\n+\t\treturn -EINVAL;\n+\tif (quic_packet_backlog_schedule(net, skb))\n+\t\treturn -EINPROGRESS;\n+\tcb-\u003elength = (u16)length;\n+\n+\t/* Install initial keys for packet decryption to crypto. */\n+\tcrypto = \u0026quic_net(net)-\u003ecrypto;\n+\terr = quic_crypto_initial_keys_install(crypto, \u0026dcid, version, 1);\n+\tif (err)\n+\t\treturn err;\n+\tcb-\u003enumber_offset = (u16)(p - skb-\u003edata);\n+\tcb-\u003esync = 1;\n+\terr = quic_crypto_decrypt(crypto, skb, GFP_KERNEL);\n+\tif (err) {\n+\t\tQUIC_INC_STATS(net, QUIC_MIB_PKT_DECDROP);\n+\t\treturn err;\n+\t}\n+\n+\tQUIC_INC_STATS(net, QUIC_MIB_PKT_DECFASTPATHS);\n+\tcb-\u003eresume = 1; /* Mark this packet as already decrypted. */\n+\n+\t/* Find the QUIC CRYPTO frame. */\n+\tp = skb-\u003edata + cb-\u003enumber_offset + cb-\u003enumber_len;\n+\tlen = cb-\u003elength - cb-\u003enumber_len - QUIC_TAG_LEN;\n+\tfor (; len \u0026\u0026 !(*p); p++, len--) /* Skip the padding frame. */\n+\t\t;\n+\tif (!len-- || *p++ != QUIC_FRAME_CRYPTO)\n+\t\treturn 0;\n+\tif (!quic_get_var(\u0026p, \u0026len, \u0026offset) || offset)\n+\t\treturn 0;\n+\tif (!quic_get_var(\u0026p, \u0026len, \u0026length) || length \u003e (u64)len)\n+\t\treturn 0;\n+\n+\t/* Parse the TLS CLIENT_HELLO message. */\n+\treturn quic_packet_get_alpn(alpn, p, length);\n+}\n+\n+/* Lookup listening socket for Client Initial packet (in process context). */\n+static struct sock *quic_packet_get_listen_sock(struct sk_buff *skb)\n+{\n+\tunion quic_addr daddr, saddr;\n+\tstruct quic_data alpns = {};\n+\tstruct sock *sk;\n+\tint err;\n+\n+\tquic_get_msg_addrs(skb, \u0026daddr, \u0026saddr);\n+\n+\terr = quic_packet_parse_alpn(skb, \u0026alpns);\n+\tif (err)\n+\t\treturn ERR_PTR(err);\n+\n+\tsk = quic_listen_sock_lookup(skb, \u0026daddr, \u0026saddr, \u0026alpns);\n+\tif (!sk)\n+\t\treturn ERR_PTR(-ENOENT);\n+\treturn sk;\n+}\n+\n+/* Determine the QUIC socket associated with an incoming packet. */\n+static struct sock *quic_packet_get_sock(struct sk_buff *skb)\n+{\n+\tstruct quic_skb_cb *cb = QUIC_SKB_CB(skb);\n+\tstruct quic_conn_id dcid = {}, *conn_id;\n+\tstruct net *net = sock_net(skb-\u003esk);\n+\tunion quic_addr daddr, saddr;\n+\tstruct quic_data alpns = {};\n+\tstruct sock *sk = NULL;\n+\tu32 len = skb-\u003elen;\n+\tu8 *p = skb-\u003edata;\n+\tint err;\n+\n+\tif (skb-\u003elen \u003c QUIC_HLEN)\n+\t\treturn ERR_PTR(-EINVAL);\n+\n+\tif (quic_hdr(skb)-\u003eform == QUIC_PACKET_FORM_SHORT) {\n+\t\t/* Short header path. */\n+\t\tif (skb-\u003elen \u003c QUIC_HLEN + QUIC_CONN_ID_DEF_LEN)\n+\t\t\treturn ERR_PTR(-EINVAL);\n+\t\t/* Fast path: look up QUIC connection by fixed-length DCID\n+\t\t * (Currently, only QUIC_CONN_ID_DEF_LEN-length SCIDs are used).\n+\t\t */\n+\t\tconn_id = quic_conn_id_lookup(net, skb-\u003edata + QUIC_HLEN,\n+\t\t\t\t\t      QUIC_CONN_ID_DEF_LEN);\n+\t\tif (conn_id) {\n+\t\t\tcb-\u003eseqno = quic_conn_id_number(conn_id);\n+\t\t\t/* Return associated socket. */\n+\t\t\treturn quic_conn_id_sk(conn_id);\n+\t\t}\n+\n+\t\t/* Fallback: listener socket lookup\n+\t\t * (May be used to send a stateless reset from a listen socket).\n+\t\t */\n+\t\tquic_get_msg_addrs(skb, \u0026daddr, \u0026saddr);\n+\t\tsk = quic_listen_sock_lookup(skb, \u0026daddr, \u0026saddr, \u0026alpns);\n+\t\tif (sk)\n+\t\t\treturn sk;\n+\t\t/* Final fallback: address-based connection lookup\n+\t\t * (May be used to receive a stateless reset).\n+\t\t */\n+\t\tsk = quic_sock_lookup(skb, \u0026daddr, \u0026saddr, skb-\u003esk, NULL);\n+\t\tif (!sk)\n+\t\t\treturn ERR_PTR(-ENOENT);\n+\t\treturn sk;\n+\t}\n+\n+\t/* Long header path. */\n+\terr = quic_packet_get_long_header(\u0026dcid, NULL, NULL, \u0026p, \u0026len);\n+\tif (err)\n+\t\treturn ERR_PTR(err);\n+\t/* Fast path: look up QUIC connection by parsed DCID. */\n+\tconn_id = quic_conn_id_lookup(net, dcid.data, dcid.len);\n+\tif (conn_id) {\n+\t\tcb-\u003eseqno = quic_conn_id_number(conn_id);\n+\t\treturn quic_conn_id_sk(conn_id); /* Return associated socket. */\n+\t}\n+\n+\t/* Fallback: address + DCID lookup\n+\t * (May be used for 0-RTT or a follow-up Client Initial packet).\n+\t */\n+\tquic_get_msg_addrs(skb, \u0026daddr, \u0026saddr);\n+\tsk = quic_sock_lookup(skb, \u0026daddr, \u0026saddr, skb-\u003esk, \u0026dcid);\n+\tif (sk)\n+\t\treturn sk;\n+\t/* Final fallback: listener socket lookup\n+\t * (Used for receiving the first Client Initial packet).\n+\t */\n+\terr = quic_packet_parse_alpn(skb, \u0026alpns);\n+\tif (err)\n+\t\treturn ERR_PTR(err);\n+\tsk = quic_listen_sock_lookup(skb, \u0026daddr, \u0026saddr, \u0026alpns);\n+\tif (!sk)\n+\t\treturn ERR_PTR(-ENOENT);\n+\treturn sk;\n+}\n+\n+/* Entry point for processing received QUIC packets. */\n+int quic_packet_rcv(struct sock *sk, struct sk_buff *skb, bool icmp)\n+{\n+\tstruct net *net = sock_net(sk);\n+\tint err;\n+\n+\tif (unlikely(icmp))\n+\t\treturn quic_packet_rcv_err(sk, skb);\n+\n+\t/* Save the UDP socket to skb-\u003esk for later QUIC socket lookup. */\n+\tif (skb_linearize(skb) || !skb_set_owner_sk_safe(skb, sk)) {\n+\t\terr = -EINVAL;\n+\t\tgoto err;\n+\t}\n+\n+\t/* Look up socket from socket or connection IDs hash tables. */\n+\tsk = quic_packet_get_sock(skb);\n+\tif (IS_ERR(sk)) {\n+\t\terr = PTR_ERR(sk);\n+\t\tif (err == -EINPROGRESS)\n+\t\t\treturn 0;\n+\t\tgoto err;\n+\t}\n+\n+\tbh_lock_sock(sk);\n+\tif (sock_owned_by_user(sk)) {\n+\t\t/* Socket is busy (owned by user context): queue to backlog. */\n+\t\terr = sk_add_backlog(sk, skb, READ_ONCE(sk-\u003esk_rcvbuf));\n+\t\tif (err) {\n+\t\t\tbh_unlock_sock(sk);\n+\t\t\tsock_put(sk);\n+\t\t\tgoto err;\n+\t\t}\n+\t\tQUIC_INC_STATS(net, QUIC_MIB_PKT_RCVBACKLOGS);\n+\t} else {\n+\t\t/* Socket not busy: process immediately. */\n+\t\tQUIC_INC_STATS(net, QUIC_MIB_PKT_RCVFASTPATHS);\n+\t\tsk-\u003esk_backlog_rcv(sk, skb); /* quic_backlog_rcv(). */\n+\t}\n+\tbh_unlock_sock(sk);\n+\tsock_put(sk);\n+\treturn 0;\n+err:\n+\tpr_debug(\"%s: failed, len: %d, err: %d\\n\", __func__, skb-\u003elen, err);\n+\tQUIC_INC_STATS(net, QUIC_MIB_PKT_RCVDROP);\n+\tkfree_skb(skb);\n+\treturn err;\n+}\n+\n+static int quic_packet_listen_process(struct sock *sk, struct sk_buff *skb,\n+\t\t\t\t      gfp_t gfp)\n+{\n+\tkfree_skb(skb);\n+\treturn -EOPNOTSUPP;\n+}\n+\n+static int quic_packet_handshake_process(struct sock *sk, struct sk_buff *skb,\n+\t\t\t\t\t gfp_t gfp)\n+{\n+\tkfree_skb(skb);\n+\treturn -EOPNOTSUPP;\n+}\n+\n+static int quic_packet_app_process(struct sock *sk, struct sk_buff *skb,\n+\t\t\t\t   gfp_t gfp)\n+{\n+\tkfree_skb(skb);\n+\treturn -EOPNOTSUPP;\n+}\n+\n+int quic_packet_process(struct sock *sk, struct sk_buff *skb, gfp_t gfp)\n+{\n+\tif (quic_is_closed(sk)) {\n+\t\tkfree_skb(skb);\n+\t\treturn 0;\n+\t}\n+\n+\tif (quic_is_listen(sk))\n+\t\treturn quic_packet_listen_process(sk, skb, gfp);\n+\n+\tif (quic_hdr(skb)-\u003eform == QUIC_PACKET_FORM_LONG)\n+\t\treturn quic_packet_handshake_process(sk, skb, gfp);\n+\n+\treturn quic_packet_app_process(sk, skb, gfp);\n+}\n+\n+/* Work function to process packets in the backlog queue. */\n+void quic_packet_backlog_work(struct work_struct *work)\n+{\n+\tstruct quic_net *qn = container_of(work, struct quic_net, work);\n+\tstruct sk_buff_head *head = \u0026qn-\u003ebacklog_list;\n+\tstruct sk_buff *skb;\n+\tstruct sock *sk;\n+\n+\twhile ((skb = skb_dequeue(head)) != NULL) {\n+\t\tsk = quic_packet_get_listen_sock(skb);\n+\t\tif (IS_ERR(sk)) {\n+\t\t\tQUIC_INC_STATS(sock_net(skb-\u003esk), QUIC_MIB_PKT_RCVDROP);\n+\t\t\tkfree_skb(skb);\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tlock_sock(sk);\n+\t\tquic_packet_process(sk, skb, GFP_KERNEL);\n+\t\trelease_sock(sk);\n+\t\tsock_put(sk);\n+\t\tcond_resched();\n+\t}\n+}\n+\n+/* Make these fixed for easy coding. */\n+#define QUIC_PACKET_NUMBER_LEN\tQUIC_PN_MAX_LEN\n+#define QUIC_PACKET_LENGTH_LEN\t4\n+\n+static struct sk_buff *quic_packet_handshake_create(struct sock *sk, gfp_t gfp)\n+{\n+\treturn NULL;\n+}\n+\n+static int quic_packet_number_check(struct sock *sk, gfp_t gfp)\n+{\n+\treturn 0;\n+}\n+\n+static struct sk_buff *quic_packet_app_create(struct sock *sk, gfp_t gfp)\n+{\n+\treturn NULL;\n+}\n+\n+/* Update the MSS and inform congestion control. */\n+void quic_packet_mss_update(struct sock *sk, u32 mss)\n+{\n+\tstruct quic_packet *packet = quic_packet(sk);\n+\tstruct quic_cong *cong = quic_cong(sk);\n+\n+\tpacket-\u003emss[QUIC_PACKET_MSS_NORMAL] = (u16)mss;\n+\tquic_cong_set_mss(cong, packet-\u003emss[QUIC_PACKET_MSS_NORMAL]);\n+}\n+\n+/* Perform routing for the QUIC packet on the specified path, update header\n+ * length and MSS accordingly, reset path and start PMTU timer.\n+ */\n+int quic_packet_route(struct sock *sk)\n+{\n+\tstruct quic_path_group *paths = quic_paths(sk);\n+\tstruct quic_packet *packet = quic_packet(sk);\n+\tunion quic_addr *sa, *da;\n+\tu32 pmtu;\n+\tint err;\n+\n+\tda = quic_path_daddr(paths, packet-\u003epath);\n+\tsa = quic_path_saddr(paths, packet-\u003epath);\n+\terr = quic_flow_route(sk, da, sa, \u0026paths-\u003efl);\n+\tif (err)\n+\t\treturn err \u003c 0 ? err : 0;\n+\n+\tpacket-\u003ehlen = quic_encap_len(da);\n+\tpmtu = clamp(dst_mtu(__sk_dst_get(sk)),\n+\t\t     QUIC_PATH_MIN_PMTU, QUIC_PATH_MAX_PMTU);\n+\tquic_packet_mss_update(sk, pmtu - packet-\u003ehlen);\n+\n+\tquic_path_pl_reset(paths);\n+\tquic_timer_reset(sk, QUIC_TIMER_PMTU, paths-\u003eplpmtud_interval);\n+\treturn 0;\n+}\n+\n+/* Return QUIC packet header overhead for the given level and path. Includes\n+ * packet number, connection IDs, and for long headers also version, length,\n+ * and Initial token (if present). Excludes payload.\n+ */\n+u16 quic_packet_overhead(struct sock *sk, u8 level, u8 path)\n+{\n+\tstruct quic_conn_id_set *source = quic_source(sk);\n+\tstruct quic_conn_id_set *dest = quic_dest(sk);\n+\tu16 len = QUIC_HLEN;\n+\n+\tlen += QUIC_PACKET_NUMBER_LEN; /* Packet number length. */\n+\tlen += quic_conn_id_choose(dest, path)-\u003elen; /* DCID length. */\n+\tif (level == QUIC_CRYPTO_APP)\n+\t\treturn len;\n+\n+\tlen += 1; /* Length byte for DCID. */\n+\t/* Length byte + SCID length. */\n+\tlen += 1 + quic_conn_id_active(source)-\u003elen;\n+\t/* Include token for Initial packets. */\n+\tif (level == QUIC_CRYPTO_INITIAL)\n+\t\tlen += quic_var_len(quic_token(sk)-\u003elen) + quic_token(sk)-\u003elen;\n+\tlen += QUIC_VERSION_LEN; /* Version length. */\n+\tlen += QUIC_PACKET_LENGTH_LEN; /* Packet length field. */\n+\n+\treturn len;\n+}\n+\n+/* Configure the QUIC packet header and routing based on encryption level and\n+ * path.\n+ */\n+int quic_packet_config(struct sock *sk, u8 level, u8 path)\n+{\n+\tstruct quic_packet *packet = quic_packet(sk);\n+\n+\t/* If packet already has data, no need to reconfigure. */\n+\tif (!quic_packet_empty(packet))\n+\t\treturn 0;\n+\n+\tpacket-\u003epath_validating = 0;\n+\tpacket-\u003eipfragok = 0;\n+\tpacket-\u003epadding = 0;\n+\tpacket-\u003eframes = 0;\n+\n+\tpacket-\u003elevel = level;\n+\tpacket-\u003eoverhead = quic_packet_overhead(sk, level, path);\n+\tpacket-\u003elen = packet-\u003eoverhead + quic_packet_taglen(packet);\n+\n+\t/* Allow fragmentation for handshake packets before PLPMTUD probing\n+\t * starts. MTU discovery does not rely on ICMP Packet Too Big once\n+\t * PLPMTUD is enabled.\n+\t */\n+\tpacket-\u003eipfragok = level \u0026\u0026 !!quic_paths(sk)-\u003eplpmtud_interval;\n+\n+\tif (packet-\u003epath != path) {\n+\t\t/* Path changed; update and reset routing cache */\n+\t\tpacket-\u003epath = path;\n+\t\t__sk_dst_reset(sk);\n+\t}\n+\n+\t/* Perform routing and MSS update for the configured packet. */\n+\treturn quic_packet_route(sk);\n+}\n+\n+static void quic_packet_encrypt_done(struct sk_buff *skb, int err)\n+{\n+\t/* Free it for now, future patches will implement the actual deferred\n+\t * transmission logic.\n+\t */\n+\tkfree_skb(skb);\n+}\n+\n+/* Coalescing Packets. */\n+static int quic_packet_bundle(struct sock *sk, struct sk_buff *skb)\n+{\n+\tstruct quic_skb_cb *head_cb, *cb = QUIC_SKB_CB(skb);\n+\tstruct quic_packet *packet = quic_packet(sk);\n+\tstruct sk_buff *p;\n+\n+\tif (!packet-\u003ehead) /* First packet to bundle: initialize the head. */\n+\t\tgoto init;\n+\n+\t/* If bundling would exceed MSS, flush the current bundle. */\n+\tif (packet-\u003ehead-\u003elen + skb-\u003elen \u003e\n+\t    packet-\u003emss[QUIC_PACKET_MSS_NORMAL]) {\n+\t\tquic_packet_flush(sk);\n+\t\tgoto init;\n+\t}\n+\t/* Bundle it and update metadata for the aggregate skb. */\n+\tskb_orphan(skb);\n+\tp = packet-\u003ehead;\n+\thead_cb = QUIC_SKB_CB(p);\n+\tif (head_cb-\u003elast == p)\n+\t\tskb_shinfo(p)-\u003efrag_list = skb;\n+\telse\n+\t\thead_cb-\u003elast-\u003enext = skb;\n+\tp-\u003edata_len += skb-\u003elen;\n+\tp-\u003etruesize += skb-\u003etruesize;\n+\tp-\u003elen += skb-\u003elen;\n+\thead_cb-\u003elast = skb;\n+\thead_cb-\u003eecn |= cb-\u003eecn;  /* Merge ECN flags. */\n+\n+out:\n+\t/* rfc9000#section-12.2: Packets with a short header (Section 17.3) do\n+\t * not contain a Length field and so cannot be followed by other\n+\t * packets in the same UDP datagram.\n+\t *\n+\t * so Return 1 to flush if it is a Short header packet.\n+\t */\n+\treturn !cb-\u003elevel;\n+init:\n+\tpacket-\u003ehead = skb;\n+\tcb-\u003elast = skb;\n+\tgoto out;\n+}\n+\n+/* Transmit a QUIC packet, possibly encrypting and bundling it. */\n+static int quic_packet_xmit(struct sock *sk, struct sk_buff *skb, gfp_t gfp)\n+{\n+\tstruct quic_packet *packet = quic_packet(sk);\n+\tstruct quic_skb_cb *cb = QUIC_SKB_CB(skb);\n+\tstruct net *net = sock_net(sk);\n+\tint err;\n+\n+\t/* Skip encryption if taglen == 0 (e.g., disable_1rtt_encryption). */\n+\tif (!packet-\u003etaglen[quic_hdr(skb)-\u003eform])\n+\t\tgoto xmit;\n+\n+\tcb-\u003ecrypto_done = quic_packet_encrypt_done;\n+\t/* Associate skb with sk to ensure sk is valid during async encryption\n+\t * completion.\n+\t */\n+\tWARN_ON_ONCE(!skb_set_owner_sk_safe(skb, sk));\n+\terr = quic_crypto_encrypt(quic_crypto(sk, packet-\u003elevel), skb, gfp);\n+\tif (err) {\n+\t\tif (err != -EINPROGRESS) {\n+\t\t\tQUIC_INC_STATS(net, QUIC_MIB_PKT_ENCDROP);\n+\t\t\tkfree_skb(skb);\n+\t\t\treturn err;\n+\t\t}\n+\t\tQUIC_INC_STATS(net, QUIC_MIB_PKT_ENCBACKLOGS);\n+\t\treturn err;\n+\t}\n+\tif (!cb-\u003eresume) /* Encryption completes synchronously. */\n+\t\tQUIC_INC_STATS(net, QUIC_MIB_PKT_ENCFASTPATHS);\n+\n+xmit:\n+\tif (quic_packet_bundle(sk, skb))\n+\t\tquic_packet_flush(sk);\n+\treturn 0;\n+}\n+\n+/* Create and transmit a new QUIC packet. */\n+int quic_packet_create_and_xmit(struct sock *sk, gfp_t gfp)\n+{\n+\tstruct quic_packet *packet = quic_packet(sk);\n+\tstruct sk_buff *skb;\n+\tint err;\n+\n+\terr = quic_packet_number_check(sk, gfp);\n+\tif (err)\n+\t\tgoto err;\n+\n+\tif (packet-\u003elevel)\n+\t\tskb = quic_packet_handshake_create(sk, gfp);\n+\telse\n+\t\tskb = quic_packet_app_create(sk, gfp);\n+\tif (!skb) {\n+\t\terr = -ENOMEM;\n+\t\tgoto err;\n+\t}\n+\n+\terr = quic_packet_xmit(sk, skb, gfp);\n+\tif (err \u0026\u0026 err != -EINPROGRESS)\n+\t\tgoto err;\n+\n+\treturn 0;\n+err:\n+\tpr_debug(\"%s: err: %d\\n\", __func__, err);\n+\treturn err;\n+}\n+\n+/* Flush any coalesced/bundled QUIC packets. */\n+void quic_packet_flush(struct sock *sk)\n+{\n+\tstruct quic_path_group *paths = quic_paths(sk);\n+\tstruct quic_packet *packet = quic_packet(sk);\n+\n+\tif (packet-\u003ehead) {\n+\t\tquic_lower_xmit(sk, packet-\u003ehead,\n+\t\t\t\tquic_path_daddr(paths, packet-\u003epath),\n+\t\t\t\t\u0026paths-\u003efl);\n+\t\tpacket-\u003ehead = NULL;\n+\t}\n+}\n+\n+void quic_packet_init(struct sock *sk)\n+{\n+\tstruct quic_packet *packet = quic_packet(sk);\n+\n+\tINIT_LIST_HEAD(\u0026packet-\u003eframe_list);\n+\tpacket-\u003etaglen[QUIC_PACKET_FORM_SHORT] = QUIC_TAG_LEN;\n+\tpacket-\u003etaglen[QUIC_PACKET_FORM_LONG] = QUIC_TAG_LEN;\n+\tpacket-\u003emss[QUIC_PACKET_MSS_NORMAL] = QUIC_MIN_UDP_PAYLOAD;\n+\tpacket-\u003emss[QUIC_PACKET_MSS_DGRAM] = QUIC_MIN_UDP_PAYLOAD;\n+\n+\tpacket-\u003eversion = QUIC_VERSION_V1;\n+}\ndiff --git a/net/quic/packet.h b/net/quic/packet.h\nnew file mode 100644\nindex 00000000000000..fefc5405844151\n--- /dev/null\n+++ b/net/quic/packet.h\n@@ -0,0 +1,120 @@\n+/* SPDX-License-Identifier: GPL-2.0-or-later */\n+/* QUIC kernel implementation\n+ * (C) Copyright Red Hat Corp. 2023\n+ *\n+ * This file is part of the QUIC kernel implementation\n+ *\n+ * Written or modified by:\n+ *    Xin Long \u003clucien.xin@gmail.com\u003e\n+ */\n+\n+struct quic_packet {\n+\tstruct quic_conn_id dcid; /* Dest Conn ID from received packet */\n+\tstruct quic_conn_id scid; /* Source Conn ID from received packet */\n+\tunion quic_addr daddr;    /* Dest address from received packet */\n+\tunion quic_addr saddr;    /* Source address from received packet */\n+\n+\tstruct list_head frame_list; /* Frames to pack into packet for send */\n+\tstruct sk_buff *head;        /* Head skb for packet bundling on send */\n+\tu32 version;   /* QUIC version used/selected during handshake */\n+\tu16 overhead;  /* QUIC header length excluding frames */\n+\tu8 taglen[2];  /* Tag length for short and long packets */\n+\tu16 padding;   /* Total padding bytes to append after frames */\n+\tu16 frames;    /* Number of ack-eliciting frames */\n+\tu16 mss[2];    /* MSS for datagram and non-datagram packets */\n+\tu16 hlen;      /* UDP + IP header length for sending */\n+\tu16 len;       /* QUIC packet length including taglen for sending */\n+\n+\tu8 path_validating:1; /* Packet contains path_validating frames */\n+\tu8 ack_eliciting:1;   /* Packet contains ack-eliciting frames */\n+\tu8 ack_immediate:1;   /* Send ACK immediately (skip ack_delay timer) */\n+\tu8 non_probing:1;     /* Packet contains non-probing frames */\n+\tu8 has_sack:1;        /* Packet contains ACK frames */\n+\tu8 ipfragok:1;        /* Allow IP fragmentation */\n+\tu8 path:1;            /* Path identifier used to send this packet */\n+\tu8 level;             /* Encryption level used */\n+};\n+\n+#define QUIC_PACKET_INITIAL_V1\t\t0\n+#define QUIC_PACKET_0RTT_V1\t\t1\n+#define QUIC_PACKET_HANDSHAKE_V1\t2\n+#define QUIC_PACKET_RETRY_V1\t\t3\n+\n+#define QUIC_PACKET_INITIAL_V2\t\t1\n+#define QUIC_PACKET_0RTT_V2\t\t2\n+#define QUIC_PACKET_HANDSHAKE_V2\t3\n+#define QUIC_PACKET_RETRY_V2\t\t0\n+\n+#define QUIC_PACKET_INITIAL\t\tQUIC_PACKET_INITIAL_V1\n+#define QUIC_PACKET_0RTT\t\tQUIC_PACKET_0RTT_V1\n+#define QUIC_PACKET_HANDSHAKE\t\tQUIC_PACKET_HANDSHAKE_V1\n+#define QUIC_PACKET_RETRY\t\tQUIC_PACKET_RETRY_V1\n+\n+#define QUIC_PACKET_INVALID\t\t0xff\n+\n+#define QUIC_VERSION_LEN\t\t4\n+\n+#define QUIC_ALPN_MAX_LEN\t\t128\n+\n+#define QUIC_PACKET_MSS_NORMAL\t\t0\n+#define QUIC_PACKET_MSS_DGRAM\t\t1\n+\n+#define QUIC_PACKET_FORM_SHORT\t\t0\n+#define QUIC_PACKET_FORM_LONG\t\t1\n+\n+static inline u8 quic_packet_taglen(struct quic_packet *packet)\n+{\n+\treturn packet-\u003etaglen[packet-\u003elevel != QUIC_CRYPTO_APP];\n+}\n+\n+static inline void quic_packet_set_taglen(struct quic_packet *packet, u8 taglen)\n+{\n+\tpacket-\u003etaglen[QUIC_PACKET_FORM_SHORT] = taglen;\n+}\n+\n+static inline u32 quic_packet_mss(struct quic_packet *packet)\n+{\n+\treturn packet-\u003emss[QUIC_PACKET_MSS_NORMAL] - quic_packet_taglen(packet);\n+}\n+\n+static inline u32 quic_packet_max_payload(struct quic_packet *packet)\n+{\n+\treturn packet-\u003emss[QUIC_PACKET_MSS_NORMAL] - packet-\u003eoverhead -\n+\t       quic_packet_taglen(packet);\n+}\n+\n+static inline u32 quic_packet_max_payload_dgram(struct quic_packet *packet)\n+{\n+\treturn packet-\u003emss[QUIC_PACKET_MSS_DGRAM] - packet-\u003eoverhead -\n+\t       quic_packet_taglen(packet);\n+}\n+\n+static inline bool quic_packet_empty(struct quic_packet *packet)\n+{\n+\treturn list_empty(\u0026packet-\u003eframe_list);\n+}\n+\n+static inline void quic_packet_reset(struct quic_packet *packet)\n+{\n+\tpacket-\u003elevel = 0;\n+\tpacket-\u003ehas_sack = 0;\n+\tpacket-\u003enon_probing = 0;\n+\tpacket-\u003eack_eliciting = 0;\n+\tpacket-\u003eack_immediate = 0;\n+}\n+\n+int quic_packet_process(struct sock *sk, struct sk_buff *skb, gfp_t gfp);\n+u16 quic_packet_overhead(struct sock *sk, u8 level, u8 path);\n+int quic_packet_config(struct sock *sk, u8 level, u8 path);\n+\n+int quic_packet_create_and_xmit(struct sock *sk, gfp_t gfp);\n+int quic_packet_route(struct sock *sk);\n+\n+void quic_packet_mss_update(struct sock *sk, u32 mss);\n+void quic_packet_flush(struct sock *sk);\n+void quic_packet_init(struct sock *sk);\n+\n+u32 *quic_packet_compatible_versions(u32 version);\n+\n+void quic_packet_backlog_work(struct work_struct *work);\n+void quic_packet_rcv_err_pmtu(struct sock *sk);\ndiff --git a/net/quic/path.c b/net/quic/path.c\nnew file mode 100644\nindex 00000000000000..f197ae1c556cc6\n--- /dev/null\n+++ b/net/quic/path.c\n@@ -0,0 +1,568 @@\n+// SPDX-License-Identifier: GPL-2.0-or-later\n+/* QUIC kernel implementation\n+ * (C) Copyright Red Hat Corp. 2023\n+ *\n+ * This file is part of the QUIC kernel implementation\n+ *\n+ * Initialization/cleanup for QUIC protocol support.\n+ *\n+ * Written or modified by:\n+ *    Xin Long \u003clucien.xin@gmail.com\u003e\n+ */\n+\n+#include \u003cnet/udp_tunnel.h\u003e\n+#include \u003clinux/quic.h\u003e\n+\n+#include \"common.h\"\n+#include \"family.h\"\n+#include \"path.h\"\n+\n+static int quic_udp_rcv(struct sock *sk, struct sk_buff *skb)\n+{\n+\tquic_set_skb_iif(skb);\n+\n+\tmemset(skb-\u003ecb, 0, sizeof(skb-\u003ecb));\n+\tQUIC_SKB_CB(skb)-\u003eseqno = -1;\n+\tQUIC_SKB_CB(skb)-\u003etime = quic_ktime_get_us();\n+\n+\tskb_pull(skb, sizeof(struct udphdr));\n+\tskb_dst_force(skb);\n+\tquic_packet_rcv(sk, skb, false);\n+\t/* .encap_rcv must return 0 if skb was either consumed or dropped. */\n+\treturn 0;\n+}\n+\n+static int quic_udp_err(struct sock *sk, struct sk_buff *skb)\n+{\n+\treturn quic_packet_rcv(sk, skb, true);\n+}\n+\n+static void quic_udp_sock_put_work(struct work_struct *work)\n+{\n+\tstruct quic_udp_sock *us = container_of(work, struct quic_udp_sock,\n+\t\t\t\t\t\twork);\n+\tstruct quic_uhash_head *head;\n+\tstruct sock *sk = us-\u003esk;\n+\n+\t/* Hold the sock to safely access it in quic_udp_sock_lookup() even\n+\t * after udp_tunnel_sock_release(). The release must occur before\n+\t * __hlist_del() so a new UDP tunnel socket can be created for the same\n+\t * address and port if quic_udp_sock_lookup() fails to find one.\n+\t *\n+\t * Note: udp_tunnel_sock_release() cannot be called under the mutex due\n+\t * to some lockdep warnings.\n+\t */\n+\tsock_hold(sk);\n+\tudp_tunnel_sock_release(sk);\n+\n+\thead = quic_udp_sock_head(sock_net(sk), ntohs(us-\u003eaddr.v4.sin_port));\n+\tmutex_lock(\u0026head-\u003elock);\n+\t__hlist_del(\u0026us-\u003enode);\n+\tmutex_unlock(\u0026head-\u003elock);\n+\n+\tsock_put(sk);\n+\tkfree(us);\n+}\n+\n+static struct quic_udp_sock *quic_udp_sock_create(struct sock *sk,\n+\t\t\t\t\t\t  union quic_addr *a)\n+{\n+\tstruct udp_tunnel_sock_cfg tuncfg = {};\n+\tstruct udp_port_cfg udp_conf = {};\n+\tstruct net *net = sock_net(sk);\n+\tstruct quic_uhash_head *head;\n+\tstruct quic_udp_sock *us;\n+\tstruct socket *sock;\n+\tint err;\n+\n+\tus = kmalloc_obj(*us);\n+\tif (!us)\n+\t\treturn ERR_PTR(-ENOMEM);\n+\n+\tquic_udp_conf_init(sk, \u0026udp_conf, a);\n+\terr = udp_sock_create(net, \u0026udp_conf, \u0026sock);\n+\tif (err) {\n+\t\tpr_debug(\"%s: failed to create udp sock\\n\", __func__);\n+\t\tkfree(us);\n+\t\treturn ERR_PTR(err);\n+\t}\n+\n+\ttuncfg.encap_type = 1;\n+\ttuncfg.encap_rcv = quic_udp_rcv;\n+\ttuncfg.encap_err_lookup = quic_udp_err;\n+\tsetup_udp_tunnel_sock(net, sock-\u003esk, \u0026tuncfg);\n+\n+\trefcount_set(\u0026us-\u003erefcnt, 1);\n+\tus-\u003esk = sock-\u003esk;\n+\tmemcpy(\u0026us-\u003eaddr, a, sizeof(*a));\n+\tus-\u003ebind_ifindex = udp_conf.bind_ifindex;\n+\n+\tINIT_WORK(\u0026us-\u003ework, quic_udp_sock_put_work);\n+\thead = quic_udp_sock_head(net, ntohs(a-\u003ev4.sin_port));\n+\thlist_add_head(\u0026us-\u003enode, \u0026head-\u003ehead);\n+\n+\treturn us;\n+}\n+\n+static bool quic_udp_sock_get(struct quic_udp_sock *us)\n+{\n+\treturn refcount_inc_not_zero(\u0026us-\u003erefcnt);\n+}\n+\n+static void quic_udp_sock_put(struct quic_udp_sock *us)\n+{\n+\t/* The UDP socket may be freed in atomic RX context during connection\n+\t * migration; defer the release to a workqueue.\n+\t */\n+\tif (refcount_dec_and_test(\u0026us-\u003erefcnt))\n+\t\tqueue_work(quic_wq, \u0026us-\u003ework);\n+}\n+\n+/* Lookup a quic_udp_sock in the global hash table by port or address.  If 'a'\n+ * is provided, it searches for a socket whose local address matches 'a' and,\n+ * if applicable, matches the device binding. If 'a' is NULL, it searches only\n+ * by port.\n+ */\n+static struct quic_udp_sock *quic_udp_sock_lookup(struct sock *sk,\n+\t\t\t\t\t\t  union quic_addr *a, u16 port)\n+{\n+\tstruct net *net = sock_net(sk);\n+\tstruct quic_uhash_head *head;\n+\tstruct quic_udp_sock *us;\n+\n+\thead = quic_udp_sock_head(net, port);\n+\thlist_for_each_entry(us, \u0026head-\u003ehead, node) {\n+\t\tif (net != sock_net(us-\u003esk))\n+\t\t\tcontinue;\n+\t\tif (a) {\n+\t\t\tif (quic_cmp_sk_addr(us-\u003esk, \u0026us-\u003eaddr, a) \u0026\u0026\n+\t\t\t    us-\u003ebind_ifindex == quic_get_dev_if(sk, a))\n+\t\t\t\treturn us;\n+\t\t\tcontinue;\n+\t\t}\n+\t\tif (ntohs(us-\u003eaddr.v4.sin_port) == port)\n+\t\t\treturn us;\n+\t}\n+\treturn NULL;\n+}\n+\n+static void quic_path_set_udp_sk(struct quic_path *path,\n+\t\t\t\t struct quic_udp_sock *us)\n+{\n+\tif (path-\u003eudp_sk)\n+\t\tquic_udp_sock_put(path-\u003eudp_sk);\n+\n+\tpath-\u003eudp_sk = us;\n+\tif (!us) {\n+\t\tpath-\u003eusk = NULL;\n+\t\tmemset(\u0026path-\u003euaddr, 0, sizeof(path-\u003euaddr));\n+\t\treturn;\n+\t}\n+\tpath-\u003eusk = us-\u003esk;\n+\tmemcpy(\u0026path-\u003euaddr, \u0026us-\u003eaddr, sizeof(us-\u003eaddr));\n+}\n+\n+/* Binds a QUIC path to a local port and sets up a UDP socket. */\n+int quic_path_bind(struct sock *sk, struct quic_path_group *paths, u8 path)\n+{\n+\tunion quic_addr *a = quic_path_saddr(paths, path);\n+\tint rover, low, high, remaining;\n+\tstruct net *net = sock_net(sk);\n+\tstruct quic_uhash_head *head;\n+\tstruct quic_udp_sock *us;\n+\tu16 port;\n+\n+\tport = ntohs(a-\u003ev4.sin_port);\n+\tif (port) {\n+\t\thead = quic_udp_sock_head(net, port);\n+\t\tmutex_lock(\u0026head-\u003elock);\n+\t\tus = quic_udp_sock_lookup(sk, a, port);\n+\t\tif (us) {\n+\t\t\t/* Allow reuse of an existing UDP tunnel socket.\n+\t\t\t * However, if it is in the middle of asynchronous\n+\t\t\t * teardown (via workqueue), it is temporarily unusable.\n+\t\t\t * Return -EAGAIN (not -EADDRINUSE) to signal the caller\n+\t\t\t * to retry soon.\n+\t\t\t */\n+\t\t\tif (!quic_udp_sock_get(us)) {\n+\t\t\t\tmutex_unlock(\u0026head-\u003elock);\n+\t\t\t\treturn -EAGAIN;\n+\t\t\t}\n+\t\t} else {\n+\t\t\tus = quic_udp_sock_create(sk, a);\n+\t\t\tif (IS_ERR(us)) {\n+\t\t\t\tmutex_unlock(\u0026head-\u003elock);\n+\t\t\t\treturn PTR_ERR(us);\n+\t\t\t}\n+\t\t}\n+\t\tmutex_unlock(\u0026head-\u003elock);\n+\t\tquic_path_set_udp_sk(\u0026paths-\u003epath[path], us);\n+\t\treturn 0;\n+\t}\n+\n+\tinet_sk_get_local_port_range(sk, \u0026low, \u0026high);\n+\tremaining = (high - low) + 1;\n+\trover = get_random_u32_below(remaining) + low;\n+\tdo {\n+\t\trover++;\n+\t\tif (rover \u003c low || rover \u003e high)\n+\t\t\trover = low;\n+\t\tport = (u16)rover;\n+\t\tif (inet_is_local_reserved_port(net, port))\n+\t\t\tcontinue;\n+\n+\t\thead = quic_udp_sock_head(net, port);\n+\t\tmutex_lock(\u0026head-\u003elock);\n+\t\tif (quic_udp_sock_lookup(sk, NULL, port)) {\n+\t\t\tmutex_unlock(\u0026head-\u003elock);\n+\t\t\tcond_resched();\n+\t\t\tcontinue;\n+\t\t}\n+\t\ta-\u003ev4.sin_port = htons(port);\n+\t\tus = quic_udp_sock_create(sk, a);\n+\t\tif (IS_ERR(us)) {\n+\t\t\ta-\u003ev4.sin_port = 0;\n+\t\t\tmutex_unlock(\u0026head-\u003elock);\n+\t\t\tif (PTR_ERR(us) == -EADDRINUSE) {\n+\t\t\t\tcond_resched();\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\t\t\treturn PTR_ERR(us);\n+\t\t}\n+\t\tmutex_unlock(\u0026head-\u003elock);\n+\n+\t\tquic_path_set_udp_sk(\u0026paths-\u003epath[path], us);\n+\t\t__sk_dst_reset(sk);\n+\t\treturn 0;\n+\t} while (--remaining \u003e 0);\n+\n+\treturn -EADDRINUSE;\n+}\n+\n+/* Swaps the active and alternate QUIC paths.\n+ *\n+ * Promotes the alternate path (path[1]) to become the new active path\n+ * (path[0]).  If the alternate path has a valid UDP socket, the entire path is\n+ * swapped.  Otherwise, only the destination address is exchanged, assuming the\n+ * source address is the same and no rebind is needed.\n+ *\n+ * This is typically used during path migration or alternate path promotion.\n+ */\n+void quic_path_swap(struct quic_path_group *paths)\n+{\n+\tstruct quic_path path = paths-\u003epath[0];\n+\n+\tpaths-\u003ealt_probes = 0;\n+\tpaths-\u003ealt_state = QUIC_PATH_ALT_SWAPPED;\n+\n+\tif (paths-\u003epath[1].udp_sk) {\n+\t\tpaths-\u003epath[0] = paths-\u003epath[1];\n+\t\tpaths-\u003epath[1] = path;\n+\t\treturn;\n+\t}\n+\n+\tpaths-\u003epath[0].daddr = paths-\u003epath[1].daddr;\n+\tpaths-\u003epath[1].daddr = path.daddr;\n+}\n+\n+/* Frees resources associated with a QUIC path.\n+ *\n+ * This is used for cleanup during error handling or when the path is no longer\n+ * needed.\n+ */\n+void quic_path_unbind(struct sock *sk, struct quic_path_group *paths, u8 path)\n+{\n+\tpaths-\u003ealt_probes = 0;\n+\tpaths-\u003ealt_state = QUIC_PATH_ALT_NONE;\n+\n+\tquic_path_set_udp_sk(\u0026paths-\u003epath[path], NULL);\n+\n+\tmemset(quic_path_daddr(paths, path), 0, sizeof(union quic_addr));\n+\tmemset(quic_path_saddr(paths, path), 0, sizeof(union quic_addr));\n+}\n+\n+/* Detects and records a potential alternate path.\n+ *\n+ * If the new source or destination address differs from the active path, and\n+ * alternate path detection is not disabled, the function updates the alternate\n+ * path slot (path[1]) with the new addresses.\n+ *\n+ * This is typically called on packet receive to detect new possible network\n+ * paths (e.g., NAT rebinding, mobility).\n+ *\n+ * Returns true if a new alternate path was detected and updated, false\n+ * otherwise.\n+ */\n+bool quic_path_detect_alt(struct quic_path_group *paths, union quic_addr *sa,\n+\t\t\t  union quic_addr *da, struct sock *sk)\n+{\n+\tbool remote = !quic_cmp_sk_addr(sk, quic_path_daddr(paths, 0), da);\n+\tbool local = !quic_cmp_sk_addr(sk, quic_path_saddr(paths, 0), sa);\n+\n+\tif (!local \u0026\u0026 !remote)\n+\t\treturn false;\n+\n+\tif ((local \u0026\u0026 paths-\u003edisable_saddr_alt) ||\n+\t    (remote \u0026\u0026 paths-\u003edisable_daddr_alt))\n+\t\treturn false;\n+\n+\tif (!quic_path_saddr(paths, 1)-\u003ev4.sin_port)\n+\t\tquic_path_set_saddr(paths, 1, sa);\n+\n+\tif (!quic_cmp_sk_addr(sk, quic_path_saddr(paths, 1), sa))\n+\t\treturn false;\n+\n+\tif (!quic_path_daddr(paths, 1)-\u003ev4.sin_port)\n+\t\tquic_path_set_daddr(paths, 1, da);\n+\n+\treturn quic_cmp_sk_addr(sk, quic_path_daddr(paths, 1), da);\n+}\n+\n+void quic_path_get_param(struct quic_path_group *paths,\n+\t\t\t struct quic_transport_param *p)\n+{\n+\tif (p-\u003eremote) {\n+\t\tp-\u003edisable_active_migration = paths-\u003edisable_saddr_alt;\n+\t\treturn;\n+\t}\n+\tp-\u003edisable_active_migration = paths-\u003edisable_daddr_alt;\n+}\n+\n+void quic_path_set_param(struct quic_path_group *paths,\n+\t\t\t struct quic_transport_param *p)\n+{\n+\tif (p-\u003eremote) {\n+\t\tpaths-\u003edisable_saddr_alt = !!p-\u003edisable_active_migration;\n+\t\treturn;\n+\t}\n+\tpaths-\u003edisable_daddr_alt = !!p-\u003edisable_active_migration;\n+}\n+\n+/* State Machine defined in rfc8899#section-5.2 */\n+enum quic_plpmtud_state {\n+\tQUIC_PL_DISABLED,\n+\tQUIC_PL_BASE,\n+\tQUIC_PL_SEARCH,\n+\tQUIC_PL_COMPLETE,\n+\tQUIC_PL_ERROR,\n+};\n+\n+#define QUIC_BASE_PLPMTU        1200\n+#define QUIC_MAX_PLPMTU         9000\n+#define QUIC_MIN_PLPMTU         512\n+\n+#define QUIC_MAX_PROBES         3\n+\n+#define QUIC_PL_BIG_STEP        32\n+#define QUIC_PL_MIN_STEP        4\n+\n+/* Handle PLPMTUD probe failure on a QUIC path.\n+ *\n+ * Called immediately after sending a probe packet in QUIC Path MTU Discovery.\n+ * Tracks probe count and manages state transitions based on the number of\n+ * probes sent and current PLPMTUD state (BASE, SEARCH, COMPLETE, ERROR).\n+ * Detects probe failures and black holes, adjusting PMTU and probe sizes\n+ * accordingly.\n+ *\n+ * Return: New PMTU value if updated, else 0.\n+ */\n+u32 quic_path_pl_send(struct quic_path_group *paths, s64 number)\n+{\n+\tu32 pathmtu = 0;\n+\n+\tpaths-\u003epl.number = number;\n+\tif (paths-\u003epl.probe_count \u003c QUIC_MAX_PROBES)\n+\t\tgoto out;\n+\n+\tpaths-\u003epl.probe_count = 0;\n+\tif (paths-\u003epl.state == QUIC_PL_BASE) {\n+\t\tif (paths-\u003epl.probe_size == QUIC_BASE_PLPMTU) {\n+\t\t\t/* BASE_PLPMTU Confirming Failed: Base -\u003e Error. */\n+\t\t\tpaths-\u003epl.state = QUIC_PL_ERROR;\n+\n+\t\t\tpaths-\u003epl.pmtu = QUIC_BASE_PLPMTU;\n+\t\t\tpathmtu = QUIC_BASE_PLPMTU;\n+\t\t}\n+\t} else if (paths-\u003epl.state == QUIC_PL_SEARCH) {\n+\t\tif (paths-\u003epl.pmtu == paths-\u003epl.probe_size) {\n+\t\t\t/* Black Hole Detected: Search -\u003e Base. */\n+\t\t\tpaths-\u003epl.state = QUIC_PL_BASE;\n+\t\t\tpaths-\u003epl.probe_size = QUIC_BASE_PLPMTU;\n+\t\t\tpaths-\u003epl.probe_high = 0;\n+\n+\t\t\tpaths-\u003epl.pmtu = QUIC_BASE_PLPMTU;\n+\t\t\tpathmtu = QUIC_BASE_PLPMTU;\n+\t\t} else { /* Normal probe failure. */\n+\t\t\tpaths-\u003epl.probe_high = paths-\u003epl.probe_size;\n+\t\t\tpaths-\u003epl.probe_size = paths-\u003epl.pmtu;\n+\t\t}\n+\t} else if (paths-\u003epl.state == QUIC_PL_COMPLETE) {\n+\t\tif (paths-\u003epl.pmtu == paths-\u003epl.probe_size) {\n+\t\t\t/* Black Hole Detected:  Search Complete -\u003e Base. */\n+\t\t\tpaths-\u003epl.state = QUIC_PL_BASE;\n+\t\t\tpaths-\u003epl.probe_size = QUIC_BASE_PLPMTU;\n+\n+\t\t\t/* probe_high already reset when entering COMPLETE. */\n+\t\t\tpaths-\u003epl.pmtu = QUIC_BASE_PLPMTU;\n+\t\t\tpathmtu = QUIC_BASE_PLPMTU;\n+\t\t}\n+\t}\n+\n+out:\n+\tpr_debug(\"%s: dst: %p, state: %d, pmtu: %d, size: %d, high: %d\\n\",\n+\t\t __func__, paths, paths-\u003epl.state, paths-\u003epl.pmtu,\n+\t\t paths-\u003epl.probe_size, paths-\u003epl.probe_high);\n+\tpaths-\u003epl.probe_count++;\n+\treturn pathmtu;\n+}\n+\n+/* Handle successful reception of a PMTU probe.\n+ *\n+ * Called when a probe packet is acknowledged. Updates probe size and\n+ * transitions state if needed (e.g., from SEARCH to COMPLETE).  Expands PMTU\n+ * using binary or linear search depending on state.\n+ *\n+ * Return: New PMTU to apply if search completes, or 0 if no change.\n+ */\n+u32 quic_path_pl_recv(struct quic_path_group *paths, bool *raise_timer,\n+\t\t      bool *complete)\n+{\n+\tu32 pathmtu = 0;\n+\tu16 next;\n+\n+\tpr_debug(\"%s: dst: %p, state: %d, pmtu: %d, size: %d, high: %d\\n\",\n+\t\t __func__, paths, paths-\u003epl.state, paths-\u003epl.pmtu,\n+\t\t paths-\u003epl.probe_size, paths-\u003epl.probe_high);\n+\n+\t*raise_timer = false;\n+\tpaths-\u003epl.number = 0;\n+\tpaths-\u003epl.pmtu = paths-\u003epl.probe_size;\n+\tpaths-\u003epl.probe_count = 0;\n+\tif (paths-\u003epl.state == QUIC_PL_BASE) {\n+\t\tpaths-\u003epl.state = QUIC_PL_SEARCH; /* Base -\u003e Search */\n+\t\tpaths-\u003epl.probe_size += QUIC_PL_BIG_STEP;\n+\t} else if (paths-\u003epl.state == QUIC_PL_ERROR) {\n+\t\tpaths-\u003epl.state = QUIC_PL_SEARCH; /* Error -\u003e Search */\n+\n+\t\tpaths-\u003epl.pmtu = paths-\u003epl.probe_size;\n+\t\tpathmtu = (u32)paths-\u003epl.pmtu;\n+\t\tpaths-\u003epl.probe_size += QUIC_PL_BIG_STEP;\n+\t} else if (paths-\u003epl.state == QUIC_PL_SEARCH) {\n+\t\tif (!paths-\u003epl.probe_high) {\n+\t\t\tif (paths-\u003epl.probe_size \u003c QUIC_MAX_PLPMTU) {\n+\t\t\t\tnext = paths-\u003epl.probe_size + QUIC_PL_BIG_STEP;\n+\t\t\t\tpaths-\u003epl.probe_size =\n+\t\t\t\t\tmin_t(u16, next, QUIC_MAX_PLPMTU);\n+\t\t\t\t*complete = false;\n+\t\t\t\treturn 0;\n+\t\t\t}\n+\t\t\tpaths-\u003epl.probe_high = QUIC_MAX_PLPMTU;\n+\t\t}\n+\t\tpaths-\u003epl.probe_size += QUIC_PL_MIN_STEP;\n+\t\tif (paths-\u003epl.probe_size \u003e= paths-\u003epl.probe_high) {\n+\t\t\tpaths-\u003epl.probe_high = 0;\n+\t\t\t/* Search -\u003e Search Complete */\n+\t\t\tpaths-\u003epl.state = QUIC_PL_COMPLETE;\n+\n+\t\t\tpaths-\u003epl.probe_size = paths-\u003epl.pmtu;\n+\t\t\tpathmtu = (u32)paths-\u003epl.pmtu;\n+\t\t\t*raise_timer = true;\n+\t\t}\n+\t} else if (paths-\u003epl.state == QUIC_PL_COMPLETE) {\n+\t\t/* Raise probe_size after 30 * interval in Search Complete;\n+\t\t * Search Complete -\u003e Search.\n+\t\t */\n+\t\tpaths-\u003epl.state = QUIC_PL_SEARCH;\n+\t\tnext = paths-\u003epl.probe_size + QUIC_PL_MIN_STEP;\n+\t\tpaths-\u003epl.probe_size = min_t(u16, next, QUIC_MAX_PLPMTU);\n+\t}\n+\n+\t*complete = (paths-\u003epl.state == QUIC_PL_COMPLETE);\n+\treturn pathmtu;\n+}\n+\n+/* Handle ICMP \"Packet Too Big\" messages.\n+ *\n+ * Responds to an incoming ICMP error by reducing the probe size or falling\n+ * back to a safe baseline PMTU depending on current state.  Also handles cases\n+ * where the PMTU hint lies between probe and current PMTU.\n+ *\n+ * Return: New PMTU to apply if state changes, or 0 if no change.\n+ */\n+u32 quic_path_pl_toobig(struct quic_path_group *paths, u32 pmtu,\n+\t\t\tbool *reset_timer)\n+{\n+\tu32 pathmtu = 0;\n+\n+\tpr_debug(\"%s: dst: %p, state: %d, pmtu: %d, size: %d, ptb: %d\\n\",\n+\t\t __func__, paths, paths-\u003epl.state, paths-\u003epl.pmtu,\n+\t\t paths-\u003epl.probe_size, pmtu);\n+\n+\t*reset_timer = false;\n+\tif (pmtu \u003c QUIC_MIN_PLPMTU || pmtu \u003e= (u32)paths-\u003epl.probe_size)\n+\t\treturn pathmtu;\n+\n+\tif (paths-\u003epl.state == QUIC_PL_BASE) {\n+\t\tif (pmtu \u003c QUIC_BASE_PLPMTU) {\n+\t\t\tpaths-\u003epl.state = QUIC_PL_ERROR; /* Base -\u003e Error */\n+\n+\t\t\tpaths-\u003epl.pmtu = QUIC_BASE_PLPMTU;\n+\t\t\tpathmtu = QUIC_BASE_PLPMTU;\n+\t\t}\n+\t} else if (paths-\u003epl.state == QUIC_PL_SEARCH) {\n+\t\tif (pmtu \u003e= QUIC_BASE_PLPMTU \u0026\u0026 pmtu \u003c (u32)paths-\u003epl.pmtu) {\n+\t\t\tpaths-\u003epl.state = QUIC_PL_BASE;  /* Search -\u003e Base */\n+\t\t\tpaths-\u003epl.probe_size = QUIC_BASE_PLPMTU;\n+\t\t\tpaths-\u003epl.probe_count = 0;\n+\n+\t\t\tpaths-\u003epl.probe_high = 0;\n+\t\t\tpaths-\u003epl.pmtu = QUIC_BASE_PLPMTU;\n+\t\t\tpathmtu = QUIC_BASE_PLPMTU;\n+\t\t} else if (pmtu \u003e (u32)paths-\u003epl.pmtu \u0026\u0026\n+\t\t\t   pmtu \u003c (u32)paths-\u003epl.probe_size) {\n+\t\t\tpaths-\u003epl.probe_size = (u16)pmtu;\n+\t\t\tpaths-\u003epl.probe_count = 0;\n+\t\t}\n+\t} else if (paths-\u003epl.state == QUIC_PL_COMPLETE) {\n+\t\tif (pmtu \u003e= QUIC_BASE_PLPMTU \u0026\u0026 pmtu \u003c (u32)paths-\u003epl.pmtu) {\n+\t\t\tpaths-\u003epl.state = QUIC_PL_BASE;  /* Complete -\u003e Base */\n+\t\t\tpaths-\u003epl.probe_size = QUIC_BASE_PLPMTU;\n+\t\t\tpaths-\u003epl.probe_count = 0;\n+\n+\t\t\tpaths-\u003epl.probe_high = 0;\n+\t\t\tpaths-\u003epl.pmtu = QUIC_BASE_PLPMTU;\n+\t\t\tpathmtu = QUIC_BASE_PLPMTU;\n+\t\t\t*reset_timer = true;\n+\t\t}\n+\t}\n+\treturn pathmtu;\n+}\n+\n+/* Reset PLPMTUD state for a path.\n+ *\n+ * Resets all PLPMTUD-related state to its initial configuration.  Called when\n+ * a new path is initialized or when recovering from errors.\n+ */\n+void quic_path_pl_reset(struct quic_path_group *paths)\n+{\n+\tpaths-\u003epl.number = 0;\n+\tpaths-\u003epl.probe_high = 0;\n+\tpaths-\u003epl.probe_count = 0;\n+\tpaths-\u003epl.state = QUIC_PL_BASE;\n+\tpaths-\u003epl.pmtu = QUIC_BASE_PLPMTU;\n+\tpaths-\u003epl.probe_size = QUIC_BASE_PLPMTU;\n+}\n+\n+/* Check if a packet number confirms PLPMTUD probe.\n+ *\n+ * Checks whether the last probe (tracked by .number) has been acknowledged.\n+ * If the probe number lies within the ACK range, confirmation is successful.\n+ *\n+ * Return: true if probe is confirmed, false otherwise.\n+ */\n+bool quic_path_pl_confirm(struct quic_path_group *paths, s64 largest,\n+\t\t\t  s64 smallest)\n+{\n+\treturn paths-\u003epl.number \u0026\u0026 paths-\u003epl.number \u003e= smallest \u0026\u0026\n+\t       paths-\u003epl.number \u003c= largest;\n+}\ndiff --git a/net/quic/path.h b/net/quic/path.h\nnew file mode 100644\nindex 00000000000000..a7eedfa2f02242\n--- /dev/null\n+++ b/net/quic/path.h\n@@ -0,0 +1,190 @@\n+/* SPDX-License-Identifier: GPL-2.0-or-later */\n+/* QUIC kernel implementation\n+ * (C) Copyright Red Hat Corp. 2023\n+ *\n+ * This file is part of the QUIC kernel implementation\n+ *\n+ * Written or modified by:\n+ *    Xin Long \u003clucien.xin@gmail.com\u003e\n+ */\n+\n+#define QUIC_PATH_MIN_PMTU\t1200U\n+#define QUIC_PATH_MAX_PMTU\t65535U\n+\n+#define QUIC_MIN_UDP_PAYLOAD\t1200\n+#define QUIC_MAX_UDP_PAYLOAD\t65527\n+\n+#define QUIC_PATH_ENTROPY_LEN\t8\n+\n+#define QUIC_PMTUD_RAISE_TIMER_FACTOR\t30\n+\n+extern struct workqueue_struct\t*quic_wq;\n+\n+/* Connection Migration State Machine:\n+ *\n+ * +--------+      recv non-probing, free old path    +----------+\n+ * |  NONE  | \u003c-------------------------------------- | SWAPPED  |\n+ * +--------+                                         +----------+\n+ *      |   ^ \\                                            ^\n+ *      |    \\ \\                                           |\n+ *      |     \\ \\   new path detected,                     | recv\n+ *      |      \\ \\  has another DCID,                      | Path\n+ *      |       \\ \\ snd Path Challenge                     | Response\n+ *      |        \\ -------------------------------         |\n+ *      |         ------------------------------- \\        |\n+ *      | new path detected,            Path     \\ \\       |\n+ *      | has no other DCID,            Challenge \\ \\      |\n+ *      | request a new DCID            failed     \\ \\     |\n+ *      v                                           \\ v    |\n+ * +----------+                                       +----------+\n+ * | PENDING  | ------------------------------------\u003e | PROBING  |\n+ * +----------+  recv a new DCID, snd Path Challenge  +----------+\n+ */\n+enum {\n+\tQUIC_PATH_ALT_NONE,\n+\tQUIC_PATH_ALT_PENDING, /* Waiting for new dest conn ID for migration */\n+\tQUIC_PATH_ALT_PROBING, /* Validating alternate path (PATH_CHALLENGE) */\n+\tQUIC_PATH_ALT_SWAPPED, /* Alternate path is now active; roles swapped */\n+};\n+\n+struct quic_udp_sock {\n+\tstruct work_struct work; /* Workqueue to destroy UDP tunnel socket */\n+\tstruct hlist_node node;  /* Node in addr-based UDP socket hash table */\n+\tunion quic_addr addr; /* Source addr of underlying UDP tunnel socket */\n+\tint bind_ifindex;\n+\trefcount_t refcnt;\n+\tstruct sock *sk; /* Underlying UDP tunnel socket */\n+};\n+\n+struct quic_path {\n+\tunion quic_addr daddr; /* Destination address */\n+\tunion quic_addr saddr; /* Source address */\n+\n+\t/* Wrapped UDP socket for receiving QUIC */\n+\tstruct quic_udp_sock *udp_sk;\n+\t/* Cached UDP tunnel socket and source addr for RCU access */\n+\tunion quic_addr uaddr;\n+\tstruct sock *usk;\n+};\n+\n+struct quic_path_group {\n+\t/* Connection ID validation during handshake (rfc9000#section-7.3) */\n+\tstruct quic_conn_id retry_dcid; /* Source CID from Retry packet */\n+\tstruct quic_conn_id orig_dcid;  /* Destination CID from first Initial */\n+\n+\t/* Path validation (rfc9000#section-8.2) */\n+\tu8 entropy[QUIC_PATH_ENTROPY_LEN]; /* Entropy for PATH_CHALLENGE */\n+\tstruct quic_path path[2]; /* Active path (0) and alternate path (1) */\n+\tstruct flowi fl;          /* Flow info from routing decisions */\n+\n+\t/* Anti-amplification limit (rfc9000#section-8) */\n+\tu32 ampl_sndlen; /* Bytes sent before address is validated */\n+\tu32 ampl_rcvlen; /* Bytes received to lift amplification limit */\n+\n+\t/* MTU discovery handling */\n+\tstruct { /* PLPMTUD probing (rfc8899) */\n+\t\ts64 number; /* Packet number used for current probe */\n+\t\tu16 pmtu;   /* Confirmed path MTU */\n+\n+\t\tu16 probe_size; /* Current probe packet size */\n+\t\tu16 probe_high; /* Highest failed probe size */\n+\t\tu8 probe_count; /* Retry count for current probe_size */\n+\t\tu8 state;       /* Probe state machine (rfc8899#section-5.2) */\n+\t} pl;\n+\tu32 mtu_info; /* PMTU value from received ICMP, pending apply */\n+\n+\tu32 plpmtud_interval;   /* Time interval for the PLPMTUD probe timer */\n+\tu32 keepalive_interval; /* Time interval to maintain path liveness */\n+\n+\tu8 ecn_probes;  /* ECN probe counter */\n+\tu8 validated:1; /* Path validated with PATH_RESPONSE */\n+\tu8 blocked:1;   /* Blocked by anti-amplification limit */\n+\tu8 version:1;   /* Version negotiation performed */\n+\tu8 retry:1;     /* Retry used in initial packet */\n+\n+\t/* Connection Migration (rfc9000#section-9) */\n+\tu8 disable_saddr_alt:1;\t/* Remote disable_active_migration parameter */\n+\tu8 disable_daddr_alt:1;\t/* Local disable_active_migration parameter */\n+\tu8 pref_addr:1; /* Preferred address offered (rfc9000#section-18.2) */\n+\tu8 alt_probes;  /* Number of PATH_CHALLENGE probes sent */\n+\tu8 alt_state;   /* Connection migration state (see above) */\n+};\n+\n+static inline union quic_addr *quic_path_saddr(struct quic_path_group *paths,\n+\t\t\t\t\t       u8 path)\n+{\n+\treturn \u0026paths-\u003epath[path].saddr;\n+}\n+\n+static inline void quic_path_set_saddr(struct quic_path_group *paths, u8 path,\n+\t\t\t\t       union quic_addr *addr)\n+{\n+\tmemcpy(quic_path_saddr(paths, path), addr, sizeof(*addr));\n+}\n+\n+static inline union quic_addr *quic_path_daddr(struct quic_path_group *paths,\n+\t\t\t\t\t       u8 path)\n+{\n+\treturn \u0026paths-\u003epath[path].daddr;\n+}\n+\n+static inline void quic_path_set_daddr(struct quic_path_group *paths, u8 path,\n+\t\t\t\t       union quic_addr *addr)\n+{\n+\tmemcpy(quic_path_daddr(paths, path), addr, sizeof(*addr));\n+}\n+\n+static inline union quic_addr *quic_path_uaddr(struct quic_path_group *paths,\n+\t\t\t\t\t       u8 path)\n+{\n+\treturn \u0026paths-\u003epath[path].uaddr;\n+}\n+\n+static inline struct sock *quic_path_usock(struct quic_path_group *paths,\n+\t\t\t\t\t   u8 path)\n+{\n+\treturn paths-\u003epath[path].usk;\n+}\n+\n+static inline bool quic_path_alt_state(struct quic_path_group *paths, u8 state)\n+{\n+\treturn paths-\u003ealt_state == state;\n+}\n+\n+static inline void quic_path_set_alt_state(struct quic_path_group *paths,\n+\t\t\t\t\t   u8 state)\n+{\n+\tpaths-\u003ealt_state = state;\n+}\n+\n+/* Returns the destination Connection ID (DCID) used for identifying the\n+ * connection.  Per rfc9000#section-7.3, handshake packets are considered part\n+ * of the same connection if their DCID matches the one returned here.\n+ */\n+static inline struct quic_conn_id *\n+quic_path_orig_dcid(struct quic_path_group *paths)\n+{\n+\treturn paths-\u003eretry ? \u0026paths-\u003eretry_dcid : \u0026paths-\u003eorig_dcid;\n+}\n+\n+int quic_packet_rcv(struct sock *sk, struct sk_buff *skb, bool icmp);\n+\n+bool quic_path_detect_alt(struct quic_path_group *paths, union quic_addr *sa,\n+\t\t\t  union quic_addr *da, struct sock *sk);\n+int quic_path_bind(struct sock *sk, struct quic_path_group *paths, u8 path);\n+void quic_path_unbind(struct sock *sk, struct quic_path_group *paths, u8 path);\n+void quic_path_swap(struct quic_path_group *paths);\n+\n+u32 quic_path_pl_recv(struct quic_path_group *paths, bool *raise_timer,\n+\t\t      bool *complete);\n+u32 quic_path_pl_toobig(struct quic_path_group *paths, u32 pmtu,\n+\t\t\tbool *reset_timer);\n+u32 quic_path_pl_send(struct quic_path_group *paths, s64 number);\n+\n+void quic_path_get_param(struct quic_path_group *paths,\n+\t\t\t struct quic_transport_param *p);\n+void quic_path_set_param(struct quic_path_group *paths,\n+\t\t\t struct quic_transport_param *p);\n+bool quic_path_pl_confirm(struct quic_path_group *paths,\n+\t\t\t  s64 largest, s64 smallest);\n+void quic_path_pl_reset(struct quic_path_group *paths);\ndiff --git a/net/quic/pnspace.c b/net/quic/pnspace.c\nnew file mode 100644\nindex 00000000000000..f4690f209dd16c\n--- /dev/null\n+++ b/net/quic/pnspace.c\n@@ -0,0 +1,251 @@\n+// SPDX-License-Identifier: GPL-2.0-or-later\n+/* QUIC kernel implementation\n+ * (C) Copyright Red Hat Corp. 2023\n+ *\n+ * This file is part of the QUIC kernel implementation\n+ *\n+ * Initialization/cleanup for QUIC protocol support.\n+ *\n+ * Written or modified by:\n+ *    Xin Long \u003clucien.xin@gmail.com\u003e\n+ */\n+\n+#include \u003clinux/slab.h\u003e\n+\n+#include \"common.h\"\n+#include \"pnspace.h\"\n+\n+int quic_pnspace_init(struct quic_pnspace *space)\n+{\n+\tspace-\u003epn_map = kzalloc(BITS_TO_BYTES(QUIC_PN_MAP_INITIAL), GFP_KERNEL);\n+\tif (!space-\u003epn_map)\n+\t\treturn -ENOMEM;\n+\tspace-\u003epn_map_len = QUIC_PN_MAP_INITIAL;\n+\n+\tspace-\u003emax_time_limit = QUIC_PNSPACE_TIME_LIMIT;\n+\tspace-\u003enext_pn = QUIC_PNSPACE_NEXT_PN;\n+\tspace-\u003emax_pn_acked_seen = -1;\n+\tspace-\u003ebase_pn = -1;\n+\treturn 0;\n+}\n+\n+void quic_pnspace_free(struct quic_pnspace *space)\n+{\n+\tspace-\u003epn_map_len = 0;\n+\tkfree(space-\u003epn_map);\n+\tspace-\u003epn_map = NULL;\n+}\n+\n+/* Expand the bitmap tracking received packet numbers.  Ensures the pn_map\n+ * bitmap can cover at least @size packet numbers.  Allocates a larger bitmap,\n+ * copies existing data, and updates metadata.\n+ *\n+ * Return: 0 on success, or a negative errno value on failure.\n+ */\n+static int quic_pnspace_grow(struct quic_pnspace *space, u16 size, gfp_t gfp)\n+{\n+\tu16 len, inc, offset;\n+\tunsigned long *new;\n+\n+\tinc = ALIGN((size - space-\u003epn_map_len), BITS_PER_LONG) +\n+\t      QUIC_PN_MAP_INCREMENT;\n+\tlen = (u16)min(space-\u003epn_map_len + inc, QUIC_PN_MAP_SIZE);\n+\n+\tnew = kzalloc(BITS_TO_BYTES(len), gfp);\n+\tif (!new)\n+\t\treturn -ENOMEM;\n+\n+\toffset = (u16)(space-\u003emax_pn_seen + 1 - space-\u003ebase_pn);\n+\tbitmap_copy(new, space-\u003epn_map, offset);\n+\tkfree(space-\u003epn_map);\n+\tspace-\u003epn_map = new;\n+\tspace-\u003epn_map_len = len;\n+\n+\treturn 0;\n+}\n+\n+/* Check if a packet number has been received.\n+ *\n+ * Returns: 0 if the packet number has not been received.  1 if it has already\n+ * been received. -EINVAL if the packet number is too old to track.\n+ */\n+int quic_pnspace_check(struct quic_pnspace *space, s64 pn)\n+{\n+\tif (pn \u003e QUIC_PN_MAX || pn \u003c 0)\n+\t\treturn -EINVAL;\n+\n+\tif (space-\u003ebase_pn == -1) /* No packet number received yet. */\n+\t\treturn 0;\n+\n+\tif (pn \u003c space-\u003emin_pn_seen)\n+\t\treturn -EINVAL;\n+\n+\tif (pn \u003c space-\u003ebase_pn)\n+\t\treturn 1;\n+\tif (pn - space-\u003ebase_pn \u003c space-\u003epn_map_len \u0026\u0026\n+\t    test_bit(pn - space-\u003ebase_pn, space-\u003epn_map))\n+\t\treturn 1;\n+\n+\treturn 0;\n+}\n+\n+/* Advance base_pn past contiguous received packet numbers.  Finds the next gap\n+ * (unreceived packet) beyond @pn, shifts the bitmap, and updates base_pn\n+ * accordingly.\n+ */\n+static void quic_pnspace_move(struct quic_pnspace *space, s64 pn)\n+{\n+\tu16 offset;\n+\n+\toffset = (u16)(pn + 1 - space-\u003ebase_pn);\n+\toffset = (u16)find_next_zero_bit(space-\u003epn_map, space-\u003epn_map_len,\n+\t\t\t\t\t offset);\n+\tspace-\u003ebase_pn += offset;\n+\tbitmap_shift_right(space-\u003epn_map, space-\u003epn_map, offset,\n+\t\t\t   space-\u003epn_map_len);\n+}\n+\n+/* Mark a packet number as received. Updates the packet number map to record\n+ * reception of @pn.  Advances base_pn if possible, and updates max/min/last\n+ * seen fields as needed.\n+ *\n+ * Returns: 0 on success or if the packet was already marked, or a negative\n+ * error returned by bitmap growth when expanding the map.\n+ */\n+int quic_pnspace_mark(struct quic_pnspace *space, s64 pn, gfp_t gfp)\n+{\n+\ts64 last_max_pn_seen, off;\n+\tu64 last_max_pn_time;\n+\tbool has_gap;\n+\tint err;\n+\n+\tif (space-\u003ebase_pn == -1) {\n+\t\t/* Initialize base_pn based on the peer's first packet number\n+\t\t * since peer's packet numbers may start at a non-zero value.\n+\t\t */\n+\t\tquic_pnspace_set_base_pn(space, pn + 1);\n+\t\treturn 0;\n+\t}\n+\n+\t/* Ignore packets with number less than current base (already\n+\t * processed).\n+\t */\n+\tif (pn \u003c space-\u003ebase_pn)\n+\t\treturn 0;\n+\n+\t/* If offset is beyond current map length, try to grow the bitmap to\n+\t * accommodate.\n+\t */\n+\toff = pn - space-\u003ebase_pn;\n+\tif (off \u003e= space-\u003epn_map_len) {\n+\t\tif (off \u003e= QUIC_PN_MAP_SIZE) {\n+\t\t\tbitmap_zero(space-\u003epn_map, space-\u003epn_map_len);\n+\t\t\tquic_pnspace_set_base_pn(space, pn + 1);\n+\t\t\treturn 0;\n+\t\t}\n+\t\terr = quic_pnspace_grow(space, off + 1, gfp);\n+\t\tif (err)\n+\t\t\treturn err;\n+\t}\n+\n+\thas_gap = quic_pnspace_has_gap(space);\n+\tif (space-\u003emax_pn_seen \u003c pn) {\n+\t\tspace-\u003emax_pn_seen = pn;\n+\t\tspace-\u003emax_pn_time = space-\u003etime;\n+\t}\n+\n+\tif (space-\u003ebase_pn == pn) { /* PN is next expected packet. */\n+\t\tif (has_gap) /* Advance to next gap. */\n+\t\t\tquic_pnspace_move(space, pn);\n+\t\telse /* Fast path: increment base_pn if no gaps. */\n+\t\t\tspace-\u003ebase_pn++;\n+\t} else { /* Mark this packet as received in the bitmap. */\n+\t\tset_bit(off, space-\u003epn_map);\n+\t}\n+\n+\t/* Only update min and last_max_pn_seen if this packet is the current\n+\t * max_pn.\n+\t */\n+\tif (space-\u003emax_pn_seen != pn)\n+\t\treturn 0;\n+\n+\t/* Check if enough time has elapsed or enough packets have been\n+\t * received to update tracking.\n+\t */\n+\tlast_max_pn_seen = min_t(s64, space-\u003elast_max_pn_seen, space-\u003ebase_pn);\n+\tlast_max_pn_time = space-\u003elast_max_pn_time;\n+\tif (space-\u003emax_pn_time \u003c last_max_pn_time + space-\u003emax_time_limit \u0026\u0026\n+\t    space-\u003emax_pn_seen \u003c= last_max_pn_seen + QUIC_PN_MAP_LIMIT)\n+\t\treturn 0;\n+\n+\t/* Advance base_pn if last_max_pn_seen is ahead of current base_pn.\n+\t * This is needed because QUIC doesn't retransmit packets;\n+\t * retransmitted frames are carried in new packets, so we move forward.\n+\t */\n+\tif (space-\u003elast_max_pn_seen + 1 \u003e space-\u003ebase_pn)\n+\t\tquic_pnspace_move(space, space-\u003elast_max_pn_seen);\n+\n+\tspace-\u003emin_pn_seen = space-\u003elast_max_pn_seen;\n+\tspace-\u003elast_max_pn_seen = space-\u003emax_pn_seen;\n+\tspace-\u003elast_max_pn_time = space-\u003emax_pn_time;\n+\treturn 0;\n+}\n+\n+/* Find the next gap in received packet numbers. Scans pn_map for a gap\n+ * starting from *@iter. A gap is a contiguous block of unreceived packets\n+ * between received ones.\n+ *\n+ * Returns: true if a gap was found, false if no more gaps exist or are\n+ * relevant.\n+ */\n+static bool quic_pnspace_next_gap_ack(const struct quic_pnspace *space,\n+\t\t\t\t      s64 *iter, u16 *start, u16 *end)\n+{\n+\tu16 start_ = 0, end_ = 0, offset = (u16)(*iter - space-\u003ebase_pn);\n+\n+\tstart_ = (u16)find_next_zero_bit(space-\u003epn_map, space-\u003epn_map_len,\n+\t\t\t\t\t offset);\n+\tif (space-\u003emax_pn_seen \u003c= space-\u003ebase_pn + start_)\n+\t\treturn false;\n+\n+\tend_ = (u16)find_next_bit(space-\u003epn_map, space-\u003epn_map_len, start_);\n+\tif (space-\u003emax_pn_seen \u003c= space-\u003ebase_pn + end_ - 1)\n+\t\treturn false;\n+\n+\t*start = start_ + 1;\n+\t*end = end_;\n+\t*iter = space-\u003ebase_pn + *end;\n+\treturn true;\n+}\n+\n+/* Generate gap acknowledgment blocks (GABs).  GABs describe ranges of\n+ * unacknowledged packets between received ones, and are used in ACK frames.\n+ *\n+ * Returns: Number of generated GABs (up to QUIC_PN_MAP_MAX_GABS).\n+ */\n+u16 quic_pnspace_num_gabs(struct quic_pnspace *space,\n+\t\t\t  struct quic_gap_ack_block *gabs)\n+{\n+\tu16 start, end, ngaps = 0;\n+\ts64 iter;\n+\n+\tif (!quic_pnspace_has_gap(space))\n+\t\treturn 0;\n+\n+\titer = space-\u003ebase_pn;\n+\t/* Loop through all gaps until the end of the window or max allowed\n+\t * gaps.\n+\t */\n+\twhile (quic_pnspace_next_gap_ack(space, \u0026iter, \u0026start, \u0026end)) {\n+\t\tgabs[ngaps].start = start;\n+\t\tif (ngaps == QUIC_PN_MAP_MAX_GABS - 1) {\n+\t\t\tgabs[ngaps].end =\n+\t\t\t\t(u16)(space-\u003emax_pn_seen - space-\u003ebase_pn);\n+\t\t\tngaps++;\n+\t\t\tbreak;\n+\t\t}\n+\t\tgabs[ngaps].end = end;\n+\t\tngaps++;\n+\t}\n+\treturn ngaps;\n+}\ndiff --git a/net/quic/pnspace.h b/net/quic/pnspace.h\nnew file mode 100644\nindex 00000000000000..15ce6d2ef72695\n--- /dev/null\n+++ b/net/quic/pnspace.h\n@@ -0,0 +1,201 @@\n+/* SPDX-License-Identifier: GPL-2.0-or-later */\n+/* QUIC kernel implementation\n+ * (C) Copyright Red Hat Corp. 2023\n+ *\n+ * This file is part of the QUIC kernel implementation\n+ *\n+ * Written or modified by:\n+ *    Xin Long \u003clucien.xin@gmail.com\u003e\n+ */\n+\n+#define QUIC_PN_MAP_MAX_GABS\t32\n+\n+#define QUIC_PN_MAP_INITIAL\t64\n+#define QUIC_PN_MAP_INCREMENT\tQUIC_PN_MAP_INITIAL\n+#define QUIC_PN_MAP_SIZE\t4096\n+#define QUIC_PN_MAP_LIMIT\t(QUIC_PN_MAP_SIZE * 3 / 4)\n+\n+#define QUIC_PNSPACE_MAX\t(QUIC_CRYPTO_MAX - 1)\n+#define QUIC_PNSPACE_NEXT_PN\t0\n+#define QUIC_PNSPACE_TIME_LIMIT\t(333000 * 3)\n+\n+enum {\n+\tQUIC_ECN_ECT1,\n+\tQUIC_ECN_ECT0,\n+\tQUIC_ECN_CE,\n+\tQUIC_ECN_MAX\n+};\n+\n+enum {\n+\tQUIC_ECN_LOCAL, /* ECN bits from incoming IP headers */\n+\tQUIC_ECN_PEER,  /* ECN bits reported by peer in ACK frames */\n+\tQUIC_ECN_ACKED, /* ECN bits from packets newly ACKed */\n+\tQUIC_ECN_DIR_MAX\n+};\n+\n+/* Represents a gap (range of missing packets) in the ACK map.  The values are\n+ * offsets from base_pn, with both 'start' and 'end' being +1.\n+ */\n+struct quic_gap_ack_block {\n+\tu16 start;\n+\tu16 end;\n+};\n+\n+/* Packet Number Map (pn_map) Layout:\n+ *\n+ *     min_pn_seen --\u003e++-----------------------+---------------------+---\n+ *         base_pn -----^   last_max_pn_seen --^       max_pn_seen --^\n+ *\n+ * Map Advancement Logic:\n+ *   - min_pn_seen = last_max_pn_seen;\n+ *   - base_pn = first zero bit after last_max_pn_seen;\n+ *   - last_max_pn_seen = max_pn_seen;\n+ *   - last_max_pn_time = current time;\n+ *\n+ * Conditions to Advance pn_map:\n+ *   - (max_pn_time - last_max_pn_time) \u003e= max_time_limit, or\n+ *   - (max_pn_seen - last_max_pn_seen) \u003e QUIC_PN_MAP_LIMIT\n+ *\n+ * Gap Search Range:\n+ *   - From (base_pn - 1) to max_pn_seen\n+ */\n+struct quic_pnspace {\n+\t/* ECN counters indexed by dir and ECN codepoint (ECT1, ECT0, CE) */\n+\tu64 ecn_count[QUIC_ECN_DIR_MAX][QUIC_ECN_MAX];\n+\tunsigned long *pn_map; /* Received PN bitmap for ACK generation */\n+\tu16 pn_map_len;        /* Length of the PN bit map (in bits) */\n+\tu8  need_sack;         /* Flag indicating a SACK frame should be sent */\n+\tu8  sack_path;         /* Path used for sending the SACK frame */\n+\tu8  sack_pending;      /* Delayed ACK pending */\n+\n+\ts64 last_max_pn_seen; /* Largest PN seen before pn_map advance */\n+\tu64 last_max_pn_time; /* Timestamp last_max_pn_seen was received */\n+\ts64 min_pn_seen;      /* Smallest PN received */\n+\ts64 max_pn_seen;      /* Largest PN received */\n+\tu64 max_pn_time;      /* Timestamp max_pn_seen was received */\n+\ts64 base_pn; /* PN corresponding to the start of the pn_map */\n+\tu64 time;    /* Cached now, or latest socket accept timestamp */\n+\n+\ts64 max_pn_acked_seen; /* Largest PN ACKed by peer */\n+\tu64 max_pn_acked_time; /* Timestamp max_pn_acked_seen was ACKed */\n+\tu64 last_sent_time;    /* Timestamp last ack-eliciting packet sent */\n+\tu64 loss_time;         /* Timestamp the packet can be declared lost */\n+\ts64 next_pn;           /* Next PN to send */\n+\n+\tu32 max_time_limit; /* Time threshold to trigger pn_map advance */\n+\tu32 inflight;       /* Ack-eliciting bytes in flight */\n+};\n+\n+static inline void\n+quic_pnspace_set_max_pn_acked_seen(struct quic_pnspace *space,\n+\t\t\t\t   s64 max_pn_acked_seen)\n+{\n+\tif (space-\u003emax_pn_acked_seen \u003e= max_pn_acked_seen)\n+\t\treturn;\n+\tspace-\u003emax_pn_acked_seen = max_pn_acked_seen;\n+\tspace-\u003emax_pn_acked_time = quic_ktime_get_us();\n+}\n+\n+static inline void quic_pnspace_set_base_pn(struct quic_pnspace *space, s64 pn)\n+{\n+\tspace-\u003ebase_pn = pn;\n+\tspace-\u003emax_pn_seen = space-\u003ebase_pn - 1;\n+\tspace-\u003elast_max_pn_seen = space-\u003emax_pn_seen;\n+\tspace-\u003emin_pn_seen = space-\u003emax_pn_seen;\n+\n+\tspace-\u003emax_pn_time = space-\u003etime;\n+\tspace-\u003elast_max_pn_time = space-\u003emax_pn_time;\n+}\n+\n+static inline bool quic_pnspace_has_gap(const struct quic_pnspace *space)\n+{\n+\treturn space-\u003ebase_pn != space-\u003emax_pn_seen + 1;\n+}\n+\n+static inline void quic_pnspace_inc_ecn_acked(struct quic_pnspace *space,\n+\t\t\t\t\t      u8 ecn)\n+{\n+\tif (!ecn)\n+\t\treturn;\n+\tspace-\u003eecn_count[QUIC_ECN_ACKED][ecn - 1]++;\n+}\n+\n+static inline void quic_pnspace_reset_ecn_acked(struct quic_pnspace *space)\n+{\n+\tspace-\u003eecn_count[QUIC_ECN_ACKED][QUIC_ECN_ECT0] = 0;\n+\tspace-\u003eecn_count[QUIC_ECN_ACKED][QUIC_ECN_ECT1] = 0;\n+\tspace-\u003eecn_count[QUIC_ECN_ACKED][QUIC_ECN_CE] = 0;\n+}\n+\n+static inline void quic_pnspace_inc_ecn_local(struct quic_pnspace *space,\n+\t\t\t\t\t      u8 ecn)\n+{\n+\tif (!ecn)\n+\t\treturn;\n+\tspace-\u003eecn_count[QUIC_ECN_LOCAL][ecn - 1]++;\n+}\n+\n+/* Check if any ECN-marked packets were received. */\n+static inline bool quic_pnspace_has_ecn_local(struct quic_pnspace *space)\n+{\n+\treturn space-\u003eecn_count[QUIC_ECN_LOCAL][QUIC_ECN_ECT0] ||\n+\t       space-\u003eecn_count[QUIC_ECN_LOCAL][QUIC_ECN_ECT1] ||\n+\t       space-\u003eecn_count[QUIC_ECN_LOCAL][QUIC_ECN_CE];\n+}\n+\n+/* Validate ECN counts received in an ACK. */\n+static inline bool quic_pnspace_validate_ecn(struct quic_pnspace *space,\n+\t\t\t\t\t     u64 *ecn_count)\n+{\n+\tu64 *acked = space-\u003eecn_count[QUIC_ECN_ACKED];\n+\tu64 *peer = space-\u003eecn_count[QUIC_ECN_PEER];\n+\tu64 ect0, ect1, ce;\n+\n+\tif (peer[QUIC_ECN_ECT0] \u003e ecn_count[QUIC_ECN_ECT0] ||\n+\t    peer[QUIC_ECN_ECT1] \u003e ecn_count[QUIC_ECN_ECT1] ||\n+\t    peer[QUIC_ECN_CE] \u003e ecn_count[QUIC_ECN_CE])\n+\t\treturn false;\n+\n+\t/* rfc9000#section-13.4.2.1:\n+\t *\n+\t * ECN validation also fails if the sum of the increase in ECT(0) and\n+\t * ECN-CE counts is less than the number of newly acknowledged packets\n+\t * that were originally sent with an ECT(0) marking (Same for ECT(1)).\n+\t */\n+\tect0 = ecn_count[QUIC_ECN_ECT0] - peer[QUIC_ECN_ECT0];\n+\tect1 = ecn_count[QUIC_ECN_ECT1] - peer[QUIC_ECN_ECT1];\n+\tce = ecn_count[QUIC_ECN_CE] - peer[QUIC_ECN_CE];\n+\n+\treturn ect0 + ce \u003e= acked[QUIC_ECN_ECT0] \u0026\u0026\n+\t       ect1 + ce \u003e= acked[QUIC_ECN_ECT1];\n+}\n+\n+/* Updates the stored ECN counters based on values received in the peer's ACK\n+ * frame. Each counter is updated only if the new value is higher.\n+ *\n+ * Returns: true if CE count was increased (congestion indicated), false\n+ * otherwise.\n+ */\n+static inline bool quic_pnspace_set_ecn_peer(struct quic_pnspace *space,\n+\t\t\t\t\t     u64 *ecn_count)\n+{\n+\tu64 *count = space-\u003eecn_count[QUIC_ECN_PEER];\n+\n+\tif (count[QUIC_ECN_ECT0] \u003c ecn_count[QUIC_ECN_ECT0])\n+\t\tcount[QUIC_ECN_ECT0] = ecn_count[QUIC_ECN_ECT0];\n+\tif (count[QUIC_ECN_ECT1] \u003c ecn_count[QUIC_ECN_ECT1])\n+\t\tcount[QUIC_ECN_ECT1] = ecn_count[QUIC_ECN_ECT1];\n+\tif (count[QUIC_ECN_CE] \u003c ecn_count[QUIC_ECN_CE]) {\n+\t\tcount[QUIC_ECN_CE] = ecn_count[QUIC_ECN_CE];\n+\t\treturn true;\n+\t}\n+\treturn false;\n+}\n+\n+u16 quic_pnspace_num_gabs(struct quic_pnspace *space,\n+\t\t\t  struct quic_gap_ack_block *gabs);\n+int quic_pnspace_check(struct quic_pnspace *space, s64 pn);\n+int quic_pnspace_mark(struct quic_pnspace *space, s64 pn, gfp_t gfp);\n+\n+void quic_pnspace_free(struct quic_pnspace *space);\n+int quic_pnspace_init(struct quic_pnspace *space);\ndiff --git a/net/quic/protocol.c b/net/quic/protocol.c\nnew file mode 100644\nindex 00000000000000..1ad95f9c9250c2\n--- /dev/null\n+++ b/net/quic/protocol.c\n@@ -0,0 +1,417 @@\n+// SPDX-License-Identifier: GPL-2.0-or-later\n+/* QUIC kernel implementation\n+ * (C) Copyright Red Hat Corp. 2023\n+ *\n+ * This file is part of the QUIC kernel implementation\n+ *\n+ * Initialization/cleanup for QUIC protocol support.\n+ *\n+ * Written or modified by:\n+ *    Xin Long \u003clucien.xin@gmail.com\u003e\n+ */\n+\n+#include \u003cnet/inet_common.h\u003e\n+#include \u003clinux/proc_fs.h\u003e\n+#include \u003cnet/protocol.h\u003e\n+#include \u003cnet/rps.h\u003e\n+#include \u003cnet/tls.h\u003e\n+\n+#include \"socket.h\"\n+\n+static unsigned int quic_net_id __read_mostly;\n+\n+struct percpu_counter quic_sockets_allocated;\n+struct workqueue_struct *quic_wq;\n+\n+DEFINE_STATIC_KEY_FALSE(quic_alpn_demux_key);\n+\n+long sysctl_quic_mem[3];\n+int sysctl_quic_rmem[3];\n+int sysctl_quic_wmem[3];\n+\n+static int quic_inet_connect(struct socket *sock, struct sockaddr_unsized *addr,\n+\t\t\t     int addr_len, int flags)\n+{\n+\tstruct sock *sk = sock-\u003esk;\n+\n+\tif (addr_len \u003c (int)sizeof(addr-\u003esa_family))\n+\t\treturn -EINVAL;\n+\n+\treturn sk-\u003esk_prot-\u003econnect(sk, addr, addr_len);\n+}\n+\n+static int quic_inet_listen(struct socket *sock, int backlog)\n+{\n+\treturn -EOPNOTSUPP;\n+}\n+\n+static int quic_inet_getname(struct socket *sock, struct sockaddr *uaddr,\n+\t\t\t     int peer)\n+{\n+\treturn quic_get_sk_addr(sock, uaddr, peer);\n+}\n+\n+static __poll_t quic_inet_poll(struct file *file, struct socket *sock,\n+\t\t\t       poll_table *wait)\n+{\n+\treturn 0;\n+}\n+\n+struct quic_net *quic_net(struct net *net)\n+{\n+\treturn net_generic(net, quic_net_id);\n+}\n+\n+#if IS_ENABLED(CONFIG_PROC_FS)\n+static const struct snmp_mib quic_snmp_list[] = {\n+\tSNMP_MIB_ITEM(\"QuicConnCurrentEstabs\", QUIC_MIB_CONN_CURRENTESTABS),\n+\tSNMP_MIB_ITEM(\"QuicConnPassiveEstabs\", QUIC_MIB_CONN_PASSIVEESTABS),\n+\tSNMP_MIB_ITEM(\"QuicConnActiveEstabs\", QUIC_MIB_CONN_ACTIVEESTABS),\n+\tSNMP_MIB_ITEM(\"QuicPktRcvFastpaths\", QUIC_MIB_PKT_RCVFASTPATHS),\n+\tSNMP_MIB_ITEM(\"QuicPktDecFastpaths\", QUIC_MIB_PKT_DECFASTPATHS),\n+\tSNMP_MIB_ITEM(\"QuicPktEncFastpaths\", QUIC_MIB_PKT_ENCFASTPATHS),\n+\tSNMP_MIB_ITEM(\"QuicPktRcvBacklogs\", QUIC_MIB_PKT_RCVBACKLOGS),\n+\tSNMP_MIB_ITEM(\"QuicPktDecBacklogs\", QUIC_MIB_PKT_DECBACKLOGS),\n+\tSNMP_MIB_ITEM(\"QuicPktEncBacklogs\", QUIC_MIB_PKT_ENCBACKLOGS),\n+\tSNMP_MIB_ITEM(\"QuicPktInvHdrDrop\", QUIC_MIB_PKT_INVHDRDROP),\n+\tSNMP_MIB_ITEM(\"QuicPktInvNumDrop\", QUIC_MIB_PKT_INVNUMDROP),\n+\tSNMP_MIB_ITEM(\"QuicPktInvFrmDrop\", QUIC_MIB_PKT_INVFRMDROP),\n+\tSNMP_MIB_ITEM(\"QuicPktRcvDrop\", QUIC_MIB_PKT_RCVDROP),\n+\tSNMP_MIB_ITEM(\"QuicPktDecDrop\", QUIC_MIB_PKT_DECDROP),\n+\tSNMP_MIB_ITEM(\"QuicPktEncDrop\", QUIC_MIB_PKT_ENCDROP),\n+\tSNMP_MIB_ITEM(\"QuicFrmRcvBufDrop\", QUIC_MIB_FRM_RCVBUFDROP),\n+\tSNMP_MIB_ITEM(\"QuicFrmRetrans\", QUIC_MIB_FRM_RETRANS),\n+\tSNMP_MIB_ITEM(\"QuicFrmOutCloses\", QUIC_MIB_FRM_OUTCLOSES),\n+\tSNMP_MIB_ITEM(\"QuicFrmInCloses\", QUIC_MIB_FRM_INCLOSES),\n+};\n+\n+static int quic_snmp_seq_show(struct seq_file *seq, void *v)\n+{\n+\tunsigned long buff[ARRAY_SIZE(quic_snmp_list)];\n+\tconst int cnt = ARRAY_SIZE(quic_snmp_list);\n+\tstruct net *net = seq-\u003eprivate;\n+\tu32 idx;\n+\n+\tmemset(buff, 0, sizeof(buff));\n+\n+\tsnmp_get_cpu_field_batch_cnt(buff, quic_snmp_list, cnt,\n+\t\t\t\t     quic_net(net)-\u003estat);\n+\tfor (idx = 0; idx \u003c cnt; idx++)\n+\t\tseq_printf(seq, \"%-32s\\t%lu\\n\", quic_snmp_list[idx].name,\n+\t\t\t   buff[idx]);\n+\n+\treturn 0;\n+}\n+\n+static int quic_net_proc_init(struct net *net)\n+{\n+\tquic_net(net)-\u003eproc_net = proc_net_mkdir(net, \"quic\", net-\u003eproc_net);\n+\tif (!quic_net(net)-\u003eproc_net)\n+\t\treturn -ENOMEM;\n+\n+\tif (!proc_create_net_single(\"snmp\", 0444, quic_net(net)-\u003eproc_net,\n+\t\t\t\t    quic_snmp_seq_show, NULL))\n+\t\tgoto free;\n+\treturn 0;\n+free:\n+\tremove_proc_subtree(\"quic\", net-\u003eproc_net);\n+\tquic_net(net)-\u003eproc_net = NULL;\n+\treturn -ENOMEM;\n+}\n+\n+static void quic_net_proc_exit(struct net *net)\n+{\n+\tremove_proc_subtree(\"quic\", net-\u003eproc_net);\n+\tquic_net(net)-\u003eproc_net = NULL;\n+}\n+#endif\n+\n+static const struct proto_ops quic_proto_ops = {\n+\t.family\t\t   = PF_INET,\n+\t.owner\t\t   = THIS_MODULE,\n+\t.release\t   = inet_release,\n+\t.bind\t\t   = inet_bind,\n+\t.connect\t   = quic_inet_connect,\n+\t.socketpair\t   = sock_no_socketpair,\n+\t.accept\t\t   = inet_accept,\n+\t.getname\t   = quic_inet_getname,\n+\t.poll\t\t   = quic_inet_poll,\n+\t.ioctl\t\t   = inet_ioctl,\n+\t.gettstamp\t   = sock_gettstamp,\n+\t.listen\t\t   = quic_inet_listen,\n+\t.shutdown\t   = inet_shutdown,\n+\t.setsockopt\t   = sock_common_setsockopt,\n+\t.getsockopt\t   = sock_common_getsockopt,\n+\t.sendmsg\t   = inet_sendmsg,\n+\t.recvmsg\t   = inet_recvmsg,\n+\t.mmap\t\t   = sock_no_mmap,\n+};\n+\n+static struct inet_protosw quic_stream_protosw = {\n+\t.type       = SOCK_STREAM,\n+\t.protocol   = IPPROTO_QUIC,\n+\t.prot       = \u0026quic_prot,\n+\t.ops        = \u0026quic_proto_ops,\n+};\n+\n+static struct inet_protosw quic_dgram_protosw = {\n+\t.type       = SOCK_DGRAM,\n+\t.protocol   = IPPROTO_QUIC,\n+\t.prot       = \u0026quic_prot,\n+\t.ops        = \u0026quic_proto_ops,\n+};\n+\n+static const struct proto_ops quicv6_proto_ops = {\n+\t.family\t\t   = PF_INET6,\n+\t.owner\t\t   = THIS_MODULE,\n+\t.release\t   = inet6_release,\n+\t.bind\t\t   = inet6_bind,\n+\t.connect\t   = quic_inet_connect,\n+\t.socketpair\t   = sock_no_socketpair,\n+\t.accept\t\t   = inet_accept,\n+\t.getname\t   = quic_inet_getname,\n+\t.poll\t\t   = quic_inet_poll,\n+\t.ioctl\t\t   = inet6_ioctl,\n+\t.gettstamp\t   = sock_gettstamp,\n+\t.listen\t\t   = quic_inet_listen,\n+\t.shutdown\t   = inet_shutdown,\n+\t.setsockopt\t   = sock_common_setsockopt,\n+\t.getsockopt\t   = sock_common_getsockopt,\n+\t.sendmsg\t   = inet_sendmsg,\n+\t.recvmsg\t   = inet_recvmsg,\n+\t.mmap\t\t   = sock_no_mmap,\n+};\n+\n+static struct inet_protosw quicv6_stream_protosw = {\n+\t.type       = SOCK_STREAM,\n+\t.protocol   = IPPROTO_QUIC,\n+\t.prot       = \u0026quicv6_prot,\n+\t.ops        = \u0026quicv6_proto_ops,\n+};\n+\n+static struct inet_protosw quicv6_dgram_protosw = {\n+\t.type       = SOCK_DGRAM,\n+\t.protocol   = IPPROTO_QUIC,\n+\t.prot       = \u0026quicv6_prot,\n+\t.ops        = \u0026quicv6_proto_ops,\n+};\n+\n+static int quic_protosw_init(void)\n+{\n+\tint err;\n+\n+\terr = proto_register(\u0026quic_prot, 1);\n+\tif (err)\n+\t\treturn err;\n+\n+\terr = proto_register(\u0026quicv6_prot, 1);\n+\tif (err) {\n+\t\tproto_unregister(\u0026quic_prot);\n+\t\treturn err;\n+\t}\n+\n+\tinet_register_protosw(\u0026quic_stream_protosw);\n+\tinet_register_protosw(\u0026quic_dgram_protosw);\n+\tinet6_register_protosw(\u0026quicv6_stream_protosw);\n+\tinet6_register_protosw(\u0026quicv6_dgram_protosw);\n+\n+\treturn 0;\n+}\n+\n+static void quic_protosw_exit(void)\n+{\n+\tinet_unregister_protosw(\u0026quic_dgram_protosw);\n+\tinet_unregister_protosw(\u0026quic_stream_protosw);\n+\tproto_unregister(\u0026quic_prot);\n+\n+\tinet6_unregister_protosw(\u0026quicv6_dgram_protosw);\n+\tinet6_unregister_protosw(\u0026quicv6_stream_protosw);\n+\tproto_unregister(\u0026quicv6_prot);\n+}\n+\n+static int __net_init quic_net_init(struct net *net)\n+{\n+\tstruct quic_net *qn = quic_net(net);\n+\tint err;\n+\n+\tqn-\u003estat = alloc_percpu(struct quic_mib);\n+\tif (!qn-\u003estat)\n+\t\treturn -ENOMEM;\n+\n+\terr = quic_crypto_set_cipher(\u0026qn-\u003ecrypto, TLS_CIPHER_AES_GCM_128);\n+\tif (err) {\n+\t\tfree_percpu(qn-\u003estat);\n+\t\tqn-\u003estat = NULL;\n+\t\treturn err;\n+\t}\n+\n+\tINIT_WORK(\u0026qn-\u003ework, quic_packet_backlog_work);\n+\tskb_queue_head_init(\u0026qn-\u003ebacklog_list);\n+\n+#if IS_ENABLED(CONFIG_PROC_FS)\n+\terr = quic_net_proc_init(net);\n+\tif (err) {\n+\t\tquic_crypto_free(\u0026qn-\u003ecrypto);\n+\t\tfree_percpu(qn-\u003estat);\n+\t\tqn-\u003estat = NULL;\n+\t}\n+#endif\n+\treturn err;\n+}\n+\n+static void __net_exit quic_net_exit(struct net *net)\n+{\n+\tstruct quic_net *qn = quic_net(net);\n+\n+#if IS_ENABLED(CONFIG_PROC_FS)\n+\tquic_net_proc_exit(net);\n+#endif\n+\tdisable_work_sync(\u0026qn-\u003ework);\n+\tskb_queue_purge(\u0026qn-\u003ebacklog_list);\n+\tquic_crypto_free(\u0026qn-\u003ecrypto);\n+\tfree_percpu(qn-\u003estat);\n+\tqn-\u003estat = NULL;\n+}\n+\n+static struct pernet_operations quic_net_ops = {\n+\t.init = quic_net_init,\n+\t.exit = quic_net_exit,\n+\t.id   = \u0026quic_net_id,\n+\t.size = sizeof(struct quic_net),\n+};\n+\n+#if IS_ENABLED(CONFIG_SYSCTL)\n+static struct ctl_table_header *quic_sysctl_header;\n+\n+static struct ctl_table quic_table[] = {\n+\t{\n+\t\t.procname\t= \"quic_mem\",\n+\t\t.data\t\t= \u0026sysctl_quic_mem,\n+\t\t.maxlen\t\t= sizeof(sysctl_quic_mem),\n+\t\t.mode\t\t= 0644,\n+\t\t.proc_handler\t= proc_doulongvec_minmax\n+\t},\n+\t{\n+\t\t.procname\t= \"quic_rmem\",\n+\t\t.data\t\t= \u0026sysctl_quic_rmem,\n+\t\t.maxlen\t\t= sizeof(sysctl_quic_rmem),\n+\t\t.mode\t\t= 0644,\n+\t\t.proc_handler\t= proc_dointvec_minmax,\n+\t\t.extra1\t\t= SYSCTL_ONE,\n+\t},\n+\t{\n+\t\t.procname\t= \"quic_wmem\",\n+\t\t.data\t\t= \u0026sysctl_quic_wmem,\n+\t\t.maxlen\t\t= sizeof(sysctl_quic_wmem),\n+\t\t.mode\t\t= 0644,\n+\t\t.proc_handler\t= proc_dointvec_minmax,\n+\t\t.extra1\t\t= SYSCTL_ONE,\n+\t},\n+};\n+\n+static void quic_sysctl_register(void)\n+{\n+\tquic_sysctl_header = register_net_sysctl(\u0026init_net, \"net/quic\",\n+\t\t\t\t\t\t quic_table);\n+}\n+\n+static void quic_sysctl_unregister(void)\n+{\n+\tunregister_net_sysctl_table(quic_sysctl_header);\n+}\n+#endif\n+\n+static __init int quic_init(void)\n+{\n+\tint max_share, err = -ENOMEM;\n+\tunsigned long limit;\n+\n+\tBUILD_BUG_ON(sizeof(struct quic_skb_cb) \u003e\n+\t\t     sizeof_field(struct sk_buff, cb));\n+\n+\t/* Set QUIC memory limits based on available system memory, similar to\n+\t * sctp_init().\n+\t */\n+\tlimit = nr_free_buffer_pages() / 8;\n+\tlimit = max(limit, 128UL);\n+\tsysctl_quic_mem[0] = (long)limit / 4 * 3;\n+\tsysctl_quic_mem[1] = (long)limit;\n+\tsysctl_quic_mem[2] = sysctl_quic_mem[0] * 2;\n+\n+\tlimit = (sysctl_quic_mem[1]) \u003c\u003c (PAGE_SHIFT - 7);\n+\tmax_share = min(4UL * 1024 * 1024, limit);\n+\n+\tsysctl_quic_rmem[0] = PAGE_SIZE;\n+\tsysctl_quic_rmem[1] = 1024 * 1024;\n+\tsysctl_quic_rmem[2] = max(sysctl_quic_rmem[1], max_share);\n+\n+\tsysctl_quic_wmem[0] = PAGE_SIZE;\n+\tsysctl_quic_wmem[1] = 16 * 1024;\n+\tsysctl_quic_wmem[2] = max(64 * 1024, max_share);\n+\n+\terr = percpu_counter_init(\u0026quic_sockets_allocated, 0, GFP_KERNEL);\n+\tif (err)\n+\t\tgoto err_percpu_counter;\n+\n+\terr = quic_hash_tables_init();\n+\tif (err)\n+\t\tgoto err_hash;\n+\n+\t/* Allocate an unbound workqueue for UDP socket destruction and backlog\n+\t * packet processing.\n+\t */\n+\tquic_wq = alloc_workqueue(\"quic_workqueue\", WQ_UNBOUND, 0);\n+\tif (!quic_wq) {\n+\t\terr = -ENOMEM;\n+\t\tgoto err_wq;\n+\t}\n+\n+\terr = register_pernet_subsys(\u0026quic_net_ops);\n+\tif (err)\n+\t\tgoto err_def_ops;\n+\n+\terr = quic_protosw_init();\n+\tif (err)\n+\t\tgoto err_protosw;\n+\n+#if IS_ENABLED(CONFIG_SYSCTL)\n+\tquic_sysctl_register();\n+#endif\n+\tpr_info(\"quic: init\\n\");\n+\treturn 0;\n+\n+err_protosw:\n+\tunregister_pernet_subsys(\u0026quic_net_ops);\n+err_def_ops:\n+\tdestroy_workqueue(quic_wq);\n+err_wq:\n+\tquic_hash_tables_destroy();\n+err_hash:\n+\tpercpu_counter_destroy(\u0026quic_sockets_allocated);\n+err_percpu_counter:\n+\treturn err;\n+}\n+\n+static __exit void quic_exit(void)\n+{\n+#if IS_ENABLED(CONFIG_SYSCTL)\n+\tquic_sysctl_unregister();\n+#endif\n+\tquic_protosw_exit();\n+\tunregister_pernet_subsys(\u0026quic_net_ops);\n+\tflush_workqueue(quic_wq);\n+\tdestroy_workqueue(quic_wq);\n+\tquic_hash_tables_destroy();\n+\tpercpu_counter_destroy(\u0026quic_sockets_allocated);\n+\trcu_barrier();\n+\tpr_info(\"quic: exit\\n\");\n+}\n+\n+module_init(quic_init);\n+module_exit(quic_exit);\n+\n+MODULE_ALIAS_NET_PF_PROTO(PF_INET, 261); /* IPPROTO_QUIC == 261 */\n+MODULE_ALIAS_NET_PF_PROTO(PF_INET6, 261);\n+MODULE_AUTHOR(\"Xin Long \u003clucien.xin@gmail.com\u003e\");\n+MODULE_DESCRIPTION(\"Support for the QUIC protocol (RFC9000)\");\n+MODULE_LICENSE(\"GPL\");\ndiff --git a/net/quic/protocol.h b/net/quic/protocol.h\nnew file mode 100644\nindex 00000000000000..25001aaaad4a51\n--- /dev/null\n+++ b/net/quic/protocol.h\n@@ -0,0 +1,63 @@\n+/* SPDX-License-Identifier: GPL-2.0-or-later */\n+/* QUIC kernel implementation\n+ * (C) Copyright Red Hat Corp. 2023\n+ *\n+ * This file is part of the QUIC kernel implementation\n+ *\n+ * Written or modified by:\n+ *    Xin Long \u003clucien.xin@gmail.com\u003e\n+ */\n+\n+extern struct percpu_counter quic_sockets_allocated;\n+\n+DECLARE_STATIC_KEY_FALSE(quic_alpn_demux_key);\n+\n+extern long sysctl_quic_mem[3];\n+extern int sysctl_quic_rmem[3];\n+extern int sysctl_quic_wmem[3];\n+\n+enum {\n+\tQUIC_MIB_NUM = 0,\n+\tQUIC_MIB_CONN_CURRENTESTABS, /* Current established connections */\n+\tQUIC_MIB_CONN_PASSIVEESTABS, /* Passively established connections */\n+\tQUIC_MIB_CONN_ACTIVEESTABS,  /* Actively established connections */\n+\tQUIC_MIB_PKT_RCVFASTPATHS,   /* Packets received on fast path */\n+\tQUIC_MIB_PKT_DECFASTPATHS,   /* Packets decrypted on fast path */\n+\tQUIC_MIB_PKT_ENCFASTPATHS,   /* Packets encrypted on fast path */\n+\tQUIC_MIB_PKT_RCVBACKLOGS,    /* Packets processed via backlog */\n+\tQUIC_MIB_PKT_DECBACKLOGS,    /* Packets decrypted in backlog */\n+\tQUIC_MIB_PKT_ENCBACKLOGS,    /* Packets encrypted in backlog */\n+\tQUIC_MIB_PKT_INVHDRDROP,     /* Dropped: invalid packet header */\n+\tQUIC_MIB_PKT_INVNUMDROP,     /* Dropped: invalid packet number */\n+\tQUIC_MIB_PKT_INVFRMDROP,     /* Dropped: invalid frame */\n+\tQUIC_MIB_PKT_RCVDROP,        /* Dropped on receive (general) */\n+\tQUIC_MIB_PKT_DECDROP,        /* Dropped: decryption failure */\n+\tQUIC_MIB_PKT_ENCDROP,        /* Dropped: encryption failure */\n+\tQUIC_MIB_FRM_RCVBUFDROP,     /* Frames dropped: recv buf limit */\n+\tQUIC_MIB_FRM_RETRANS,        /* Frames retransmitted */\n+\tQUIC_MIB_FRM_OUTCLOSES,      /* CONNECTION_CLOSE frames sent */\n+\tQUIC_MIB_FRM_INCLOSES,       /* CONNECTION_CLOSE frames rcvd */\n+\tQUIC_MIB_MAX\n+};\n+\n+struct quic_mib {\n+\tunsigned long mibs[QUIC_MIB_MAX]; /* Counters indexed by QUIC_MIB_* */\n+};\n+\n+struct quic_net {\n+\tDEFINE_SNMP_STAT(struct quic_mib, stat); /* Per-net QUIC MIB stats */\n+#if IS_ENABLED(CONFIG_PROC_FS)\n+\tstruct proc_dir_entry *proc_net; /* procfs entry for QUIC stats */\n+#endif\n+\t/* Context for decrypting Initial packets for ALPN */\n+\tstruct quic_crypto crypto;\n+\n+\t/* Queue of packets deferred for processing in process context */\n+\tstruct sk_buff_head backlog_list;\n+\tstruct work_struct work; /* Work to drain/process backlog_list */\n+};\n+\n+struct quic_net *quic_net(struct net *net);\n+\n+#define QUIC_INC_STATS(net, field) SNMP_INC_STATS(quic_net(net)-\u003estat, field)\n+#define QUIC_DEC_STATS(net, field) SNMP_DEC_STATS(quic_net(net)-\u003estat, field)\ndiff --git a/net/quic/socket.c b/net/quic/socket.c\nnew file mode 100644\nindex 00000000000000..ce1c86c0da3e0c\n--- /dev/null\n+++ b/net/quic/socket.c\n@@ -0,0 +1,482 @@\n+// SPDX-License-Identifier: GPL-2.0-or-later\n+/* QUIC kernel implementation\n+ * (C) Copyright Red Hat Corp. 2023\n+ *\n+ * This file is part of the QUIC kernel implementation\n+ *\n+ * Initialization/cleanup for QUIC protocol support.\n+ *\n+ * Written or modified by:\n+ *    Xin Long \u003clucien.xin@gmail.com\u003e\n+ */\n+\n+#include \u003cnet/inet_common.h\u003e\n+#include \u003cnet/tls.h\u003e\n+\n+#include \"socket.h\"\n+\n+static DEFINE_PER_CPU(int, quic_memory_per_cpu_fw_alloc);\n+static unsigned long quic_memory_pressure;\n+static atomic_long_t quic_memory_allocated;\n+\n+static void quic_enter_memory_pressure(struct sock *sk)\n+{\n+\tWRITE_ONCE(quic_memory_pressure, 1);\n+}\n+\n+/* Lookup a connected QUIC socket based on address and dest connection ID.\n+ *\n+ * This function searches the established (non-listening) QUIC socket table for\n+ * a socket that matches the source and dest addresses and, optionally, the\n+ * dest connection ID (DCID). The value returned by quic_path_orig_dcid() might\n+ * be the original dest connection ID from the ClientHello or the Source\n+ * Connection ID from a Retry packet before.\n+ *\n+ * The DCID is provided from a handshake packet when searching by source\n+ * connection ID fails, such as when the peer has not yet received server's\n+ * response and updated the DCID.\n+ *\n+ * Return: A pointer to the matching connected socket, or NULL if no match is\n+ * found.\n+ */\n+struct sock *quic_sock_lookup(struct sk_buff *skb, union quic_addr *sa,\n+\t\t\t      union quic_addr *da, struct sock *usk,\n+\t\t\t      struct quic_conn_id *dcid)\n+{\n+\tstruct net *net = sock_net(usk);\n+\tstruct quic_path_group *paths;\n+\tstruct hlist_nulls_node *node;\n+\tstruct quic_shash_head *head;\n+\tstruct sock *sk = NULL, *tmp;\n+\tstruct quic_conn_id *odcid;\n+\tunsigned int hash;\n+\n+\thash = quic_sock_hash(net, sa, da);\n+\thead = quic_sock_head(hash);\n+\n+\trcu_read_lock();\n+begin:\n+\tsk_nulls_for_each_rcu(tmp, node, \u0026head-\u003ehead) {\n+\t\tif (net != sock_net(tmp))\n+\t\t\tcontinue;\n+\t\tpaths = quic_paths(tmp);\n+\t\todcid = quic_path_orig_dcid(paths);\n+\t\tif (quic_cmp_sk_addr(tmp, quic_path_saddr(paths, 0), sa) \u0026\u0026\n+\t\t    quic_cmp_sk_addr(tmp, quic_path_daddr(paths, 0), da) \u0026\u0026\n+\t\t    quic_path_usock(paths, 0) == usk \u0026\u0026\n+\t\t    (!dcid || !quic_conn_id_cmp(odcid, dcid))) {\n+\t\t\tsk = tmp;\n+\t\t\tbreak;\n+\t\t}\n+\t}\n+\t/* If the final nulls value differs from the expected one, restart the\n+\t * lookup as the node may have been rehashed (e.g., due to connection\n+\t * migration).\n+\t */\n+\tif (!sk \u0026\u0026 get_nulls_value(node) != hash)\n+\t\tgoto begin;\n+\n+\tif (sk \u0026\u0026 unlikely(!refcount_inc_not_zero(\u0026sk-\u003esk_refcnt)))\n+\t\tsk = NULL;\n+\trcu_read_unlock();\n+\treturn sk;\n+}\n+\n+/* Find the listening QUIC socket for an incoming packet.\n+ *\n+ * This function searches the QUIC socket table for a listening socket that\n+ * matches the dest address and port, and the ALPN(s) if presented in the\n+ * ClientHello.  If multiple listening sockets are bound to the same address,\n+ * port, and ALPN(s) (e.g., via SO_REUSEPORT), this function selects a socket\n+ * from the reuseport group.\n+ *\n+ * Return: A pointer to the matching listening socket, or NULL if no match is\n+ * found.\n+ */\n+struct sock *quic_listen_sock_lookup(struct sk_buff *skb, union quic_addr *sa,\n+\t\t\t\t     union quic_addr *da,\n+\t\t\t\t     struct quic_data *alpns)\n+{\n+\tstruct net *net = sock_net(skb-\u003esk);\n+\tstruct hlist_nulls_node *node;\n+\tstruct sock *sk = NULL, *tmp;\n+\tstruct quic_shash_head *head;\n+\tstruct quic_data alpn;\n+\tunion quic_addr *a;\n+\tu32 hash, len;\n+\tu64 length;\n+\tu8 *p;\n+\n+\thash = quic_listen_sock_hash(net, ntohs(sa-\u003ev4.sin_port));\n+\thead = quic_listen_sock_head(hash);\n+\n+\trcu_read_lock();\n+\tif (!alpns-\u003elen) { /* No ALPNs or parse failed */\n+\t\tsk_nulls_for_each_rcu(tmp, node, \u0026head-\u003ehead) {\n+\t\t\t/* If alpns-\u003edata != NULL, TLS parsing succeeded but no\n+\t\t\t * ALPN was found.  In this case, only match sockets\n+\t\t\t * that have no ALPN set.\n+\t\t\t */\n+\t\t\ta = quic_path_saddr(quic_paths(tmp), 0);\n+\t\t\tif (net == sock_net(tmp) \u0026\u0026\n+\t\t\t    quic_cmp_sk_addr(tmp, a, sa) \u0026\u0026\n+\t\t\t    quic_path_usock(quic_paths(tmp), 0) == skb-\u003esk \u0026\u0026\n+\t\t\t    (!alpns-\u003edata || !quic_alpn(tmp)-\u003elen)) {\n+\t\t\t\tif (!quic_is_any_addr(a)) {\n+\t\t\t\t\tsk = tmp;\n+\t\t\t\t\tbreak; /* Prefer specific addr match. */\n+\t\t\t\t}\n+\t\t\t\t/* Prefer ipv4 ANY over ipv6 ANY for v4 addr. */\n+\t\t\t\tif (!sk || a-\u003esa.sa_family == sa-\u003esa.sa_family)\n+\t\t\t\t\tsk = tmp;\n+\t\t\t}\n+\t\t}\n+\t\t/* No need to check get_nulls_value(node) != hash for !sk, as\n+\t\t * hashtable size is fixed and a listen sk can not rehashed.\n+\t\t */\n+\t\tgoto out;\n+\t}\n+\n+\t/* ALPN present: loop through each ALPN entry. */\n+\tfor (p = alpns-\u003edata, len = alpns-\u003elen; len;\n+\t     len -= length, p += length) {\n+\t\tquic_get_int(\u0026p, \u0026len, \u0026length, 1);\n+\t\tquic_data(\u0026alpn, p, length);\n+\t\tsk_nulls_for_each_rcu(tmp, node, \u0026head-\u003ehead) {\n+\t\t\ta = quic_path_saddr(quic_paths(tmp), 0);\n+\t\t\tif (net == sock_net(tmp) \u0026\u0026\n+\t\t\t    quic_cmp_sk_addr(tmp, a, sa) \u0026\u0026\n+\t\t\t    quic_path_usock(quic_paths(tmp), 0) == skb-\u003esk \u0026\u0026\n+\t\t\t    quic_data_has(quic_alpn(tmp), \u0026alpn)) {\n+\t\t\t\tif (!quic_is_any_addr(a)) {\n+\t\t\t\t\tsk = tmp;\n+\t\t\t\t\tbreak;\n+\t\t\t\t}\n+\t\t\t\tif (!sk || a-\u003esa.sa_family == sa-\u003esa.sa_family)\n+\t\t\t\t\tsk = tmp;\n+\t\t\t}\n+\t\t}\n+\t\t/* No need to check get_nulls_value(node) != hash for !sk, as\n+\t\t * hashtable size is fixed and a listen sk can not rehashed.\n+\t\t */\n+\t\tif (sk)\n+\t\t\tbreak;\n+\t}\n+out:\n+\tif (sk \u0026\u0026 sk-\u003esk_reuseport)\n+\t\tsk = reuseport_select_sock(sk, quic_addr_hash(net, da), skb, 1);\n+\n+\tif (sk \u0026\u0026 unlikely(!refcount_inc_not_zero(\u0026sk-\u003esk_refcnt)))\n+\t\tsk = NULL;\n+\trcu_read_unlock();\n+\treturn sk;\n+}\n+\n+static void quic_write_space(struct sock *sk)\n+{\n+\t__poll_t mask = EPOLLOUT | EPOLLWRNORM | EPOLLWRBAND;\n+\tstruct socket_wq *wq;\n+\n+\t/* Do not check sock_writeable(). Also wakes stream-open waiters\n+\t * blocked on stream limits, where sock_writeable() may be false.\n+\t */\n+\trcu_read_lock();\n+\twq = rcu_dereference(sk-\u003esk_wq);\n+\tif (skwq_has_sleeper(wq))\n+\t\twake_up_interruptible_sync_poll(\u0026wq-\u003ewait, mask);\n+\tsk_wake_async_rcu(sk, SOCK_WAKE_SPACE, POLL_OUT);\n+\trcu_read_unlock();\n+}\n+\n+static void quic_sock_destruct(struct sock *sk)\n+{\n+\tu8 i;\n+\n+\t/* Deferred crypto free for async encryption/decryption. */\n+\tfor (i = 0; i \u003c QUIC_CRYPTO_MAX; i++)\n+\t\tquic_crypto_free(quic_crypto(sk, i));\n+\n+\t/* Deferred ALPN free for RCU readers in quic_listen_sock_lookup(). */\n+\tquic_data_free(quic_alpn(sk));\n+\n+\tquic_sk_destruct(sk);\n+}\n+\n+static int quic_init_sock(struct sock *sk)\n+{\n+\tu8 i;\n+\n+\tsk-\u003esk_destruct = quic_sock_destruct;\n+\tsk-\u003esk_write_space = quic_write_space;\n+\tsock_set_flag(sk, SOCK_USE_WRITE_QUEUE);\n+\n+\tsk_sockets_allocated_inc(sk);\n+\tsock_prot_inuse_add(sock_net(sk), sk-\u003esk_prot, 1);\n+\tINIT_LIST_HEAD(quic_reqs(sk));\n+\n+\tquic_conn_id_set_init(quic_source(sk), true);\n+\tquic_conn_id_set_init(quic_dest(sk), false);\n+\tquic_cong_init(quic_cong(sk));\n+\n+\tquic_timer_init(sk);\n+\tquic_packet_init(sk);\n+\n+\tif (quic_stream_init(quic_streams(sk)))\n+\t\treturn -ENOMEM;\n+\n+\tfor (i = 0; i \u003c QUIC_PNSPACE_MAX; i++) {\n+\t\tif (quic_pnspace_init(quic_pnspace(sk, i)))\n+\t\t\treturn -ENOMEM;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static void quic_destroy_sock(struct sock *sk)\n+{\n+\tu8 i;\n+\n+\tquic_timer_free(sk);\n+\n+\tfor (i = 0; i \u003c QUIC_PNSPACE_MAX; i++)\n+\t\tquic_pnspace_free(quic_pnspace(sk, i));\n+\n+\tquic_path_unbind(sk, quic_paths(sk), 0);\n+\tquic_path_unbind(sk, quic_paths(sk), 1);\n+\n+\tquic_conn_id_set_free(quic_source(sk));\n+\tquic_conn_id_set_free(quic_dest(sk));\n+\n+\tquic_stream_free(quic_streams(sk));\n+\n+\tquic_data_free(quic_ticket(sk));\n+\tquic_data_free(quic_token(sk));\n+\n+\tsk_sockets_allocated_dec(sk);\n+\tsock_prot_inuse_add(sock_net(sk), sk-\u003esk_prot, -1);\n+}\n+\n+static int quic_bind(struct sock *sk, struct sockaddr_unsized *addr,\n+\t\t     int addr_len)\n+{\n+\treturn -EOPNOTSUPP;\n+}\n+\n+static int quic_connect(struct sock *sk, struct sockaddr_unsized *addr,\n+\t\t\tint addr_len)\n+{\n+\treturn -EOPNOTSUPP;\n+}\n+\n+static int quic_hash(struct sock *sk)\n+{\n+\treturn 0;\n+}\n+\n+static void quic_unhash(struct sock *sk)\n+{\n+}\n+\n+static int quic_sendmsg(struct sock *sk, struct msghdr *msg, size_t msg_len)\n+{\n+\treturn -EOPNOTSUPP;\n+}\n+\n+static int quic_recvmsg(struct sock *sk, struct msghdr *msg, size_t len,\n+\t\t\tint flags)\n+{\n+\treturn -EOPNOTSUPP;\n+}\n+\n+static struct sock *quic_accept(struct sock *sk, struct proto_accept_arg *arg)\n+{\n+\targ-\u003eerr = -EOPNOTSUPP;\n+\treturn NULL;\n+}\n+\n+static void quic_close(struct sock *sk, long timeout)\n+{\n+\tlock_sock(sk);\n+\n+\tquic_set_state(sk, QUIC_SS_CLOSED);\n+\n+\trelease_sock(sk);\n+\n+\tsk_common_release(sk);\n+}\n+\n+/**\n+ * quic_do_setsockopt - set a QUIC socket option\n+ * @sk: socket to configure\n+ * @optname: option name (QUIC-level)\n+ * @optval: user buffer containing the option value\n+ * @optlen: size of the option value\n+ *\n+ * Sets a QUIC socket option on a given socket.\n+ *\n+ * Return:\n+ * - On success, 0 is returned.\n+ * - On error, a negative error value is returned.\n+ */\n+int quic_do_setsockopt(struct sock *sk, int optname, sockptr_t optval,\n+\t\t       unsigned int optlen)\n+{\n+\treturn -EOPNOTSUPP;\n+}\n+EXPORT_SYMBOL_GPL(quic_do_setsockopt);\n+\n+static int quic_setsockopt(struct sock *sk, int level, int optname,\n+\t\t\t   sockptr_t optval, unsigned int optlen)\n+{\n+\tif (level != SOL_QUIC)\n+\t\treturn quic_common_setsockopt(sk, level, optname, optval,\n+\t\t\t\t\t      optlen);\n+\n+\treturn quic_do_setsockopt(sk, optname, optval, optlen);\n+}\n+\n+/**\n+ * quic_do_getsockopt - get a QUIC socket option\n+ * @sk: socket to query\n+ * @optname: option name (QUIC-level)\n+ * @optval: user buffer to receive the option value\n+ * @optlen: pointer to buffer size; updated with actual size on return\n+ *\n+ * Gets a QUIC socket option from a given socket.\n+ *\n+ * Return:\n+ * - On success, 0 is returned.\n+ * - On error, a negative error value is returned.\n+ */\n+int quic_do_getsockopt(struct sock *sk, int optname, sockptr_t optval,\n+\t\t       sockptr_t optlen)\n+{\n+\treturn -EOPNOTSUPP;\n+}\n+EXPORT_SYMBOL_GPL(quic_do_getsockopt);\n+\n+static int quic_getsockopt(struct sock *sk, int level, int optname,\n+\t\t\t   char __user *optval, int __user *optlen)\n+{\n+\tif (level != SOL_QUIC)\n+\t\treturn quic_common_getsockopt(sk, level, optname, optval,\n+\t\t\t\t\t      optlen);\n+\n+\treturn quic_do_getsockopt(sk, optname, USER_SOCKPTR(optval),\n+\t\t\t\t  USER_SOCKPTR(optlen));\n+}\n+\n+static void quic_release_cb(struct sock *sk)\n+{\n+\t/* Similar to tcp_release_cb(). */\n+\tunsigned long nflags, flags = smp_load_acquire(\u0026sk-\u003esk_tsq_flags);\n+\n+\tdo {\n+\t\tif (!(flags \u0026 QUIC_DEFERRED_ALL))\n+\t\t\treturn;\n+\t\tnflags = flags \u0026 ~QUIC_DEFERRED_ALL;\n+\t} while (!try_cmpxchg(\u0026sk-\u003esk_tsq_flags, \u0026flags, nflags));\n+\n+\tif (flags \u0026 QUIC_F_MTU_REDUCED_DEFERRED) {\n+\t\tquic_packet_rcv_err_pmtu(sk);\n+\t\t__sock_put(sk);\n+\t}\n+\tif (flags \u0026 QUIC_F_LOSS_DEFERRED) {\n+\t\tquic_timer_loss_handler(sk);\n+\t\t__sock_put(sk);\n+\t}\n+\tif (flags \u0026 QUIC_F_SACK_DEFERRED) {\n+\t\tquic_timer_sack_handler(sk);\n+\t\t__sock_put(sk);\n+\t}\n+\tif (flags \u0026 QUIC_F_PATH_DEFERRED) {\n+\t\tquic_timer_path_handler(sk);\n+\t\t__sock_put(sk);\n+\t}\n+\tif (flags \u0026 QUIC_F_PMTU_DEFERRED) {\n+\t\tquic_timer_pmtu_handler(sk);\n+\t\t__sock_put(sk);\n+\t}\n+\tif (flags \u0026 QUIC_F_PACE_DEFERRED) {\n+\t\tquic_timer_pace_handler(sk);\n+\t\t__sock_put(sk);\n+\t}\n+}\n+\n+static int quic_disconnect(struct sock *sk, int flags)\n+{\n+\treturn -EOPNOTSUPP;\n+}\n+\n+static void quic_shutdown(struct sock *sk, int how)\n+{\n+\tquic_set_state(sk, QUIC_SS_CLOSED);\n+}\n+\n+static int quic_backlog_rcv(struct sock *sk, struct sk_buff *skb)\n+{\n+\treturn quic_packet_process(sk, skb, GFP_ATOMIC);\n+}\n+\n+struct proto quic_prot = {\n+\t.name\t\t=  \"QUIC\",\n+\t.owner\t\t=  THIS_MODULE,\n+\t.init\t\t=  quic_init_sock,\n+\t.destroy\t=  quic_destroy_sock,\n+\t.shutdown\t=  quic_shutdown,\n+\t.setsockopt\t=  quic_setsockopt,\n+\t.getsockopt\t=  quic_getsockopt,\n+\t.connect\t=  quic_connect,\n+\t.bind\t\t=  quic_bind,\n+\t.close\t\t=  quic_close,\n+\t.disconnect\t=  quic_disconnect,\n+\t.sendmsg\t=  quic_sendmsg,\n+\t.recvmsg\t=  quic_recvmsg,\n+\t.accept\t\t=  quic_accept,\n+\t.hash\t\t=  quic_hash,\n+\t.unhash\t\t=  quic_unhash,\n+\t.backlog_rcv\t=  quic_backlog_rcv,\n+\t.release_cb\t=  quic_release_cb,\n+\t.no_autobind\t=  true,\n+\t.obj_size\t=  sizeof(struct quic_sock),\n+\t.sysctl_mem\t\t=  sysctl_quic_mem,\n+\t.sysctl_rmem\t\t=  sysctl_quic_rmem,\n+\t.sysctl_wmem\t\t=  sysctl_quic_wmem,\n+\t.memory_pressure\t=  \u0026quic_memory_pressure,\n+\t.enter_memory_pressure\t=  quic_enter_memory_pressure,\n+\t.memory_allocated\t=  \u0026quic_memory_allocated,\n+\t.per_cpu_fw_alloc\t=  \u0026quic_memory_per_cpu_fw_alloc,\n+\t.sockets_allocated\t=  \u0026quic_sockets_allocated,\n+};\n+\n+struct proto quicv6_prot = {\n+\t.name\t\t=  \"QUICv6\",\n+\t.owner\t\t=  THIS_MODULE,\n+\t.init\t\t=  quic_init_sock,\n+\t.destroy\t=  quic_destroy_sock,\n+\t.shutdown\t=  quic_shutdown,\n+\t.setsockopt\t=  quic_setsockopt,\n+\t.getsockopt\t=  quic_getsockopt,\n+\t.connect\t=  quic_connect,\n+\t.bind\t\t=  quic_bind,\n+\t.close\t\t=  quic_close,\n+\t.disconnect\t=  quic_disconnect,\n+\t.sendmsg\t=  quic_sendmsg,\n+\t.recvmsg\t=  quic_recvmsg,\n+\t.accept\t\t=  quic_accept,\n+\t.hash\t\t=  quic_hash,\n+\t.unhash\t\t=  quic_unhash,\n+\t.backlog_rcv\t=  quic_backlog_rcv,\n+\t.release_cb\t=  quic_release_cb,\n+\t.no_autobind\t=  true,\n+\t.obj_size\t= sizeof(struct quic6_sock),\n+\t.ipv6_pinfo_offset\t=  offsetof(struct quic6_sock, inet6),\n+\t.sysctl_mem\t\t=  sysctl_quic_mem,\n+\t.sysctl_rmem\t\t=  sysctl_quic_rmem,\n+\t.sysctl_wmem\t\t=  sysctl_quic_wmem,\n+\t.memory_pressure\t=  \u0026quic_memory_pressure,\n+\t.enter_memory_pressure\t=  quic_enter_memory_pressure,\n+\t.memory_allocated\t=  \u0026quic_memory_allocated,\n+\t.per_cpu_fw_alloc\t=  \u0026quic_memory_per_cpu_fw_alloc,\n+\t.sockets_allocated\t=  \u0026quic_sockets_allocated,\n+};\ndiff --git a/net/quic/socket.h b/net/quic/socket.h\nnew file mode 100644\nindex 00000000000000..3c1bea767be9c8\n--- /dev/null\n+++ b/net/quic/socket.h\n@@ -0,0 +1,209 @@\n+/* SPDX-License-Identifier: GPL-2.0-or-later */\n+/* QUIC kernel implementation\n+ * (C) Copyright Red Hat Corp. 2023\n+ *\n+ * This file is part of the QUIC kernel implementation\n+ *\n+ * Written or modified by:\n+ *    Xin Long \u003clucien.xin@gmail.com\u003e\n+ */\n+\n+#include \u003cnet/udp_tunnel.h\u003e\n+#include \u003clinux/quic.h\u003e\n+\n+#include \"common.h\"\n+#include \"pnspace.h\"\n+#include \"family.h\"\n+#include \"stream.h\"\n+#include \"connid.h\"\n+#include \"crypto.h\"\n+#include \"path.h\"\n+#include \"cong.h\"\n+\n+#include \"packet.h\"\n+\n+#include \"protocol.h\"\n+#include \"timer.h\"\n+\n+extern struct proto quic_prot;\n+extern struct proto quicv6_prot;\n+\n+enum quic_state {\n+\tQUIC_SS_CLOSED\t\t= TCP_CLOSE,\n+\tQUIC_SS_LISTENING\t= TCP_LISTEN,\n+\tQUIC_SS_ESTABLISHING\t= TCP_SYN_RECV,\n+\tQUIC_SS_ESTABLISHED\t= TCP_ESTABLISHED,\n+};\n+\n+enum quic_tsq_enum {\n+\tQUIC_MTU_REDUCED_DEFERRED,\n+\tQUIC_LOSS_DEFERRED,\n+\tQUIC_SACK_DEFERRED,\n+\tQUIC_PATH_DEFERRED,\n+\tQUIC_PMTU_DEFERRED,\n+\tQUIC_PACE_DEFERRED,\n+};\n+\n+enum quic_tsq_flags {\n+\tQUIC_F_MTU_REDUCED_DEFERRED\t= BIT(QUIC_MTU_REDUCED_DEFERRED),\n+\tQUIC_F_LOSS_DEFERRED\t\t= BIT(QUIC_LOSS_DEFERRED),\n+\tQUIC_F_SACK_DEFERRED\t\t= BIT(QUIC_SACK_DEFERRED),\n+\tQUIC_F_PATH_DEFERRED\t\t= BIT(QUIC_PATH_DEFERRED),\n+\tQUIC_F_PMTU_DEFERRED\t\t= BIT(QUIC_PMTU_DEFERRED),\n+\tQUIC_F_PACE_DEFERRED\t\t= BIT(QUIC_PACE_DEFERRED),\n+};\n+\n+#define QUIC_DEFERRED_ALL (QUIC_F_MTU_REDUCED_DEFERRED |\t\\\n+\t\t\t   QUIC_F_LOSS_DEFERRED |\t\t\\\n+\t\t\t   QUIC_F_SACK_DEFERRED |\t\t\\\n+\t\t\t   QUIC_F_PATH_DEFERRED |\t\t\\\n+\t\t\t   QUIC_F_PMTU_DEFERRED |\t\t\\\n+\t\t\t   QUIC_F_PACE_DEFERRED)\n+\n+struct quic_sock {\n+\tstruct inet_sock\t\tinet;\n+\tstruct list_head\t\treqs;\n+\n+\tstruct quic_data\t\tticket;\n+\tstruct quic_data\t\ttoken;\n+\tstruct quic_data\t\talpn;\n+\n+\tstruct quic_stream_table\tstreams;\n+\tstruct quic_conn_id_set\t\tsource;\n+\tstruct quic_conn_id_set\t\tdest;\n+\tstruct quic_path_group\t\tpaths;\n+\tstruct quic_cong\t\tcong;\n+\tstruct quic_pnspace\t\tspace[QUIC_PNSPACE_MAX];\n+\tstruct quic_crypto\t\tcrypto[QUIC_CRYPTO_MAX];\n+\n+\tstruct quic_packet\t\tpacket;\n+\tstruct quic_timer\t\ttimers[QUIC_TIMER_MAX];\n+};\n+\n+struct quic6_sock {\n+\tstruct quic_sock\tquic;\n+\tstruct ipv6_pinfo\tinet6;\n+};\n+\n+static inline struct quic_sock *quic_sk(const struct sock *sk)\n+{\n+\treturn (struct quic_sock *)sk;\n+}\n+\n+static inline struct list_head *quic_reqs(const struct sock *sk)\n+{\n+\treturn \u0026quic_sk(sk)-\u003ereqs;\n+}\n+\n+static inline struct quic_data *quic_token(const struct sock *sk)\n+{\n+\treturn \u0026quic_sk(sk)-\u003etoken;\n+}\n+\n+static inline struct quic_data *quic_ticket(const struct sock *sk)\n+{\n+\treturn \u0026quic_sk(sk)-\u003eticket;\n+}\n+\n+static inline struct quic_data *quic_alpn(const struct sock *sk)\n+{\n+\treturn \u0026quic_sk(sk)-\u003ealpn;\n+}\n+\n+static inline struct quic_stream_table *quic_streams(const struct sock *sk)\n+{\n+\treturn \u0026quic_sk(sk)-\u003estreams;\n+}\n+\n+static inline struct quic_conn_id_set *quic_source(const struct sock *sk)\n+{\n+\treturn \u0026quic_sk(sk)-\u003esource;\n+}\n+\n+static inline struct quic_conn_id_set *quic_dest(const struct sock *sk)\n+{\n+\treturn \u0026quic_sk(sk)-\u003edest;\n+}\n+\n+static inline struct quic_path_group *quic_paths(const struct sock *sk)\n+{\n+\treturn \u0026quic_sk(sk)-\u003epaths;\n+}\n+\n+static inline bool quic_is_serv(const struct sock *sk)\n+{\n+\treturn !!sk-\u003esk_max_ack_backlog;\n+}\n+\n+static inline struct quic_cong *quic_cong(const struct sock *sk)\n+{\n+\treturn \u0026quic_sk(sk)-\u003econg;\n+}\n+\n+static inline struct quic_pnspace *quic_pnspace(const struct sock *sk, u8 level)\n+{\n+\treturn \u0026quic_sk(sk)-\u003espace[level % QUIC_CRYPTO_EARLY];\n+}\n+\n+static inline struct quic_crypto *quic_crypto(const struct sock *sk, u8 level)\n+{\n+\treturn \u0026quic_sk(sk)-\u003ecrypto[level];\n+}\n+\n+static inline struct quic_packet *quic_packet(const struct sock *sk)\n+{\n+\treturn \u0026quic_sk(sk)-\u003epacket;\n+}\n+\n+static inline void *quic_timer(const struct sock *sk, u8 type)\n+{\n+\treturn (void *)\u0026quic_sk(sk)-\u003etimers[type];\n+}\n+\n+static inline bool quic_is_establishing(struct sock *sk)\n+{\n+\treturn sk-\u003esk_state == QUIC_SS_ESTABLISHING;\n+}\n+\n+static inline bool quic_is_established(struct sock *sk)\n+{\n+\treturn sk-\u003esk_state == QUIC_SS_ESTABLISHED;\n+}\n+\n+static inline bool quic_is_listen(struct sock *sk)\n+{\n+\treturn sk-\u003esk_state == QUIC_SS_LISTENING;\n+}\n+\n+static inline bool quic_is_closed(struct sock *sk)\n+{\n+\treturn sk-\u003esk_state == QUIC_SS_CLOSED;\n+}\n+\n+static inline void quic_set_state(struct sock *sk, int state)\n+{\n+\tstruct net *net = sock_net(sk);\n+\tint mib;\n+\n+\tif (sk-\u003esk_state == state)\n+\t\treturn;\n+\n+\tif (state == QUIC_SS_ESTABLISHED) {\n+\t\tmib = quic_is_serv(sk) ? QUIC_MIB_CONN_PASSIVEESTABS :\n+\t\t\t\t\t QUIC_MIB_CONN_ACTIVEESTABS;\n+\t\tQUIC_INC_STATS(net, mib);\n+\t\tQUIC_INC_STATS(net, QUIC_MIB_CONN_CURRENTESTABS);\n+\t} else if (quic_is_established(sk)) {\n+\t\tQUIC_DEC_STATS(net, QUIC_MIB_CONN_CURRENTESTABS);\n+\t}\n+\n+\tinet_sk_set_state(sk, state);\n+\tsk-\u003esk_state_change(sk);\n+}\n+\n+struct sock *quic_listen_sock_lookup(struct sk_buff *skb, union quic_addr *sa,\n+\t\t\t\t     union quic_addr *da,\n+\t\t\t\t     struct quic_data *alpns);\n+struct sock *quic_sock_lookup(struct sk_buff *skb, union quic_addr *sa,\n+\t\t\t      union quic_addr *da, struct sock *usk,\n+\t\t\t      struct quic_conn_id *dcid);\ndiff --git a/net/quic/stream.c b/net/quic/stream.c\nnew file mode 100644\nindex 00000000000000..6b34b117103a8e\n--- /dev/null\n+++ b/net/quic/stream.c\n@@ -0,0 +1,416 @@\n+// SPDX-License-Identifier: GPL-2.0-or-later\n+/* QUIC kernel implementation\n+ * (C) Copyright Red Hat Corp. 2023\n+ *\n+ * This file is part of the QUIC kernel implementation\n+ *\n+ * Initialization/cleanup for QUIC protocol support.\n+ *\n+ * Written or modified by:\n+ *    Xin Long \u003clucien.xin@gmail.com\u003e\n+ */\n+\n+#include \u003clinux/quic.h\u003e\n+\n+#include \"common.h\"\n+#include \"stream.h\"\n+\n+/* Check if a stream ID is valid for sending or receiving. */\n+static bool quic_stream_id_valid(s64 stream_id, bool is_serv, bool send)\n+{\n+\tu8 type = (stream_id \u0026 QUIC_STREAM_TYPE_MASK);\n+\n+\tif (send) {\n+\t\tif (is_serv)\n+\t\t\treturn type != QUIC_STREAM_TYPE_CLIENT_UNI;\n+\t\treturn type != QUIC_STREAM_TYPE_SERVER_UNI;\n+\t}\n+\tif (is_serv)\n+\t\treturn type != QUIC_STREAM_TYPE_SERVER_UNI;\n+\treturn type != QUIC_STREAM_TYPE_CLIENT_UNI;\n+}\n+\n+/* Check if a stream ID was initiated locally. */\n+static bool quic_stream_id_local(s64 stream_id, bool is_serv)\n+{\n+\treturn is_serv ^ !(stream_id \u0026 QUIC_STREAM_TYPE_SERVER_MASK);\n+}\n+\n+/* Check if a stream ID represents a unidirectional stream. */\n+static bool quic_stream_id_uni(s64 stream_id)\n+{\n+\treturn stream_id \u0026 QUIC_STREAM_TYPE_UNI_MASK;\n+}\n+\n+#define QUIC_STREAM_HT_SIZE\t64\n+\n+static struct hlist_head *quic_stream_head(struct quic_stream_table *streams,\n+\t\t\t\t\t   s64 stream_id)\n+{\n+\t/* Skip the SERVER initiator bit, which is constant per endpoint. */\n+\treturn \u0026streams-\u003ehead[(stream_id \u003e\u003e 1) \u0026 (QUIC_STREAM_HT_SIZE - 1)];\n+}\n+\n+struct quic_stream *quic_stream_find(struct quic_stream_table *streams,\n+\t\t\t\t     s64 stream_id)\n+{\n+\tstruct hlist_head *head = quic_stream_head(streams, stream_id);\n+\tstruct quic_stream *stream;\n+\n+\thlist_for_each_entry(stream, head, node) {\n+\t\tif (stream-\u003eid == stream_id)\n+\t\t\tbreak;\n+\t}\n+\treturn stream;\n+}\n+\n+static void quic_stream_add(struct quic_stream_table *streams,\n+\t\t\t    struct quic_stream *stream)\n+{\n+\tstruct hlist_head *head;\n+\n+\thead = quic_stream_head(streams, stream-\u003eid);\n+\thlist_add_head(\u0026stream-\u003enode, head);\n+}\n+\n+static void quic_stream_delete(struct quic_stream *stream)\n+{\n+\thlist_del_init(\u0026stream-\u003enode);\n+\tkfree(stream);\n+}\n+\n+/* Create and register new streams for sending or receiving. */\n+static struct quic_stream *quic_stream_create(struct quic_stream_table *streams,\n+\t\t\t\t\t      s64 max_stream_id, bool send,\n+\t\t\t\t\t      bool is_serv, gfp_t gfp)\n+{\n+\tstruct quic_stream *pos, *stream = NULL;\n+\tstruct quic_stream_limits *limits;\n+\tstruct hlist_node *tmp;\n+\tHLIST_HEAD(head);\n+\ts64 stream_id;\n+\tu32 count = 0;\n+\n+\tgfp |= __GFP_ACCOUNT;\n+\tlimits = send ? \u0026streams-\u003esend : \u0026streams-\u003erecv;\n+\tstream_id = limits-\u003enext_bidi_stream_id;\n+\tif (quic_stream_id_uni(max_stream_id))\n+\t\tstream_id = limits-\u003enext_uni_stream_id;\n+\n+\t/* rfc9000#section-2.1: A stream ID that is used out of order results in\n+\t * all streams of that type with lower-numbered stream IDs also being\n+\t * opened.\n+\t */\n+\twhile (stream_id \u003c= max_stream_id) {\n+\t\tstream = kzalloc_obj(*stream, gfp);\n+\t\tif (!stream)\n+\t\t\tgoto free;\n+\n+\t\tstream-\u003eid = stream_id;\n+\t\tif (quic_stream_id_uni(stream_id)) {\n+\t\t\tif (send) {\n+\t\t\t\tstream-\u003esend.max_bytes =\n+\t\t\t\t\tlimits-\u003emax_stream_data_uni;\n+\t\t\t} else {\n+\t\t\t\tstream-\u003erecv.max_bytes =\n+\t\t\t\t\tlimits-\u003emax_stream_data_uni;\n+\t\t\t\tstream-\u003erecv.window = stream-\u003erecv.max_bytes;\n+\t\t\t}\n+\t\t\thlist_add_head(\u0026stream-\u003enode, \u0026head);\n+\t\t\tstream_id += QUIC_STREAM_ID_STEP;\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tif (quic_stream_id_local(stream_id, is_serv)) {\n+\t\t\tstream-\u003esend.max_bytes =\n+\t\t\t\tstreams-\u003esend.max_stream_data_bidi_remote;\n+\t\t\tstream-\u003erecv.max_bytes =\n+\t\t\t\tstreams-\u003erecv.max_stream_data_bidi_local;\n+\t\t} else {\n+\t\t\tstream-\u003esend.max_bytes =\n+\t\t\t\tstreams-\u003esend.max_stream_data_bidi_local;\n+\t\t\tstream-\u003erecv.max_bytes =\n+\t\t\t\tstreams-\u003erecv.max_stream_data_bidi_remote;\n+\t\t}\n+\t\tstream-\u003erecv.window = stream-\u003erecv.max_bytes;\n+\t\thlist_add_head(\u0026stream-\u003enode, \u0026head);\n+\t\tstream_id += QUIC_STREAM_ID_STEP;\n+\t}\n+\n+\thlist_for_each_entry_safe(pos, tmp, \u0026head, node) {\n+\t\thlist_del_init(\u0026pos-\u003enode);\n+\t\tquic_stream_add(streams, pos);\n+\t\tcount++;\n+\t}\n+\n+\t/* Streams must be opened sequentially. Update the next stream ID so the\n+\t * correct starting point is known if an out-of-order open is requested.\n+\t * Note overflow of next_uni/bidi_stream_id is impossible with s64.\n+\t */\n+\tif (quic_stream_id_uni(stream_id)) {\n+\t\tlimits-\u003enext_uni_stream_id = stream_id;\n+\t\tlimits-\u003estreams_uni += count;\n+\t\treturn stream;\n+\t}\n+\n+\tlimits-\u003enext_bidi_stream_id = stream_id;\n+\tlimits-\u003estreams_bidi += count;\n+\treturn stream;\n+\n+free:\n+\thlist_for_each_entry_safe(pos, tmp, \u0026head, node) {\n+\t\thlist_del_init(\u0026pos-\u003enode);\n+\t\tkfree(pos);\n+\t}\n+\treturn NULL;\n+}\n+\n+/* Check if a send or receive stream ID is already closed. */\n+static bool quic_stream_id_closed(struct quic_stream_table *streams,\n+\t\t\t\t  s64 stream_id, bool send)\n+{\n+\tstruct quic_stream_limits *limits = send ? \u0026streams-\u003esend :\n+\t\t\t\t\t\t   \u0026streams-\u003erecv;\n+\n+\tif (quic_stream_id_uni(stream_id))\n+\t\treturn stream_id \u003c limits-\u003enext_uni_stream_id;\n+\treturn stream_id \u003c limits-\u003enext_bidi_stream_id;\n+}\n+\n+/* Check if a stream ID would exceed local (recv) or peer (send) limits. */\n+bool quic_stream_id_exceeds(struct quic_stream_table *streams, s64 stream_id,\n+\t\t\t    bool send)\n+{\n+\tu64 nstreams;\n+\n+\tif (!send) {\n+\t\t/* recv.max_uni/bidi_stream_id is updated in\n+\t\t * quic_stream_max_streams_update() already based on\n+\t\t * next_uni/bidi_stream_id, max_streams_uni/bidi, and\n+\t\t * streams_uni/bidi, so only recv.max_uni/bidi_stream_id needs\n+\t\t * to be checked.\n+\t\t */\n+\t\tif (quic_stream_id_uni(stream_id))\n+\t\t\treturn stream_id \u003e streams-\u003erecv.max_uni_stream_id;\n+\n+\t\treturn stream_id \u003e streams-\u003erecv.max_bidi_stream_id;\n+\t}\n+\n+\tif (quic_stream_id_uni(stream_id)) {\n+\t\tif (stream_id \u003e streams-\u003esend.max_uni_stream_id)\n+\t\t\treturn true;\n+\t\tstream_id -= streams-\u003esend.next_uni_stream_id;\n+\t\tnstreams = quic_stream_id_to_streams(stream_id);\n+\n+\t\treturn nstreams + streams-\u003esend.streams_uni \u003e\n+\t\t       streams-\u003esend.max_streams_uni;\n+\t}\n+\n+\tif (stream_id \u003e streams-\u003esend.max_bidi_stream_id)\n+\t\treturn true;\n+\tstream_id -= streams-\u003esend.next_bidi_stream_id;\n+\tnstreams = quic_stream_id_to_streams(stream_id);\n+\n+\treturn nstreams + streams-\u003esend.streams_bidi \u003e\n+\t       streams-\u003esend.max_streams_bidi;\n+}\n+\n+/* Get or create a send or recv stream by ID. Requires sock lock held. */\n+struct quic_stream *quic_stream_get(struct quic_stream_table *streams,\n+\t\t\t\t    s64 stream_id, u32 flags, bool is_serv,\n+\t\t\t\t    bool send, gfp_t gfp)\n+{\n+\tstruct quic_stream *stream;\n+\n+\tif (!quic_stream_id_valid(stream_id, is_serv, send))\n+\t\treturn ERR_PTR(-EINVAL);\n+\n+\tstream = quic_stream_find(streams, stream_id);\n+\tif (stream)\n+\t\treturn stream;\n+\n+\tif (!send \u0026\u0026 quic_stream_id_local(stream_id, is_serv)) {\n+\t\tif (quic_stream_id_closed(streams, stream_id, !send))\n+\t\t\treturn ERR_PTR(-ENOSTR);\n+\t\treturn ERR_PTR(-EINVAL);\n+\t}\n+\tif (send \u0026\u0026 !quic_stream_id_local(stream_id, is_serv))\n+\t\treturn ERR_PTR(-EINVAL);\n+\n+\tif (quic_stream_id_closed(streams, stream_id, send))\n+\t\treturn ERR_PTR(-ENOSTR);\n+\n+\tif (!(flags \u0026 MSG_QUIC_STREAM_NEW))\n+\t\treturn ERR_PTR(-EINVAL);\n+\n+\tif (quic_stream_id_exceeds(streams, stream_id, send))\n+\t\treturn ERR_PTR(-EAGAIN);\n+\n+\tstream = quic_stream_create(streams, stream_id, send, is_serv, gfp);\n+\tif (!stream)\n+\t\treturn ERR_PTR(-ENOMEM);\n+\n+\tif (send || quic_stream_id_valid(stream_id, is_serv, !send))\n+\t\tstreams-\u003esend.active_stream_id = stream_id;\n+\n+\treturn stream;\n+}\n+\n+/* Release or clean up a send or recv stream. This function updates stream\n+ * counters and state when a send stream has either successfully sent all data\n+ * or has been reset, or when a recv stream has either received all data or has\n+ * been reset. Requires sock lock held.\n+ */\n+void quic_stream_put(struct quic_stream_table *streams,\n+\t\t     struct quic_stream *stream, bool is_serv, bool send)\n+{\n+\tif (quic_stream_id_uni(stream-\u003eid)) {\n+\t\t/* For uni streams, decrement uni count and delete stream. */\n+\t\tif (send) {\n+\t\t\tstreams-\u003esend.streams_uni--;\n+\t\t\tquic_stream_delete(stream);\n+\t\t\treturn;\n+\t\t}\n+\t\tstreams-\u003erecv.streams_uni--;\n+\t\tstreams-\u003erecv.uni_pending = 1;\n+\t\tquic_stream_delete(stream);\n+\t\treturn;\n+\t}\n+\n+\t/* For bidi streams, proceed only if both send and receive in a final\n+\t * state.\n+\t */\n+\tif (send) {\n+\t\tif (stream-\u003erecv.state \u003c QUIC_STREAM_RECV_STATE_RECVD)\n+\t\t\treturn;\n+\t} else {\n+\t\tif (stream-\u003esend.state != QUIC_STREAM_SEND_STATE_RECVD \u0026\u0026\n+\t\t    stream-\u003esend.state != QUIC_STREAM_SEND_STATE_RESET_RECVD)\n+\t\t\treturn;\n+\t}\n+\tif (quic_stream_id_local(stream-\u003eid, is_serv)) {\n+\t\t/* Local-initiated stream: decrement send.bidi count. */\n+\t\tstreams-\u003esend.streams_bidi--;\n+\t} else {\n+\t\t/* Remote-initiated stream: decrement recv.bidi count. */\n+\t\tstreams-\u003erecv.streams_bidi--;\n+\t\tstreams-\u003erecv.bidi_pending = 1;\n+\t}\n+\tquic_stream_delete(stream);\n+}\n+\n+/* Updates the maximum allowed incoming stream IDs if any streams were recently\n+ * closed.  Recalculates the max_uni and max_bidi stream ID limits based on the\n+ * number of open streams and whether any were marked for deletion.\n+ *\n+ * Returns true if either max_uni or max_bidi was updated, indicating that a\n+ * MAX_STREAMS_UNI or MAX_STREAMS_BIDI frame should be sent to the peer.\n+ */\n+bool quic_stream_max_streams_update(struct quic_stream_table *streams,\n+\t\t\t\t    s64 *max_uni, s64 *max_bidi)\n+{\n+\ts64 max, rem;\n+\n+\t*max_uni = 0;\n+\t*max_bidi = 0;\n+\tif (streams-\u003erecv.uni_pending) {\n+\t\trem = streams-\u003erecv.max_streams_uni - streams-\u003erecv.streams_uni;\n+\t\tmax = streams-\u003erecv.next_uni_stream_id - QUIC_STREAM_ID_STEP +\n+\t\t      (rem \u003c\u003c QUIC_STREAM_TYPE_BITS);\n+\n+\t\tstreams-\u003erecv.max_uni_stream_id = max;\n+\t\t*max_uni = quic_stream_id_to_streams(max);\n+\t\tstreams-\u003erecv.uni_pending = 0;\n+\t}\n+\tif (streams-\u003erecv.bidi_pending) {\n+\t\trem = streams-\u003erecv.max_streams_bidi -\n+\t\t      streams-\u003erecv.streams_bidi;\n+\t\tmax = streams-\u003erecv.next_bidi_stream_id - QUIC_STREAM_ID_STEP +\n+\t\t\t(rem \u003c\u003c QUIC_STREAM_TYPE_BITS);\n+\n+\t\tstreams-\u003erecv.max_bidi_stream_id = max;\n+\t\t*max_bidi = quic_stream_id_to_streams(max);\n+\t\tstreams-\u003erecv.bidi_pending = 0;\n+\t}\n+\n+\treturn *max_uni || *max_bidi;\n+}\n+\n+int quic_stream_init(struct quic_stream_table *streams)\n+{\n+\tstruct hlist_head *head;\n+\tint i;\n+\n+\thead = kmalloc_objs(*head, QUIC_STREAM_HT_SIZE);\n+\tif (!head)\n+\t\treturn -ENOMEM;\n+\tfor (i = 0; i \u003c QUIC_STREAM_HT_SIZE; i++)\n+\t\tINIT_HLIST_HEAD(\u0026head[i]);\n+\tstreams-\u003ehead = head;\n+\treturn 0;\n+}\n+\n+void quic_stream_free(struct quic_stream_table *streams)\n+{\n+\tstruct quic_stream *stream;\n+\tstruct hlist_head *head;\n+\tstruct hlist_node *tmp;\n+\tint i;\n+\n+\tif (!streams-\u003ehead)\n+\t\treturn;\n+\n+\tfor (i = 0; i \u003c QUIC_STREAM_HT_SIZE; i++) {\n+\t\thead = \u0026streams-\u003ehead[i];\n+\t\thlist_for_each_entry_safe(stream, tmp, head, node)\n+\t\t\tquic_stream_delete(stream);\n+\t}\n+\tkfree(streams-\u003ehead);\n+\tstreams-\u003ehead = NULL;\n+}\n+\n+/* Populate transport parameters from stream hash table. */\n+void quic_stream_get_param(struct quic_stream_table *streams,\n+\t\t\t   struct quic_transport_param *p)\n+{\n+\tstruct quic_stream_limits *limits = p-\u003eremote ? \u0026streams-\u003esend :\n+\t\t\t\t\t\t\t\u0026streams-\u003erecv;\n+\n+\tp-\u003emax_stream_data_bidi_remote = limits-\u003emax_stream_data_bidi_remote;\n+\tp-\u003emax_stream_data_bidi_local = limits-\u003emax_stream_data_bidi_local;\n+\tp-\u003emax_stream_data_uni = limits-\u003emax_stream_data_uni;\n+\tp-\u003emax_streams_bidi = limits-\u003emax_streams_bidi;\n+\tp-\u003emax_streams_uni = limits-\u003emax_streams_uni;\n+}\n+\n+/* Configure stream hashtable from transport parameters. */\n+void quic_stream_set_param(struct quic_stream_table *streams,\n+\t\t\t   struct quic_transport_param *p, bool is_serv)\n+{\n+\tstruct quic_stream_limits *limits = p-\u003eremote ? \u0026streams-\u003esend :\n+\t\t\t\t\t\t\t\u0026streams-\u003erecv;\n+\tu8 bidi_type, uni_type;\n+\n+\tlimits-\u003emax_stream_data_bidi_local = p-\u003emax_stream_data_bidi_local;\n+\tlimits-\u003emax_stream_data_bidi_remote = p-\u003emax_stream_data_bidi_remote;\n+\tlimits-\u003emax_stream_data_uni = p-\u003emax_stream_data_uni;\n+\tlimits-\u003emax_streams_bidi = p-\u003emax_streams_bidi;\n+\tlimits-\u003emax_streams_uni = p-\u003emax_streams_uni;\n+\tlimits-\u003eactive_stream_id = -1;\n+\n+\tif (p-\u003eremote ^ is_serv) {\n+\t\tbidi_type = QUIC_STREAM_TYPE_CLIENT_BIDI;\n+\t\tuni_type = QUIC_STREAM_TYPE_CLIENT_UNI;\n+\t} else {\n+\t\tbidi_type = QUIC_STREAM_TYPE_SERVER_BIDI;\n+\t\tuni_type = QUIC_STREAM_TYPE_SERVER_UNI;\n+\t}\n+\n+\tlimits-\u003emax_bidi_stream_id =\n+\t\tquic_stream_streams_to_id(p-\u003emax_streams_bidi, bidi_type);\n+\tlimits-\u003enext_bidi_stream_id = bidi_type;\n+\n+\tlimits-\u003emax_uni_stream_id =\n+\t\tquic_stream_streams_to_id(p-\u003emax_streams_uni, uni_type);\n+\tlimits-\u003enext_uni_stream_id = uni_type;\n+}\ndiff --git a/net/quic/stream.h b/net/quic/stream.h\nnew file mode 100644\nindex 00000000000000..d915712f3d5fb1\n--- /dev/null\n+++ b/net/quic/stream.h\n@@ -0,0 +1,133 @@\n+/* SPDX-License-Identifier: GPL-2.0-or-later */\n+/* QUIC kernel implementation\n+ * (C) Copyright Red Hat Corp. 2023\n+ *\n+ * This file is part of the QUIC kernel implementation\n+ *\n+ * Written or modified by:\n+ *    Xin Long \u003clucien.xin@gmail.com\u003e\n+ */\n+\n+#define QUIC_DEF_STREAMS\t100\n+#define QUIC_MAX_STREAMS\t4096ULL\n+\n+/*\n+ * rfc9000#section-2.1:\n+ *\n+ *   The least significant bit (0x01) of the stream ID identifies the initiator\n+ *   of the stream.  Client-initiated streams have even-numbered stream IDs\n+ *   (with the bit set to 0), and server-initiated streams have odd-numbered\n+ *   stream IDs (with the bit set to 1).\n+ *\n+ *   The second least significant bit (0x02) of the stream ID distinguishes\n+ *   between bidirectional streams (with the bit set to 0) and unidirectional\n+ *   streams (with the bit set to 1).\n+ */\n+#define QUIC_STREAM_TYPE_BITS\t2\n+#define QUIC_STREAM_ID_STEP\tBIT(QUIC_STREAM_TYPE_BITS)\n+\n+#define QUIC_STREAM_TYPE_CLIENT_BIDI\t0x00\n+#define QUIC_STREAM_TYPE_SERVER_BIDI\t0x01\n+#define QUIC_STREAM_TYPE_CLIENT_UNI\t0x02\n+#define QUIC_STREAM_TYPE_SERVER_UNI\t0x03\n+\n+struct quic_stream {\n+\tstruct hlist_node node;\n+\ts64 id; /* Stream ID as defined in RFC 9000 Section 2.1 */\n+\tstruct {\n+\t\t/* Sending-side stream level flow control */\n+\t\tu64 last_max_bytes; /* Max send offset advertised by peer */\n+\t\tu64 max_bytes;      /* Max offset allowed to send */\n+\t\tu64 bytes;          /* Bytes already sent to peer */\n+\n+\t\tu32 errcode; /* App error code for RESET_STREAM */\n+\t\tu32 frags;   /* STREAM frames sent but not yet acked */\n+\t\tu8 state;    /* Send stream state, per rfc9000#section-3.1 */\n+\n+\t\tu8 data_blocked; /* True if flow control blocks sending */\n+\t} send;\n+\tstruct {\n+\t\t/* Receiving-side stream level flow control */\n+\t\tu64 max_bytes; /* Max offset peer can send */\n+\t\tu64 window;    /* Remaining receive window */\n+\t\tu64 bytes;     /* Bytes consumed by app */\n+\n+\t\tu64 highest; /* Highest received offset */\n+\t\tu64 offset;  /* Data buffered or consumed */\n+\t\tu64 finalsz; /* Final stream size if FIN received */\n+\n+\t\tu32 frags; /* STREAM frames pending reassembly */\n+\t\tu8 state;  /* Receive stream state, per rfc9000#section-3.2 */\n+\n+\t\tu8 stop_sent; /* True if STOP_SENDING has been sent */\n+\t} recv;\n+};\n+\n+struct quic_stream_limits {\n+\t/* Stream limit parameters defined in rfc9000#section-18.2:\n+\t *\n+\t * - initial_max_stream_data_bidi_remote\n+\t * - initial_max_stream_data_bidi_local\n+\t * - initial_max_stream_data_uni\n+\t * - initial_max_streams_bidi\n+\t * - initial_max_streams_uni\n+\t */\n+\tu64 max_stream_data_bidi_remote;\n+\tu64 max_stream_data_bidi_local;\n+\tu64 max_stream_data_uni;\n+\tu64 max_streams_bidi;\n+\tu64 max_streams_uni;\n+\n+\ts64 next_bidi_stream_id; /* Next bidi stream ID to open or accept */\n+\ts64 next_uni_stream_id;  /* Next uni stream ID to open or accept */\n+\ts64 max_bidi_stream_id;  /* Highest allowed bidi stream ID */\n+\ts64 max_uni_stream_id;   /* Highest allowed uni stream ID */\n+\ts64 active_stream_id;    /* Most recently opened stream ID */\n+\n+\tu8 bidi_blocked;  /* STREAMS_BLOCKED_BIDI sent, awaiting ACK */\n+\tu8 uni_blocked;   /* STREAMS_BLOCKED_UNI sent, awaiting ACK */\n+\tu8 bidi_pending;  /* MAX_STREAMS_BIDI needs to be sent */\n+\tu8 uni_pending;   /* MAX_STREAMS_UNI needs to be sent */\n+\n+\tu16 streams_bidi; /* Number of open bidi streams */\n+\tu16 streams_uni;  /* Number of open uni streams */\n+};\n+\n+struct quic_stream_table {\n+\tstruct hlist_head *head; /* Hash table storing all active streams */\n+\n+\tstruct quic_stream_limits send; /* Limits advertised by peer */\n+\tstruct quic_stream_limits recv; /* Limits we advertise to peer */\n+};\n+\n+static inline u64 quic_stream_id_to_streams(s64 stream_id)\n+{\n+\treturn (u64)(stream_id \u003e\u003e QUIC_STREAM_TYPE_BITS) + 1;\n+}\n+\n+static inline s64 quic_stream_streams_to_id(u64 streams, u8 type)\n+{\n+\tif (!streams)\n+\t\treturn -1;\n+\treturn (s64)((streams - 1) \u003c\u003c QUIC_STREAM_TYPE_BITS) | type;\n+}\n+\n+struct quic_stream *quic_stream_get(struct quic_stream_table *streams,\n+\t\t\t\t    s64 stream_id, u32 flags, bool is_serv,\n+\t\t\t\t    bool send, gfp_t gfp);\n+void quic_stream_put(struct quic_stream_table *streams,\n+\t\t     struct quic_stream *stream, bool is_serv, bool send);\n+\n+bool quic_stream_max_streams_update(struct quic_stream_table *streams,\n+\t\t\t\t    s64 *max_uni, s64 *max_bidi);\n+bool quic_stream_id_exceeds(struct quic_stream_table *streams,\n+\t\t\t    s64 stream_id, bool send);\n+struct quic_stream *quic_stream_find(struct quic_stream_table *streams,\n+\t\t\t\t     s64 stream_id);\n+\n+void quic_stream_get_param(struct quic_stream_table *streams,\n+\t\t\t   struct quic_transport_param *p);\n+void quic_stream_set_param(struct quic_stream_table *streams,\n+\t\t\t   struct quic_transport_param *p, bool is_serv);\n+void quic_stream_free(struct quic_stream_table *streams);\n+int quic_stream_init(struct quic_stream_table *streams);\ndiff --git a/net/quic/timer.c b/net/quic/timer.c\nnew file mode 100644\nindex 00000000000000..0dd6d6580bbd93\n--- /dev/null\n+++ b/net/quic/timer.c\n@@ -0,0 +1,154 @@\n+// SPDX-License-Identifier: GPL-2.0-or-later\n+/* QUIC kernel implementation\n+ * (C) Copyright Red Hat Corp. 2023\n+ *\n+ * This file is part of the QUIC kernel implementation\n+ *\n+ * Initialization/cleanup for QUIC protocol support.\n+ *\n+ * Written or modified by:\n+ *    Xin Long \u003clucien.xin@gmail.com\u003e\n+ */\n+\n+#include \"socket.h\"\n+\n+static void quic_timer_timeout(struct quic_timer *t, int type, int defer_bit,\n+\t\t\t       void (*handler)(struct sock *sk))\n+{\n+\tstruct quic_sock *qs = container_of(t, struct quic_sock, timers[type]);\n+\tstruct sock *sk = \u0026qs-\u003einet.sk;\n+\n+\tbh_lock_sock(sk);\n+\tif (sock_owned_by_user(sk)) {\n+\t\tif (!test_and_set_bit(defer_bit, \u0026sk-\u003esk_tsq_flags))\n+\t\t\tsock_hold(sk);\n+\t\tgoto out;\n+\t}\n+\n+\thandler(sk);\n+out:\n+\tbh_unlock_sock(sk);\n+\tsock_put(sk);\n+}\n+\n+void quic_timer_sack_handler(struct sock *sk)\n+{\n+}\n+\n+static void quic_timer_sack_timeout(struct timer_list *t)\n+{\n+\tquic_timer_timeout((struct quic_timer *)t, QUIC_TIMER_SACK,\n+\t\t\t   QUIC_SACK_DEFERRED, quic_timer_sack_handler);\n+}\n+\n+void quic_timer_loss_handler(struct sock *sk)\n+{\n+}\n+\n+static void quic_timer_loss_timeout(struct timer_list *t)\n+{\n+\tquic_timer_timeout((struct quic_timer *)t, QUIC_TIMER_LOSS,\n+\t\t\t   QUIC_LOSS_DEFERRED, quic_timer_loss_handler);\n+}\n+\n+void quic_timer_path_handler(struct sock *sk)\n+{\n+}\n+\n+static void quic_timer_path_timeout(struct timer_list *t)\n+{\n+\tquic_timer_timeout((struct quic_timer *)t, QUIC_TIMER_PATH,\n+\t\t\t   QUIC_PATH_DEFERRED, quic_timer_path_handler);\n+}\n+\n+void quic_timer_pmtu_handler(struct sock *sk)\n+{\n+}\n+\n+static void quic_timer_pmtu_timeout(struct timer_list *t)\n+{\n+\tquic_timer_timeout((struct quic_timer *)t, QUIC_TIMER_PMTU,\n+\t\t\t   QUIC_PMTU_DEFERRED, quic_timer_pmtu_handler);\n+}\n+\n+void quic_timer_pace_handler(struct sock *sk)\n+{\n+}\n+\n+static enum hrtimer_restart quic_timer_pace_timeout(struct hrtimer *hr)\n+{\n+\tquic_timer_timeout((struct quic_timer *)hr, QUIC_TIMER_PACE,\n+\t\t\t   QUIC_PACE_DEFERRED, quic_timer_pace_handler);\n+\treturn HRTIMER_NORESTART;\n+}\n+\n+void quic_timer_reset(struct sock *sk, u8 type, u64 timeout)\n+{\n+\tstruct timer_list *t = quic_timer(sk, type);\n+\n+\t/* Note that type must never be QUIC_TIMER_PACE for this helper. */\n+\tif (WARN_ON_ONCE(type == QUIC_TIMER_PACE))\n+\t\treturn;\n+\tif (timeout \u0026\u0026 !mod_timer(t, jiffies + usecs_to_jiffies(timeout)))\n+\t\tsock_hold(sk);\n+}\n+\n+void quic_timer_start(struct sock *sk, u8 type, u64 timeout)\n+{\n+\tstruct timer_list *t;\n+\tstruct hrtimer *hr;\n+\n+\tif (type == QUIC_TIMER_PACE) {\n+\t\thr = quic_timer(sk, type);\n+\n+\t\tif (!hrtimer_is_queued(hr)) {\n+\t\t\thrtimer_start(hr, ns_to_ktime(timeout),\n+\t\t\t\t      HRTIMER_MODE_ABS_PINNED_SOFT);\n+\t\t\tsock_hold(sk);\n+\t\t}\n+\t\treturn;\n+\t}\n+\n+\tt = quic_timer(sk, type);\n+\tif (timeout \u0026\u0026 !timer_pending(t)) {\n+\t\tif (!mod_timer(t, jiffies + usecs_to_jiffies(timeout)))\n+\t\t\tsock_hold(sk);\n+\t}\n+}\n+\n+void quic_timer_stop(struct sock *sk, u8 type)\n+{\n+\tif (type == QUIC_TIMER_PACE) {\n+\t\tif (hrtimer_try_to_cancel(quic_timer(sk, type)) == 1)\n+\t\t\tsock_put(sk);\n+\t\treturn;\n+\t}\n+\tif (timer_delete(quic_timer(sk, type)))\n+\t\tsock_put(sk);\n+}\n+\n+void quic_timer_init(struct sock *sk)\n+{\n+\ttimer_setup(quic_timer(sk, QUIC_TIMER_LOSS), quic_timer_loss_timeout,\n+\t\t    0);\n+\ttimer_setup(quic_timer(sk, QUIC_TIMER_SACK), quic_timer_sack_timeout,\n+\t\t    0);\n+\ttimer_setup(quic_timer(sk, QUIC_TIMER_PATH), quic_timer_path_timeout,\n+\t\t    0);\n+\ttimer_setup(quic_timer(sk, QUIC_TIMER_PMTU), quic_timer_pmtu_timeout,\n+\t\t    0);\n+\t/* Use hrtimer for pace timer, ensuring precise control over send\n+\t * timing.\n+\t */\n+\thrtimer_setup(quic_timer(sk, QUIC_TIMER_PACE), quic_timer_pace_timeout,\n+\t\t      CLOCK_MONOTONIC, HRTIMER_MODE_ABS_PINNED_SOFT);\n+}\n+\n+void quic_timer_free(struct sock *sk)\n+{\n+\tquic_timer_stop(sk, QUIC_TIMER_LOSS);\n+\tquic_timer_stop(sk, QUIC_TIMER_SACK);\n+\tquic_timer_stop(sk, QUIC_TIMER_PATH);\n+\tquic_timer_stop(sk, QUIC_TIMER_PMTU);\n+\tquic_timer_stop(sk, QUIC_TIMER_PACE);\n+}\ndiff --git a/net/quic/timer.h b/net/quic/timer.h\nnew file mode 100644\nindex 00000000000000..4f63660376027a\n--- /dev/null\n+++ b/net/quic/timer.h\n@@ -0,0 +1,45 @@\n+/* SPDX-License-Identifier: GPL-2.0-or-later */\n+/* QUIC kernel implementation\n+ * (C) Copyright Red Hat Corp. 2023\n+ *\n+ * This file is part of the QUIC kernel implementation\n+ *\n+ * Written or modified by:\n+ *    Xin Long \u003clucien.xin@gmail.com\u003e\n+ */\n+\n+enum {\n+\tQUIC_TIMER_LOSS, /* Loss detection timer: retransmit on packet loss */\n+\tQUIC_TIMER_SACK, /* ACK delay timer, also used as idle timer alias */\n+\tQUIC_TIMER_PATH, /* Path validation timer: verifies path connectivity */\n+\tQUIC_TIMER_PMTU, /* PLPMTUD probing timer */\n+\tQUIC_TIMER_PACE, /* Pacing timer: controls packet transmission pacing */\n+\tQUIC_TIMER_MAX,\n+\tQUIC_TIMER_IDLE = QUIC_TIMER_SACK,\n+};\n+\n+struct quic_timer {\n+\tunion {\n+\t\tstruct timer_list t;\n+\t\tstruct hrtimer hr;\n+\t};\n+};\n+\n+#define QUIC_MIN_PROBE_TIMEOUT\t5000000\n+\n+#define QUIC_MIN_PATH_TIMEOUT\t1500000\n+\n+#define QUIC_MIN_IDLE_TIMEOUT\t1000000\n+#define QUIC_DEF_IDLE_TIMEOUT\t30000000\n+\n+void quic_timer_reset(struct sock *sk, u8 type, u64 timeout);\n+void quic_timer_start(struct sock *sk, u8 type, u64 timeout);\n+void quic_timer_stop(struct sock *sk, u8 type);\n+void quic_timer_init(struct sock *sk);\n+void quic_timer_free(struct sock *sk);\n+\n+void quic_timer_loss_handler(struct sock *sk);\n+void quic_timer_pace_handler(struct sock *sk);\n+void quic_timer_path_handler(struct sock *sk);\n+void quic_timer_sack_handler(struct sock *sk);\n+void quic_timer_pmtu_handler(struct sock *sk);\ndiff --git a/usr/include/Makefile b/usr/include/Makefile\nindex ee69dd9d970e53..8b4133d38da2d6 100644\n--- a/usr/include/Makefile\n+++ b/usr/include/Makefile\n@@ -31,6 +31,7 @@ no-header-test += linux/omap3isp.h\n no-header-test += linux/omapfb.h\n no-header-test += linux/patchkey.h\n no-header-test += linux/phonet.h\n+no-header-test += linux/quic.h\n no-header-test += linux/sctp.h\n no-header-test += linux/sysctl.h\n no-header-test += linux/usb/audio.h\n"
}

2/1 2026/07/16 03:53 agent patch-evaluator
1m Model:
gemini-3.1-pro-preview

Args:
null

Results:
{
  "EnableConfigs": null,
  "FocusSymbols": [],
  "Reasoning": "The patch introduces the core structures and functions for the QUIC protocol, but it is a skeleton/preliminary patch. The socket operations (bind, connect, sendmsg, recvmsg, setsockopt) all return -EOPNOTSUPP. Furthermore, the UDP tunnel socket required to receive packets is never created because `quic_path_bind` is not called anywhere, making the packet reception and processing logic completely unreachable from user space or the network.",
  "WorthFuzzing": false
}

Instruction:
You are an expert Linux kernel maintainer.
Your job is to review a provided patch series and determine
if it makes functional changes to the kernel that should be fuzzed.

IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in
your workspace. Do NOT rely on your internal knowledge of the kernel. You must actively
use your code access tools to examine the actual source code and confirm any assumptions.

Return WorthFuzzing=false if the patch only contains:
- Modifications to Documentation/, Kconfig files, or code comments.
- Purely decorative changes, such as logging (e.g., pr_err, printk) or tracepoints.
- Changes to numeric constants or macros that do not functionally alter execution flow.
- Code paths that are impossible to reach in virtualized environments like GCE or QEMU,
even when utilizing software-emulated hardware (e.g., usb gadget, mac80211_hwsim).

If it modifies reachable core kernel logic, drivers, or architectures, use your code search
tools to verify the code can be executed, then return WorthFuzzing=true.

When returning WorthFuzzing=true, you MUST ALSO:
1. Extract any specific kernel functions that should be heavily fuzzed into FocusSymbols.
   Avoid listing generic hot-path functions to prevent skewed test distributions.
2. Identify any specific CONFIG_ options required to properly test this new/modified feature.
   Go and look into the Kconfig files and check for ifdefs around the code, do not make assumptions.
   Do not list too generic configs (we already have them enabled). Only list those that
   specifically cover the modified code. List them in the EnableConfigs output array,
   and DO NOT add a 'CONFIG_' prefix (e.g., return "NET_IPV4" instead of "CONFIG_NET_IPV4").
Prefer calling several tools at the same time to save round-trips.


Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.

Prompt:
For your convenience, here is the diff of the changes:
commit f275446826c8678c88010162b6126dc1f6d31c5f
Author: syz-cluster <triage@syzkaller.com>
Date:   Thu Jul 16 03:53:04 2026 +0000

    syz-cluster: applied patch under review

diff --git a/Documentation/networking/ip-sysctl.rst b/Documentation/networking/ip-sysctl.rst
index 208f46967ee59b..6dd9d6970cec45 100644
--- a/Documentation/networking/ip-sysctl.rst
+++ b/Documentation/networking/ip-sysctl.rst
@@ -3809,6 +3809,45 @@ l3mdev_accept - BOOLEAN
 	Default: 1 (enabled)
 
 
+``/proc/sys/net/quic/*`` Variables
+===================================
+
+quic_mem - vector of 3 LONGs: min, pressure, max
+	Number of pages allowed for queueing by all QUIC sockets.
+
+	min: below this number of pages QUIC is not bothered about its
+	memory appetite.
+
+	pressure: when amount of memory allocated by QUIC exceeds this number
+	of pages, QUIC moderates its memory consumption and enters memory
+	pressure mode, which is exited when memory consumption falls
+	under "min".
+
+	max: number of pages allowed for queueing by all QUIC sockets.
+
+	Defaults are calculated at boot time from amount of available
+	memory.
+
+quic_rmem - vector of 3 INTEGERs: min, default, max
+	Only the first value ("min") is used, "default" and "max" are
+	ignored.
+
+	min: Minimal size of receive buffer used by QUIC sockets.
+	It is guaranteed to each QUIC socket, even under moderate memory
+	pressure.
+
+	Default: 4K
+
+quic_wmem - vector of 3 INTEGERs: min, default, max
+	Only the first value ("min") is used, "default" and "max" are
+	ignored.
+
+	min: Amount of memory reserved for send buffers for QUIC sockets.
+	Each QUIC socket has rights to use it due to fact of its birth.
+
+	Default: 4K
+
+
 ``/proc/sys/net/core/*``
 ========================
 
diff --git a/MAINTAINERS b/MAINTAINERS
index f3218abefd0c6b..5ce461662a0c9c 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -22404,6 +22404,15 @@ L:	linux-wireless@vger.kernel.org
 S:	Maintained
 F:	drivers/net/wireless/quantenna/
 
+QUIC PROTOCOL
+M:	Xin Long <lucien.xin@gmail.com>
+L:	quic@lists.linux.dev
+S:	Maintained
+W:	https://github.com/lxin/quic
+F:	include/linux/quic.h
+F:	include/uapi/linux/quic.h
+F:	net/quic/
+
 RADEON and AMDGPU DRM DRIVERS
 M:	Alex Deucher <alexander.deucher@amd.com>
 M:	Christian König <christian.koenig@amd.com>
diff --git a/include/linux/quic.h b/include/linux/quic.h
new file mode 100644
index 00000000000000..908308a6bb8320
--- /dev/null
+++ b/include/linux/quic.h
@@ -0,0 +1,24 @@
+/* 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>
+ */
+
+#ifndef _LINUX_QUIC_H
+#define _LINUX_QUIC_H
+
+#include <linux/sockptr.h>
+#include <uapi/linux/quic.h>
+
+struct sock;
+
+int quic_do_setsockopt(struct sock *sk, int optname, sockptr_t optval,
+		       unsigned int optlen);
+int quic_do_getsockopt(struct sock *sk, int optname, sockptr_t optval,
+		       sockptr_t optlen);
+
+#endif
diff --git a/include/linux/socket.h b/include/linux/socket.h
index 2a8d7b14f1d119..9d6ec1b2c50ba7 100644
--- a/include/linux/socket.h
+++ b/include/linux/socket.h
@@ -400,6 +400,7 @@ struct ucred {
 #define SOL_MCTP	285
 #define SOL_SMC		286
 #define SOL_VSOCK	287
+#define SOL_QUIC	288
 
 /* IPX options */
 #define IPX_TYPE	1
diff --git a/include/uapi/linux/in.h b/include/uapi/linux/in.h
index ced0fc3c3aa534..e4072152f2e62f 100644
--- a/include/uapi/linux/in.h
+++ b/include/uapi/linux/in.h
@@ -85,6 +85,8 @@ enum {
 #define IPPROTO_RAW		IPPROTO_RAW
   IPPROTO_SMC = 256,		/* Shared Memory Communications		*/
 #define IPPROTO_SMC		IPPROTO_SMC
+  IPPROTO_QUIC = 261,		/* A UDP-Based Multiplexed Secure Transport */
+#define IPPROTO_QUIC		IPPROTO_QUIC
   IPPROTO_MPTCP = 262,		/* Multipath TCP connection		*/
 #define IPPROTO_MPTCP		IPPROTO_MPTCP
   IPPROTO_MAX
diff --git a/include/uapi/linux/quic.h b/include/uapi/linux/quic.h
new file mode 100644
index 00000000000000..d29b21f7b77cb9
--- /dev/null
+++ b/include/uapi/linux/quic.h
@@ -0,0 +1,241 @@
+/* SPDX-License-Identifier: GPL-2.0+ WITH Linux-syscall-note */
+/* 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>
+ */
+
+#ifndef _UAPI_LINUX_QUIC_H
+#define _UAPI_LINUX_QUIC_H
+
+#include <linux/types.h>
+#ifdef __KERNEL__
+#include <linux/socket.h>
+#else
+#include <sys/socket.h> /* for MSG_* flags */
+#endif
+
+/* NOTE: Structure descriptions are specified in:
+ * https://datatracker.ietf.org/doc/html/draft-lxin-quic-socket-apis
+ */
+
+/* Send or Receive Options APIs */
+enum quic_cmsg_type {
+	QUIC_STREAM_INFO,
+	QUIC_HANDSHAKE_INFO,
+};
+
+#define QUIC_STREAM_TYPE_SERVER_MASK	0x01
+#define QUIC_STREAM_TYPE_UNI_MASK	0x02
+#define QUIC_STREAM_TYPE_MASK		0x03
+
+enum quic_msg_flags {
+	/* flags for stream_flags */
+	MSG_QUIC_STREAM_NEW		= MSG_SYN,
+	MSG_QUIC_STREAM_FIN		= MSG_FIN,
+	MSG_QUIC_STREAM_UNI		= MSG_CONFIRM,
+	MSG_QUIC_STREAM_DONTWAIT	= MSG_WAITFORONE,
+	MSG_QUIC_STREAM_SNDBLOCK	= MSG_ERRQUEUE,
+
+	/* extended flags for msg_flags */
+	MSG_QUIC_DATAGRAM		= MSG_RST,
+	MSG_QUIC_NOTIFICATION		= MSG_MORE,
+};
+
+enum quic_crypto_level {
+	QUIC_CRYPTO_APP,
+	QUIC_CRYPTO_INITIAL,
+	QUIC_CRYPTO_HANDSHAKE,
+	QUIC_CRYPTO_EARLY,
+	QUIC_CRYPTO_MAX,
+};
+
+struct quic_handshake_info {
+	__u8	crypto_level;
+};
+
+struct quic_stream_info {
+	__s64	stream_id;
+	__u32	stream_flags;
+};
+
+/* Socket Options APIs */
+#define QUIC_SOCKOPT_EVENT				0
+#define QUIC_SOCKOPT_STREAM_OPEN			1
+#define QUIC_SOCKOPT_STREAM_RESET			2
+#define QUIC_SOCKOPT_STREAM_STOP_SENDING		3
+#define QUIC_SOCKOPT_CONNECTION_ID			4
+#define QUIC_SOCKOPT_CONNECTION_CLOSE			5
+#define QUIC_SOCKOPT_CONNECTION_MIGRATION		6
+#define QUIC_SOCKOPT_KEY_UPDATE				7
+#define QUIC_SOCKOPT_TRANSPORT_PARAM			8
+#define QUIC_SOCKOPT_CONFIG				9
+#define QUIC_SOCKOPT_TOKEN				10
+#define QUIC_SOCKOPT_ALPN				11
+#define QUIC_SOCKOPT_SESSION_TICKET			12
+#define QUIC_SOCKOPT_CRYPTO_SECRET			13
+#define QUIC_SOCKOPT_TRANSPORT_PARAM_EXT		14
+
+#define QUIC_VERSION_V1			0x1
+#define QUIC_VERSION_V2			0x6b3343cf
+
+struct quic_transport_param {
+	__u8	remote;
+	__u8	disable_active_migration;
+	__u8	grease_quic_bit;
+	__u8	stateless_reset;
+	__u8	disable_1rtt_encryption;
+	__u8	disable_compatible_version;
+	__u8	active_connection_id_limit;
+	__u8	ack_delay_exponent;
+	__u16	max_datagram_frame_size;
+	__u16	max_udp_payload_size;
+	__u32	max_idle_timeout;
+	__u32	max_ack_delay;
+	__u16	max_streams_bidi;
+	__u16	max_streams_uni;
+	__u64	max_data;
+	__u64	max_stream_data_bidi_local;
+	__u64	max_stream_data_bidi_remote;
+	__u64	max_stream_data_uni;
+};
+
+struct quic_config {
+	__u32	version;
+	__u32	plpmtud_probe_interval;
+	__u32	initial_smoothed_rtt;
+	__u32	payload_cipher_type;
+	__u8	congestion_control_algo;
+	__u8	validate_peer_address;
+	__u8	stream_data_nodelay;
+	__u8	receive_session_ticket;
+	__u8	certificate_request;
+	__u8	reserved[3];
+	__u32	keepalive_probe_interval;
+};
+
+struct quic_crypto_secret {
+	__u8	send;  /* send or recv */
+	__u8	level; /* crypto level */
+	__u16	reserved;
+	__u32	type; /* TLS_CIPHER_* */
+#define QUIC_CRYPTO_SECRET_BUFFER_SIZE 48
+	__u8	secret[QUIC_CRYPTO_SECRET_BUFFER_SIZE];
+};
+
+enum quic_cong_algo {
+	QUIC_CONG_ALG_RENO,
+	QUIC_CONG_ALG_MAX,
+};
+
+struct quic_errinfo {
+	__s64	stream_id;
+	__u32	errcode;
+};
+
+struct quic_connection_id_info {
+	__u8	dest;
+	__u8	reserved[3];
+	__u32	active;
+	__u32	prior_to;
+};
+
+struct quic_event_option {
+	__u8	type;
+	__u8	on;
+};
+
+/* Event APIs */
+enum quic_event_type {
+	QUIC_EVENT_NONE,
+	QUIC_EVENT_STREAM_UPDATE,
+	QUIC_EVENT_STREAM_MAX_DATA,
+	QUIC_EVENT_STREAM_MAX_STREAM,
+	QUIC_EVENT_CONNECTION_ID,
+	QUIC_EVENT_CONNECTION_CLOSE,
+	QUIC_EVENT_CONNECTION_MIGRATION,
+	QUIC_EVENT_KEY_UPDATE,
+	QUIC_EVENT_NEW_TOKEN,
+	QUIC_EVENT_NEW_SESSION_TICKET,
+	QUIC_EVENT_MAX,
+};
+
+enum {
+	QUIC_STREAM_SEND_STATE_READY,
+	QUIC_STREAM_SEND_STATE_SEND,
+	QUIC_STREAM_SEND_STATE_SENT,
+	QUIC_STREAM_SEND_STATE_RECVD,
+	QUIC_STREAM_SEND_STATE_RESET_SENT,
+	QUIC_STREAM_SEND_STATE_RESET_RECVD,
+
+	QUIC_STREAM_RECV_STATE_RECV,
+	QUIC_STREAM_RECV_STATE_SIZE_KNOWN,
+	QUIC_STREAM_RECV_STATE_RECVD,
+	QUIC_STREAM_RECV_STATE_READ,
+	QUIC_STREAM_RECV_STATE_RESET_RECVD,
+	QUIC_STREAM_RECV_STATE_RESET_READ,
+};
+
+struct quic_stream_update {
+	__s64	id;
+	__u8	state;
+	__u8	reserved[3];
+	__u32	errcode;
+	__u64	finalsz;
+};
+
+struct quic_stream_max_data {
+	__s64	id;
+	__u64	max_data;
+};
+
+struct quic_connection_close {
+	__u32	errcode;
+	__u8	frame;
+	__u8	reserved[3];
+#define QUIC_CLOSE_PHRASE_BUFFER_SIZE 64
+	__u8	phrase[QUIC_CLOSE_PHRASE_BUFFER_SIZE];
+};
+
+union quic_event {
+	struct quic_stream_update	update;
+	struct quic_stream_max_data	max_data;
+	struct quic_connection_close	close;
+	struct quic_connection_id_info	info;
+	__u64	max_stream;
+	__u8	local_migration;
+	__u8	key_update_phase;
+};
+
+enum {
+	QUIC_TRANSPORT_ERROR_NONE			= 0x00,
+	QUIC_TRANSPORT_ERROR_INTERNAL			= 0x01,
+	QUIC_TRANSPORT_ERROR_CONNECTION_REFUSED		= 0x02,
+	QUIC_TRANSPORT_ERROR_FLOW_CONTROL		= 0x03,
+	QUIC_TRANSPORT_ERROR_STREAM_LIMIT		= 0x04,
+	QUIC_TRANSPORT_ERROR_STREAM_STATE		= 0x05,
+	QUIC_TRANSPORT_ERROR_FINAL_SIZE			= 0x06,
+	QUIC_TRANSPORT_ERROR_FRAME_ENCODING		= 0x07,
+	QUIC_TRANSPORT_ERROR_TRANSPORT_PARAM		= 0x08,
+	QUIC_TRANSPORT_ERROR_CONNECTION_ID_LIMIT	= 0x09,
+	QUIC_TRANSPORT_ERROR_PROTOCOL_VIOLATION		= 0x0a,
+	QUIC_TRANSPORT_ERROR_INVALID_TOKEN		= 0x0b,
+	QUIC_TRANSPORT_ERROR_APPLICATION		= 0x0c,
+	QUIC_TRANSPORT_ERROR_CRYPTO_BUF_EXCEEDED	= 0x0d,
+	QUIC_TRANSPORT_ERROR_KEY_UPDATE			= 0x0e,
+	QUIC_TRANSPORT_ERROR_AEAD_LIMIT_REACHED		= 0x0f,
+	QUIC_TRANSPORT_ERROR_NO_VIABLE_PATH		= 0x10,
+	QUIC_TRANSPORT_ERROR_VERSION_NEGOTIATION	= 0x11,
+
+	/* The cryptographic handshake failed. A range of 256 values is reserved
+	 * for carrying error codes specific to the cryptographic handshake that
+	 * is used. Codes for errors occurring when TLS is used for the
+	 * cryptographic handshake are described in Section 4.8 of [QUIC-TLS].
+	 */
+	QUIC_TRANSPORT_ERROR_CRYPTO			= 0x0100,
+};
+
+#endif /* _UAPI_LINUX_QUIC_H */
diff --git a/net/Kconfig b/net/Kconfig
index e384773935515d..ab0d82e108ea96 100644
--- a/net/Kconfig
+++ b/net/Kconfig
@@ -251,6 +251,7 @@ source "net/bridge/netfilter/Kconfig"
 
 endif # if NETFILTER
 
+source "net/quic/Kconfig"
 source "net/sctp/Kconfig"
 source "net/rds/Kconfig"
 source "net/tipc/Kconfig"
diff --git a/net/Makefile b/net/Makefile
index 5b2dd7f07a85e9..e2b25ede017d7c 100644
--- a/net/Makefile
+++ b/net/Makefile
@@ -39,6 +39,7 @@ obj-$(CONFIG_PHONET)		+= phonet/
 ifneq ($(CONFIG_VLAN_8021Q),)
 obj-y				+= 8021q/
 endif
+obj-$(CONFIG_IP_QUIC)		+= quic/
 obj-$(CONFIG_IP_SCTP)		+= sctp/
 obj-$(CONFIG_RDS)		+= rds/
 obj-$(CONFIG_WIRELESS)		+= wireless/
diff --git a/net/quic/Kconfig b/net/quic/Kconfig
new file mode 100644
index 00000000000000..602e6d89eafdd2
--- /dev/null
+++ b/net/quic/Kconfig
@@ -0,0 +1,35 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# QUIC configuration
+#
+
+menuconfig IP_QUIC
+	tristate "QUIC: A UDP-Based Multiplexed Secure Transport (Experimental)"
+	depends on INET
+	depends on IPV6
+	select CRYPTO
+	select CRYPTO_HMAC
+	select CRYPTO_AES
+	select CRYPTO_GCM
+	select CRYPTO_CCM
+	select CRYPTO_CHACHA20POLY1305
+	select NET_UDP_TUNNEL
+	default n
+	help
+	  QUIC: A UDP-Based Multiplexed and Secure Transport
+
+	  From rfc9000 <https://www.rfc-editor.org/rfc/rfc9000.html>.
+
+	  QUIC provides applications with flow-controlled streams for structured
+	  communication, low-latency connection establishment, and network path
+	  migration.  QUIC includes security measures that ensure
+	  confidentiality, integrity, and availability in a range of deployment
+	  circumstances.  Accompanying documents describe the integration of
+	  TLS for key negotiation, loss detection, and an exemplary congestion
+	  control algorithm.
+
+	  To compile this protocol support as a module, choose M here: the
+	  module will be called quic. Debug messages are handled by the
+	  kernel's dynamic debugging framework.
+
+	  If in doubt, say N.
diff --git a/net/quic/Makefile b/net/quic/Makefile
new file mode 100644
index 00000000000000..0f903f4a7ff1fc
--- /dev/null
+++ b/net/quic/Makefile
@@ -0,0 +1,9 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# Makefile for QUIC support code.
+#
+
+obj-$(CONFIG_IP_QUIC) += quic.o
+
+quic-y := common.o family.o protocol.o socket.o stream.o connid.o path.o \
+	  cong.o pnspace.o crypto.o timer.o packet.o
diff --git a/net/quic/common.c b/net/quic/common.c
new file mode 100644
index 00000000000000..51d71b6733c7d0
--- /dev/null
+++ b/net/quic/common.c
@@ -0,0 +1,565 @@
+// 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
+ *
+ * Initialization/cleanup for QUIC protocol support.
+ *
+ * Written or modified by:
+ *    Xin Long <lucien.xin@gmail.com>
+ */
+
+#include <linux/unaligned.h>
+#include <net/netns/hash.h>
+#include <linux/vmalloc.h>
+#include <linux/jhash.h>
+
+#include "common.h"
+
+#define QUIC_VARINT_2BYTE_PREFIX	0x40
+#define QUIC_VARINT_4BYTE_PREFIX	0x80
+#define QUIC_VARINT_8BYTE_PREFIX	0xc0
+
+#define QUIC_VARINT_LENGTH(p)		BIT((*(p)) >> 6)
+
+struct quic_hashinfo {
+	struct quic_shash_table shash; /* Source connection ID hashtable */
+	struct quic_shash_table lhash; /* Listening sock hashtable */
+	struct quic_shash_table chash; /* Connection sock hashtable */
+	struct quic_uhash_table uhash; /* UDP sock hashtable */
+};
+
+static struct quic_hashinfo quic_hashinfo;
+
+u32 quic_sock_hash_size(void)
+{
+	return quic_hashinfo.chash.size;
+}
+
+u32 quic_sock_hash(struct net *net, union quic_addr *s, union quic_addr *d)
+{
+	u32 ports = ((__force u32)s->v4.sin_port) << 16 |
+		    (__force u32)d->v4.sin_port;
+	u32 saddr = (s->sa.sa_family == AF_INET6) ?
+		    jhash(&s->v6.sin6_addr, 16, 0) :
+		    (__force u32)s->v4.sin_addr.s_addr;
+	u32 daddr = (d->sa.sa_family == AF_INET6) ?
+		    jhash(&d->v6.sin6_addr, 16, 0) :
+		    (__force u32)d->v4.sin_addr.s_addr;
+	u32 hash = jhash_3words(saddr, daddr, ports, net_hash_mix(net));
+
+	return hash & (quic_sock_hash_size() - 1);
+}
+
+struct quic_shash_head *quic_sock_head(u32 hash)
+{
+	return &quic_hashinfo.chash.hash[hash];
+}
+
+u32 quic_listen_sock_hash_size(void)
+{
+	return quic_hashinfo.lhash.size;
+}
+
+u32 quic_listen_sock_hash(struct net *net, u16 port)
+{
+	u32 hash = jhash_1word((__force u32)port, net_hash_mix(net));
+
+	return hash & (quic_listen_sock_hash_size() - 1);
+}
+
+struct quic_shash_head *quic_listen_sock_head(u32 hash)
+{
+	return &quic_hashinfo.lhash.hash[hash];
+}
+
+struct quic_shash_head *quic_source_conn_id_head(struct net *net, u8 *scid,
+						 u32 len)
+{
+	u32 hash = jhash_1word(jhash(scid, len, 0), net_hash_mix(net));
+	struct quic_shash_table *ht = &quic_hashinfo.shash;
+
+	return &ht->hash[hash & (ht->size - 1)];
+}
+
+struct quic_uhash_head *quic_udp_sock_head(struct net *net, u16 port)
+{
+	u32 hash = jhash_1word((__force u32)port, net_hash_mix(net));
+	struct quic_uhash_table *ht = &quic_hashinfo.uhash;
+
+	return &ht->hash[hash & (ht->size - 1)];
+}
+
+u32 quic_addr_hash(struct net *net, union quic_addr *a)
+{
+	u32 addr = (a->sa.sa_family == AF_INET6) ?
+		   jhash(&a->v6.sin6_addr, 16, 0) :
+		   (__force u32)a->v4.sin_addr.s_addr;
+
+	return jhash_2words(addr, (__force u32)a->v4.sin_port,
+			    net_hash_mix(net));
+}
+
+void quic_hash_tables_destroy(void)
+{
+	vfree(quic_hashinfo.shash.hash);
+	vfree(quic_hashinfo.lhash.hash);
+	vfree(quic_hashinfo.chash.hash);
+	vfree(quic_hashinfo.uhash.hash);
+}
+
+static int quic_shash_table_init(struct quic_shash_table *ht, u32 size)
+{
+	int i;
+
+	ht->hash = vmalloc_array(size, sizeof(struct quic_shash_head));
+	if (!ht->hash)
+		return -ENOMEM;
+
+	ht->size = size;
+	for (i = 0; i < ht->size; i++) {
+		spin_lock_init(&ht->hash[i].lock);
+		INIT_HLIST_NULLS_HEAD(&ht->hash[i].head, i);
+	}
+	return 0;
+}
+
+static int quic_uhash_table_init(struct quic_uhash_table *ht, u32 size)
+{
+	int i;
+
+	ht->hash = vmalloc_array(size, sizeof(struct quic_uhash_head));
+	if (!ht->hash)
+		return -ENOMEM;
+
+	ht->size = size;
+	for (i = 0; i < ht->size; i++) {
+		mutex_init(&ht->hash[i].lock);
+		INIT_HLIST_HEAD(&ht->hash[i].head);
+	}
+	return 0;
+}
+
+int quic_hash_tables_init(void)
+{
+	unsigned long nr_pages = totalram_pages();
+	unsigned long limit;
+	u32 size;
+	int err;
+
+	/* Scale hash table size based on system memory, similar to SCTP. */
+	if (nr_pages >= (128 * 1024))
+		limit = nr_pages >> (22 - PAGE_SHIFT);
+	else
+		limit = nr_pages >> (24 - PAGE_SHIFT);
+
+	limit = roundup_pow_of_two(limit ?: 1);
+
+	/* Source connection ID table (fast lookup, larger size) */
+	size = min_t(unsigned long, limit, 64 * 1024UL);
+	err = quic_shash_table_init(&quic_hashinfo.shash, size);
+	if (err)
+		goto err;
+	size = min_t(unsigned long, limit, 16 * 1024UL);
+	err = quic_shash_table_init(&quic_hashinfo.lhash, size);
+	if (err)
+		goto err;
+	err = quic_shash_table_init(&quic_hashinfo.chash, size);
+	if (err)
+		goto err;
+	err = quic_uhash_table_init(&quic_hashinfo.uhash, size);
+	if (err)
+		goto err;
+	return 0;
+err:
+	quic_hash_tables_destroy();
+	return err;
+}
+
+/* Returns the number of bytes required to encode a QUIC variable-length
+ * integer.
+ */
+u8 quic_var_len(u64 n)
+{
+	if (n <= QUIC_VARINT_1BYTE_MAX)
+		return 1;
+	if (n <= QUIC_VARINT_2BYTE_MAX)
+		return 2;
+	if (n <= QUIC_VARINT_4BYTE_MAX)
+		return 4;
+	return 8;
+}
+
+/* Decodes a QUIC variable-length integer from a buffer. */
+u8 quic_get_var(u8 **pp, u32 *plen, u64 *val)
+{
+	u8 *p = *pp, len;
+	u64 v = 0;
+
+	if (!*plen)
+		return 0;
+
+	len = QUIC_VARINT_LENGTH(p);
+	if (*plen < len)
+		return 0;
+
+	switch (len) {
+	case 1:
+		v = *p;
+		break;
+	case 2:
+		v = get_unaligned_be16(p) & QUIC_VARINT_2BYTE_MAX;
+		break;
+	case 4:
+		v = get_unaligned_be32(p) & QUIC_VARINT_4BYTE_MAX;
+		break;
+	case 8:
+		v = get_unaligned_be64(p) & QUIC_VARINT_8BYTE_MAX;
+		break;
+	default:
+		return 0;
+	}
+
+	*plen -= len;
+	*pp = p + len;
+	*val = v;
+	return len;
+}
+
+/* Reads a fixed-length integer from the buffer. */
+u32 quic_get_int(u8 **pp, u32 *plen, u64 *val, u32 len)
+{
+	u8 *p = *pp;
+	u64 v = 0;
+
+	if (*plen < len)
+		return 0;
+
+	switch (len) {
+	case 1:
+		v = *p;
+		break;
+	case 2:
+		v = get_unaligned_be16(p);
+		break;
+	case 3:
+		v = get_unaligned_be24(p);
+		break;
+	case 4:
+		v = get_unaligned_be32(p);
+		break;
+	case 8:
+		v = get_unaligned_be64(p);
+		break;
+	default:
+		return 0;
+	}
+	*plen -= len;
+	*pp = p + len;
+	*val = v;
+	return len;
+}
+
+u32 quic_get_data(u8 **pp, u32 *plen, u8 *data, u32 len)
+{
+	if (*plen < len)
+		return 0;
+
+	memcpy(data, *pp, len);
+	*pp += len;
+	*plen -= len;
+
+	return len;
+}
+
+/* Encodes a value into the QUIC variable-length integer format. */
+u8 *quic_put_var(u8 *p, u64 num)
+{
+	if (num <= QUIC_VARINT_1BYTE_MAX) {
+		*p++ = (u8)num;
+		return p;
+	}
+	if (num <= QUIC_VARINT_2BYTE_MAX) {
+		put_unaligned_be16((u16)num, p);
+		*p |= QUIC_VARINT_2BYTE_PREFIX;
+		return p + 2;
+	}
+	if (num <= QUIC_VARINT_4BYTE_MAX) {
+		put_unaligned_be32((u32)num, p);
+		*p |= QUIC_VARINT_4BYTE_PREFIX;
+		return p + 4;
+	}
+	put_unaligned_be64(num, p);
+	*p |= QUIC_VARINT_8BYTE_PREFIX;
+	return p + 8;
+}
+
+/* Writes a fixed-length integer to the buffer in network byte order. */
+u8 *quic_put_int(u8 *p, u64 num, u8 len)
+{
+	switch (len) {
+	case 1:
+		*p++ = (u8)num;
+		return p;
+	case 2:
+		put_unaligned_be16((u16)num, p);
+		return p + 2;
+	case 4:
+		put_unaligned_be32((u32)num, p);
+		return p + 4;
+	case 8:
+		put_unaligned_be64(num, p);
+		return p + 8;
+	default:
+		return NULL;
+	}
+}
+
+/* Encodes a value as a variable-length integer with explicit length. */
+u8 *quic_put_varint(u8 *p, u64 num, u8 len)
+{
+	switch (len) {
+	case 1:
+		*p++ = (u8)num;
+		return p;
+	case 2:
+		put_unaligned_be16((u16)num, p);
+		*p |= QUIC_VARINT_2BYTE_PREFIX;
+		return p + 2;
+	case 4:
+		put_unaligned_be32((u32)num, p);
+		*p |= QUIC_VARINT_4BYTE_PREFIX;
+		return p + 4;
+	default:
+		return NULL;
+	}
+}
+
+u8 *quic_put_data(u8 *p, u8 *data, u32 len)
+{
+	if (!len)
+		return p;
+
+	memcpy(p, data, len);
+	return p + len;
+}
+
+/* Writes a transport parameter as two varints: ID and value length, followed
+ * by value.
+ */
+u8 *quic_put_param(u8 *p, u16 id, u64 value)
+{
+	p = quic_put_var(p, id);
+	p = quic_put_var(p, quic_var_len(value));
+	return quic_put_var(p, value);
+}
+
+/* Reads a QUIC transport parameter value. */
+u8 quic_get_param(u64 *pdest, u8 **pp, u32 *plen)
+{
+	u64 valuelen;
+
+	if (!quic_get_var(pp, plen, &valuelen))
+		return 0;
+
+	if (*plen < valuelen)
+		return 0;
+
+	if (quic_get_var(pp, plen, pdest) != valuelen)
+		return 0;
+
+	return (u8)valuelen;
+}
+
+/* rfc9000#section-a.3: DecodePacketNumber()
+ *
+ * Reconstructs the full packet number from a truncated one.
+ */
+s64 quic_get_num(s64 max_pkt_num, s64 pkt_num, u32 n)
+{
+	s64 expected = max_pkt_num + 1;
+	s64 win = BIT_ULL(n * 8);
+	s64 hwin = win / 2;
+	s64 mask = win - 1;
+	s64 cand;
+
+	cand = (expected & ~mask) | pkt_num;
+	if (cand <= expected - hwin && cand < BIT_ULL(QUIC_PN_BITS) - win)
+		return cand + win;
+	if (cand > expected + hwin && cand >= win)
+		return cand - win;
+	return cand;
+}
+
+int quic_data_dup(struct quic_data *to, u8 *data, u32 len, gfp_t gfp)
+{
+	if (!len) {
+		quic_data_free(to);
+		return 0;
+	}
+
+	data = kmemdup(data, len, gfp);
+	if (!data)
+		return -ENOMEM;
+
+	kfree(to->data);
+	to->data = data;
+	to->len = len;
+	return 0;
+}
+
+int quic_data_append(struct quic_data *to, u8 *data, u32 len, gfp_t gfp)
+{
+	u8 *p;
+
+	if (!len)
+		return 0;
+
+	if (to->len > U32_MAX - len)
+		return -EOVERFLOW;
+
+	p = kmalloc(to->len + len, gfp);
+	if (!p)
+		return -ENOMEM;
+	p = quic_put_data(p, to->data, to->len);
+	p = quic_put_data(p, data, len);
+
+	kfree(to->data);
+	to->len = to->len + len;
+	to->data = p - to->len;
+	return 0;
+}
+
+/* Check whether 'd2' is equal to any element inside the list 'd1'.
+ *
+ * 'd1' is assumed to be a sequence of length-prefixed elements. Each element
+ * is compared to 'd2' using 'quic_data_cmp()'.
+ *
+ * Returns true if a match is found, false otherwise.
+ */
+bool quic_data_has(struct quic_data *d1, struct quic_data *d2)
+{
+	struct quic_data d;
+	u64 length;
+	u32 len;
+	u8 *p;
+
+	for (p = d1->data, len = d1->len; len; len -= length, p += length) {
+		if (!quic_get_int(&p, &len, &length, 1) || len < length)
+			return false;
+		quic_data(&d, p, length);
+		if (!quic_data_cmp(&d, d2))
+			return true;
+	}
+	return false;
+}
+
+/* Check if any element of 'd1' is present in the list 'd2'.
+ *
+ * Iterates through each element in 'd1', and uses 'quic_data_has()' to check
+ * for its presence in 'd2'.
+ *
+ * Returns true if any match is found, false otherwise.
+ */
+bool quic_data_match(struct quic_data *d1, struct quic_data *d2)
+{
+	struct quic_data d;
+	u64 length;
+	u32 len;
+	u8 *p;
+
+	for (p = d1->data, len = d1->len; len; len -= length, p += length) {
+		if (!quic_get_int(&p, &len, &length, 1) || len < length)
+			return false;
+		quic_data(&d, p, length);
+		if (quic_data_has(d2, &d))
+			return true;
+	}
+	return false;
+}
+
+/* Serialize a list of 'quic_data' elements into a comma-separated string.
+ *
+ * Each element in 'from' is length-prefixed. This function copies their raw
+ * content into the output buffer 'to', inserting commas in between. The
+ * resulting string length is written to '*plen'.
+ */
+int quic_data_to_string(u8 *to, u32 *plen, struct quic_data *from)
+{
+	u32 remlen = *plen;
+	struct quic_data d;
+	u8 *data = to, *p;
+	u64 length;
+	u32 len;
+
+	p = from->data;
+	len = from->len;
+	while (len) {
+		if (!quic_get_int(&p, &len, &length, 1) || len < length)
+			return -EINVAL;
+
+		quic_data(&d, p, length);
+		if (d.len > remlen)
+			return -EOVERFLOW;
+
+		data = quic_put_data(data, d.data, d.len);
+		remlen -= d.len;
+		p += d.len;
+		len -= d.len;
+		if (len) {
+			if (!remlen)
+				return -EOVERFLOW;
+			data = quic_put_int(data, ',', 1);
+			remlen--;
+		}
+	}
+	*plen = data - to;
+	return 0;
+}
+
+/* Parse a comma-separated string into a 'quic_data' list format.
+ *
+ * Each comma-separated token is turned into a length-prefixed element. The
+ * first byte of each element stores the length. Elements are stored in
+ * 'to->data', and 'to->len' is updated.
+ */
+int quic_data_from_string(struct quic_data *to, u8 *from, u32 len)
+{
+	u32 remlen = to->len;
+	struct quic_data d;
+	u8 *p = to->data;
+
+	to->len = 0;
+	while (len) {
+		while (len && *from == ' ') {
+			from++;
+			len--;
+		}
+		if (!len)
+			break;
+		if (!remlen)
+			return -EOVERFLOW;
+		d.data = p++;
+		d.len  = 0;
+		remlen--;
+		while (len) {
+			if (*from == ',') {
+				from++;
+				len--;
+				break;
+			}
+			if (!remlen)
+				return -EOVERFLOW;
+			*p++ = *from++;
+			len--;
+			d.len++;
+			remlen--;
+		}
+		if (d.len > U8_MAX)
+			return -EINVAL;
+		*d.data = (u8)(d.len);
+		to->len += d.len + 1;
+	}
+	return 0;
+}
diff --git a/net/quic/common.h b/net/quic/common.h
new file mode 100644
index 00000000000000..6cb2b1b89cfb17
--- /dev/null
+++ b/net/quic/common.h
@@ -0,0 +1,220 @@
+/* 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>
+ */
+
+#include <net/net_namespace.h>
+
+#define QUIC_MAX_ACK_DELAY	(16384 * 1000)
+#define QUIC_DEF_ACK_DELAY	25000
+
+#define QUIC_STREAM_BIT_FIN	0x01
+#define QUIC_STREAM_BIT_LEN	0x02
+#define QUIC_STREAM_BIT_OFF	0x04
+#define QUIC_STREAM_BIT_MASK	0x08
+
+#define QUIC_CONN_ID_MAX_LEN	20
+#define QUIC_CONN_ID_DEF_LEN	8
+
+#define QUIC_PN_MAX_LEN		4 /* For encoded packet number */
+#define QUIC_PN_BITS		62
+#define QUIC_PN_MAX		(BIT_ULL(QUIC_PN_BITS) - 1)
+
+#define QUIC_VARINT_1BYTE_MAX		0x3fULL
+#define QUIC_VARINT_2BYTE_MAX		0x3fffULL
+#define QUIC_VARINT_4BYTE_MAX		0x3fffffffULL
+#define QUIC_VARINT_8BYTE_MAX		0x3fffffffffffffffULL
+
+struct quic_conn_id {
+	u8 data[QUIC_CONN_ID_MAX_LEN];
+	u8 len;
+};
+
+static inline void quic_conn_id_update(struct quic_conn_id *conn_id, u8 *data,
+				       u32 len)
+{
+	/* The caller must ensure len does not exceed QUIC_CONN_ID_MAX_LEN. */
+	if (WARN_ON_ONCE(len > QUIC_CONN_ID_MAX_LEN))
+		return;
+	memcpy(conn_id->data, data, len);
+	conn_id->len = (u8)len;
+}
+
+struct quic_skb_cb {
+	/* Callback and temporary context when encryption/decryption completes
+	 * in async mode
+	 */
+	void (*crypto_done)(struct sk_buff *skb, int err);
+	void *crypto_ctx;
+	union {
+		struct sk_buff *last; /* Last packet in bundle on TX */
+		u64 time; /* Arrival timestamp in UDP tunnel on RX */
+	};
+	s64 number;  /* Parsed packet number, or the largest previously seen */
+	u32 seqno;   /* Dest connection ID number on RX */
+	u16 length;  /* Payload length + packet number length */
+
+	u16 number_offset; /* Offset of packet number field */
+	u8 number_len;     /* Length of the packet number field */
+	u8 level; /* Encryption level: Initial, Handshake, App, or Early */
+
+	u16 errcode;     /* Error code on packet processing failure */
+	u8 errframe;     /* Frame type causing packet processing failure */
+
+	u8 key_update:1; /* Key update triggered by this packet */
+	u8 key_phase:1;  /* Key phase used (0 or 1) */
+	u8 backlog:1;    /* Enqueued into backlog list */
+	u8 resume:1;     /* Crypto already processed (encrypted or decrypted) */
+	u8 path:1;       /* Packet arrived from a new or migrating path */
+	u8 sync:1;       /* Force synchronous crypto (process context only) */
+	u8 ecn:2;        /* ECN marking used on TX */
+};
+
+#define QUIC_SKB_CB(skb)	((struct quic_skb_cb *)&((skb)->cb[0]))
+
+struct quichdr {
+#if defined(__LITTLE_ENDIAN_BITFIELD)
+	__u8 pnl:2,
+	     key:1,
+	     reserved:2,
+	     spin:1,
+	     fixed:1,
+	     form:1;
+#elif defined(__BIG_ENDIAN_BITFIELD)
+	__u8 form:1,
+	     fixed:1,
+	     spin:1,
+	     reserved:2,
+	     key:1,
+	     pnl:2;
+#endif
+};
+
+static inline struct quichdr *quic_hdr(struct sk_buff *skb)
+{
+	return (struct quichdr *)skb->data;
+}
+
+struct quichshdr {
+#if defined(__LITTLE_ENDIAN_BITFIELD)
+	__u8 pnl:2,
+	     reserved:2,
+	     type:2,
+	     fixed:1,
+	     form:1;
+#elif defined(__BIG_ENDIAN_BITFIELD)
+	__u8 form:1,
+	     fixed:1,
+	     type:2,
+	     reserved:2,
+	     pnl:2;
+#endif
+};
+
+static inline struct quichshdr *quic_hshdr(struct sk_buff *skb)
+{
+	return (struct quichshdr *)skb->data;
+}
+
+union quic_addr {
+	struct sockaddr_in6 v6;
+	struct sockaddr_in v4;
+	struct sockaddr sa;
+};
+
+static inline union quic_addr *quic_addr(const void *addr)
+{
+	return (union quic_addr *)addr;
+}
+
+struct quic_shash_head {
+	struct hlist_nulls_head	head;
+	spinlock_t		lock; /* Protects 'head' in atomic context */
+};
+
+struct quic_shash_table {
+	struct quic_shash_head *hash;
+	u32 size;
+};
+
+struct quic_uhash_head {
+	struct hlist_head	head;
+	struct mutex		lock; /* Protects 'head' in process context */
+};
+
+struct quic_uhash_table {
+	struct quic_uhash_head *hash;
+	u32 size;
+};
+
+struct quic_data {
+	u8 *data;
+	u32 len;
+};
+
+static inline struct quic_data *quic_data(struct quic_data *d, u8 *data,
+					  u32 len)
+{
+	d->data = data;
+	d->len  = len;
+	return d;
+}
+
+static inline int quic_data_cmp(struct quic_data *d1, struct quic_data *d2)
+{
+	return d1->len != d2->len || memcmp(d1->data, d2->data, d1->len);
+}
+
+static inline void quic_data_free(struct quic_data *d)
+{
+	kfree(d->data);
+	d->data = NULL;
+	d->len = 0;
+}
+
+static inline u64 quic_ktime_get_us(void)
+{
+	return ktime_to_us(ktime_get());
+}
+
+u32 quic_sock_hash(struct net *net, union quic_addr *s, union quic_addr *d);
+struct quic_shash_head *quic_sock_head(u32 hash);
+u32 quic_sock_hash_size(void);
+
+u32 quic_listen_sock_hash(struct net *net, u16 port);
+struct quic_shash_head *quic_listen_sock_head(u32 hash);
+u32 quic_listen_sock_hash_size(void);
+
+struct quic_shash_head *quic_source_conn_id_head(struct net *net, u8 *scid,
+						 u32 len);
+struct quic_uhash_head *quic_udp_sock_head(struct net *net, u16 port);
+u32 quic_addr_hash(struct net *net, union quic_addr *a);
+
+void quic_hash_tables_destroy(void);
+int quic_hash_tables_init(void);
+
+u32 quic_get_data(u8 **pp, u32 *plen, u8 *data, u32 len);
+u32 quic_get_int(u8 **pp, u32 *plen, u64 *val, u32 len);
+s64 quic_get_num(s64 max_pkt_num, s64 pkt_num, u32 n);
+u8 quic_get_param(u64 *pdest, u8 **pp, u32 *plen);
+u8 quic_get_var(u8 **pp, u32 *plen, u64 *val);
+u8 quic_var_len(u64 n);
+
+u8 *quic_put_param(u8 *p, u16 id, u64 value);
+u8 *quic_put_data(u8 *p, u8 *data, u32 len);
+u8 *quic_put_varint(u8 *p, u64 num, u8 len);
+u8 *quic_put_int(u8 *p, u64 num, u8 len);
+u8 *quic_put_var(u8 *p, u64 num);
+
+int quic_data_from_string(struct quic_data *to, u8 *from, u32 len);
+int quic_data_to_string(u8 *to, u32 *plen, struct quic_data *from);
+
+int quic_data_append(struct quic_data *to, u8 *data, u32 len, gfp_t gfp);
+int quic_data_dup(struct quic_data *to, u8 *data, u32 len, gfp_t gfp);
+bool quic_data_match(struct quic_data *d1, struct quic_data *d2);
+bool quic_data_has(struct quic_data *d1, struct quic_data *d2);
diff --git a/net/quic/cong.c b/net/quic/cong.c
new file mode 100644
index 00000000000000..dc04bde55c6022
--- /dev/null
+++ b/net/quic/cong.c
@@ -0,0 +1,338 @@
+// 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
+ *
+ * Initialization/cleanup for QUIC protocol support.
+ *
+ * Written or modified by:
+ *    Xin Long <lucien.xin@gmail.com>
+ */
+
+#include <linux/quic.h>
+
+#include "common.h"
+#include "cong.h"
+
+/* NEW RENO APIs */
+static void quic_reno_handle_packet_lost(struct quic_cong *cong)
+{
+	switch (cong->state) {
+	case QUIC_CONG_SLOW_START:
+		pr_debug("%s: slow_start -> recovery, cwnd: %u, ssth: %u\n",
+			 __func__, cong->window, cong->ssthresh);
+		break;
+	case QUIC_CONG_RECOVERY_PERIOD:
+		return;
+	case QUIC_CONG_CONGESTION_AVOIDANCE:
+		pr_debug("%s: cong_avoid -> recovery, cwnd: %u, ssth: %u\n",
+			 __func__, cong->window, cong->ssthresh);
+		break;
+	default:
+		pr_debug("%s: wrong congestion state: %d\n", __func__,
+			 cong->state);
+		return;
+	}
+
+	cong->recovery_time = cong->time;
+	cong->state = QUIC_CONG_RECOVERY_PERIOD;
+	cong->ssthresh = max(cong->window >> 1U, cong->min_window);
+	cong->window = cong->ssthresh;
+}
+
+static void quic_reno_on_packet_lost(struct quic_cong *cong, u64 time,
+				     u32 bytes, s64 number)
+{
+	quic_reno_handle_packet_lost(cong);
+}
+
+static void quic_reno_on_packet_acked(struct quic_cong *cong, u64 time,
+				      u32 bytes, s64 number)
+{
+	u64 new_window;
+
+	switch (cong->state) {
+	case QUIC_CONG_SLOW_START:
+		new_window = (u64)cong->window + bytes;
+		cong->window = min_t(u64, new_window, cong->max_window);
+		if (cong->window < cong->ssthresh)
+			break;
+		cong->state = QUIC_CONG_CONGESTION_AVOIDANCE;
+		pr_debug("%s: slow_start -> cong_avoid, cwnd: %u, ssth: %u\n",
+			 __func__, cong->window, cong->ssthresh);
+		break;
+	case QUIC_CONG_RECOVERY_PERIOD:
+		if (cong->recovery_time >= time)
+			break;
+		cong->state = QUIC_CONG_CONGESTION_AVOIDANCE;
+		pr_debug("%s: recovery -> cong_avoid, cwnd: %u, ssth: %u\n",
+			 __func__, cong->window, cong->ssthresh);
+		break;
+	case QUIC_CONG_CONGESTION_AVOIDANCE:
+		/* cong->window is never zero; it is initialized by
+		 * quic_packet_route() during connect/accept.
+		 */
+		new_window = div64_ul((u64)cong->mss * bytes, cong->window) +
+			     cong->window;
+		cong->window = min_t(u64, new_window, cong->max_window);
+		break;
+	default:
+		pr_debug("%s: wrong congestion state: %d\n", __func__,
+			 cong->state);
+		return;
+	}
+}
+
+static void quic_reno_on_process_ecn(struct quic_cong *cong)
+{
+	quic_reno_handle_packet_lost(cong);
+}
+
+static void quic_reno_on_init(struct quic_cong *cong)
+{
+}
+
+static const struct quic_cong_ops quic_congs[] = {
+	{ /* QUIC_CONG_ALG_RENO */
+		.on_packet_acked = quic_reno_on_packet_acked,
+		.on_packet_lost = quic_reno_on_packet_lost,
+		.on_process_ecn = quic_reno_on_process_ecn,
+		.on_init = quic_reno_on_init,
+	},
+};
+
+static bool quic_cong_check_persistent_congestion(struct quic_cong *cong,
+						  u64 time)
+{
+	u32 ssthresh;
+
+	time -= cong->pc_start_time;
+
+	/* rfc9002#section-7.6.1:
+	 *   (smoothed_rtt + max(4*rttvar, kGranularity) + max_ack_delay) *
+	 *      kPersistentCongestionThreshold
+	 */
+	ssthresh = cong->smoothed_rtt +
+		   max(4 * cong->rttvar, QUIC_KGRANULARITY);
+	ssthresh = (ssthresh + cong->max_ack_delay) *
+		   QUIC_KPERSISTENT_CONGESTION_THRESHOLD;
+
+	return time > ssthresh;
+}
+
+/* COMMON APIs */
+void quic_cong_on_packet_lost(struct quic_cong *cong, u64 time, u32 bytes,
+			      s64 number)
+{
+	if (cong->pc_start_time && time > cong->pc_start_time &&
+	    quic_cong_check_persistent_congestion(cong, time)) {
+		cong->pc_start_time = 0;
+		cong->min_rtt_valid = 0;
+		cong->window = cong->min_window;
+		cong->state = QUIC_CONG_SLOW_START;
+		return;
+	}
+
+	if (!cong->pc_start_time && cong->is_rtt_set)
+		cong->pc_start_time = time;
+
+	cong->ops->on_packet_lost(cong, time, bytes, number);
+}
+
+void quic_cong_on_packet_acked(struct quic_cong *cong, u64 time, u32 bytes,
+			       s64 number)
+{
+	/* When a packet is acked, if time - cong->pc_start_time <= duration
+	 * threshold, it means the acked packet was sent within the persistent
+	 * congestion window.
+	 *
+	 * This breaks the condition in rfc9002#section-7.6.2:
+	 *
+	 * - across all packet number spaces, none of the packets sent between
+	 *   the send times of these two packets are acknowledged;
+	 *
+	 * so pc_start_time is reset to 0.
+	 */
+	if (cong->pc_start_time && time > cong->pc_start_time &&
+	    !quic_cong_check_persistent_congestion(cong, time))
+		cong->pc_start_time = 0;
+
+	cong->ops->on_packet_acked(cong, time, bytes, number);
+}
+
+void quic_cong_on_process_ecn(struct quic_cong *cong)
+{
+	cong->ops->on_process_ecn(cong);
+}
+
+/* Update Probe Timeout (PTO) and loss detection delay based on RTT stats. */
+static void quic_cong_pto_update(struct quic_cong *cong)
+{
+	u32 pto, loss_delay;
+
+	/* rfc9002#section-6.2.1:
+	 *   PTO = smoothed_rtt + max(4*rttvar, kGranularity) + max_ack_delay
+	 */
+	pto = cong->smoothed_rtt + max(4 * cong->rttvar, QUIC_KGRANULARITY);
+	cong->pto = pto + cong->max_ack_delay;
+
+	/* rfc9002#section-6.1.2:
+	 *   max(kTimeThreshold * max(smoothed_rtt, latest_rtt), kGranularity)
+	 */
+	loss_delay = QUIC_KTIME_THRESHOLD(max(cong->smoothed_rtt,
+					      cong->latest_rtt));
+	cong->loss_delay = max(loss_delay, QUIC_KGRANULARITY);
+
+	pr_debug("%s: update pto: %u\n", __func__, pto);
+}
+
+/* Update pacing timestamp after sending 'bytes' bytes.
+ *
+ * This function tracks when the next packet is allowed to be sent based on
+ * pacing rate.
+ */
+static void quic_cong_update_pacing_time(struct quic_cong *cong, u32 bytes)
+{
+	u64 prior_time, credit, len_ns, rate = cong->pacing_rate;
+
+	if (!rate)
+		return;
+
+	prior_time = cong->pacing_time;
+	cong->pacing_time = max(cong->pacing_time, ktime_get_ns());
+	credit = cong->pacing_time - prior_time;
+
+	/* take into account OS jitter */
+	len_ns = div64_ul((u64)bytes * NSEC_PER_SEC, rate);
+	len_ns -= min_t(u64, len_ns / 2, credit);
+	cong->pacing_time += len_ns;
+}
+
+/* Compute and update the pacing rate based on congestion window and smoothed
+ * RTT.
+ */
+static void quic_cong_pace_update(struct quic_cong *cong, u32 bytes,
+				  u64 max_rate)
+{
+	u64 rate;
+
+	if (unlikely(!cong->smoothed_rtt))
+		return;
+
+	/* rate = N * congestion_window / smoothed_rtt */
+	rate = div64_ul((u64)cong->window * USEC_PER_SEC * 2,
+			cong->smoothed_rtt);
+
+	cong->pacing_rate = min_t(u64, rate, max_rate);
+	pr_debug("%s: update pacing rate: %llu, max rate: %llu, srtt: %u\n",
+		 __func__, cong->pacing_rate, max_rate, cong->smoothed_rtt);
+}
+
+void quic_cong_on_packet_sent(struct quic_cong *cong, u64 time, u32 bytes,
+			      s64 number)
+{
+	if (!bytes)
+		return;
+	if (cong->ops->on_packet_sent)
+		cong->ops->on_packet_sent(cong, time, bytes, number);
+	quic_cong_update_pacing_time(cong, bytes);
+}
+
+void quic_cong_on_ack_recv(struct quic_cong *cong, u32 bytes, u64 max_rate)
+{
+	if (!bytes)
+		return;
+	if (cong->ops->on_ack_recv)
+		cong->ops->on_ack_recv(cong, bytes, max_rate);
+	quic_cong_pace_update(cong, bytes, max_rate);
+}
+
+/* rfc9002#section-5: Estimating the Round-Trip Time */
+void quic_cong_rtt_update(struct quic_cong *cong, u64 time, u32 ack_delay)
+{
+	u32 adjusted_rtt, rttvar_sample;
+
+	/* Ignore RTT sample if ACK delay is suspiciously large. */
+	if (ack_delay > cong->max_ack_delay * 2 ||
+	    cong->time - time > QUIC_RTT_MAX)
+		return;
+
+	/* rfc9002#section-5.1:
+	 *   latest_rtt = ack_time - send_time_of_largest_acked
+	 */
+	cong->latest_rtt = cong->time - time;
+
+	/* rfc9002#section-5.2: Estimating min_rtt */
+	if (!cong->min_rtt_valid) {
+		cong->min_rtt = cong->latest_rtt;
+		cong->min_rtt_valid = 1;
+	}
+	if (cong->min_rtt > cong->latest_rtt)
+		cong->min_rtt = cong->latest_rtt;
+
+	if (!cong->is_rtt_set) {
+		/* rfc9002#section-5.3:
+		 *   smoothed_rtt = latest_rtt
+		 *   rttvar = latest_rtt / 2
+		 */
+		cong->smoothed_rtt = cong->latest_rtt;
+		cong->rttvar = cong->smoothed_rtt / 2;
+		quic_cong_pto_update(cong);
+		cong->is_rtt_set = 1;
+		return;
+	}
+
+	/* rfc9002#section-5.3:
+	 *   adjusted_rtt = latest_rtt
+	 *   if (latest_rtt >= min_rtt + ack_delay):
+	 *     adjusted_rtt = latest_rtt - ack_delay
+	 *   smoothed_rtt = 7/8 * smoothed_rtt + 1/8 * adjusted_rtt
+	 *   rttvar_sample = abs(smoothed_rtt - adjusted_rtt)
+	 *   rttvar = 3/4 * rttvar + 1/4 * rttvar_sample
+	 */
+	adjusted_rtt = cong->latest_rtt;
+	if (cong->latest_rtt >= cong->min_rtt + ack_delay)
+		adjusted_rtt = cong->latest_rtt - ack_delay;
+
+	cong->smoothed_rtt = (cong->smoothed_rtt * 7 + adjusted_rtt) / 8;
+	rttvar_sample = abs_diff(cong->smoothed_rtt, adjusted_rtt);
+	cong->rttvar = (cong->rttvar * 3 + rttvar_sample) / 4;
+	quic_cong_pto_update(cong);
+
+	if (cong->ops->on_rtt_update)
+		cong->ops->on_rtt_update(cong);
+}
+
+void quic_cong_set_algo(struct quic_cong *cong, u8 algo)
+{
+	/* The caller must ensure algo < QUIC_CONG_ALG_MAX. */
+	if (WARN_ON_ONCE(algo >= QUIC_CONG_ALG_MAX))
+		return;
+	cong->algo = algo;
+	cong->state = QUIC_CONG_SLOW_START;
+	cong->ssthresh = U32_MAX;
+	cong->ops = &quic_congs[algo];
+	cong->ops->on_init(cong);
+}
+
+void quic_cong_set_srtt(struct quic_cong *cong, u32 srtt)
+{
+	/* rfc9002#section-5.3:
+	 *   smoothed_rtt = kInitialRtt
+	 *   rttvar = kInitialRtt / 2
+	 */
+	cong->initial_srtt = srtt;
+	cong->latest_rtt = srtt;
+	cong->smoothed_rtt = cong->latest_rtt;
+	cong->rttvar = cong->smoothed_rtt / 2;
+	quic_cong_pto_update(cong);
+}
+
+void quic_cong_init(struct quic_cong *cong)
+{
+	cong->max_ack_delay = QUIC_DEF_ACK_DELAY;
+	cong->max_window = S32_MAX / 4;
+	quic_cong_set_algo(cong, QUIC_CONG_ALG_RENO);
+	quic_cong_set_srtt(cong, QUIC_RTT_INIT);
+}
diff --git a/net/quic/cong.h b/net/quic/cong.h
new file mode 100644
index 00000000000000..a9cd5e93ec000f
--- /dev/null
+++ b/net/quic/cong.h
@@ -0,0 +1,130 @@
+/* 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_KPERSISTENT_CONGESTION_THRESHOLD	3
+#define QUIC_KPACKET_THRESHOLD			3
+#define QUIC_KTIME_THRESHOLD(rtt)		((rtt) * 9 / 8)
+#define QUIC_KGRANULARITY			1000U
+
+#define QUIC_RTT_INIT		333000U
+#define QUIC_RTT_MAX		6000000U
+#define QUIC_RTT_MIN		QUIC_KGRANULARITY
+
+/* rfc9002#section-7.3: Congestion Control States
+ *
+ *                  New path or      +------------+
+ *             persistent congestion |   Slow     |
+ *         (O)---------------------->|   Start    |
+ *                                   +------------+
+ *                                         |
+ *                                 Loss or |
+ *                         ECN-CE increase |
+ *                                         v
+ *  +------------+     Loss or       +------------+
+ *  | Congestion |  ECN-CE increase  |  Recovery  |
+ *  | Avoidance  |------------------>|   Period   |
+ *  +------------+                   +------------+
+ *            ^                            |
+ *            |                            |
+ *            +----------------------------+
+ *               Acknowledgment of packet
+ *                 sent during recovery
+ */
+enum quic_cong_state {
+	QUIC_CONG_SLOW_START,
+	QUIC_CONG_RECOVERY_PERIOD,
+	QUIC_CONG_CONGESTION_AVOIDANCE,
+};
+
+struct quic_cong {
+	/* RTT tracking */
+	u32 max_ack_delay;	/* max_ack_delay from rfc9000#section-18.2 */
+	u32 smoothed_rtt;	/* Smoothed RTT */
+	u32 latest_rtt;		/* Latest RTT sample */
+	u32 min_rtt;		/* Lowest observed RTT */
+	u32 rttvar;		/* RTT variation */
+	u32 pto;		/* Probe timeout */
+
+	/* Timing & pacing */
+	u64 pc_start_time;      /* Persistent congestion tracking timestamp */
+	u64 recovery_time;	/* Recovery period start timestamp */
+	u64 pacing_rate;	/* Packet sending speed Bytes/sec */
+	u64 pacing_time;	/* Next scheduled send timestamp (ns) */
+	u64 time;		/* Cached current timestamp */
+
+	/* Congestion window */
+	u32 max_window;		/* Max growth cap */
+	u32 min_window;		/* Min window limit */
+	u32 loss_delay;		/* Time before marking loss */
+	u32 ssthresh;		/* Slow start threshold */
+	u32 window;		/* Bytes in flight allowed */
+	u32 mss;		/* QUIC MSS (excl. UDP) */
+
+	/* Algorithm-specific */
+	const struct quic_cong_ops *ops;
+	u64 priv[8];		/* Algo private data */
+
+	u32 initial_srtt;	/* Initial smoothed RTT */
+	u8 algo;		/* Congestion control algorithm */
+
+	/* Flags & state */
+	u8 min_rtt_valid;	/* min_rtt initialized */
+	u8 is_rtt_set;		/* RTT samples exist */
+	u8 state;		/* State machine in rfc9002#section-7.3 */
+};
+
+/* Hooks for congestion control algorithms */
+struct quic_cong_ops {
+	void (*on_packet_acked)(struct quic_cong *cong, u64 time, u32 bytes,
+				s64 number);
+	void (*on_packet_lost)(struct quic_cong *cong, u64 time, u32 bytes,
+			       s64 number);
+	void (*on_process_ecn)(struct quic_cong *cong);
+	void (*on_init)(struct quic_cong *cong);
+
+	/* Optional callbacks */
+	void (*on_packet_sent)(struct quic_cong *cong, u64 time, u32 bytes,
+			       s64 number);
+	void (*on_ack_recv)(struct quic_cong *cong, u32 bytes, u64 max_rate);
+	void (*on_rtt_update)(struct quic_cong *cong);
+};
+
+static inline void quic_cong_set_mss(struct quic_cong *cong, u32 mss)
+{
+	if (cong->mss == mss)
+		return;
+
+	/* rfc9002#section-7.2: Initial and Minimum Congestion Window */
+	cong->mss = mss;
+	cong->min_window = max(min(mss * 10, 14720U), mss * 2);
+
+	if (cong->window < cong->min_window)
+		cong->window = cong->min_window;
+}
+
+static inline void *quic_cong_priv(struct quic_cong *cong)
+{
+	return (void *)cong->priv;
+}
+
+void quic_cong_on_packet_acked(struct quic_cong *cong, u64 time, u32 bytes,
+			       s64 number);
+void quic_cong_on_packet_lost(struct quic_cong *cong, u64 time, u32 bytes,
+			      s64 number);
+void quic_cong_on_process_ecn(struct quic_cong *cong);
+
+void quic_cong_on_packet_sent(struct quic_cong *cong, u64 time, u32 bytes,
+			      s64 number);
+void quic_cong_on_ack_recv(struct quic_cong *cong, u32 bytes, u64 max_rate);
+void quic_cong_rtt_update(struct quic_cong *cong, u64 time, u32 ack_delay);
+
+void quic_cong_set_srtt(struct quic_cong *cong, u32 srtt);
+void quic_cong_set_algo(struct quic_cong *cong, u8 algo);
+void quic_cong_init(struct quic_cong *cong);
diff --git a/net/quic/connid.c b/net/quic/connid.c
new file mode 100644
index 00000000000000..a6d42679ee97a6
--- /dev/null
+++ b/net/quic/connid.c
@@ -0,0 +1,271 @@
+// 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
+ *
+ * Initialization/cleanup for QUIC protocol support.
+ *
+ * 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"
+
+/* Lookup a source connection ID (scid) in the global source connection ID hash
+ * table.
+ */
+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 any connection ID in the
+ * connection ID set.
+ */
+bool quic_conn_id_token_exists(struct quic_conn_id_set *id_set, u8 *token)
+{
+	struct quic_common_conn_id *common;
+	struct quic_dest_conn_id *dcid;
+
+	dcid = (struct quic_dest_conn_id *)id_set->active;
+	if (memchr_inv(dcid->token, 0, QUIC_CONN_ID_TOKEN_LEN) &&
+	    !crypto_memneq(dcid->token, token, QUIC_CONN_ID_TOKEN_LEN))
+		return true; /* Fast path. */
+
+	list_for_each_entry(common, &id_set->head, list) {
+		dcid = (struct quic_dest_conn_id *)common;
+		if (common == id_set->active)
+			continue;
+		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;
+
+	if (!hlist_nulls_unhashed(&s_conn_id->node)) {
+		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)
+{
+	bool dest = id_set->entry_size == sizeof(struct quic_dest_conn_id);
+	struct quic_source_conn_id *s_conn_id;
+	struct quic_dest_conn_id *d_conn_id;
+	struct quic_common_conn_id *common;
+	struct quic_shash_head *head;
+	struct list_head *list;
+
+	/* 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 (dest && 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 (dest) {
+		/* 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 = 1;
+		s_conn_id = (struct quic_source_conn_id *)common;
+		s_conn_id->sk = data;
+
+		head = quic_source_conn_id_head(sock_net(s_conn_id->sk),
+						common->id.data,
+						common->id.len);
+		spin_lock_bh(&head->lock);
+		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++;
+
+		/* Increment count for consecutive following IDs. */
+		list_for_each_entry_continue(common, &id_set->head, list) {
+			if (common->number != ++number)
+				break;
+			id_set->count++;
+		}
+	}
+	return 0;
+}
+
+/* Remove consecutive connection IDs from the set with sequence numbers less
+ * than or equal to a number.
+ */
+void quic_conn_id_remove(struct quic_conn_id_set *id_set, u32 number)
+{
+	struct quic_common_conn_id *common, *tmp;
+	struct list_head *list;
+
+	/* The number must be less than the sequence number of the last
+	 * consecutive connection ID in the set.
+	 */
+	if (WARN_ON_ONCE(number >= quic_conn_id_last_number(id_set)))
+		return;
+	list = &id_set->head;
+	list_for_each_entry_safe(common, tmp, list, list) {
+		if (common->number > number)
+			break;
+		if (id_set->active == common)
+			id_set->active = tmp;
+		if (id_set->alt == common)
+			id_set->alt = tmp;
+		quic_conn_id_del(common);
+		id_set->count--;
+	}
+}
+
+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)
+		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);
+	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->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 00000000000000..abad396a6ad6a7
--- /dev/null
+++ b/net/quic/connid.h
@@ -0,0 +1,183 @@
+/* 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 */
+	u8 hashed;  /* Non-zero if stored in source_conn_id hash table */
+};
+
+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 count;      /* Current number of connection IDs in the list */
+};
+
+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 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_remove(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/crypto.c b/net/quic/crypto.c
new file mode 100644
index 00000000000000..8b254bb0556cf8
--- /dev/null
+++ b/net/quic/crypto.c
@@ -0,0 +1,1249 @@
+// 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
+ *
+ * Initialization/cleanup for QUIC protocol support.
+ *
+ * Written or modified by:
+ *    Xin Long <lucien.xin@gmail.com>
+ */
+
+#include <crypto/skcipher.h>
+#include <linux/skbuff.h>
+#include <crypto/utils.h>
+#include <crypto/aead.h>
+#include <crypto/hash.h>
+#include <linux/quic.h>
+#include <net/tls.h>
+
+#include "common.h"
+#include "crypto.h"
+
+/* HKDF-Extract. */
+static int quic_crypto_hkdf_extract(struct crypto_shash *tfm,
+				    struct quic_data *srt,
+				    struct quic_data *hash,
+				    struct quic_data *key)
+{
+	int err;
+
+	err = crypto_shash_setkey(tfm, srt->data, srt->len);
+	if (err)
+		return err;
+
+	return crypto_shash_tfm_digest(tfm, hash->data, hash->len, key->data);
+}
+
+#define QUIC_MAX_INFO_LEN	256
+
+/* HKDF-Expand-Label. */
+static int quic_crypto_hkdf_expand(struct crypto_shash *tfm,
+				   struct quic_data *srt,
+				   struct quic_data *label,
+				   struct quic_data *key)
+{
+	u8 info[QUIC_MAX_INFO_LEN], *p = info, tmp[QUIC_SECRET_LEN];
+	unsigned int i, infolen, hashlen = srt->len;
+	SHASH_DESC_ON_STACK(desc, tfm);
+	u8 LABEL[] = "tls13 ", cnt = 1;
+	const u8 *prev = NULL;
+	int err;
+
+	/* rfc8446#section-7.1:
+	 *
+	 *  HKDF-Expand-Label(Secret, Label, Context, Length) =
+	 *       HKDF-Expand(Secret, HkdfLabel, Length)
+	 *
+	 *  Where HkdfLabel is specified as:
+	 *
+	 *  struct {
+	 *      uint16 length = Length;
+	 *      opaque label<7..255> = "tls13 " + Label;
+	 *      opaque context<0..255> = Context;
+	 *  } HkdfLabel;
+	 */
+	put_unaligned_be16(key->len, p);
+	p += 2;
+	*p++ = (u8)(sizeof(LABEL) - 1 + label->len);
+	p = quic_put_data(p, LABEL, sizeof(LABEL) - 1);
+	p = quic_put_data(p, label->data, label->len);
+	*p++ = 0;
+	infolen = (unsigned int)(p - info);
+
+	err = crypto_shash_setkey(tfm, srt->data, srt->len);
+	if (err)
+		return err;
+	desc->tfm = tfm;
+
+	for (i = 0; i < key->len; i += hashlen) {
+		err = crypto_shash_init(desc);
+		if (err)
+			goto out;
+
+		if (prev) {
+			err = crypto_shash_update(desc, prev, hashlen);
+			if (err)
+				goto out;
+		}
+
+		err = crypto_shash_update(desc, info, infolen);
+		if (err)
+			goto out;
+
+		BUILD_BUG_ON(sizeof(cnt) != 1);
+		if (key->len - i < hashlen) {
+			err = crypto_shash_finup(desc, &cnt, 1, tmp);
+			if (err)
+				goto out;
+			memcpy(&key->data[i], tmp, key->len - i);
+			memzero_explicit(tmp, sizeof(tmp));
+		} else {
+			err = crypto_shash_finup(desc, &cnt, 1, &key->data[i]);
+			if (err)
+				goto out;
+		}
+		cnt++;
+		prev = &key->data[i];
+	}
+out:
+	shash_desc_zero(desc);
+	memzero_explicit(tmp, sizeof(tmp));
+	return err;
+}
+
+#define KEY_LABEL_V1		"quic key"
+#define IV_LABEL_V1		"quic iv"
+#define HP_KEY_LABEL_V1		"quic hp"
+
+#define KU_LABEL_V1		"quic ku"
+
+/* rfc9369#section-3.3.2:
+ *
+ * The labels used in rfc9001 to derive packet protection keys, header
+ * protection keys, Retry Integrity Tag keys, and key updates change from "quic
+ * key" to "quicv2 key", from "quic iv" to "quicv2 iv", from "quic hp" to
+ * "quicv2 hp", and from "quic ku" to "quicv2 ku".
+ */
+#define KEY_LABEL_V2		"quicv2 key"
+#define IV_LABEL_V2		"quicv2 iv"
+#define HP_KEY_LABEL_V2		"quicv2 hp"
+
+#define KU_LABEL_V2		"quicv2 ku"
+
+/* Packet Protection Keys. */
+static int quic_crypto_keys_derive(struct crypto_shash *tfm,
+				   struct quic_data *s, struct quic_data *k,
+				   struct quic_data *i, struct quic_data *hp_k,
+				   u32 version)
+{
+	struct quic_data hp_k_l = {HP_KEY_LABEL_V1, strlen(HP_KEY_LABEL_V1)};
+	struct quic_data k_l = {KEY_LABEL_V1, strlen(KEY_LABEL_V1)};
+	struct quic_data i_l = {IV_LABEL_V1, strlen(IV_LABEL_V1)};
+	int err;
+
+	/* rfc9001#section-5.1:
+	 *
+	 * The current encryption level secret and the label "quic key" are
+	 * input to the KDF to produce the AEAD key; the label "quic iv" is
+	 * used to derive the Initialization Vector (IV). The header protection
+	 * key uses the "quic hp" label.  Using these labels provides key
+	 * separation between QUIC and TLS.
+	 */
+	if (version == QUIC_VERSION_V2) {
+		quic_data(&hp_k_l, HP_KEY_LABEL_V2, strlen(HP_KEY_LABEL_V2));
+		quic_data(&k_l, KEY_LABEL_V2, strlen(KEY_LABEL_V2));
+		quic_data(&i_l, IV_LABEL_V2, strlen(IV_LABEL_V2));
+	}
+
+	err = quic_crypto_hkdf_expand(tfm, s, &k_l, k);
+	if (err)
+		return err;
+	err = quic_crypto_hkdf_expand(tfm, s, &i_l, i);
+	if (err)
+		return err;
+	/* Don't change hp key for key update. */
+	if (!hp_k)
+		return 0;
+
+	return quic_crypto_hkdf_expand(tfm, s, &hp_k_l, hp_k);
+}
+
+/* Derive and install reception (RX) or transmission (TX) packet protection
+ * keys for the current key phase.  This installs AEAD protection key, IV, and
+ * optionally header protection key.
+ */
+static int quic_crypto_keys_derive_and_install(struct quic_crypto *crypto,
+					       bool rx, u8 phase)
+{
+	struct quic_data srt = {}, k, iv, hp_k = {}, *hp = NULL;
+	u8 key[QUIC_KEY_LEN], hp_key[QUIC_KEY_LEN] = {};
+	u32 keylen, ivlen = QUIC_IV_LEN;
+	struct crypto_skcipher *hp_tfm;
+	struct crypto_aead *tfm;
+	int err;
+
+	keylen = crypto->cipher->keylen;
+	quic_data(&k, key, keylen);
+
+	if (rx) {
+		quic_data(&srt, crypto->rx_secret[phase],
+			  crypto->cipher->secretlen);
+		quic_data(&iv, crypto->rx_iv[phase], ivlen);
+		tfm = crypto->rx_tfm[phase];
+		hp_tfm = crypto->rx_hp_tfm;
+	} else {
+		quic_data(&srt, crypto->tx_secret[phase],
+			  crypto->cipher->secretlen);
+		quic_data(&iv, crypto->tx_iv[phase], ivlen);
+		tfm = crypto->tx_tfm[phase];
+		hp_tfm = crypto->tx_hp_tfm;
+	}
+
+	/* Only derive header protection key when not in key update. */
+	if (crypto->key_phase == phase)
+		hp = quic_data(&hp_k, hp_key, keylen);
+	err = quic_crypto_keys_derive(crypto->secret_tfm, &srt, &k, &iv, hp,
+				      crypto->version);
+	if (err)
+		goto out;
+	err = crypto_aead_setauthsize(tfm, QUIC_TAG_LEN);
+	if (err)
+		goto out;
+	err = crypto_aead_setkey(tfm, key, keylen);
+	if (err)
+		goto out;
+	if (hp) {
+		err = crypto_skcipher_setkey(hp_tfm, hp_key, keylen);
+		if (err)
+			goto out;
+	}
+out:
+	memzero_explicit(key, sizeof(key));
+	memzero_explicit(hp_key, sizeof(hp_key));
+	return err;
+}
+
+#define QUIC_CIPHER_MIN TLS_CIPHER_AES_GCM_128
+#define QUIC_CIPHER_MAX TLS_CIPHER_CHACHA20_POLY1305
+
+#define TLS_CIPHER_AES_GCM_128_SECRET_SIZE		32
+#define TLS_CIPHER_AES_GCM_256_SECRET_SIZE		48
+#define TLS_CIPHER_AES_CCM_128_SECRET_SIZE		32
+#define TLS_CIPHER_CHACHA20_POLY1305_SECRET_SIZE	32
+
+#define CIPHER_DESC(type, aead_n, skc_n, sha_n)[type - QUIC_CIPHER_MIN] = { \
+	.secretlen = type ## _SECRET_SIZE, \
+	.keylen = type ## _KEY_SIZE, \
+	.aead = aead_n, \
+	.skc = skc_n, \
+	.shash = sha_n, \
+}
+
+static const struct quic_cipher
+ciphers[QUIC_CIPHER_MAX + 1 - QUIC_CIPHER_MIN] = {
+	CIPHER_DESC(TLS_CIPHER_AES_GCM_128,
+		    "gcm(aes)", "ecb(aes)", "hmac(sha256)"),
+	CIPHER_DESC(TLS_CIPHER_AES_GCM_256,
+		    "gcm(aes)", "ecb(aes)", "hmac(sha384)"),
+	CIPHER_DESC(TLS_CIPHER_AES_CCM_128,
+		    "ccm(aes)", "ecb(aes)", "hmac(sha256)"),
+	CIPHER_DESC(TLS_CIPHER_CHACHA20_POLY1305,
+		    "rfc7539(chacha20,poly1305)", "chacha20", "hmac(sha256)"),
+};
+
+static bool quic_crypto_is_cipher_ccm(struct quic_crypto *crypto)
+{
+	return crypto->cipher_type == TLS_CIPHER_AES_CCM_128;
+}
+
+static bool quic_crypto_is_cipher_chacha(struct quic_crypto *crypto)
+{
+	return crypto->cipher_type == TLS_CIPHER_CHACHA20_POLY1305;
+}
+
+static void *quic_crypto_skcipher_mem_alloc(struct crypto_skcipher *tfm,
+					    u32 mask_size, u8 **iv,
+					    struct skcipher_request **req,
+					    gfp_t gfp)
+{
+	unsigned int iv_size, req_size;
+	unsigned int len;
+	u8 *mem;
+
+	iv_size = crypto_skcipher_ivsize(tfm);
+	req_size = sizeof(**req) + crypto_skcipher_reqsize(tfm);
+
+	len = mask_size;
+	len += iv_size;
+	len += crypto_skcipher_alignmask(tfm) &
+	       ~(crypto_tfm_ctx_alignment() - 1);
+	len = ALIGN(len, crypto_tfm_ctx_alignment());
+	len += req_size;
+
+	mem = kzalloc(len, gfp);
+	if (!mem)
+		return NULL;
+
+	*iv = (u8 *)PTR_ALIGN(mem + mask_size,
+			      crypto_skcipher_alignmask(tfm) + 1);
+	*req = (struct skcipher_request *)PTR_ALIGN(*iv + iv_size,
+						    crypto_tfm_ctx_alignment());
+
+	return (void *)mem;
+}
+
+/* Extracts and reconstructs the packet number from an incoming QUIC packet. */
+static int quic_crypto_get_number(struct sk_buff *skb)
+{
+	struct quic_skb_cb *cb = QUIC_SKB_CB(skb);
+	s64 number_max = cb->number;
+	u32 len = cb->length;
+	u8 *p;
+
+	/* rfc9000#section-17.1:
+	 *
+	 * Once header protection is removed, the packet number is decoded by
+	 * finding the packet number value that is closest to the next expected
+	 * packet. The next expected packet is the highest received packet
+	 * number plus one.
+	 */
+	p = (u8 *)quic_hdr(skb) + cb->number_offset;
+	if (!quic_get_int(&p, &len, &cb->number, cb->number_len))
+		return -EINVAL;
+	cb->number = quic_get_num(number_max, cb->number, cb->number_len);
+	return 0;
+}
+
+#define QUIC_SAMPLE_LEN		16
+
+#define QUIC_HEADER_FORM_BIT	0x80
+#define QUIC_LONG_HEADER_MASK	0x0f
+#define QUIC_SHORT_HEADER_MASK	0x1f
+
+/* Header Protection. */
+static int quic_crypto_header_protect(struct quic_crypto *crypto,
+				      struct sk_buff *skb, bool enc, gfp_t gfp)
+{
+	struct quic_skb_cb *cb = QUIC_SKB_CB(skb);
+	u8 *mask, *iv, *p, h_mask, chacha;
+	struct skcipher_request *req;
+	struct crypto_skcipher *tfm;
+	struct sk_buff *trailer;
+	struct scatterlist sg;
+	int err, i;
+
+	chacha = quic_crypto_is_cipher_chacha(crypto);
+	if (!enc) {
+		tfm = crypto->rx_hp_tfm;
+		if (cb->length < QUIC_PN_MAX_LEN + QUIC_SAMPLE_LEN)
+			return -EINVAL;
+
+		err = skb_cow_data(skb, 0, &trailer);
+		if (err < 0)
+			return err;
+	} else {
+		tfm = crypto->tx_hp_tfm;
+	}
+
+	mask = quic_crypto_skcipher_mem_alloc(tfm, QUIC_SAMPLE_LEN, &iv, &req,
+					      gfp);
+	if (!mask)
+		return -ENOMEM;
+
+	/* rfc9001#section-5.4.2: Header Protection Sample:
+	 *
+	 *   # pn_offset is the start of the Packet Number field.
+	 *   sample_offset = pn_offset + 4
+	 *
+	 *   sample = packet[sample_offset..sample_offset+sample_length]
+	 *
+	 * rfc9001#section-5.4.3: AES-Based Header Protection:
+	 *
+	 *   header_protection(hp_key, sample):
+	 *     mask = AES-ECB(hp_key, sample)
+	 *
+	 * rfc9001#section-5.4.4: ChaCha20-Based Header Protection:
+	 *
+	 *   header_protection(hp_key, sample):
+	 *     counter = sample[0..3]
+	 *     nonce = sample[4..15]
+	 *     mask = ChaCha20(hp_key, counter, nonce, {0,0,0,0,0})
+	 */
+	p = skb->data + cb->number_offset + QUIC_PN_MAX_LEN;
+	memcpy((chacha ? iv : mask), p, QUIC_SAMPLE_LEN);
+	sg_init_one(&sg, mask, QUIC_SAMPLE_LEN);
+	skcipher_request_set_tfm(req, tfm);
+	skcipher_request_set_crypt(req, &sg, &sg, QUIC_SAMPLE_LEN, iv);
+	err = crypto_skcipher_encrypt(req);
+	if (err)
+		goto out;
+
+	/* rfc9001#section-5.4.1:
+	 *
+	 * mask = header_protection(hp_key, sample)
+	 *
+	 * pn_length = (packet[0] & 0x03) + 1
+	 * if (packet[0] & 0x80) == 0x80:
+	 *    # Long header: 4 bits masked
+	 *    packet[0] ^= mask[0] & 0x0f
+	 * else:
+	 *    # Short header: 5 bits masked
+	 *    packet[0] ^= mask[0] & 0x1f
+	 *
+	 * # pn_offset is the start of the Packet Number field.
+	 * packet[pn_offset:pn_offset+pn_length] ^= mask[1:1+pn_length]
+	 */
+	p = skb->data;
+	h_mask = ((*p & QUIC_HEADER_FORM_BIT) == QUIC_HEADER_FORM_BIT) ?
+		 QUIC_LONG_HEADER_MASK : QUIC_SHORT_HEADER_MASK;
+	*p = (u8)(*p ^ (mask[0] & h_mask));
+	if (!enc) {
+		if (!quic_hdr(skb)->form)
+			cb->key_phase = quic_hdr(skb)->key;
+		cb->number_len = quic_hdr(skb)->pnl + 1;
+	}
+	p += cb->number_offset;
+	for (i = 1; i <= cb->number_len; i++)
+		*p++ ^= mask[i];
+
+	if (!enc)
+		err = quic_crypto_get_number(skb);
+out:
+	kfree_sensitive(mask);
+	return err;
+}
+
+static void *quic_crypto_aead_mem_alloc(struct crypto_aead *tfm, u32 ctx_size,
+					u8 **iv, struct aead_request **req,
+					struct scatterlist **sg, u32 nsg,
+					gfp_t gfp)
+{
+	unsigned int iv_size, req_size;
+	unsigned int len;
+	u8 *mem;
+
+	iv_size = crypto_aead_ivsize(tfm);
+	req_size = sizeof(**req) + crypto_aead_reqsize(tfm);
+
+	len = ctx_size;
+	len += iv_size;
+	len += crypto_aead_alignmask(tfm) & ~(crypto_tfm_ctx_alignment() - 1);
+	len = ALIGN(len, crypto_tfm_ctx_alignment());
+	len += req_size;
+	len = ALIGN(len, __alignof__(struct scatterlist));
+	len += nsg * sizeof(**sg);
+
+	mem = kzalloc(len, gfp);
+	if (!mem)
+		return NULL;
+
+	*iv = (u8 *)PTR_ALIGN(mem + ctx_size, crypto_aead_alignmask(tfm) + 1);
+	*req = (struct aead_request *)PTR_ALIGN(*iv + iv_size,
+						crypto_tfm_ctx_alignment());
+	*sg = (struct scatterlist *)PTR_ALIGN((u8 *)*req + req_size,
+					      __alignof__(struct scatterlist));
+
+	return (void *)mem;
+}
+
+static void quic_crypto_done(void *data, int err)
+{
+	struct sk_buff *skb = data;
+	struct quic_crypto *crypto;
+	struct quic_skb_cb *cb;
+
+	if (err == -EINPROGRESS)
+		return;
+
+	cb = QUIC_SKB_CB(skb);
+	crypto = *(struct quic_crypto **)cb->crypto_ctx;
+	atomic_dec(&crypto->async_pending[cb->key_phase]);
+
+	kfree_sensitive(cb->crypto_ctx);
+	cb->crypto_done(skb, err);
+}
+
+/* AEAD Usage. */
+static int quic_crypto_payload_protect(struct quic_crypto *crypto,
+				       struct sk_buff *skb, bool enc, gfp_t gfp)
+{
+	u8 *base_iv, *iv, i, nonce[QUIC_IV_LEN], ccm, phase;
+	struct quic_skb_cb *cb = QUIC_SKB_CB(skb);
+	u32 len, hlen, sglen, nsg;
+	struct aead_request *req;
+	struct crypto_aead *tfm;
+	struct sk_buff *trailer;
+	struct scatterlist *sg;
+	void *ctx;
+	__be64 n;
+	int err;
+
+	ccm = quic_crypto_is_cipher_ccm(crypto);
+	phase = cb->key_phase;
+	hlen = cb->number_offset + cb->number_len;
+	if (enc) {
+		tfm = crypto->tx_tfm[phase];
+		base_iv = crypto->tx_iv[phase];
+		len = skb->len;
+		err = skb_cow_data(skb, QUIC_TAG_LEN, &trailer);
+		if (err < 0)
+			return err;
+		pskb_put(skb, trailer, QUIC_TAG_LEN);
+		if (!quic_hdr(skb)->form)
+			quic_hdr(skb)->key = phase;
+		sglen = skb->len;
+		nsg = (u32)err;
+	} else {
+		tfm = crypto->rx_tfm[phase];
+		base_iv = crypto->rx_iv[phase];
+		len = cb->length + cb->number_offset;
+		if (len - hlen < QUIC_TAG_LEN)
+			return -EINVAL;
+		sglen = len;
+		nsg = 1;
+	}
+
+	ctx = quic_crypto_aead_mem_alloc(tfm, sizeof(void *), &iv, &req, &sg,
+					 nsg, gfp);
+	if (!ctx)
+		return -ENOMEM;
+
+	sg_init_table(sg, nsg);
+	err = skb_to_sgvec(skb, sg, 0, sglen);
+	if (err < 0)
+		goto out;
+
+	/* rfc9001#section-5.3:
+	 *
+	 * The associated data, A, for the AEAD is the contents of the QUIC
+	 * header, starting from the first byte of either the short or long
+	 * header, up to and including the unprotected packet number.
+	 *
+	 * The nonce, N, is formed by combining the packet protection IV with
+	 * the packet number.  The 62 bits of the reconstructed QUIC packet
+	 * number in network byte order are left-padded with zeros to the size
+	 * of the IV. The exclusive OR of the padded packet number and the IV
+	 * forms the AEAD nonce.
+	 */
+	memcpy(nonce, base_iv, QUIC_IV_LEN);
+	n = cpu_to_be64(cb->number);
+	for (i = 0; i < sizeof(n); i++)
+		nonce[QUIC_IV_LEN - sizeof(n) + i] ^= ((u8 *)&n)[i];
+
+	/* For CCM based ciphers, first byte of IV is a constant. */
+	iv[0] = TLS_AES_CCM_IV_B0_BYTE;
+	memcpy(&iv[ccm], nonce, QUIC_IV_LEN);
+	aead_request_set_tfm(req, tfm);
+	aead_request_set_ad(req, hlen);
+	aead_request_set_crypt(req, sg, sg, len - hlen, iv);
+	if (cb->sync) {
+		DECLARE_CRYPTO_WAIT(wait);
+
+		aead_request_set_callback(req, CRYPTO_TFM_REQ_MAY_BACKLOG,
+					  crypto_req_done, &wait);
+		err = enc ? crypto_aead_encrypt(req) : crypto_aead_decrypt(req);
+		if (err == -EINPROGRESS || err == -EBUSY)
+			err = crypto_wait_req(err, &wait);
+		goto out;
+	}
+
+	aead_request_set_callback(req, CRYPTO_TFM_REQ_MAY_BACKLOG,
+				  quic_crypto_done, skb);
+	*(struct quic_crypto **)ctx = crypto;
+	cb->crypto_ctx = ctx; /* Async free context for quic_crypto_done() */
+	err = enc ? crypto_aead_encrypt(req) : crypto_aead_decrypt(req);
+	if (err == -EINPROGRESS || err == -EBUSY) {
+		atomic_inc(&crypto->async_pending[phase]);
+		memzero_explicit(nonce, sizeof(nonce));
+		return -EINPROGRESS;
+	}
+
+out:
+	kfree_sensitive(ctx);
+	memzero_explicit(nonce, sizeof(nonce));
+	return err;
+}
+
+/* Encrypts a QUIC packet before transmission.  This function performs AEAD
+ * encryption of the packet payload and applies header protection. It handles
+ * key phase tracking and key update timing.
+ *
+ * Return: 0 on success, or a negative error code.
+ */
+int quic_crypto_encrypt(struct quic_crypto *crypto, struct sk_buff *skb,
+			gfp_t gfp)
+{
+	struct quic_skb_cb *cb = QUIC_SKB_CB(skb);
+	int err;
+
+	cb->key_phase = crypto->key_phase;
+	/* Packet payload is already encrypted (e.g., resumed from async),
+	 * proceed to header protection only.
+	 */
+	if (cb->resume)
+		goto out;
+
+	/* If a key update is pending and this is the first packet using the
+	 * new key, save the current time. Later used to clear old keys after
+	 * some time has passed (see quic_crypto_decrypt()).
+	 */
+	if (crypto->key_pending && !crypto->key_update_send_time)
+		crypto->key_update_send_time = quic_ktime_get_us();
+
+	err = quic_crypto_payload_protect(crypto, skb, true, gfp);
+	if (err)
+		return err;
+out:
+	return quic_crypto_header_protect(crypto, skb, true, gfp);
+}
+
+/* Decrypts a QUIC packet after reception.  This function removes header
+ * protection, decrypts the payload, and processes any key updates if the key
+ * phase bit changes.
+ *
+ * Return: 0 on success, or a negative error code.
+ */
+int quic_crypto_decrypt(struct quic_crypto *crypto, struct sk_buff *skb,
+			gfp_t gfp)
+{
+	struct quic_skb_cb *cb = QUIC_SKB_CB(skb);
+	int err = 0;
+	u64 time;
+	u8 phase;
+
+	/* Payload was decrypted asynchronously.  Proceed with parsing packet
+	 * number and key phase.
+	 */
+	if (cb->resume) {
+		err = quic_crypto_get_number(skb);
+		if (err)
+			return err;
+		goto out;
+	}
+	if (!cb->number_len) { /* Packet header not yet decrypted. */
+		err = quic_crypto_header_protect(crypto, skb, false, gfp);
+		if (err) {
+			pr_debug("%s: hd decrypt err %d\n", __func__, err);
+			return err;
+		}
+	}
+
+	/* rfc9001#section-6:
+	 *
+	 * The Key Phase bit allows a recipient to detect a change in keying
+	 * material without needing to receive the first packet that triggered
+	 * the change. An endpoint that notices a changed Key Phase bit updates
+	 * keys and decrypts the packet that contains the changed value.
+	 */
+	phase = cb->key_phase;
+	if (phase != crypto->key_phase && !crypto->key_pending) {
+		if (!crypto->send_ready) /* Not ready for key update. */
+			return -EINVAL;
+		if (!cb->backlog) /* Key update requires process context. */
+			return -EKEYREVOKED;
+		/* Cannot do key update while async crypto is in progress. */
+		if (unlikely(atomic_read(&crypto->async_pending[phase])))
+			return -EBUSY;
+		err = quic_crypto_key_update(crypto); /* Perform key update. */
+		if (err) {
+			cb->errcode = QUIC_TRANSPORT_ERROR_KEY_UPDATE;
+			return err;
+		}
+		cb->sync = 1;
+		cb->key_update = 1; /* Mark packet as triggering key update. */
+	}
+
+	err = quic_crypto_payload_protect(crypto, skb, false, gfp);
+	if (err) {
+		if (err == -EINPROGRESS)
+			return err;
+		/* When using the old keys can not decrypt the packets, the
+		 * peer might start another key_update. Thus, clear the last
+		 * key_pending so that next packets will trigger the new
+		 * key-update.
+		 */
+		if (crypto->key_pending && phase != crypto->key_phase) {
+			crypto->key_pending = 0;
+			crypto->key_update_time = 0;
+			crypto->key_update_send_time = 0;
+		}
+		return err;
+	}
+
+out:
+	/* rfc9001#section-6.2:
+	 *
+	 * If a packet is successfully processed using the next key and IV,
+	 * then the peer has initiated a key update.
+	 */
+	if (cb->key_update) {
+		crypto->key_pending = 1;
+		crypto->key_derived = 0;
+		crypto->key_phase = !crypto->key_phase;
+	}
+	/* rfc9001#section-6.1:
+	 *
+	 * An endpoint MUST retain old keys until it has successfully
+	 * unprotected a packet sent using the new keys. An endpoint SHOULD
+	 * retain old keys for some time after unprotecting a packet sent using
+	 * the new keys.
+	 */
+	if (crypto->key_pending && cb->key_phase == crypto->key_phase) {
+		time = crypto->key_update_send_time;
+		if (time &&
+		    quic_ktime_get_us() - time >= crypto->key_update_time) {
+			crypto->key_pending = 0;
+			crypto->key_update_time = 0;
+			crypto->key_update_send_time = 0;
+		}
+	}
+	return err;
+}
+
+int quic_crypto_set_cipher(struct quic_crypto *crypto, u32 type)
+{
+	const struct quic_cipher *cipher;
+	void *tfm;
+	int err;
+
+	if (type < QUIC_CIPHER_MIN || type > QUIC_CIPHER_MAX)
+		return -EINVAL;
+
+	cipher = &ciphers[type - QUIC_CIPHER_MIN];
+	tfm = crypto_alloc_shash(cipher->shash, 0, 0);
+	if (IS_ERR(tfm))
+		return PTR_ERR(tfm);
+	crypto->secret_tfm = tfm;
+
+	/* Allocate AEAD and HP transform for each RX key phase. */
+	tfm = crypto_alloc_aead(cipher->aead, 0, 0);
+	if (IS_ERR(tfm)) {
+		err = PTR_ERR(tfm);
+		goto err;
+	}
+	crypto->rx_tfm[0] = tfm;
+	tfm = crypto_alloc_aead(cipher->aead, 0, 0);
+	if (IS_ERR(tfm)) {
+		err = PTR_ERR(tfm);
+		goto err;
+	}
+	crypto->rx_tfm[1] = tfm;
+	tfm = crypto_alloc_sync_skcipher(cipher->skc, 0, 0);
+	if (IS_ERR(tfm)) {
+		err = PTR_ERR(tfm);
+		goto err;
+	}
+	crypto->rx_hp_tfm = tfm;
+
+	/* Allocate AEAD and HP transform for each TX key phase. */
+	tfm = crypto_alloc_aead(cipher->aead, 0, 0);
+	if (IS_ERR(tfm)) {
+		err = PTR_ERR(tfm);
+		goto err;
+	}
+	crypto->tx_tfm[0] = tfm;
+	tfm = crypto_alloc_aead(cipher->aead, 0, 0);
+	if (IS_ERR(tfm)) {
+		err = PTR_ERR(tfm);
+		goto err;
+	}
+	crypto->tx_tfm[1] = tfm;
+	tfm = crypto_alloc_sync_skcipher(cipher->skc, 0, 0);
+	if (IS_ERR(tfm)) {
+		err = PTR_ERR(tfm);
+		goto err;
+	}
+	crypto->tx_hp_tfm = tfm;
+
+	crypto->cipher = cipher;
+	crypto->cipher_type = type;
+	return 0;
+err:
+	quic_crypto_free(crypto);
+	return err;
+}
+
+int quic_crypto_set_secret(struct quic_crypto *crypto,
+			   struct quic_crypto_secret *srt, u32 version)
+{
+	const struct quic_cipher *cipher;
+	u8 phase = crypto->key_phase;
+	int err;
+
+	/* If no cipher has been initialized yet, set it up. */
+	if (!crypto->cipher) {
+		err = quic_crypto_set_cipher(crypto, srt->type);
+		if (err)
+			return err;
+	}
+	cipher = crypto->cipher;
+
+	/* Handle RX path setup. */
+	if (!srt->send) {
+		crypto->version = version;
+		memcpy(crypto->rx_secret[phase], srt->secret,
+		       cipher->secretlen);
+		err = quic_crypto_keys_derive_and_install(crypto, true, phase);
+		if (err)
+			return err;
+		crypto->recv_ready = 1;
+		return 0;
+	}
+
+	/* Handle TX path setup. */
+	crypto->version = version;
+	memcpy(crypto->tx_secret[phase], srt->secret, cipher->secretlen);
+	err = quic_crypto_keys_derive_and_install(crypto, false, phase);
+	if (err)
+		return err;
+	crypto->send_ready = 1;
+	return 0;
+}
+
+/* Save token secret in Initial TX secret (phase 1) for token generation. */
+int quic_crypto_set_token_secret(struct quic_crypto *crypto)
+{
+	/* Reuse TX AEAD (phase 1) in Initial crypto. */
+	u8 key[TLS_CIPHER_AES_GCM_128_KEY_SIZE], *srt = crypto->tx_secret[1];
+	struct crypto_aead *tfm = crypto->tx_tfm[1];
+	struct quic_data s = {}, k, i;
+	int err;
+
+	if (!memchr_inv(srt, 0, TLS_CIPHER_AES_GCM_128_SECRET_SIZE))
+		get_random_bytes(srt, TLS_CIPHER_AES_GCM_128_SECRET_SIZE);
+
+	quic_data(&s, srt, TLS_CIPHER_AES_GCM_128_SECRET_SIZE);
+	quic_data(&k, key, TLS_CIPHER_AES_GCM_128_KEY_SIZE);
+	quic_data(&i, crypto->tx_iv[1], QUIC_IV_LEN);
+	err = quic_crypto_keys_derive(crypto->secret_tfm, &s, &k, &i, NULL,
+				      QUIC_VERSION_V1);
+	if (err)
+		goto out;
+	err = crypto_aead_setauthsize(tfm, QUIC_TAG_LEN);
+	if (err)
+		goto out;
+	err = crypto_aead_setkey(tfm, key, TLS_CIPHER_AES_GCM_128_KEY_SIZE);
+out:
+	memzero_explicit(key, sizeof(key));
+	return err;
+}
+
+/* Initiating a Key Update. */
+int quic_crypto_key_update(struct quic_crypto *crypto)
+{
+	struct quic_data l = {KU_LABEL_V1, strlen(KU_LABEL_V1)};
+	u8 phase = crypto->key_phase;
+	struct quic_data k, srt;
+	u32 secret_len;
+	int err;
+
+	if (crypto->key_pending || !crypto->recv_ready)
+		return -EINVAL;
+	if (crypto->key_derived)
+		return 0;
+
+	/* rfc9001#section-6.1:
+	 *
+	 * Endpoints maintain separate read and write secrets for packet
+	 * protection. An endpoint initiates a key update by updating its
+	 * packet protection write secret and using that to protect new
+	 * packets. The endpoint creates a new write secret from the existing
+	 * write secret. This uses the KDF function provided by TLS with a
+	 * label of "quic ku". The corresponding key and IV are created from
+	 * that secret. The header protection key is not updated.
+	 *
+	 * For example, to update write keys with TLS 1.3, HKDF-Expand-Label is
+	 * used as:
+	 *   secret_<n+1> = HKDF-Expand-Label(secret_<n>, "quic ku",
+	 *                                    "", Hash.length)
+	 */
+	secret_len = crypto->cipher->secretlen;
+	if (crypto->version == QUIC_VERSION_V2)
+		quic_data(&l, KU_LABEL_V2, strlen(KU_LABEL_V2));
+
+	quic_data(&srt, crypto->tx_secret[phase], secret_len);
+	quic_data(&k, crypto->tx_secret[!phase], secret_len);
+	err = quic_crypto_hkdf_expand(crypto->secret_tfm, &srt, &l, &k);
+	if (err)
+		return err;
+	err = quic_crypto_keys_derive_and_install(crypto, false, !phase);
+	if (err)
+		return err;
+
+	quic_data(&srt, crypto->rx_secret[phase], secret_len);
+	quic_data(&k, crypto->rx_secret[!phase], secret_len);
+	err = quic_crypto_hkdf_expand(crypto->secret_tfm, &srt, &l, &k);
+	if (err)
+		return err;
+	err = quic_crypto_keys_derive_and_install(crypto, true, !phase);
+	if (err)
+		return err;
+
+	crypto->key_derived = 1;
+	return 0;
+}
+
+void quic_crypto_free(struct quic_crypto *crypto)
+{
+	if (crypto->rx_tfm[0])
+		crypto_free_aead(crypto->rx_tfm[0]);
+	if (crypto->rx_tfm[1])
+		crypto_free_aead(crypto->rx_tfm[1]);
+	if (crypto->tx_tfm[0])
+		crypto_free_aead(crypto->tx_tfm[0]);
+	if (crypto->tx_tfm[1])
+		crypto_free_aead(crypto->tx_tfm[1]);
+	if (crypto->secret_tfm)
+		crypto_free_shash(crypto->secret_tfm);
+	if (crypto->rx_hp_tfm)
+		crypto_free_skcipher(crypto->rx_hp_tfm);
+	if (crypto->tx_hp_tfm)
+		crypto_free_skcipher(crypto->tx_hp_tfm);
+
+	memzero_explicit(crypto, offsetof(struct quic_crypto, send_offset));
+}
+
+#define QUIC_INITIAL_SALT_V1 \
+	"\x38\x76\x2c\xf7\xf5\x59\x34\xb3\x4d\x17" \
+	"\x9a\xe6\xa4\xc8\x0c\xad\xcc\xbb\x7f\x0a"
+
+#define QUIC_INITIAL_SALT_V2 \
+	"\x0d\xed\xe3\xde\xf7\x00\xa6\xdb\x81\x93" \
+	"\x81\xbe\x6e\x26\x9d\xcb\xf9\xbd\x2e\xd9"
+
+#define QUIC_INITIAL_SALT_LEN	20
+
+/* Initial Secrets. */
+int quic_crypto_initial_keys_install(struct quic_crypto *crypto,
+				     struct quic_conn_id *conn_id,
+				     u32 version, bool is_serv)
+{
+	u8 secret[TLS_CIPHER_AES_GCM_128_SECRET_SIZE];
+	struct quic_data salt, s, k, l, dcid;
+	struct quic_crypto_secret srt = {};
+	char *tl, *rl, *sal;
+	int err;
+
+	/* rfc9001#section-5.2:
+	 *
+	 * The secret used by clients to construct Initial packets uses the PRK
+	 * and the label "client in" as input to the HKDF-Expand-Label function
+	 * from TLS [TLS13] to produce a 32-byte secret. Packets constructed by
+	 * the server use the same process with the label "server in". The hash
+	 * function for HKDF when deriving initial secrets and keys is SHA-256
+	 * [SHA].
+	 *
+	 * This process in pseudocode is:
+	 *
+	 *   initial_salt = 0x38762cf7f55934b34d179ae6a4c80cadccbb7f0a
+	 *   initial_secret = HKDF-Extract(initial_salt,
+	 *                                 client_dst_connection_id)
+	 *
+	 *   client_initial_secret = HKDF-Expand-Label(initial_secret,
+	 *                                             "client in", "",
+	 *                                             Hash.length)
+	 *   server_initial_secret = HKDF-Expand-Label(initial_secret,
+	 *                                             "server in", "",
+	 *                                             Hash.length)
+	 */
+	if (is_serv) {
+		rl = "client in";
+		tl = "server in";
+	} else {
+		tl = "client in";
+		rl = "server in";
+	}
+	sal = QUIC_INITIAL_SALT_V1;
+	if (version == QUIC_VERSION_V2)
+		sal = QUIC_INITIAL_SALT_V2;
+	quic_data(&salt, sal, QUIC_INITIAL_SALT_LEN);
+	quic_data(&dcid, conn_id->data, conn_id->len);
+	quic_data(&s, secret, TLS_CIPHER_AES_GCM_128_SECRET_SIZE);
+	err = quic_crypto_hkdf_extract(crypto->secret_tfm, &salt, &dcid, &s);
+	if (err)
+		goto out;
+
+	quic_data(&l, tl, strlen(tl));
+	quic_data(&k, srt.secret, TLS_CIPHER_AES_GCM_128_SECRET_SIZE);
+	srt.type = TLS_CIPHER_AES_GCM_128;
+	srt.send = 1;
+	err = quic_crypto_hkdf_expand(crypto->secret_tfm, &s, &l, &k);
+	if (err)
+		goto out;
+	err = quic_crypto_set_secret(crypto, &srt, version);
+	if (err)
+		goto out;
+
+	quic_data(&l, rl, strlen(rl));
+	quic_data(&k, srt.secret, TLS_CIPHER_AES_GCM_128_SECRET_SIZE);
+	srt.type = TLS_CIPHER_AES_GCM_128;
+	srt.send = 0;
+	err = quic_crypto_hkdf_expand(crypto->secret_tfm, &s, &l, &k);
+	if (err)
+		goto out;
+	err = quic_crypto_set_secret(crypto, &srt, version);
+out:
+	memzero_explicit(secret, sizeof(secret));
+	memzero_explicit(&srt, sizeof(srt));
+	return err;
+}
+
+#define QUIC_RETRY_KEY_V1 \
+	"\xbe\x0c\x69\x0b\x9f\x66\x57\x5a\x1d\x76\x6b\x54\xe3\x68\xc8\x4e"
+#define QUIC_RETRY_KEY_V2 \
+	"\x8f\xb4\xb0\x1b\x56\xac\x48\xe2\x60\xfb\xcb\xce\xad\x7c\xcc\x92"
+
+#define QUIC_RETRY_NONCE_V1 "\x46\x15\x99\xd3\x5d\x63\x2b\xf2\x23\x98\x25\xbb"
+#define QUIC_RETRY_NONCE_V2 "\xd8\x69\x69\xbc\x2d\x7c\x6d\x99\x90\xef\xb0\x4a"
+
+/* Retry Packet Integrity. */
+int quic_crypto_get_retry_tag(struct quic_crypto *crypto, struct sk_buff *skb,
+			      struct quic_conn_id *odcid, u32 version, u8 *tag)
+{
+	/* Reuse RX AEAD (phase 1) in Initial crypto. */
+	struct crypto_aead *tfm = crypto->rx_tfm[1];
+	u8 *pseudo_retry, *p, *iv, *key;
+	DECLARE_CRYPTO_WAIT(wait);
+	struct aead_request *req;
+	struct scatterlist *sg;
+	u32 plen;
+	int err;
+
+	/* The caller must ensure skb->len > QUIC_TAG_LEN. */
+	if (skb->len <= QUIC_TAG_LEN)
+		return -EINVAL;
+
+	/* rfc9001#section-5.8:
+	 *
+	 * The Retry Integrity Tag is a 128-bit field that is computed as the
+	 * output of AEAD_AES_128_GCM used with the following inputs:
+	 *
+	 * - The secret key, K, is 128 bits equal to
+	 *   0xbe0c690b9f66575a1d766b54e368c84e.
+	 * - The nonce, N, is 96 bits equal to 0x461599d35d632bf2239825bb.
+	 * - The plaintext, P, is empty.
+	 * - The associated data, A, is the contents of the Retry
+	 *   Pseudo-Packet,
+	 *
+	 * The Retry Pseudo-Packet is not sent over the wire. It is computed by
+	 * taking the transmitted Retry packet, removing the Retry Integrity
+	 * Tag, and prepending the two following fields: ODCID Length +
+	 * Original Destination Connection ID (ODCID).
+	 */
+	err = crypto_aead_setauthsize(tfm, QUIC_TAG_LEN);
+	if (err)
+		return err;
+	key = QUIC_RETRY_KEY_V1;
+	if (version == QUIC_VERSION_V2)
+		key = QUIC_RETRY_KEY_V2;
+	err = crypto_aead_setkey(tfm, key, TLS_CIPHER_AES_GCM_128_KEY_SIZE);
+	if (err)
+		return err;
+
+	plen = 1 + odcid->len + skb->len - QUIC_TAG_LEN;
+	pseudo_retry = quic_crypto_aead_mem_alloc(tfm, plen + QUIC_TAG_LEN, &iv,
+						  &req, &sg, 1, GFP_KERNEL);
+	if (!pseudo_retry)
+		return -ENOMEM;
+
+	p = pseudo_retry;
+	p = quic_put_int(p, odcid->len, 1);
+	p = quic_put_data(p, odcid->data, odcid->len);
+	p = quic_put_data(p, skb->data, skb->len - QUIC_TAG_LEN);
+	sg_init_one(sg, pseudo_retry, plen + QUIC_TAG_LEN);
+
+	memcpy(iv, QUIC_RETRY_NONCE_V1, QUIC_IV_LEN);
+	if (version == QUIC_VERSION_V2)
+		memcpy(iv, QUIC_RETRY_NONCE_V2, QUIC_IV_LEN);
+	aead_request_set_tfm(req, tfm);
+	aead_request_set_ad(req, plen);
+	aead_request_set_crypt(req, sg, sg, 0, iv);
+	aead_request_set_callback(req, CRYPTO_TFM_REQ_MAY_BACKLOG,
+				  crypto_req_done, &wait);
+	err = crypto_aead_encrypt(req);
+	if (err == -EINPROGRESS || err == -EBUSY)
+		err = crypto_wait_req(err, &wait);
+	if (!err)
+		memcpy(tag, p, QUIC_TAG_LEN);
+
+	kfree_sensitive(pseudo_retry);
+	return err;
+}
+
+/* Derives a key and IV using HKDF, configures the AEAD transform and performs
+ * AEAD encryption/decryption for the provided token.
+ */
+static int quic_crypto_token_protect(struct quic_crypto *crypto, u8 *token,
+				     u32 len, u32 adlen, bool enc)
+{
+	/* Reuse TX AEAD (phase 1) in Initial crypto. */
+	struct crypto_aead *tfm = crypto->tx_tfm[1];
+	u32 extra = enc ? QUIC_TAG_LEN : 0, tslen;
+	DECLARE_CRYPTO_WAIT(wait);
+	struct aead_request *req;
+	struct scatterlist *sg;
+	void *ctx = NULL;
+	u8 *nonce, *p, i;
+	__be64 n;
+	int err;
+	u64 ts;
+
+	ctx = quic_crypto_aead_mem_alloc(tfm, 0, &nonce, &req, &sg, 1,
+					 GFP_KERNEL);
+	if (!ctx) {
+		err = -ENOMEM;
+		goto out;
+	}
+	memcpy(nonce, crypto->tx_iv[1], QUIC_IV_LEN);
+
+	tslen = sizeof(ts);
+	p = token + adlen - tslen;
+	quic_get_int(&p, &tslen, &ts, tslen);
+
+	n = cpu_to_be64(ts);
+	for (i = 0; i < sizeof(n); i++)
+		nonce[QUIC_IV_LEN - sizeof(n) + i] ^= ((u8 *)&n)[i];
+
+	sg_init_one(sg, token, len);
+	aead_request_set_tfm(req, tfm);
+	aead_request_set_ad(req, adlen);
+	aead_request_set_crypt(req, sg, sg, len - adlen - extra, nonce);
+	aead_request_set_callback(req, CRYPTO_TFM_REQ_MAY_BACKLOG,
+				  crypto_req_done, &wait);
+	err = enc ? crypto_aead_encrypt(req) : crypto_aead_decrypt(req);
+	if (err == -EINPROGRESS || err == -EBUSY)
+		err = crypto_wait_req(err, &wait);
+
+out:
+	kfree_sensitive(ctx);
+	return err;
+}
+
+/* Generate a token for Retry or address validation.
+ *
+ * Builds a token with the format: [flag][client address][timestamp][original
+ * DCID][auth tag]
+ *
+ * Encrypts the token (excluding the first flag byte) using AES-GCM with a key
+ * and IV derived via HKDF. The original DCID is stored to be recovered later
+ * from a Client Initial packet.  Ensures the token is bound to the client
+ * address and time, preventing reuse or tampering.
+ *
+ * Returns 0 on success or a negative error code on failure.
+ */
+int quic_crypto_generate_token(struct quic_crypto *crypto, void *addr,
+			       u32 addrlen, struct quic_conn_id *conn_id,
+			       u8 *token, u32 *tlen)
+{
+	u8 *token_buf, *p, flag = *token;
+	u64 ts = quic_ktime_get_us();
+	u32 tslen = sizeof(ts);
+	int err, len;
+
+	len = sizeof(flag) + addrlen + tslen + conn_id->len + QUIC_TAG_LEN;
+	token_buf = kmalloc(len, GFP_KERNEL);
+	if (!token_buf)
+		return -ENOMEM;
+
+	p = token_buf;
+	p = quic_put_int(p, flag, sizeof(flag));
+	p = quic_put_data(p, addr, addrlen);
+	p = quic_put_int(p, ts, tslen);
+	quic_put_data(p, conn_id->data, conn_id->len);
+
+	err = quic_crypto_token_protect(crypto, token_buf, len,
+					sizeof(flag) + addrlen + tslen, true);
+	if (err)
+		goto out;
+
+	memcpy(token, token_buf, len);
+	*tlen = len;
+out:
+	kfree(token_buf);
+	return err;
+}
+
+/* Validate a Retry or address validation token.
+ *
+ * Decrypts the token using derived key and IV. Checks that the decrypted
+ * address matches the provided address, validates the embedded timestamp
+ * against current time with a version-specific timeout. If applicable, it
+ * extracts and returns the original destination connection ID (ODCID) for
+ * Retry packets.
+ *
+ * Returns 0 if the token is valid, -EINVAL if invalid, or another negative
+ * error code.
+ */
+int quic_crypto_verify_token(struct quic_crypto *crypto, void *addr,
+			     u32 addrlen, struct quic_conn_id *conn_id,
+			     u8 *token, u32 len)
+{
+	u64 t, ts = quic_ktime_get_us(), timeout = QUIC_TOKEN_TIMEOUT_RETRY;
+	u8 *token_buf, *p, flag;
+	u32 tslen = sizeof(ts);
+	int err;
+
+	if (len < sizeof(flag) + addrlen + tslen + QUIC_TAG_LEN)
+		return -EINVAL;
+	token_buf = kmemdup(token, len, GFP_KERNEL);
+	if (!token_buf)
+		return -ENOMEM;
+
+	err = quic_crypto_token_protect(crypto, token_buf, len,
+					sizeof(flag) + addrlen + tslen, false);
+	if (err)
+		goto out;
+
+	err = -EINVAL;
+	p = token_buf;
+	flag = *p++;
+	len -= sizeof(flag);
+	if (crypto_memneq(p, addr, addrlen))
+		goto out;
+
+	p += addrlen;
+	len -= addrlen;
+	if (flag == QUIC_TOKEN_FLAG_REGULAR)
+		timeout = QUIC_TOKEN_TIMEOUT_REGULAR;
+	if (!quic_get_int(&p, &len, &t, tslen) || t > ts || ts - t > timeout)
+		goto out;
+
+	len -= QUIC_TAG_LEN;
+	if (len > QUIC_CONN_ID_MAX_LEN)
+		goto out;
+
+	if (flag == QUIC_TOKEN_FLAG_RETRY)
+		quic_conn_id_update(conn_id, p, len);
+	err = 0;
+out:
+	kfree(token_buf);
+	return err;
+}
+
+/* Derive a secret using HKDF-Extract and HKDF-Expand with the given label.
+ * Used to generate a stateless reset token or session resumption master key.
+ */
+int quic_crypto_derive_secret(struct quic_crypto *crypto, void *data, u32 len,
+			      char *label, u8 *srt, u32 srt_len)
+{
+	struct crypto_shash *tfm = crypto->secret_tfm;
+	u8 secret[TLS_CIPHER_AES_GCM_128_SECRET_SIZE];
+	struct quic_data salt, s, l, k;
+	int err;
+
+	quic_data(&salt, data, len);
+	quic_data(&k, crypto->tx_secret[1], TLS_CIPHER_AES_GCM_128_SECRET_SIZE);
+	quic_data(&s, secret, TLS_CIPHER_AES_GCM_128_SECRET_SIZE);
+	err = quic_crypto_hkdf_extract(tfm, &salt, &k, &s);
+	if (err)
+		goto out;
+
+	quic_data(&l, label, strlen(label));
+	quic_data(&k, srt, srt_len);
+	err = quic_crypto_hkdf_expand(tfm, &s, &l, &k);
+out:
+	memzero_explicit(secret, sizeof(secret));
+	return err;
+}
diff --git a/net/quic/crypto.h b/net/quic/crypto.h
new file mode 100644
index 00000000000000..77281a824f721c
--- /dev/null
+++ b/net/quic/crypto.h
@@ -0,0 +1,88 @@
+/* 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_TAG_LEN	16
+#define QUIC_IV_LEN	12
+#define QUIC_KEY_LEN	32
+#define QUIC_SECRET_LEN	48
+
+#define QUIC_TOKEN_FLAG_REGULAR		0
+#define QUIC_TOKEN_FLAG_RETRY		1
+#define QUIC_TOKEN_TIMEOUT_RETRY	3000000
+#define QUIC_TOKEN_TIMEOUT_REGULAR	600000000
+
+struct quic_cipher {
+	u32 secretlen; /* Length of the traffic secret */
+	u32 keylen;    /* Length of the AEAD key */
+
+	char *shash; /* Name of hash algorithm used for key derivation */
+	char *aead;  /* Name of AEAD algorithm used for payload en/decryption */
+	char *skc;   /* Name of cipher algorithm used for header protection */
+};
+
+struct quic_crypto {
+	struct crypto_skcipher *tx_hp_tfm; /* TX header protection tfm */
+	struct crypto_skcipher *rx_hp_tfm; /* RX header protection tfm */
+	struct crypto_shash *secret_tfm;   /* Key derivation (HKDF) tfm */
+	struct crypto_aead *tx_tfm[2]; /* AEAD tfm for TX (key phase 0 and 1) */
+	struct crypto_aead *rx_tfm[2]; /* AEAD tfm for RX (key phase 0 and 1) */
+
+	const struct quic_cipher *cipher;  /* Cipher info (selected cipher) */
+	u32 cipher_type; /* Cipher suite (e.g., AES_GCM_128, etc.) */
+
+	u8 tx_secret[2][QUIC_SECRET_LEN]; /* TX secret (key phase 0 and 1) */
+	u8 rx_secret[2][QUIC_SECRET_LEN]; /* RX secret (key phase 0 and 1) */
+	u8 tx_iv[2][QUIC_IV_LEN];      /* IVs for TX (key phase 0 and 1) */
+	u8 rx_iv[2][QUIC_IV_LEN];      /* IVs for RX (key phase 0 and 1) */
+	atomic_t async_pending[2]; /* Async pending count (key phase 0 and 1) */
+
+	/* Timestamp 1st packet sent after key update */
+	u64 key_update_send_time;
+	u64 key_update_time; /* Timestamp old keys retained after key update */
+	u32 version;         /* QUIC version in use */
+
+	u8 ticket_ready:1; /* True if a session ticket is ready to read */
+	u8 key_pending:1;  /* A key update is in progress */
+	u8 key_derived:1;  /* Key derived for the key update */
+	u8 send_ready:1;   /* TX encryption context is initialized */
+	u8 recv_ready:1;   /* RX decryption context is initialized */
+	u8 key_phase:1;    /* Current key phase being used (0 or 1) */
+
+	u64 send_offset; /* Number of handshake bytes sent by user */
+	u64 recv_offset; /* Number of handshake bytes read by user */
+};
+
+int quic_crypto_set_secret(struct quic_crypto *crypto,
+			   struct quic_crypto_secret *srt, u32 version);
+int quic_crypto_set_cipher(struct quic_crypto *crypto, u32 type);
+int quic_crypto_key_update(struct quic_crypto *crypto);
+
+int quic_crypto_encrypt(struct quic_crypto *crypto, struct sk_buff *skb,
+			gfp_t gfp);
+int quic_crypto_decrypt(struct quic_crypto *crypto, struct sk_buff *skb,
+			gfp_t gfp);
+
+int quic_crypto_derive_secret(struct quic_crypto *crypto, void *data, u32 len,
+			      char *label, u8 *srt, u32 srt_len);
+int quic_crypto_initial_keys_install(struct quic_crypto *crypto,
+				     struct quic_conn_id *conn_id,
+				     u32 version, bool is_serv);
+int quic_crypto_set_token_secret(struct quic_crypto *crypto);
+
+int quic_crypto_generate_token(struct quic_crypto *crypto, void *addr,
+			       u32 addrlen, struct quic_conn_id *conn_id,
+			       u8 *token, u32 *tlen);
+int quic_crypto_get_retry_tag(struct quic_crypto *crypto, struct sk_buff *skb,
+			      struct quic_conn_id *odcid, u32 version, u8 *tag);
+int quic_crypto_verify_token(struct quic_crypto *crypto, void *addr,
+			     u32 addrlen, struct quic_conn_id *conn_id,
+			     u8 *token, u32 len);
+
+void quic_crypto_free(struct quic_crypto *crypto);
diff --git a/net/quic/family.c b/net/quic/family.c
new file mode 100644
index 00000000000000..be792cfadabc9e
--- /dev/null
+++ b/net/quic/family.c
@@ -0,0 +1,439 @@
+// 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
+ *
+ * Initialization/cleanup for QUIC protocol support.
+ *
+ * Written or modified by:
+ *    Xin Long <lucien.xin@gmail.com>
+ */
+
+#include <net/inet_common.h>
+#include <net/udp_tunnel.h>
+#include <linux/icmp.h>
+
+#include "common.h"
+#include "family.h"
+
+static bool quic_v4_is_any_addr(union quic_addr *addr)
+{
+	return addr->v4.sin_addr.s_addr == htonl(INADDR_ANY);
+}
+
+static bool quic_v6_is_any_addr(union quic_addr *addr)
+{
+	return ipv6_addr_any(&addr->v6.sin6_addr);
+}
+
+static void quic_v4_udp_conf_init(struct sock *sk, struct udp_port_cfg *conf,
+				  union quic_addr *a)
+{
+	conf->family = AF_INET;
+	conf->local_ip.s_addr = a->v4.sin_addr.s_addr;
+	conf->local_udp_port = a->v4.sin_port;
+	conf->bind_ifindex = sk->sk_bound_dev_if;
+}
+
+static void quic_v6_udp_conf_init(struct sock *sk, struct udp_port_cfg *conf,
+				  union quic_addr *a)
+{
+	conf->family = AF_INET6;
+	conf->local_ip6 = a->v6.sin6_addr;
+	conf->local_udp_port = a->v6.sin6_port;
+	conf->use_udp6_rx_checksums = true;
+	conf->use_udp6_tx_checksums = true;
+	conf->ipv6_v6only = ipv6_only_sock(sk);
+	conf->bind_ifindex = quic_get_dev_if(sk, a);
+}
+
+static int quic_v4_flow_route(struct sock *sk, union quic_addr *da,
+			      union quic_addr *sa, struct flowi *fl)
+{
+	struct flowi4 *fl4;
+	struct rtable *rt;
+
+	if (__sk_dst_check(sk, 0))
+		return 1;
+
+	memset(fl, 0x00, sizeof(*fl));
+	fl4 = &fl->u.ip4;
+	fl4->saddr = sa->v4.sin_addr.s_addr;
+	fl4->fl4_sport = sa->v4.sin_port;
+	fl4->daddr = da->v4.sin_addr.s_addr;
+	fl4->fl4_dport = da->v4.sin_port;
+	fl4->flowi4_proto = IPPROTO_UDP;
+	fl4->flowi4_oif = quic_get_dev_if(sk, da);
+
+	fl4->flowi4_scope = ip_sock_rt_scope(sk);
+	fl4->flowi4_dscp = inet_sk_dscp(inet_sk(sk));
+
+	fl4->flowi4_uid = sk_uid(sk);
+	fl4->flowi4_mark = sk->sk_mark;
+
+	rt = ip_route_output_flow(sock_net(sk), fl4, sk);
+	if (IS_ERR(rt))
+		return PTR_ERR(rt);
+
+	if (quic_v4_is_any_addr(sa)) {
+		sa->v4.sin_family = AF_INET;
+		sa->v4.sin_addr.s_addr = fl4->saddr;
+	}
+	sk_setup_caps(sk, &rt->dst);
+	return 0;
+}
+
+static int quic_v6_flow_route(struct sock *sk, union quic_addr *da,
+			      union quic_addr *sa, struct flowi *fl)
+{
+	struct ipv6_pinfo *np = inet6_sk(sk);
+	struct in6_addr *final_p, final;
+	struct ip6_flowlabel *flowlabel;
+	struct dst_entry *dst;
+	struct flowi6 *fl6;
+
+	if (__sk_dst_check(sk, np->dst_cookie))
+		return 1;
+
+	memset(fl, 0x00, sizeof(*fl));
+	fl6 = &fl->u.ip6;
+	fl6->saddr = sa->v6.sin6_addr;
+	fl6->fl6_sport = sa->v6.sin6_port;
+	fl6->daddr = da->v6.sin6_addr;
+	fl6->fl6_dport = da->v6.sin6_port;
+	fl6->flowi6_proto = IPPROTO_UDP;
+	fl6->flowi6_oif = quic_get_dev_if(sk, da);
+
+	if (inet6_test_bit(SNDFLOW, sk)) {
+		fl6->flowlabel = (da->v6.sin6_flowinfo & IPV6_FLOWINFO_MASK);
+		if (fl6->flowlabel & IPV6_FLOWLABEL_MASK) {
+			flowlabel = fl6_sock_lookup(sk, fl6->flowlabel);
+			if (IS_ERR(flowlabel))
+				return -EINVAL;
+			fl6_sock_release(flowlabel);
+		}
+	}
+	fl6->flowlabel = ip6_make_flowinfo(np->tclass, fl6->flowlabel);
+
+	fl6->flowi6_uid = sk_uid(sk);
+	fl6->flowi6_mark = sk->sk_mark;
+
+	rcu_read_lock();
+	final_p = fl6_update_dst(fl6, rcu_dereference(np->opt), &final);
+	rcu_read_unlock();
+
+	dst = ip6_dst_lookup_flow(sock_net(sk), sk, fl6, final_p);
+	if (IS_ERR(dst))
+		return PTR_ERR(dst);
+
+	if (quic_v6_is_any_addr(sa)) {
+		sa->v6.sin6_family = AF_INET6;
+		sa->v6.sin6_addr = fl6->saddr;
+		if ((ipv6_addr_type(&fl6->saddr) & IPV6_ADDR_LINKLOCAL))
+			sa->v6.sin6_scope_id = fl6->flowi6_oif;
+	}
+	ip6_dst_store(sk, dst, false, false);
+	return 0;
+}
+
+static void quic_v4_lower_xmit(struct sock *sk, struct sk_buff *skb,
+			       struct flowi *fl)
+{
+	struct quic_skb_cb *cb = QUIC_SKB_CB(skb);
+	u8 tos = (inet_sk(sk)->tos | cb->ecn), ttl;
+	struct flowi4 *fl4 = &fl->u.ip4;
+	struct dst_entry *dst;
+	__be16 df = 0;
+
+	pr_debug("%s: skb: %p, len: %d, num: %lld, %pI4:%d -> %pI4:%d\n",
+		 __func__, skb, skb->len, cb->number, &fl4->saddr,
+		 ntohs(fl4->fl4_sport), &fl4->daddr, ntohs(fl4->fl4_dport));
+
+	dst = sk_dst_get(sk);
+	if (!dst) {
+		kfree_skb(skb);
+		return;
+	}
+	if (ip_dont_fragment(sk, dst) && !skb->ignore_df)
+		df = htons(IP_DF);
+
+	ttl = (u8)ip4_dst_hoplimit(dst);
+	udp_tunnel_xmit_skb((struct rtable *)dst, sk, skb, fl4->saddr,
+			    fl4->daddr, tos, ttl, df, fl4->fl4_sport,
+			    fl4->fl4_dport, false, false, 0);
+}
+
+static void quic_v6_lower_xmit(struct sock *sk, struct sk_buff *skb,
+			       struct flowi *fl)
+{
+	struct quic_skb_cb *cb = QUIC_SKB_CB(skb);
+	u8 tc = (inet6_sk(sk)->tclass | cb->ecn), ttl;
+	struct flowi6 *fl6 = &fl->u.ip6;
+	struct dst_entry *dst;
+	__be32 label;
+
+	pr_debug("%s: skb: %p, len: %d, num: %lld, %pI6c:%d -> %pI6c:%d\n",
+		 __func__, skb, skb->len, cb->number, &fl6->saddr,
+		 ntohs(fl6->fl6_sport), &fl6->daddr, ntohs(fl6->fl6_dport));
+
+	dst = sk_dst_get(sk);
+	if (!dst) {
+		kfree_skb(skb);
+		return;
+	}
+
+	ttl = (u8)ip6_dst_hoplimit(dst);
+	label = ip6_make_flowlabel(sock_net(sk), skb, fl6->flowlabel, true,
+				   fl6);
+	udp_tunnel6_xmit_skb(dst, sk, skb, NULL, &fl6->saddr, &fl6->daddr, tc,
+			     ttl, label, fl6->fl6_sport, fl6->fl6_dport, false,
+			     0);
+}
+
+static void quic_v4_get_msg_addrs(struct sk_buff *skb, union quic_addr *da,
+				  union quic_addr *sa)
+{
+	struct udphdr *uh = udp_hdr(skb);
+
+	sa->v4.sin_family = AF_INET;
+	sa->v4.sin_port = uh->source;
+	sa->v4.sin_addr.s_addr = ip_hdr(skb)->saddr;
+
+	da->v4.sin_family = AF_INET;
+	da->v4.sin_port = uh->dest;
+	da->v4.sin_addr.s_addr = ip_hdr(skb)->daddr;
+}
+
+static void quic_v6_get_msg_addrs(struct sk_buff *skb, union quic_addr *da,
+				  union quic_addr *sa)
+{
+	struct udphdr *uh = udp_hdr(skb);
+
+	sa->v6.sin6_family = AF_INET6;
+	sa->v6.sin6_port = uh->source;
+	sa->v6.sin6_addr = ipv6_hdr(skb)->saddr;
+	sa->v6.sin6_scope_id = skb->skb_iif;
+
+	da->v6.sin6_family = AF_INET6;
+	da->v6.sin6_port = uh->dest;
+	da->v6.sin6_addr = ipv6_hdr(skb)->daddr;
+	da->v6.sin6_scope_id = skb->skb_iif;
+}
+
+static int quic_v4_get_mtu_info(struct sk_buff *skb, u32 *info)
+{
+	struct icmphdr *hdr;
+
+	hdr = (struct icmphdr *)(skb_network_header(skb) -
+				 sizeof(struct icmphdr));
+	if (hdr->type == ICMP_DEST_UNREACH && hdr->code == ICMP_FRAG_NEEDED) {
+		*info = ntohs(hdr->un.frag.mtu);
+		return 0;
+	}
+
+	/* Defer other types' processing to UDP error handler. */
+	return -EINVAL;
+}
+
+static int quic_v6_get_mtu_info(struct sk_buff *skb, u32 *info)
+{
+	struct icmp6hdr *hdr;
+
+	hdr = (struct icmp6hdr *)(skb_network_header(skb) -
+				  sizeof(struct icmp6hdr));
+	if (hdr->icmp6_type == ICMPV6_PKT_TOOBIG) {
+		*info = ntohl(hdr->icmp6_mtu);
+		return 0;
+	}
+
+	/* Defer other types' processing to UDP error handler. */
+	return -EINVAL;
+}
+
+static bool quic_v4_cmp_sk_addr(struct sock *sk, union quic_addr *a,
+				union quic_addr *addr)
+{
+	if (a->v4.sin_port != addr->v4.sin_port)
+		return false;
+	if (a->v4.sin_family != addr->v4.sin_family)
+		return false;
+	/* No match: specific socket vs ANY lookup. */
+	if (addr->v4.sin_addr.s_addr == htonl(INADDR_ANY))
+		return a->v4.sin_addr.s_addr == htonl(INADDR_ANY);
+	if (a->v4.sin_addr.s_addr == htonl(INADDR_ANY))
+		return true;
+	return a->v4.sin_addr.s_addr == addr->v4.sin_addr.s_addr;
+}
+
+static bool quic_v6_cmp_sk_addr(struct sock *sk, union quic_addr *a,
+				union quic_addr *addr)
+{
+	if (a->sa.sa_family == AF_INET && addr->sa.sa_family == AF_INET)
+		return quic_v4_cmp_sk_addr(sk, a, addr);
+
+	if (a->v4.sin_port != addr->v4.sin_port)
+		return false;
+
+	if (a->sa.sa_family != addr->sa.sa_family) {
+		if (ipv6_only_sock(sk))
+			return false;
+		return quic_is_any_addr(a);
+	}
+
+	/* No match: specific socket vs ANY lookup. */
+	if (ipv6_addr_any(&addr->v6.sin6_addr))
+		return ipv6_addr_any(&a->v6.sin6_addr);
+	if (ipv6_addr_any(&a->v6.sin6_addr))
+		return true;
+	if (!ipv6_addr_equal(&a->v6.sin6_addr, &addr->v6.sin6_addr))
+		return false;
+	if ((ipv6_addr_type(&a->v6.sin6_addr) & IPV6_ADDR_LINKLOCAL) &&
+	    a->v6.sin6_scope_id && addr->v6.sin6_scope_id &&
+	    a->v6.sin6_scope_id != addr->v6.sin6_scope_id)
+		return false;
+	return true;
+}
+
+static int quic_v4_get_sk_addr(struct socket *sock, struct sockaddr *uaddr,
+			       int peer)
+{
+	return inet_getname(sock, uaddr, peer);
+}
+
+static int quic_v6_get_sk_addr(struct socket *sock, struct sockaddr *uaddr,
+			       int peer)
+{
+	union quic_addr *a = quic_addr(uaddr);
+	int ret;
+
+	ret = inet6_getname(sock, uaddr, peer);
+	if (ret < 0)
+		return ret;
+
+	if (a->sa.sa_family == AF_INET6 &&
+	    ipv6_addr_v4mapped(&a->v6.sin6_addr)) {
+		a->v4.sin_family = AF_INET;
+		a->v4.sin_port = a->v6.sin6_port;
+		a->v4.sin_addr.s_addr = a->v6.sin6_addr.s6_addr32[3];
+	}
+
+	if (a->sa.sa_family == AF_INET) {
+		memset(a->v4.sin_zero, 0, sizeof(a->v4.sin_zero));
+		return sizeof(struct sockaddr_in);
+	}
+	return sizeof(struct sockaddr_in6);
+}
+
+#define quic_af_ipv4(a)		((a)->sa.sa_family == AF_INET)
+
+u32 quic_encap_len(union quic_addr *a)
+{
+	return (quic_af_ipv4(a) ? sizeof(struct iphdr) :
+				  sizeof(struct ipv6hdr)) +
+	       sizeof(struct udphdr);
+}
+
+bool quic_is_any_addr(union quic_addr *a)
+{
+	return quic_af_ipv4(a) ? quic_v4_is_any_addr(a) :
+				 quic_v6_is_any_addr(a);
+}
+
+void quic_udp_conf_init(struct sock *sk, struct udp_port_cfg *conf,
+			union quic_addr *a)
+{
+	quic_af_ipv4(a) ? quic_v4_udp_conf_init(sk, conf, a) :
+			  quic_v6_udp_conf_init(sk, conf, a);
+}
+
+int quic_flow_route(struct sock *sk, union quic_addr *da, union quic_addr *sa,
+		    struct flowi *fl)
+{
+	return quic_af_ipv4(da) ? quic_v4_flow_route(sk, da, sa, fl) :
+				  quic_v6_flow_route(sk, da, sa, fl);
+}
+
+void quic_lower_xmit(struct sock *sk, struct sk_buff *skb, union quic_addr *da,
+		     struct flowi *fl)
+{
+	local_bh_disable();
+	quic_af_ipv4(da) ? quic_v4_lower_xmit(sk, skb, fl) :
+			   quic_v6_lower_xmit(sk, skb, fl);
+	local_bh_enable();
+}
+
+#define quic_skb_ipv4(skb)	(ip_hdr(skb)->version == 4)
+
+void quic_get_msg_addrs(struct sk_buff *skb, union quic_addr *da,
+			union quic_addr *sa)
+{
+	memset(sa, 0, sizeof(*sa));
+	memset(da, 0, sizeof(*da));
+	quic_skb_ipv4(skb) ? quic_v4_get_msg_addrs(skb, da, sa) :
+			     quic_v6_get_msg_addrs(skb, da, sa);
+}
+
+int quic_get_mtu_info(struct sk_buff *skb, u32 *info)
+{
+	return quic_skb_ipv4(skb) ? quic_v4_get_mtu_info(skb, info) :
+				    quic_v6_get_mtu_info(skb, info);
+}
+
+#define quic_pf_ipv4(sk)	((sk)->sk_family == PF_INET)
+
+bool quic_cmp_sk_addr(struct sock *sk, union quic_addr *a,
+		      union quic_addr *addr)
+{
+	return quic_pf_ipv4(sk) ? quic_v4_cmp_sk_addr(sk, a, addr) :
+				  quic_v6_cmp_sk_addr(sk, a, addr);
+}
+
+int quic_get_sk_addr(struct socket *sock, struct sockaddr *a, int peer)
+{
+	return quic_pf_ipv4(sock->sk) ? quic_v4_get_sk_addr(sock, a, peer) :
+					quic_v6_get_sk_addr(sock, a, peer);
+}
+
+int quic_get_dev_if(struct sock *sk, union quic_addr *a)
+{
+	if (!quic_af_ipv4(a) &&
+	    ipv6_addr_type(&a->v6.sin6_addr) & IPV6_ADDR_LINKLOCAL &&
+	    a->v6.sin6_scope_id)
+		return a->v6.sin6_scope_id;
+
+	return sk->sk_bound_dev_if;
+}
+
+void quic_set_skb_iif(struct sk_buff *skb)
+{
+	/* Save the inet/inet6 iif before skb dst/cb are cleared. */
+	skb->skb_iif = quic_skb_ipv4(skb) ? inet_iif(skb) : inet6_iif(skb);
+}
+
+int quic_common_setsockopt(struct sock *sk, int level, int optname,
+			   sockptr_t optval, unsigned int optlen)
+{
+	return quic_pf_ipv4(sk) ?
+	       ip_setsockopt(sk, level, optname, optval, optlen) :
+	       ipv6_setsockopt(sk, level, optname, optval, optlen);
+}
+
+int quic_common_getsockopt(struct sock *sk, int level, int optname,
+			   char __user *optval, int __user *optlen)
+{
+	return quic_pf_ipv4(sk) ?
+	       ip_getsockopt(sk, level, optname, optval, optlen) :
+	       ipv6_getsockopt(sk, level, optname, optval, optlen);
+}
+
+bool quic_sk_accept_pmtu(struct sock *sk, struct sk_buff *skb)
+{
+	return quic_skb_ipv4(skb) ? ip_sk_accept_pmtu(sk) :
+				    ip6_sk_accept_pmtu(sk);
+}
+
+void quic_sk_destruct(struct sock *sk)
+{
+	quic_pf_ipv4(sk) ? inet_sock_destruct(sk) : inet6_sock_destruct(sk);
+}
diff --git a/net/quic/family.h b/net/quic/family.h
new file mode 100644
index 00000000000000..02342c4b7c99e2
--- /dev/null
+++ b/net/quic/family.h
@@ -0,0 +1,44 @@
+/* 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_PORT_LEN		2
+#define QUIC_ADDR4_LEN		4
+#define QUIC_ADDR6_LEN		16
+
+#define QUIC_PREF_ADDR_LEN \
+	(QUIC_ADDR4_LEN + QUIC_PORT_LEN + QUIC_ADDR6_LEN + QUIC_PORT_LEN)
+
+bool quic_is_any_addr(union quic_addr *a);
+u32 quic_encap_len(union quic_addr *a);
+
+void quic_lower_xmit(struct sock *sk, struct sk_buff *skb, union quic_addr *da,
+		     struct flowi *fl);
+int quic_flow_route(struct sock *sk, union quic_addr *da, union quic_addr *sa,
+		    struct flowi *fl);
+void quic_udp_conf_init(struct sock *sk, struct udp_port_cfg *conf,
+			union quic_addr *a);
+
+void quic_get_msg_addrs(struct sk_buff *skb, union quic_addr *da,
+			union quic_addr *sa);
+int quic_get_mtu_info(struct sk_buff *skb, u32 *info);
+
+bool quic_cmp_sk_addr(struct sock *sk, union quic_addr *a,
+		      union quic_addr *addr);
+int quic_get_sk_addr(struct socket *sock, struct sockaddr *a, int peer);
+
+int quic_get_dev_if(struct sock *sk, union quic_addr *a);
+void quic_set_skb_iif(struct sk_buff *skb);
+
+int quic_common_setsockopt(struct sock *sk, int level, int optname,
+			   sockptr_t optval, unsigned int optlen);
+int quic_common_getsockopt(struct sock *sk, int level, int optname,
+			   char __user *optval, int __user *optlen);
+bool quic_sk_accept_pmtu(struct sock *sk, struct sk_buff *skb);
+void quic_sk_destruct(struct sock *sk);
diff --git a/net/quic/packet.c b/net/quic/packet.c
new file mode 100644
index 00000000000000..57808cc5d95d3c
--- /dev/null
+++ b/net/quic/packet.c
@@ -0,0 +1,890 @@
+// 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
+ *
+ * Initialization/cleanup for QUIC protocol support.
+ *
+ * Written or modified by:
+ *    Xin Long <lucien.xin@gmail.com>
+ */
+
+#include "socket.h"
+
+#define QUIC_HLEN		1
+
+#define QUIC_LONG_HLEN(dcid, scid) \
+	(QUIC_HLEN + QUIC_VERSION_LEN + 1 + (dcid)->len + 1 + (scid)->len)
+
+#define QUIC_VERSION_NUM	2
+
+/* Supported QUIC versions and their compatible versions. Used for Compatible
+ * Version Negotiation in rfc9368#section-2.3.
+ */
+static u32 quic_versions[QUIC_VERSION_NUM][4] = {
+	/* Version,	Compatible Versions */
+	{ QUIC_VERSION_V1, QUIC_VERSION_V2, QUIC_VERSION_V1, 0 },
+	{ QUIC_VERSION_V2, QUIC_VERSION_V2, QUIC_VERSION_V1, 0 },
+};
+
+/* Get the compatible version list for a given QUIC version. */
+u32 *quic_packet_compatible_versions(u32 version)
+{
+	u8 i;
+
+	for (i = 0; i < QUIC_VERSION_NUM; i++)
+		if (version == quic_versions[i][0])
+			return quic_versions[i];
+	return NULL;
+}
+
+/* Convert version-specific type to internal standard packet type. */
+static u8 quic_packet_version_get_type(u32 version, u8 type)
+{
+	if (version == QUIC_VERSION_V1)
+		return type;
+
+	switch (type) {
+	case QUIC_PACKET_INITIAL_V2:
+		return QUIC_PACKET_INITIAL;
+	case QUIC_PACKET_0RTT_V2:
+		return QUIC_PACKET_0RTT;
+	case QUIC_PACKET_HANDSHAKE_V2:
+		return QUIC_PACKET_HANDSHAKE;
+	case QUIC_PACKET_RETRY_V2:
+		return QUIC_PACKET_RETRY;
+	default:
+		return QUIC_PACKET_INVALID;
+	}
+}
+
+/* Extracts a QUIC Connection ID from a buffer in the long header packet. */
+static int quic_packet_get_connid(struct quic_conn_id *connid, u8 **pp,
+				  u32 *plen)
+{
+	u64 len;
+
+	if (!quic_get_int(pp, plen, &len, 1) ||
+	    len > *plen || len > QUIC_CONN_ID_MAX_LEN)
+		return -EINVAL;
+
+	quic_conn_id_update(connid, *pp, len);
+	*plen -= len;
+	*pp += len;
+	return 0;
+}
+
+/* Parse QUIC version and connection IDs (DCID and SCID) from a Long header
+ * packet buffer.
+ */
+static int quic_packet_get_long_header(struct quic_conn_id *dcid,
+				       struct quic_conn_id *scid, u32 *version,
+				       u8 **pp, u32 *plen)
+{
+	int err;
+	u64 v;
+
+	*pp += QUIC_HLEN;
+	*plen -= QUIC_HLEN;
+
+	if (!quic_get_int(pp, plen, &v, QUIC_VERSION_LEN))
+		return -EINVAL;
+	if (version)
+		*version = v;
+
+	err = quic_packet_get_connid(dcid, pp, plen);
+	if (err)
+		return err;
+	if (!scid)
+		return 0;
+	return quic_packet_get_connid(scid, pp, plen);
+}
+
+/* Extracts a QUIC token from a buffer in the Client Initial packet. */
+static int quic_packet_get_token(struct quic_data *token, u8 **pp, u32 *plen)
+{
+	u64 len;
+
+	if (!quic_get_var(pp, plen, &len) || len > *plen)
+		return -EINVAL;
+	quic_data(token, *pp, len);
+	*plen -= len;
+	*pp += len;
+	return 0;
+}
+
+/* Process PMTU reduction event on a QUIC socket. */
+void quic_packet_rcv_err_pmtu(struct sock *sk)
+{
+	struct quic_path_group *paths = quic_paths(sk);
+	struct quic_packet *packet = quic_packet(sk);
+	u32 pathmtu, info, taglen;
+	struct dst_entry *dst;
+	bool reset_timer;
+
+	if (quic_is_closed(sk))
+		return;
+
+	info = clamp(paths->mtu_info, QUIC_PATH_MIN_PMTU, QUIC_PATH_MAX_PMTU);
+	/* If PLPMTUD is not enabled, update MSS using route and ICMP info. */
+	if (!paths->plpmtud_interval) {
+		if (quic_packet_route(sk))
+			return;
+
+		dst = __sk_dst_get(sk);
+		if (dst)
+			dst->ops->update_pmtu(dst, sk, NULL, info, true);
+		quic_packet_mss_update(sk, info - packet->hlen);
+		return;
+	}
+	/* PLPMTUD is enabled: adjust to smaller PMTU, subtract headers and
+	 * AEAD tag.  Also notify the QUIC path layer for possible state
+	 * changes and probing.
+	 */
+	packet->level = QUIC_CRYPTO_APP;
+	taglen = quic_packet_taglen(packet);
+	info = info - packet->hlen - taglen;
+	pathmtu = quic_path_pl_toobig(paths, info, &reset_timer);
+	if (reset_timer)
+		quic_timer_reset(sk, QUIC_TIMER_PMTU, paths->plpmtud_interval);
+	if (pathmtu)
+		quic_packet_mss_update(sk, pathmtu + taglen);
+}
+
+/* Handle ICMP Toobig packet and update QUIC socket path MTU. */
+static int quic_packet_rcv_err(struct sock *sk, struct sk_buff *skb)
+{
+	union quic_addr daddr, saddr;
+	u32 info;
+
+	/* ICMP embeds the original outgoing QUIC packet, so saddr/daddr are
+	 * reversed when parsed. Only address-based socket lookup is possible
+	 * in this case.
+	 */
+	quic_get_msg_addrs(skb, &saddr, &daddr);
+	sk = quic_sock_lookup(skb, &daddr, &saddr, sk, NULL);
+	if (!sk)
+		return -ENOENT;
+
+	if (quic_get_mtu_info(skb, &info) || !quic_sk_accept_pmtu(sk, skb)) {
+		sock_put(sk);
+		return 0;
+	}
+
+	/* Success: update socket path MTU info. */
+	bh_lock_sock(sk);
+	quic_paths(sk)->mtu_info = info;
+	if (sock_owned_by_user(sk)) {
+		/* Socket locked by userspace. Defer MTU processing via
+		 * release_cb. Hold socket reference to prevent it being
+		 * freed before deferral.
+		 */
+		if (!test_and_set_bit(QUIC_MTU_REDUCED_DEFERRED,
+				      &sk->sk_tsq_flags))
+			sock_hold(sk);
+		goto out;
+	}
+	/* Otherwise, process the MTU reduction now. */
+	quic_packet_rcv_err_pmtu(sk);
+out:
+	bh_unlock_sock(sk);
+	sock_put(sk);
+	return 1;
+}
+
+#define QUIC_PACKET_BACKLOG_MAX		4096
+
+/* Queue a packet for later processing when sleeping is allowed. */
+static int quic_packet_backlog_schedule(struct net *net, struct sk_buff *skb)
+{
+	struct quic_skb_cb *cb = QUIC_SKB_CB(skb);
+	struct quic_net *qn = quic_net(net);
+	struct sk_buff_head *head;
+
+	if (cb->backlog)
+		return 0;
+
+	head = &qn->backlog_list;
+	spin_lock_bh(&head->lock);
+	if (head->qlen >= QUIC_PACKET_BACKLOG_MAX) {
+		spin_unlock_bh(&head->lock);
+		QUIC_INC_STATS(net, QUIC_MIB_PKT_RCVDROP);
+		kfree_skb(skb);
+		return -ENOBUFS;
+	}
+	cb->backlog = 1;
+	__skb_queue_tail(head, skb);
+	spin_unlock_bh(&head->lock);
+
+	queue_work(quic_wq, &qn->work);
+	return 1;
+}
+
+#define TLS_MT_CLIENT_HELLO	1
+#define TLS_EXT_alpn		16
+
+/*  TLS Client Hello Msg:
+ *
+ *    uint16 ProtocolVersion;
+ *    opaque Random[32];
+ *    uint8 CipherSuite[2];
+ *
+ *    struct {
+ *        ExtensionType extension_type;
+ *        opaque extension_data<0..2^16-1>;
+ *    } Extension;
+ *
+ *    struct {
+ *        ProtocolVersion legacy_version = 0x0303;
+ *        Random rand;
+ *        opaque legacy_session_id<0..32>;
+ *        CipherSuite cipher_suites<2..2^16-2>;
+ *        opaque legacy_compression_methods<1..2^8-1>;
+ *        Extension extensions<8..2^16-1>;
+ *    } ClientHello;
+ */
+
+#define TLS_CH_RANDOM_LEN	32
+#define TLS_CH_VERSION_LEN	2
+#define TLS_MAX_EXTENSIONS	128
+
+/* Extract ALPN data from a TLS ClientHello message.
+ *
+ * Parses the TLS ClientHello handshake message to find the ALPN (Application
+ * Layer Protocol Negotiation) TLS extension. It validates the TLS ClientHello
+ * structure, including version, random, session ID, cipher suites, compression
+ * methods, and extensions. Once the ALPN extension is found, the ALPN
+ * protocols list is extracted and stored in @alpn.
+ *
+ * Return: 0 on success or no ALPN found, a negative error code on failed
+ * parsing.
+ */
+static int quic_packet_get_alpn(struct quic_data *alpn, u8 *p, u32 len)
+{
+	int err = -EINVAL, found = 0, exts = 0;
+	u64 length, type;
+
+	/* Verify handshake message type (ClientHello) and its length. */
+	if (!quic_get_int(&p, &len, &type, 1) || type != TLS_MT_CLIENT_HELLO)
+		return err;
+	if (!quic_get_int(&p, &len, &length, 3) ||
+	    len < TLS_CH_RANDOM_LEN + TLS_CH_VERSION_LEN ||
+	    length < TLS_CH_RANDOM_LEN + TLS_CH_VERSION_LEN)
+		return err;
+	if (len > (u32)length) /* Cap len to handshake msg length. */
+		len = length;
+	/* Skip legacy_version (2 bytes) + random (32 bytes). */
+	p += TLS_CH_RANDOM_LEN + TLS_CH_VERSION_LEN;
+	len -= TLS_CH_RANDOM_LEN + TLS_CH_VERSION_LEN;
+	/* legacy_session_id_len must be zero (QUIC requirement). */
+	if (!quic_get_int(&p, &len, &length, 1) || length)
+		return err;
+
+	/* Skip cipher_suites (2 bytes length + variable data). */
+	if (!quic_get_int(&p, &len, &length, 2) || length > (u64)len)
+		return err;
+	len -= length;
+	p += length;
+
+	/* Skip legacy_compression_methods (1 byte length + variable data). */
+	if (!quic_get_int(&p, &len, &length, 1) || length > (u64)len)
+		return err;
+	len -= length;
+	p += length;
+
+	/* Read TLS extensions length (2 bytes). */
+	if (!quic_get_int(&p, &len, &length, 2))
+		return err;
+	if (len > (u32)length) /* Limit len to extensions length if larger. */
+		len = length;
+	while (len >= 4) { /* Scan extensions for ALPN (TLS_EXT_alpn). */
+		if (exts++ >= TLS_MAX_EXTENSIONS)
+			return err;
+		if (!quic_get_int(&p, &len, &type, 2))
+			break;
+		if (!quic_get_int(&p, &len, &length, 2))
+			break;
+		if (len < (u32)length) /* Incomplete TLS extensions. */
+			return 0;
+		if (type == TLS_EXT_alpn) { /* Found ALPN extension. */
+			if (length > QUIC_ALPN_MAX_LEN)
+				return err;
+			len = length;
+			found = 1;
+			break;
+		}
+		/* Skip non-ALPN extensions. */
+		p += length;
+		len -= length;
+	}
+	if (!found) { /* No ALPN ext: set alpn->len = 0 and alpn->data = p. */
+		quic_data(alpn, p, 0);
+		return 0;
+	}
+
+	/* Parse ALPN protocols list length (2 bytes). */
+	if (!quic_get_int(&p, &len, &length, 2) || length > (u64)len)
+		return err;
+	quic_data(alpn, p, length); /* Store ALPN list in alpn->data. */
+	len = length;
+	while (len) { /* Validate ALPN protocols list format. */
+		if (!quic_get_int(&p, &len, &length, 1) || length > (u64)len) {
+			/* Bad ALPN: set alpn->len = 0, alpn->data = NULL. */
+			quic_data(alpn, NULL, 0);
+			return err;
+		}
+		len -= length;
+		p += length;
+	}
+	pr_debug("%s: alpn_len: %d\n", __func__, alpn->len);
+	return 0;
+}
+
+#define QUIC_FRAME_CRYPTO	0x06
+
+/* Parse ALPN from a QUIC Initial packet.
+ *
+ * This function processes a QUIC Initial packet to extract the ALPN from the
+ * TLS ClientHello message inside the QUIC CRYPTO frame. It verifies packet
+ * type, version compatibility, decrypts the packet payload, and locates the
+ * CRYPTO frame to parse the TLS ClientHello.  Finally, it calls
+ * quic_packet_get_alpn() to extract the ALPN extension data.
+ *
+ * Return: 0 on success or no ALPN found, a negative error code on failed
+ * parsing.
+ */
+static int quic_packet_parse_alpn(struct sk_buff *skb, struct quic_data *alpn)
+{
+	struct quic_skb_cb *cb = QUIC_SKB_CB(skb);
+	struct quic_conn_id dcid = {}, scid = {};
+	struct net *net = sock_net(skb->sk);
+	u32 len = skb->len, version;
+	struct quic_crypto *crypto;
+	u8 *p = skb->data, type;
+	struct quic_data token;
+	u64 offset, length;
+	int err;
+
+	if (!static_branch_unlikely(&quic_alpn_demux_key))
+		return 0;
+	err = quic_packet_get_long_header(&dcid, &scid, &version, &p, &len);
+	if (err)
+		return err;
+	if (!quic_packet_compatible_versions(version))
+		return 0;
+	/* Only parse Initial packets. */
+	type = quic_packet_version_get_type(version, quic_hshdr(skb)->type);
+	if (type != QUIC_PACKET_INITIAL)
+		return 0;
+	err = quic_packet_get_token(&token, &p, &len);
+	if (err)
+		return err;
+	if (!quic_get_var(&p, &len, &length) || length > (u64)len)
+		return -EINVAL;
+	if (quic_packet_backlog_schedule(net, skb))
+		return -EINPROGRESS;
+	cb->length = (u16)length;
+
+	/* Install initial keys for packet decryption to crypto. */
+	crypto = &quic_net(net)->crypto;
+	err = quic_crypto_initial_keys_install(crypto, &dcid, version, 1);
+	if (err)
+		return err;
+	cb->number_offset = (u16)(p - skb->data);
+	cb->sync = 1;
+	err = quic_crypto_decrypt(crypto, skb, GFP_KERNEL);
+	if (err) {
+		QUIC_INC_STATS(net, QUIC_MIB_PKT_DECDROP);
+		return err;
+	}
+
+	QUIC_INC_STATS(net, QUIC_MIB_PKT_DECFASTPATHS);
+	cb->resume = 1; /* Mark this packet as already decrypted. */
+
+	/* Find the QUIC CRYPTO frame. */
+	p = skb->data + cb->number_offset + cb->number_len;
+	len = cb->length - cb->number_len - QUIC_TAG_LEN;
+	for (; len && !(*p); p++, len--) /* Skip the padding frame. */
+		;
+	if (!len-- || *p++ != QUIC_FRAME_CRYPTO)
+		return 0;
+	if (!quic_get_var(&p, &len, &offset) || offset)
+		return 0;
+	if (!quic_get_var(&p, &len, &length) || length > (u64)len)
+		return 0;
+
+	/* Parse the TLS CLIENT_HELLO message. */
+	return quic_packet_get_alpn(alpn, p, length);
+}
+
+/* Lookup listening socket for Client Initial packet (in process context). */
+static struct sock *quic_packet_get_listen_sock(struct sk_buff *skb)
+{
+	union quic_addr daddr, saddr;
+	struct quic_data alpns = {};
+	struct sock *sk;
+	int err;
+
+	quic_get_msg_addrs(skb, &daddr, &saddr);
+
+	err = quic_packet_parse_alpn(skb, &alpns);
+	if (err)
+		return ERR_PTR(err);
+
+	sk = quic_listen_sock_lookup(skb, &daddr, &saddr, &alpns);
+	if (!sk)
+		return ERR_PTR(-ENOENT);
+	return sk;
+}
+
+/* Determine the QUIC socket associated with an incoming packet. */
+static struct sock *quic_packet_get_sock(struct sk_buff *skb)
+{
+	struct quic_skb_cb *cb = QUIC_SKB_CB(skb);
+	struct quic_conn_id dcid = {}, *conn_id;
+	struct net *net = sock_net(skb->sk);
+	union quic_addr daddr, saddr;
+	struct quic_data alpns = {};
+	struct sock *sk = NULL;
+	u32 len = skb->len;
+	u8 *p = skb->data;
+	int err;
+
+	if (skb->len < QUIC_HLEN)
+		return ERR_PTR(-EINVAL);
+
+	if (quic_hdr(skb)->form == QUIC_PACKET_FORM_SHORT) {
+		/* Short header path. */
+		if (skb->len < QUIC_HLEN + QUIC_CONN_ID_DEF_LEN)
+			return ERR_PTR(-EINVAL);
+		/* Fast path: look up QUIC connection by fixed-length DCID
+		 * (Currently, only QUIC_CONN_ID_DEF_LEN-length SCIDs are used).
+		 */
+		conn_id = quic_conn_id_lookup(net, skb->data + QUIC_HLEN,
+					      QUIC_CONN_ID_DEF_LEN);
+		if (conn_id) {
+			cb->seqno = quic_conn_id_number(conn_id);
+			/* Return associated socket. */
+			return quic_conn_id_sk(conn_id);
+		}
+
+		/* Fallback: listener socket lookup
+		 * (May be used to send a stateless reset from a listen socket).
+		 */
+		quic_get_msg_addrs(skb, &daddr, &saddr);
+		sk = quic_listen_sock_lookup(skb, &daddr, &saddr, &alpns);
+		if (sk)
+			return sk;
+		/* Final fallback: address-based connection lookup
+		 * (May be used to receive a stateless reset).
+		 */
+		sk = quic_sock_lookup(skb, &daddr, &saddr, skb->sk, NULL);
+		if (!sk)
+			return ERR_PTR(-ENOENT);
+		return sk;
+	}
+
+	/* Long header path. */
+	err = quic_packet_get_long_header(&dcid, NULL, NULL, &p, &len);
+	if (err)
+		return ERR_PTR(err);
+	/* Fast path: look up QUIC connection by parsed DCID. */
+	conn_id = quic_conn_id_lookup(net, dcid.data, dcid.len);
+	if (conn_id) {
+		cb->seqno = quic_conn_id_number(conn_id);
+		return quic_conn_id_sk(conn_id); /* Return associated socket. */
+	}
+
+	/* Fallback: address + DCID lookup
+	 * (May be used for 0-RTT or a follow-up Client Initial packet).
+	 */
+	quic_get_msg_addrs(skb, &daddr, &saddr);
+	sk = quic_sock_lookup(skb, &daddr, &saddr, skb->sk, &dcid);
+	if (sk)
+		return sk;
+	/* Final fallback: listener socket lookup
+	 * (Used for receiving the first Client Initial packet).
+	 */
+	err = quic_packet_parse_alpn(skb, &alpns);
+	if (err)
+		return ERR_PTR(err);
+	sk = quic_listen_sock_lookup(skb, &daddr, &saddr, &alpns);
+	if (!sk)
+		return ERR_PTR(-ENOENT);
+	return sk;
+}
+
+/* Entry point for processing received QUIC packets. */
+int quic_packet_rcv(struct sock *sk, struct sk_buff *skb, bool icmp)
+{
+	struct net *net = sock_net(sk);
+	int err;
+
+	if (unlikely(icmp))
+		return quic_packet_rcv_err(sk, skb);
+
+	/* Save the UDP socket to skb->sk for later QUIC socket lookup. */
+	if (skb_linearize(skb) || !skb_set_owner_sk_safe(skb, sk)) {
+		err = -EINVAL;
+		goto err;
+	}
+
+	/* Look up socket from socket or connection IDs hash tables. */
+	sk = quic_packet_get_sock(skb);
+	if (IS_ERR(sk)) {
+		err = PTR_ERR(sk);
+		if (err == -EINPROGRESS)
+			return 0;
+		goto err;
+	}
+
+	bh_lock_sock(sk);
+	if (sock_owned_by_user(sk)) {
+		/* Socket is busy (owned by user context): queue to backlog. */
+		err = sk_add_backlog(sk, skb, READ_ONCE(sk->sk_rcvbuf));
+		if (err) {
+			bh_unlock_sock(sk);
+			sock_put(sk);
+			goto err;
+		}
+		QUIC_INC_STATS(net, QUIC_MIB_PKT_RCVBACKLOGS);
+	} else {
+		/* Socket not busy: process immediately. */
+		QUIC_INC_STATS(net, QUIC_MIB_PKT_RCVFASTPATHS);
+		sk->sk_backlog_rcv(sk, skb); /* quic_backlog_rcv(). */
+	}
+	bh_unlock_sock(sk);
+	sock_put(sk);
+	return 0;
+err:
+	pr_debug("%s: failed, len: %d, err: %d\n", __func__, skb->len, err);
+	QUIC_INC_STATS(net, QUIC_MIB_PKT_RCVDROP);
+	kfree_skb(skb);
+	return err;
+}
+
+static int quic_packet_listen_process(struct sock *sk, struct sk_buff *skb,
+				      gfp_t gfp)
+{
+	kfree_skb(skb);
+	return -EOPNOTSUPP;
+}
+
+static int quic_packet_handshake_process(struct sock *sk, struct sk_buff *skb,
+					 gfp_t gfp)
+{
+	kfree_skb(skb);
+	return -EOPNOTSUPP;
+}
+
+static int quic_packet_app_process(struct sock *sk, struct sk_buff *skb,
+				   gfp_t gfp)
+{
+	kfree_skb(skb);
+	return -EOPNOTSUPP;
+}
+
+int quic_packet_process(struct sock *sk, struct sk_buff *skb, gfp_t gfp)
+{
+	if (quic_is_closed(sk)) {
+		kfree_skb(skb);
+		return 0;
+	}
+
+	if (quic_is_listen(sk))
+		return quic_packet_listen_process(sk, skb, gfp);
+
+	if (quic_hdr(skb)->form == QUIC_PACKET_FORM_LONG)
+		return quic_packet_handshake_process(sk, skb, gfp);
+
+	return quic_packet_app_process(sk, skb, gfp);
+}
+
+/* Work function to process packets in the backlog queue. */
+void quic_packet_backlog_work(struct work_struct *work)
+{
+	struct quic_net *qn = container_of(work, struct quic_net, work);
+	struct sk_buff_head *head = &qn->backlog_list;
+	struct sk_buff *skb;
+	struct sock *sk;
+
+	while ((skb = skb_dequeue(head)) != NULL) {
+		sk = quic_packet_get_listen_sock(skb);
+		if (IS_ERR(sk)) {
+			QUIC_INC_STATS(sock_net(skb->sk), QUIC_MIB_PKT_RCVDROP);
+			kfree_skb(skb);
+			continue;
+		}
+
+		lock_sock(sk);
+		quic_packet_process(sk, skb, GFP_KERNEL);
+		release_sock(sk);
+		sock_put(sk);
+		cond_resched();
+	}
+}
+
+/* Make these fixed for easy coding. */
+#define QUIC_PACKET_NUMBER_LEN	QUIC_PN_MAX_LEN
+#define QUIC_PACKET_LENGTH_LEN	4
+
+static struct sk_buff *quic_packet_handshake_create(struct sock *sk, gfp_t gfp)
+{
+	return NULL;
+}
+
+static int quic_packet_number_check(struct sock *sk, gfp_t gfp)
+{
+	return 0;
+}
+
+static struct sk_buff *quic_packet_app_create(struct sock *sk, gfp_t gfp)
+{
+	return NULL;
+}
+
+/* Update the MSS and inform congestion control. */
+void quic_packet_mss_update(struct sock *sk, u32 mss)
+{
+	struct quic_packet *packet = quic_packet(sk);
+	struct quic_cong *cong = quic_cong(sk);
+
+	packet->mss[QUIC_PACKET_MSS_NORMAL] = (u16)mss;
+	quic_cong_set_mss(cong, packet->mss[QUIC_PACKET_MSS_NORMAL]);
+}
+
+/* Perform routing for the QUIC packet on the specified path, update header
+ * length and MSS accordingly, reset path and start PMTU timer.
+ */
+int quic_packet_route(struct sock *sk)
+{
+	struct quic_path_group *paths = quic_paths(sk);
+	struct quic_packet *packet = quic_packet(sk);
+	union quic_addr *sa, *da;
+	u32 pmtu;
+	int err;
+
+	da = quic_path_daddr(paths, packet->path);
+	sa = quic_path_saddr(paths, packet->path);
+	err = quic_flow_route(sk, da, sa, &paths->fl);
+	if (err)
+		return err < 0 ? err : 0;
+
+	packet->hlen = quic_encap_len(da);
+	pmtu = clamp(dst_mtu(__sk_dst_get(sk)),
+		     QUIC_PATH_MIN_PMTU, QUIC_PATH_MAX_PMTU);
+	quic_packet_mss_update(sk, pmtu - packet->hlen);
+
+	quic_path_pl_reset(paths);
+	quic_timer_reset(sk, QUIC_TIMER_PMTU, paths->plpmtud_interval);
+	return 0;
+}
+
+/* Return QUIC packet header overhead for the given level and path. Includes
+ * packet number, connection IDs, and for long headers also version, length,
+ * and Initial token (if present). Excludes payload.
+ */
+u16 quic_packet_overhead(struct sock *sk, u8 level, u8 path)
+{
+	struct quic_conn_id_set *source = quic_source(sk);
+	struct quic_conn_id_set *dest = quic_dest(sk);
+	u16 len = QUIC_HLEN;
+
+	len += QUIC_PACKET_NUMBER_LEN; /* Packet number length. */
+	len += quic_conn_id_choose(dest, path)->len; /* DCID length. */
+	if (level == QUIC_CRYPTO_APP)
+		return len;
+
+	len += 1; /* Length byte for DCID. */
+	/* Length byte + SCID length. */
+	len += 1 + quic_conn_id_active(source)->len;
+	/* Include token for Initial packets. */
+	if (level == QUIC_CRYPTO_INITIAL)
+		len += quic_var_len(quic_token(sk)->len) + quic_token(sk)->len;
+	len += QUIC_VERSION_LEN; /* Version length. */
+	len += QUIC_PACKET_LENGTH_LEN; /* Packet length field. */
+
+	return len;
+}
+
+/* Configure the QUIC packet header and routing based on encryption level and
+ * path.
+ */
+int quic_packet_config(struct sock *sk, u8 level, u8 path)
+{
+	struct quic_packet *packet = quic_packet(sk);
+
+	/* If packet already has data, no need to reconfigure. */
+	if (!quic_packet_empty(packet))
+		return 0;
+
+	packet->path_validating = 0;
+	packet->ipfragok = 0;
+	packet->padding = 0;
+	packet->frames = 0;
+
+	packet->level = level;
+	packet->overhead = quic_packet_overhead(sk, level, path);
+	packet->len = packet->overhead + quic_packet_taglen(packet);
+
+	/* Allow fragmentation for handshake packets before PLPMTUD probing
+	 * starts. MTU discovery does not rely on ICMP Packet Too Big once
+	 * PLPMTUD is enabled.
+	 */
+	packet->ipfragok = level && !!quic_paths(sk)->plpmtud_interval;
+
+	if (packet->path != path) {
+		/* Path changed; update and reset routing cache */
+		packet->path = path;
+		__sk_dst_reset(sk);
+	}
+
+	/* Perform routing and MSS update for the configured packet. */
+	return quic_packet_route(sk);
+}
+
+static void quic_packet_encrypt_done(struct sk_buff *skb, int err)
+{
+	/* Free it for now, future patches will implement the actual deferred
+	 * transmission logic.
+	 */
+	kfree_skb(skb);
+}
+
+/* Coalescing Packets. */
+static int quic_packet_bundle(struct sock *sk, struct sk_buff *skb)
+{
+	struct quic_skb_cb *head_cb, *cb = QUIC_SKB_CB(skb);
+	struct quic_packet *packet = quic_packet(sk);
+	struct sk_buff *p;
+
+	if (!packet->head) /* First packet to bundle: initialize the head. */
+		goto init;
+
+	/* If bundling would exceed MSS, flush the current bundle. */
+	if (packet->head->len + skb->len >
+	    packet->mss[QUIC_PACKET_MSS_NORMAL]) {
+		quic_packet_flush(sk);
+		goto init;
+	}
+	/* Bundle it and update metadata for the aggregate skb. */
+	skb_orphan(skb);
+	p = packet->head;
+	head_cb = QUIC_SKB_CB(p);
+	if (head_cb->last == p)
+		skb_shinfo(p)->frag_list = skb;
+	else
+		head_cb->last->next = skb;
+	p->data_len += skb->len;
+	p->truesize += skb->truesize;
+	p->len += skb->len;
+	head_cb->last = skb;
+	head_cb->ecn |= cb->ecn;  /* Merge ECN flags. */
+
+out:
+	/* rfc9000#section-12.2: Packets with a short header (Section 17.3) do
+	 * not contain a Length field and so cannot be followed by other
+	 * packets in the same UDP datagram.
+	 *
+	 * so Return 1 to flush if it is a Short header packet.
+	 */
+	return !cb->level;
+init:
+	packet->head = skb;
+	cb->last = skb;
+	goto out;
+}
+
+/* Transmit a QUIC packet, possibly encrypting and bundling it. */
+static int quic_packet_xmit(struct sock *sk, struct sk_buff *skb, gfp_t gfp)
+{
+	struct quic_packet *packet = quic_packet(sk);
+	struct quic_skb_cb *cb = QUIC_SKB_CB(skb);
+	struct net *net = sock_net(sk);
+	int err;
+
+	/* Skip encryption if taglen == 0 (e.g., disable_1rtt_encryption). */
+	if (!packet->taglen[quic_hdr(skb)->form])
+		goto xmit;
+
+	cb->crypto_done = quic_packet_encrypt_done;
+	/* Associate skb with sk to ensure sk is valid during async encryption
+	 * completion.
+	 */
+	WARN_ON_ONCE(!skb_set_owner_sk_safe(skb, sk));
+	err = quic_crypto_encrypt(quic_crypto(sk, packet->level), skb, gfp);
+	if (err) {
+		if (err != -EINPROGRESS) {
+			QUIC_INC_STATS(net, QUIC_MIB_PKT_ENCDROP);
+			kfree_skb(skb);
+			return err;
+		}
+		QUIC_INC_STATS(net, QUIC_MIB_PKT_ENCBACKLOGS);
+		return err;
+	}
+	if (!cb->resume) /* Encryption completes synchronously. */
+		QUIC_INC_STATS(net, QUIC_MIB_PKT_ENCFASTPATHS);
+
+xmit:
+	if (quic_packet_bundle(sk, skb))
+		quic_packet_flush(sk);
+	return 0;
+}
+
+/* Create and transmit a new QUIC packet. */
+int quic_packet_create_and_xmit(struct sock *sk, gfp_t gfp)
+{
+	struct quic_packet *packet = quic_packet(sk);
+	struct sk_buff *skb;
+	int err;
+
+	err = quic_packet_number_check(sk, gfp);
+	if (err)
+		goto err;
+
+	if (packet->level)
+		skb = quic_packet_handshake_create(sk, gfp);
+	else
+		skb = quic_packet_app_create(sk, gfp);
+	if (!skb) {
+		err = -ENOMEM;
+		goto err;
+	}
+
+	err = quic_packet_xmit(sk, skb, gfp);
+	if (err && err != -EINPROGRESS)
+		goto err;
+
+	return 0;
+err:
+	pr_debug("%s: err: %d\n", __func__, err);
+	return err;
+}
+
+/* Flush any coalesced/bundled QUIC packets. */
+void quic_packet_flush(struct sock *sk)
+{
+	struct quic_path_group *paths = quic_paths(sk);
+	struct quic_packet *packet = quic_packet(sk);
+
+	if (packet->head) {
+		quic_lower_xmit(sk, packet->head,
+				quic_path_daddr(paths, packet->path),
+				&paths->fl);
+		packet->head = NULL;
+	}
+}
+
+void quic_packet_init(struct sock *sk)
+{
+	struct quic_packet *packet = quic_packet(sk);
+
+	INIT_LIST_HEAD(&packet->frame_list);
+	packet->taglen[QUIC_PACKET_FORM_SHORT] = QUIC_TAG_LEN;
+	packet->taglen[QUIC_PACKET_FORM_LONG] = QUIC_TAG_LEN;
+	packet->mss[QUIC_PACKET_MSS_NORMAL] = QUIC_MIN_UDP_PAYLOAD;
+	packet->mss[QUIC_PACKET_MSS_DGRAM] = QUIC_MIN_UDP_PAYLOAD;
+
+	packet->version = QUIC_VERSION_V1;
+}
diff --git a/net/quic/packet.h b/net/quic/packet.h
new file mode 100644
index 00000000000000..fefc5405844151
--- /dev/null
+++ b/net/quic/packet.h
@@ -0,0 +1,120 @@
+/* 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>
+ */
+
+struct quic_packet {
+	struct quic_conn_id dcid; /* Dest Conn ID from received packet */
+	struct quic_conn_id scid; /* Source Conn ID from received packet */
+	union quic_addr daddr;    /* Dest address from received packet */
+	union quic_addr saddr;    /* Source address from received packet */
+
+	struct list_head frame_list; /* Frames to pack into packet for send */
+	struct sk_buff *head;        /* Head skb for packet bundling on send */
+	u32 version;   /* QUIC version used/selected during handshake */
+	u16 overhead;  /* QUIC header length excluding frames */
+	u8 taglen[2];  /* Tag length for short and long packets */
+	u16 padding;   /* Total padding bytes to append after frames */
+	u16 frames;    /* Number of ack-eliciting frames */
+	u16 mss[2];    /* MSS for datagram and non-datagram packets */
+	u16 hlen;      /* UDP + IP header length for sending */
+	u16 len;       /* QUIC packet length including taglen for sending */
+
+	u8 path_validating:1; /* Packet contains path_validating frames */
+	u8 ack_eliciting:1;   /* Packet contains ack-eliciting frames */
+	u8 ack_immediate:1;   /* Send ACK immediately (skip ack_delay timer) */
+	u8 non_probing:1;     /* Packet contains non-probing frames */
+	u8 has_sack:1;        /* Packet contains ACK frames */
+	u8 ipfragok:1;        /* Allow IP fragmentation */
+	u8 path:1;            /* Path identifier used to send this packet */
+	u8 level;             /* Encryption level used */
+};
+
+#define QUIC_PACKET_INITIAL_V1		0
+#define QUIC_PACKET_0RTT_V1		1
+#define QUIC_PACKET_HANDSHAKE_V1	2
+#define QUIC_PACKET_RETRY_V1		3
+
+#define QUIC_PACKET_INITIAL_V2		1
+#define QUIC_PACKET_0RTT_V2		2
+#define QUIC_PACKET_HANDSHAKE_V2	3
+#define QUIC_PACKET_RETRY_V2		0
+
+#define QUIC_PACKET_INITIAL		QUIC_PACKET_INITIAL_V1
+#define QUIC_PACKET_0RTT		QUIC_PACKET_0RTT_V1
+#define QUIC_PACKET_HANDSHAKE		QUIC_PACKET_HANDSHAKE_V1
+#define QUIC_PACKET_RETRY		QUIC_PACKET_RETRY_V1
+
+#define QUIC_PACKET_INVALID		0xff
+
+#define QUIC_VERSION_LEN		4
+
+#define QUIC_ALPN_MAX_LEN		128
+
+#define QUIC_PACKET_MSS_NORMAL		0
+#define QUIC_PACKET_MSS_DGRAM		1
+
+#define QUIC_PACKET_FORM_SHORT		0
+#define QUIC_PACKET_FORM_LONG		1
+
+static inline u8 quic_packet_taglen(struct quic_packet *packet)
+{
+	return packet->taglen[packet->level != QUIC_CRYPTO_APP];
+}
+
+static inline void quic_packet_set_taglen(struct quic_packet *packet, u8 taglen)
+{
+	packet->taglen[QUIC_PACKET_FORM_SHORT] = taglen;
+}
+
+static inline u32 quic_packet_mss(struct quic_packet *packet)
+{
+	return packet->mss[QUIC_PACKET_MSS_NORMAL] - quic_packet_taglen(packet);
+}
+
+static inline u32 quic_packet_max_payload(struct quic_packet *packet)
+{
+	return packet->mss[QUIC_PACKET_MSS_NORMAL] - packet->overhead -
+	       quic_packet_taglen(packet);
+}
+
+static inline u32 quic_packet_max_payload_dgram(struct quic_packet *packet)
+{
+	return packet->mss[QUIC_PACKET_MSS_DGRAM] - packet->overhead -
+	       quic_packet_taglen(packet);
+}
+
+static inline bool quic_packet_empty(struct quic_packet *packet)
+{
+	return list_empty(&packet->frame_list);
+}
+
+static inline void quic_packet_reset(struct quic_packet *packet)
+{
+	packet->level = 0;
+	packet->has_sack = 0;
+	packet->non_probing = 0;
+	packet->ack_eliciting = 0;
+	packet->ack_immediate = 0;
+}
+
+int quic_packet_process(struct sock *sk, struct sk_buff *skb, gfp_t gfp);
+u16 quic_packet_overhead(struct sock *sk, u8 level, u8 path);
+int quic_packet_config(struct sock *sk, u8 level, u8 path);
+
+int quic_packet_create_and_xmit(struct sock *sk, gfp_t gfp);
+int quic_packet_route(struct sock *sk);
+
+void quic_packet_mss_update(struct sock *sk, u32 mss);
+void quic_packet_flush(struct sock *sk);
+void quic_packet_init(struct sock *sk);
+
+u32 *quic_packet_compatible_versions(u32 version);
+
+void quic_packet_backlog_work(struct work_struct *work);
+void quic_packet_rcv_err_pmtu(struct sock *sk);
diff --git a/net/quic/path.c b/net/quic/path.c
new file mode 100644
index 00000000000000..f197ae1c556cc6
--- /dev/null
+++ b/net/quic/path.c
@@ -0,0 +1,568 @@
+// 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
+ *
+ * Initialization/cleanup for QUIC protocol support.
+ *
+ * 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);
+	quic_packet_rcv(sk, skb, false);
+	/* .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 quic_packet_rcv(sk, skb, true);
+}
+
+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);
+
+	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;
+
+	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) {
+		head = quic_udp_sock_head(net, port);
+		mutex_lock(&head->lock);
+		us = quic_udp_sock_lookup(sk, a, port);
+		if (us) {
+			/* 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;
+
+	if (paths->path[1].udp_sk) {
+		paths->path[0] = paths->path[1];
+		paths->path[1] = path;
+		return;
+	}
+
+	paths->path[0].daddr = paths->path[1].daddr;
+	paths->path[1].daddr = path.daddr;
+}
+
+/* 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 a new alternate path was detected and updated, false
+ * otherwise.
+ */
+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
+
+/* Handle PLPMTUD probe failure on a QUIC path.
+ *
+ * Called immediately after 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: dst: %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 binary or linear search depending on state.
+ *
+ * Return: New PMTU to apply if search completes, or 0 if no change.
+ */
+u32 quic_path_pl_recv(struct quic_path_group *paths, bool *raise_timer,
+		      bool *complete)
+{
+	u32 pathmtu = 0;
+	u16 next;
+
+	pr_debug("%s: dst: %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: dst: %p, state: %d, pmtu: %d, size: %d, ptb: %d\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;
+}
diff --git a/net/quic/path.h b/net/quic/path.h
new file mode 100644
index 00000000000000..a7eedfa2f02242
--- /dev/null
+++ b/net/quic/path.h
@@ -0,0 +1,190 @@
+/* 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 */
+};
+
+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) */
+	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;
+}
+
+int quic_packet_rcv(struct sock *sk, struct sk_buff *skb, bool icmp);
+
+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);
diff --git a/net/quic/pnspace.c b/net/quic/pnspace.c
new file mode 100644
index 00000000000000..f4690f209dd16c
--- /dev/null
+++ b/net/quic/pnspace.c
@@ -0,0 +1,251 @@
+// 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
+ *
+ * Initialization/cleanup for QUIC protocol support.
+ *
+ * Written or modified by:
+ *    Xin Long <lucien.xin@gmail.com>
+ */
+
+#include <linux/slab.h>
+
+#include "common.h"
+#include "pnspace.h"
+
+int quic_pnspace_init(struct quic_pnspace *space)
+{
+	space->pn_map = kzalloc(BITS_TO_BYTES(QUIC_PN_MAP_INITIAL), GFP_KERNEL);
+	if (!space->pn_map)
+		return -ENOMEM;
+	space->pn_map_len = QUIC_PN_MAP_INITIAL;
+
+	space->max_time_limit = QUIC_PNSPACE_TIME_LIMIT;
+	space->next_pn = QUIC_PNSPACE_NEXT_PN;
+	space->max_pn_acked_seen = -1;
+	space->base_pn = -1;
+	return 0;
+}
+
+void quic_pnspace_free(struct quic_pnspace *space)
+{
+	space->pn_map_len = 0;
+	kfree(space->pn_map);
+	space->pn_map = NULL;
+}
+
+/* Expand the bitmap tracking received packet numbers.  Ensures the pn_map
+ * bitmap can cover at least @size packet numbers.  Allocates a larger bitmap,
+ * copies existing data, and updates metadata.
+ *
+ * Return: 0 on success, or a negative errno value on failure.
+ */
+static int quic_pnspace_grow(struct quic_pnspace *space, u16 size, gfp_t gfp)
+{
+	u16 len, inc, offset;
+	unsigned long *new;
+
+	inc = ALIGN((size - space->pn_map_len), BITS_PER_LONG) +
+	      QUIC_PN_MAP_INCREMENT;
+	len = (u16)min(space->pn_map_len + inc, QUIC_PN_MAP_SIZE);
+
+	new = kzalloc(BITS_TO_BYTES(len), gfp);
+	if (!new)
+		return -ENOMEM;
+
+	offset = (u16)(space->max_pn_seen + 1 - space->base_pn);
+	bitmap_copy(new, space->pn_map, offset);
+	kfree(space->pn_map);
+	space->pn_map = new;
+	space->pn_map_len = len;
+
+	return 0;
+}
+
+/* Check if a packet number has been received.
+ *
+ * Returns: 0 if the packet number has not been received.  1 if it has already
+ * been received. -EINVAL if the packet number is too old to track.
+ */
+int quic_pnspace_check(struct quic_pnspace *space, s64 pn)
+{
+	if (pn > QUIC_PN_MAX || pn < 0)
+		return -EINVAL;
+
+	if (space->base_pn == -1) /* No packet number received yet. */
+		return 0;
+
+	if (pn < space->min_pn_seen)
+		return -EINVAL;
+
+	if (pn < space->base_pn)
+		return 1;
+	if (pn - space->base_pn < space->pn_map_len &&
+	    test_bit(pn - space->base_pn, space->pn_map))
+		return 1;
+
+	return 0;
+}
+
+/* Advance base_pn past contiguous received packet numbers.  Finds the next gap
+ * (unreceived packet) beyond @pn, shifts the bitmap, and updates base_pn
+ * accordingly.
+ */
+static void quic_pnspace_move(struct quic_pnspace *space, s64 pn)
+{
+	u16 offset;
+
+	offset = (u16)(pn + 1 - space->base_pn);
+	offset = (u16)find_next_zero_bit(space->pn_map, space->pn_map_len,
+					 offset);
+	space->base_pn += offset;
+	bitmap_shift_right(space->pn_map, space->pn_map, offset,
+			   space->pn_map_len);
+}
+
+/* Mark a packet number as received. Updates the packet number map to record
+ * reception of @pn.  Advances base_pn if possible, and updates max/min/last
+ * seen fields as needed.
+ *
+ * Returns: 0 on success or if the packet was already marked, or a negative
+ * error returned by bitmap growth when expanding the map.
+ */
+int quic_pnspace_mark(struct quic_pnspace *space, s64 pn, gfp_t gfp)
+{
+	s64 last_max_pn_seen, off;
+	u64 last_max_pn_time;
+	bool has_gap;
+	int err;
+
+	if (space->base_pn == -1) {
+		/* Initialize base_pn based on the peer's first packet number
+		 * since peer's packet numbers may start at a non-zero value.
+		 */
+		quic_pnspace_set_base_pn(space, pn + 1);
+		return 0;
+	}
+
+	/* Ignore packets with number less than current base (already
+	 * processed).
+	 */
+	if (pn < space->base_pn)
+		return 0;
+
+	/* If offset is beyond current map length, try to grow the bitmap to
+	 * accommodate.
+	 */
+	off = pn - space->base_pn;
+	if (off >= space->pn_map_len) {
+		if (off >= QUIC_PN_MAP_SIZE) {
+			bitmap_zero(space->pn_map, space->pn_map_len);
+			quic_pnspace_set_base_pn(space, pn + 1);
+			return 0;
+		}
+		err = quic_pnspace_grow(space, off + 1, gfp);
+		if (err)
+			return err;
+	}
+
+	has_gap = quic_pnspace_has_gap(space);
+	if (space->max_pn_seen < pn) {
+		space->max_pn_seen = pn;
+		space->max_pn_time = space->time;
+	}
+
+	if (space->base_pn == pn) { /* PN is next expected packet. */
+		if (has_gap) /* Advance to next gap. */
+			quic_pnspace_move(space, pn);
+		else /* Fast path: increment base_pn if no gaps. */
+			space->base_pn++;
+	} else { /* Mark this packet as received in the bitmap. */
+		set_bit(off, space->pn_map);
+	}
+
+	/* Only update min and last_max_pn_seen if this packet is the current
+	 * max_pn.
+	 */
+	if (space->max_pn_seen != pn)
+		return 0;
+
+	/* Check if enough time has elapsed or enough packets have been
+	 * received to update tracking.
+	 */
+	last_max_pn_seen = min_t(s64, space->last_max_pn_seen, space->base_pn);
+	last_max_pn_time = space->last_max_pn_time;
+	if (space->max_pn_time < last_max_pn_time + space->max_time_limit &&
+	    space->max_pn_seen <= last_max_pn_seen + QUIC_PN_MAP_LIMIT)
+		return 0;
+
+	/* Advance base_pn if last_max_pn_seen is ahead of current base_pn.
+	 * This is needed because QUIC doesn't retransmit packets;
+	 * retransmitted frames are carried in new packets, so we move forward.
+	 */
+	if (space->last_max_pn_seen + 1 > space->base_pn)
+		quic_pnspace_move(space, space->last_max_pn_seen);
+
+	space->min_pn_seen = space->last_max_pn_seen;
+	space->last_max_pn_seen = space->max_pn_seen;
+	space->last_max_pn_time = space->max_pn_time;
+	return 0;
+}
+
+/* Find the next gap in received packet numbers. Scans pn_map for a gap
+ * starting from *@iter. A gap is a contiguous block of unreceived packets
+ * between received ones.
+ *
+ * Returns: true if a gap was found, false if no more gaps exist or are
+ * relevant.
+ */
+static bool quic_pnspace_next_gap_ack(const struct quic_pnspace *space,
+				      s64 *iter, u16 *start, u16 *end)
+{
+	u16 start_ = 0, end_ = 0, offset = (u16)(*iter - space->base_pn);
+
+	start_ = (u16)find_next_zero_bit(space->pn_map, space->pn_map_len,
+					 offset);
+	if (space->max_pn_seen <= space->base_pn + start_)
+		return false;
+
+	end_ = (u16)find_next_bit(space->pn_map, space->pn_map_len, start_);
+	if (space->max_pn_seen <= space->base_pn + end_ - 1)
+		return false;
+
+	*start = start_ + 1;
+	*end = end_;
+	*iter = space->base_pn + *end;
+	return true;
+}
+
+/* Generate gap acknowledgment blocks (GABs).  GABs describe ranges of
+ * unacknowledged packets between received ones, and are used in ACK frames.
+ *
+ * Returns: Number of generated GABs (up to QUIC_PN_MAP_MAX_GABS).
+ */
+u16 quic_pnspace_num_gabs(struct quic_pnspace *space,
+			  struct quic_gap_ack_block *gabs)
+{
+	u16 start, end, ngaps = 0;
+	s64 iter;
+
+	if (!quic_pnspace_has_gap(space))
+		return 0;
+
+	iter = space->base_pn;
+	/* Loop through all gaps until the end of the window or max allowed
+	 * gaps.
+	 */
+	while (quic_pnspace_next_gap_ack(space, &iter, &start, &end)) {
+		gabs[ngaps].start = start;
+		if (ngaps == QUIC_PN_MAP_MAX_GABS - 1) {
+			gabs[ngaps].end =
+				(u16)(space->max_pn_seen - space->base_pn);
+			ngaps++;
+			break;
+		}
+		gabs[ngaps].end = end;
+		ngaps++;
+	}
+	return ngaps;
+}
diff --git a/net/quic/pnspace.h b/net/quic/pnspace.h
new file mode 100644
index 00000000000000..15ce6d2ef72695
--- /dev/null
+++ b/net/quic/pnspace.h
@@ -0,0 +1,201 @@
+/* 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_PN_MAP_MAX_GABS	32
+
+#define QUIC_PN_MAP_INITIAL	64
+#define QUIC_PN_MAP_INCREMENT	QUIC_PN_MAP_INITIAL
+#define QUIC_PN_MAP_SIZE	4096
+#define QUIC_PN_MAP_LIMIT	(QUIC_PN_MAP_SIZE * 3 / 4)
+
+#define QUIC_PNSPACE_MAX	(QUIC_CRYPTO_MAX - 1)
+#define QUIC_PNSPACE_NEXT_PN	0
+#define QUIC_PNSPACE_TIME_LIMIT	(333000 * 3)
+
+enum {
+	QUIC_ECN_ECT1,
+	QUIC_ECN_ECT0,
+	QUIC_ECN_CE,
+	QUIC_ECN_MAX
+};
+
+enum {
+	QUIC_ECN_LOCAL, /* ECN bits from incoming IP headers */
+	QUIC_ECN_PEER,  /* ECN bits reported by peer in ACK frames */
+	QUIC_ECN_ACKED, /* ECN bits from packets newly ACKed */
+	QUIC_ECN_DIR_MAX
+};
+
+/* Represents a gap (range of missing packets) in the ACK map.  The values are
+ * offsets from base_pn, with both 'start' and 'end' being +1.
+ */
+struct quic_gap_ack_block {
+	u16 start;
+	u16 end;
+};
+
+/* Packet Number Map (pn_map) Layout:
+ *
+ *     min_pn_seen -->++-----------------------+---------------------+---
+ *         base_pn -----^   last_max_pn_seen --^       max_pn_seen --^
+ *
+ * Map Advancement Logic:
+ *   - min_pn_seen = last_max_pn_seen;
+ *   - base_pn = first zero bit after last_max_pn_seen;
+ *   - last_max_pn_seen = max_pn_seen;
+ *   - last_max_pn_time = current time;
+ *
+ * Conditions to Advance pn_map:
+ *   - (max_pn_time - last_max_pn_time) >= max_time_limit, or
+ *   - (max_pn_seen - last_max_pn_seen) > QUIC_PN_MAP_LIMIT
+ *
+ * Gap Search Range:
+ *   - From (base_pn - 1) to max_pn_seen
+ */
+struct quic_pnspace {
+	/* ECN counters indexed by dir and ECN codepoint (ECT1, ECT0, CE) */
+	u64 ecn_count[QUIC_ECN_DIR_MAX][QUIC_ECN_MAX];
+	unsigned long *pn_map; /* Received PN bitmap for ACK generation */
+	u16 pn_map_len;        /* Length of the PN bit map (in bits) */
+	u8  need_sack;         /* Flag indicating a SACK frame should be sent */
+	u8  sack_path;         /* Path used for sending the SACK frame */
+	u8  sack_pending;      /* Delayed ACK pending */
+
+	s64 last_max_pn_seen; /* Largest PN seen before pn_map advance */
+	u64 last_max_pn_time; /* Timestamp last_max_pn_seen was received */
+	s64 min_pn_seen;      /* Smallest PN received */
+	s64 max_pn_seen;      /* Largest PN received */
+	u64 max_pn_time;      /* Timestamp max_pn_seen was received */
+	s64 base_pn; /* PN corresponding to the start of the pn_map */
+	u64 time;    /* Cached now, or latest socket accept timestamp */
+
+	s64 max_pn_acked_seen; /* Largest PN ACKed by peer */
+	u64 max_pn_acked_time; /* Timestamp max_pn_acked_seen was ACKed */
+	u64 last_sent_time;    /* Timestamp last ack-eliciting packet sent */
+	u64 loss_time;         /* Timestamp the packet can be declared lost */
+	s64 next_pn;           /* Next PN to send */
+
+	u32 max_time_limit; /* Time threshold to trigger pn_map advance */
+	u32 inflight;       /* Ack-eliciting bytes in flight */
+};
+
+static inline void
+quic_pnspace_set_max_pn_acked_seen(struct quic_pnspace *space,
+				   s64 max_pn_acked_seen)
+{
+	if (space->max_pn_acked_seen >= max_pn_acked_seen)
+		return;
+	space->max_pn_acked_seen = max_pn_acked_seen;
+	space->max_pn_acked_time = quic_ktime_get_us();
+}
+
+static inline void quic_pnspace_set_base_pn(struct quic_pnspace *space, s64 pn)
+{
+	space->base_pn = pn;
+	space->max_pn_seen = space->base_pn - 1;
+	space->last_max_pn_seen = space->max_pn_seen;
+	space->min_pn_seen = space->max_pn_seen;
+
+	space->max_pn_time = space->time;
+	space->last_max_pn_time = space->max_pn_time;
+}
+
+static inline bool quic_pnspace_has_gap(const struct quic_pnspace *space)
+{
+	return space->base_pn != space->max_pn_seen + 1;
+}
+
+static inline void quic_pnspace_inc_ecn_acked(struct quic_pnspace *space,
+					      u8 ecn)
+{
+	if (!ecn)
+		return;
+	space->ecn_count[QUIC_ECN_ACKED][ecn - 1]++;
+}
+
+static inline void quic_pnspace_reset_ecn_acked(struct quic_pnspace *space)
+{
+	space->ecn_count[QUIC_ECN_ACKED][QUIC_ECN_ECT0] = 0;
+	space->ecn_count[QUIC_ECN_ACKED][QUIC_ECN_ECT1] = 0;
+	space->ecn_count[QUIC_ECN_ACKED][QUIC_ECN_CE] = 0;
+}
+
+static inline void quic_pnspace_inc_ecn_local(struct quic_pnspace *space,
+					      u8 ecn)
+{
+	if (!ecn)
+		return;
+	space->ecn_count[QUIC_ECN_LOCAL][ecn - 1]++;
+}
+
+/* Check if any ECN-marked packets were received. */
+static inline bool quic_pnspace_has_ecn_local(struct quic_pnspace *space)
+{
+	return space->ecn_count[QUIC_ECN_LOCAL][QUIC_ECN_ECT0] ||
+	       space->ecn_count[QUIC_ECN_LOCAL][QUIC_ECN_ECT1] ||
+	       space->ecn_count[QUIC_ECN_LOCAL][QUIC_ECN_CE];
+}
+
+/* Validate ECN counts received in an ACK. */
+static inline bool quic_pnspace_validate_ecn(struct quic_pnspace *space,
+					     u64 *ecn_count)
+{
+	u64 *acked = space->ecn_count[QUIC_ECN_ACKED];
+	u64 *peer = space->ecn_count[QUIC_ECN_PEER];
+	u64 ect0, ect1, ce;
+
+	if (peer[QUIC_ECN_ECT0] > ecn_count[QUIC_ECN_ECT0] ||
+	    peer[QUIC_ECN_ECT1] > ecn_count[QUIC_ECN_ECT1] ||
+	    peer[QUIC_ECN_CE] > ecn_count[QUIC_ECN_CE])
+		return false;
+
+	/* rfc9000#section-13.4.2.1:
+	 *
+	 * ECN validation also fails if the sum of the increase in ECT(0) and
+	 * ECN-CE counts is less than the number of newly acknowledged packets
+	 * that were originally sent with an ECT(0) marking (Same for ECT(1)).
+	 */
+	ect0 = ecn_count[QUIC_ECN_ECT0] - peer[QUIC_ECN_ECT0];
+	ect1 = ecn_count[QUIC_ECN_ECT1] - peer[QUIC_ECN_ECT1];
+	ce = ecn_count[QUIC_ECN_CE] - peer[QUIC_ECN_CE];
+
+	return ect0 + ce >= acked[QUIC_ECN_ECT0] &&
+	       ect1 + ce >= acked[QUIC_ECN_ECT1];
+}
+
+/* Updates the stored ECN counters based on values received in the peer's ACK
+ * frame. Each counter is updated only if the new value is higher.
+ *
+ * Returns: true if CE count was increased (congestion indicated), false
+ * otherwise.
+ */
+static inline bool quic_pnspace_set_ecn_peer(struct quic_pnspace *space,
+					     u64 *ecn_count)
+{
+	u64 *count = space->ecn_count[QUIC_ECN_PEER];
+
+	if (count[QUIC_ECN_ECT0] < ecn_count[QUIC_ECN_ECT0])
+		count[QUIC_ECN_ECT0] = ecn_count[QUIC_ECN_ECT0];
+	if (count[QUIC_ECN_ECT1] < ecn_count[QUIC_ECN_ECT1])
+		count[QUIC_ECN_ECT1] = ecn_count[QUIC_ECN_ECT1];
+	if (count[QUIC_ECN_CE] < ecn_count[QUIC_ECN_CE]) {
+		count[QUIC_ECN_CE] = ecn_count[QUIC_ECN_CE];
+		return true;
+	}
+	return false;
+}
+
+u16 quic_pnspace_num_gabs(struct quic_pnspace *space,
+			  struct quic_gap_ack_block *gabs);
+int quic_pnspace_check(struct quic_pnspace *space, s64 pn);
+int quic_pnspace_mark(struct quic_pnspace *space, s64 pn, gfp_t gfp);
+
+void quic_pnspace_free(struct quic_pnspace *space);
+int quic_pnspace_init(struct quic_pnspace *space);
diff --git a/net/quic/protocol.c b/net/quic/protocol.c
new file mode 100644
index 00000000000000..1ad95f9c9250c2
--- /dev/null
+++ b/net/quic/protocol.c
@@ -0,0 +1,417 @@
+// 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
+ *
+ * Initialization/cleanup for QUIC protocol support.
+ *
+ * Written or modified by:
+ *    Xin Long <lucien.xin@gmail.com>
+ */
+
+#include <net/inet_common.h>
+#include <linux/proc_fs.h>
+#include <net/protocol.h>
+#include <net/rps.h>
+#include <net/tls.h>
+
+#include "socket.h"
+
+static unsigned int quic_net_id __read_mostly;
+
+struct percpu_counter quic_sockets_allocated;
+struct workqueue_struct *quic_wq;
+
+DEFINE_STATIC_KEY_FALSE(quic_alpn_demux_key);
+
+long sysctl_quic_mem[3];
+int sysctl_quic_rmem[3];
+int sysctl_quic_wmem[3];
+
+static int quic_inet_connect(struct socket *sock, struct sockaddr_unsized *addr,
+			     int addr_len, int flags)
+{
+	struct sock *sk = sock->sk;
+
+	if (addr_len < (int)sizeof(addr->sa_family))
+		return -EINVAL;
+
+	return sk->sk_prot->connect(sk, addr, addr_len);
+}
+
+static int quic_inet_listen(struct socket *sock, int backlog)
+{
+	return -EOPNOTSUPP;
+}
+
+static int quic_inet_getname(struct socket *sock, struct sockaddr *uaddr,
+			     int peer)
+{
+	return quic_get_sk_addr(sock, uaddr, peer);
+}
+
+static __poll_t quic_inet_poll(struct file *file, struct socket *sock,
+			       poll_table *wait)
+{
+	return 0;
+}
+
+struct quic_net *quic_net(struct net *net)
+{
+	return net_generic(net, quic_net_id);
+}
+
+#if IS_ENABLED(CONFIG_PROC_FS)
+static const struct snmp_mib quic_snmp_list[] = {
+	SNMP_MIB_ITEM("QuicConnCurrentEstabs", QUIC_MIB_CONN_CURRENTESTABS),
+	SNMP_MIB_ITEM("QuicConnPassiveEstabs", QUIC_MIB_CONN_PASSIVEESTABS),
+	SNMP_MIB_ITEM("QuicConnActiveEstabs", QUIC_MIB_CONN_ACTIVEESTABS),
+	SNMP_MIB_ITEM("QuicPktRcvFastpaths", QUIC_MIB_PKT_RCVFASTPATHS),
+	SNMP_MIB_ITEM("QuicPktDecFastpaths", QUIC_MIB_PKT_DECFASTPATHS),
+	SNMP_MIB_ITEM("QuicPktEncFastpaths", QUIC_MIB_PKT_ENCFASTPATHS),
+	SNMP_MIB_ITEM("QuicPktRcvBacklogs", QUIC_MIB_PKT_RCVBACKLOGS),
+	SNMP_MIB_ITEM("QuicPktDecBacklogs", QUIC_MIB_PKT_DECBACKLOGS),
+	SNMP_MIB_ITEM("QuicPktEncBacklogs", QUIC_MIB_PKT_ENCBACKLOGS),
+	SNMP_MIB_ITEM("QuicPktInvHdrDrop", QUIC_MIB_PKT_INVHDRDROP),
+	SNMP_MIB_ITEM("QuicPktInvNumDrop", QUIC_MIB_PKT_INVNUMDROP),
+	SNMP_MIB_ITEM("QuicPktInvFrmDrop", QUIC_MIB_PKT_INVFRMDROP),
+	SNMP_MIB_ITEM("QuicPktRcvDrop", QUIC_MIB_PKT_RCVDROP),
+	SNMP_MIB_ITEM("QuicPktDecDrop", QUIC_MIB_PKT_DECDROP),
+	SNMP_MIB_ITEM("QuicPktEncDrop", QUIC_MIB_PKT_ENCDROP),
+	SNMP_MIB_ITEM("QuicFrmRcvBufDrop", QUIC_MIB_FRM_RCVBUFDROP),
+	SNMP_MIB_ITEM("QuicFrmRetrans", QUIC_MIB_FRM_RETRANS),
+	SNMP_MIB_ITEM("QuicFrmOutCloses", QUIC_MIB_FRM_OUTCLOSES),
+	SNMP_MIB_ITEM("QuicFrmInCloses", QUIC_MIB_FRM_INCLOSES),
+};
+
+static int quic_snmp_seq_show(struct seq_file *seq, void *v)
+{
+	unsigned long buff[ARRAY_SIZE(quic_snmp_list)];
+	const int cnt = ARRAY_SIZE(quic_snmp_list);
+	struct net *net = seq->private;
+	u32 idx;
+
+	memset(buff, 0, sizeof(buff));
+
+	snmp_get_cpu_field_batch_cnt(buff, quic_snmp_list, cnt,
+				     quic_net(net)->stat);
+	for (idx = 0; idx < cnt; idx++)
+		seq_printf(seq, "%-32s\t%lu\n", quic_snmp_list[idx].name,
+			   buff[idx]);
+
+	return 0;
+}
+
+static int quic_net_proc_init(struct net *net)
+{
+	quic_net(net)->proc_net = proc_net_mkdir(net, "quic", net->proc_net);
+	if (!quic_net(net)->proc_net)
+		return -ENOMEM;
+
+	if (!proc_create_net_single("snmp", 0444, quic_net(net)->proc_net,
+				    quic_snmp_seq_show, NULL))
+		goto free;
+	return 0;
+free:
+	remove_proc_subtree("quic", net->proc_net);
+	quic_net(net)->proc_net = NULL;
+	return -ENOMEM;
+}
+
+static void quic_net_proc_exit(struct net *net)
+{
+	remove_proc_subtree("quic", net->proc_net);
+	quic_net(net)->proc_net = NULL;
+}
+#endif
+
+static const struct proto_ops quic_proto_ops = {
+	.family		   = PF_INET,
+	.owner		   = THIS_MODULE,
+	.release	   = inet_release,
+	.bind		   = inet_bind,
+	.connect	   = quic_inet_connect,
+	.socketpair	   = sock_no_socketpair,
+	.accept		   = inet_accept,
+	.getname	   = quic_inet_getname,
+	.poll		   = quic_inet_poll,
+	.ioctl		   = inet_ioctl,
+	.gettstamp	   = sock_gettstamp,
+	.listen		   = quic_inet_listen,
+	.shutdown	   = inet_shutdown,
+	.setsockopt	   = sock_common_setsockopt,
+	.getsockopt	   = sock_common_getsockopt,
+	.sendmsg	   = inet_sendmsg,
+	.recvmsg	   = inet_recvmsg,
+	.mmap		   = sock_no_mmap,
+};
+
+static struct inet_protosw quic_stream_protosw = {
+	.type       = SOCK_STREAM,
+	.protocol   = IPPROTO_QUIC,
+	.prot       = &quic_prot,
+	.ops        = &quic_proto_ops,
+};
+
+static struct inet_protosw quic_dgram_protosw = {
+	.type       = SOCK_DGRAM,
+	.protocol   = IPPROTO_QUIC,
+	.prot       = &quic_prot,
+	.ops        = &quic_proto_ops,
+};
+
+static const struct proto_ops quicv6_proto_ops = {
+	.family		   = PF_INET6,
+	.owner		   = THIS_MODULE,
+	.release	   = inet6_release,
+	.bind		   = inet6_bind,
+	.connect	   = quic_inet_connect,
+	.socketpair	   = sock_no_socketpair,
+	.accept		   = inet_accept,
+	.getname	   = quic_inet_getname,
+	.poll		   = quic_inet_poll,
+	.ioctl		   = inet6_ioctl,
+	.gettstamp	   = sock_gettstamp,
+	.listen		   = quic_inet_listen,
+	.shutdown	   = inet_shutdown,
+	.setsockopt	   = sock_common_setsockopt,
+	.getsockopt	   = sock_common_getsockopt,
+	.sendmsg	   = inet_sendmsg,
+	.recvmsg	   = inet_recvmsg,
+	.mmap		   = sock_no_mmap,
+};
+
+static struct inet_protosw quicv6_stream_protosw = {
+	.type       = SOCK_STREAM,
+	.protocol   = IPPROTO_QUIC,
+	.prot       = &quicv6_prot,
+	.ops        = &quicv6_proto_ops,
+};
+
+static struct inet_protosw quicv6_dgram_protosw = {
+	.type       = SOCK_DGRAM,
+	.protocol   = IPPROTO_QUIC,
+	.prot       = &quicv6_prot,
+	.ops        = &quicv6_proto_ops,
+};
+
+static int quic_protosw_init(void)
+{
+	int err;
+
+	err = proto_register(&quic_prot, 1);
+	if (err)
+		return err;
+
+	err = proto_register(&quicv6_prot, 1);
+	if (err) {
+		proto_unregister(&quic_prot);
+		return err;
+	}
+
+	inet_register_protosw(&quic_stream_protosw);
+	inet_register_protosw(&quic_dgram_protosw);
+	inet6_register_protosw(&quicv6_stream_protosw);
+	inet6_register_protosw(&quicv6_dgram_protosw);
+
+	return 0;
+}
+
+static void quic_protosw_exit(void)
+{
+	inet_unregister_protosw(&quic_dgram_protosw);
+	inet_unregister_protosw(&quic_stream_protosw);
+	proto_unregister(&quic_prot);
+
+	inet6_unregister_protosw(&quicv6_dgram_protosw);
+	inet6_unregister_protosw(&quicv6_stream_protosw);
+	proto_unregister(&quicv6_prot);
+}
+
+static int __net_init quic_net_init(struct net *net)
+{
+	struct quic_net *qn = quic_net(net);
+	int err;
+
+	qn->stat = alloc_percpu(struct quic_mib);
+	if (!qn->stat)
+		return -ENOMEM;
+
+	err = quic_crypto_set_cipher(&qn->crypto, TLS_CIPHER_AES_GCM_128);
+	if (err) {
+		free_percpu(qn->stat);
+		qn->stat = NULL;
+		return err;
+	}
+
+	INIT_WORK(&qn->work, quic_packet_backlog_work);
+	skb_queue_head_init(&qn->backlog_list);
+
+#if IS_ENABLED(CONFIG_PROC_FS)
+	err = quic_net_proc_init(net);
+	if (err) {
+		quic_crypto_free(&qn->crypto);
+		free_percpu(qn->stat);
+		qn->stat = NULL;
+	}
+#endif
+	return err;
+}
+
+static void __net_exit quic_net_exit(struct net *net)
+{
+	struct quic_net *qn = quic_net(net);
+
+#if IS_ENABLED(CONFIG_PROC_FS)
+	quic_net_proc_exit(net);
+#endif
+	disable_work_sync(&qn->work);
+	skb_queue_purge(&qn->backlog_list);
+	quic_crypto_free(&qn->crypto);
+	free_percpu(qn->stat);
+	qn->stat = NULL;
+}
+
+static struct pernet_operations quic_net_ops = {
+	.init = quic_net_init,
+	.exit = quic_net_exit,
+	.id   = &quic_net_id,
+	.size = sizeof(struct quic_net),
+};
+
+#if IS_ENABLED(CONFIG_SYSCTL)
+static struct ctl_table_header *quic_sysctl_header;
+
+static struct ctl_table quic_table[] = {
+	{
+		.procname	= "quic_mem",
+		.data		= &sysctl_quic_mem,
+		.maxlen		= sizeof(sysctl_quic_mem),
+		.mode		= 0644,
+		.proc_handler	= proc_doulongvec_minmax
+	},
+	{
+		.procname	= "quic_rmem",
+		.data		= &sysctl_quic_rmem,
+		.maxlen		= sizeof(sysctl_quic_rmem),
+		.mode		= 0644,
+		.proc_handler	= proc_dointvec_minmax,
+		.extra1		= SYSCTL_ONE,
+	},
+	{
+		.procname	= "quic_wmem",
+		.data		= &sysctl_quic_wmem,
+		.maxlen		= sizeof(sysctl_quic_wmem),
+		.mode		= 0644,
+		.proc_handler	= proc_dointvec_minmax,
+		.extra1		= SYSCTL_ONE,
+	},
+};
+
+static void quic_sysctl_register(void)
+{
+	quic_sysctl_header = register_net_sysctl(&init_net, "net/quic",
+						 quic_table);
+}
+
+static void quic_sysctl_unregister(void)
+{
+	unregister_net_sysctl_table(quic_sysctl_header);
+}
+#endif
+
+static __init int quic_init(void)
+{
+	int max_share, err = -ENOMEM;
+	unsigned long limit;
+
+	BUILD_BUG_ON(sizeof(struct quic_skb_cb) >
+		     sizeof_field(struct sk_buff, cb));
+
+	/* Set QUIC memory limits based on available system memory, similar to
+	 * sctp_init().
+	 */
+	limit = nr_free_buffer_pages() / 8;
+	limit = max(limit, 128UL);
+	sysctl_quic_mem[0] = (long)limit / 4 * 3;
+	sysctl_quic_mem[1] = (long)limit;
+	sysctl_quic_mem[2] = sysctl_quic_mem[0] * 2;
+
+	limit = (sysctl_quic_mem[1]) << (PAGE_SHIFT - 7);
+	max_share = min(4UL * 1024 * 1024, limit);
+
+	sysctl_quic_rmem[0] = PAGE_SIZE;
+	sysctl_quic_rmem[1] = 1024 * 1024;
+	sysctl_quic_rmem[2] = max(sysctl_quic_rmem[1], max_share);
+
+	sysctl_quic_wmem[0] = PAGE_SIZE;
+	sysctl_quic_wmem[1] = 16 * 1024;
+	sysctl_quic_wmem[2] = max(64 * 1024, max_share);
+
+	err = percpu_counter_init(&quic_sockets_allocated, 0, GFP_KERNEL);
+	if (err)
+		goto err_percpu_counter;
+
+	err = quic_hash_tables_init();
+	if (err)
+		goto err_hash;
+
+	/* Allocate an unbound workqueue for UDP socket destruction and backlog
+	 * packet processing.
+	 */
+	quic_wq = alloc_workqueue("quic_workqueue", WQ_UNBOUND, 0);
+	if (!quic_wq) {
+		err = -ENOMEM;
+		goto err_wq;
+	}
+
+	err = register_pernet_subsys(&quic_net_ops);
+	if (err)
+		goto err_def_ops;
+
+	err = quic_protosw_init();
+	if (err)
+		goto err_protosw;
+
+#if IS_ENABLED(CONFIG_SYSCTL)
+	quic_sysctl_register();
+#endif
+	pr_info("quic: init\n");
+	return 0;
+
+err_protosw:
+	unregister_pernet_subsys(&quic_net_ops);
+err_def_ops:
+	destroy_workqueue(quic_wq);
+err_wq:
+	quic_hash_tables_destroy();
+err_hash:
+	percpu_counter_destroy(&quic_sockets_allocated);
+err_percpu_counter:
+	return err;
+}
+
+static __exit void quic_exit(void)
+{
+#if IS_ENABLED(CONFIG_SYSCTL)
+	quic_sysctl_unregister();
+#endif
+	quic_protosw_exit();
+	unregister_pernet_subsys(&quic_net_ops);
+	flush_workqueue(quic_wq);
+	destroy_workqueue(quic_wq);
+	quic_hash_tables_destroy();
+	percpu_counter_destroy(&quic_sockets_allocated);
+	rcu_barrier();
+	pr_info("quic: exit\n");
+}
+
+module_init(quic_init);
+module_exit(quic_exit);
+
+MODULE_ALIAS_NET_PF_PROTO(PF_INET, 261); /* IPPROTO_QUIC == 261 */
+MODULE_ALIAS_NET_PF_PROTO(PF_INET6, 261);
+MODULE_AUTHOR("Xin Long <lucien.xin@gmail.com>");
+MODULE_DESCRIPTION("Support for the QUIC protocol (RFC9000)");
+MODULE_LICENSE("GPL");
diff --git a/net/quic/protocol.h b/net/quic/protocol.h
new file mode 100644
index 00000000000000..25001aaaad4a51
--- /dev/null
+++ b/net/quic/protocol.h
@@ -0,0 +1,63 @@
+/* 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>
+ */
+
+extern struct percpu_counter quic_sockets_allocated;
+
+DECLARE_STATIC_KEY_FALSE(quic_alpn_demux_key);
+
+extern long sysctl_quic_mem[3];
+extern int sysctl_quic_rmem[3];
+extern int sysctl_quic_wmem[3];
+
+enum {
+	QUIC_MIB_NUM = 0,
+	QUIC_MIB_CONN_CURRENTESTABS, /* Current established connections */
+	QUIC_MIB_CONN_PASSIVEESTABS, /* Passively established connections */
+	QUIC_MIB_CONN_ACTIVEESTABS,  /* Actively established connections */
+	QUIC_MIB_PKT_RCVFASTPATHS,   /* Packets received on fast path */
+	QUIC_MIB_PKT_DECFASTPATHS,   /* Packets decrypted on fast path */
+	QUIC_MIB_PKT_ENCFASTPATHS,   /* Packets encrypted on fast path */
+	QUIC_MIB_PKT_RCVBACKLOGS,    /* Packets processed via backlog */
+	QUIC_MIB_PKT_DECBACKLOGS,    /* Packets decrypted in backlog */
+	QUIC_MIB_PKT_ENCBACKLOGS,    /* Packets encrypted in backlog */
+	QUIC_MIB_PKT_INVHDRDROP,     /* Dropped: invalid packet header */
+	QUIC_MIB_PKT_INVNUMDROP,     /* Dropped: invalid packet number */
+	QUIC_MIB_PKT_INVFRMDROP,     /* Dropped: invalid frame */
+	QUIC_MIB_PKT_RCVDROP,        /* Dropped on receive (general) */
+	QUIC_MIB_PKT_DECDROP,        /* Dropped: decryption failure */
+	QUIC_MIB_PKT_ENCDROP,        /* Dropped: encryption failure */
+	QUIC_MIB_FRM_RCVBUFDROP,     /* Frames dropped: recv buf limit */
+	QUIC_MIB_FRM_RETRANS,        /* Frames retransmitted */
+	QUIC_MIB_FRM_OUTCLOSES,      /* CONNECTION_CLOSE frames sent */
+	QUIC_MIB_FRM_INCLOSES,       /* CONNECTION_CLOSE frames rcvd */
+	QUIC_MIB_MAX
+};
+
+struct quic_mib {
+	unsigned long mibs[QUIC_MIB_MAX]; /* Counters indexed by QUIC_MIB_* */
+};
+
+struct quic_net {
+	DEFINE_SNMP_STAT(struct quic_mib, stat); /* Per-net QUIC MIB stats */
+#if IS_ENABLED(CONFIG_PROC_FS)
+	struct proc_dir_entry *proc_net; /* procfs entry for QUIC stats */
+#endif
+	/* Context for decrypting Initial packets for ALPN */
+	struct quic_crypto crypto;
+
+	/* Queue of packets deferred for processing in process context */
+	struct sk_buff_head backlog_list;
+	struct work_struct work; /* Work to drain/process backlog_list */
+};
+
+struct quic_net *quic_net(struct net *net);
+
+#define QUIC_INC_STATS(net, field) SNMP_INC_STATS(quic_net(net)->stat, field)
+#define QUIC_DEC_STATS(net, field) SNMP_DEC_STATS(quic_net(net)->stat, field)
diff --git a/net/quic/socket.c b/net/quic/socket.c
new file mode 100644
index 00000000000000..ce1c86c0da3e0c
--- /dev/null
+++ b/net/quic/socket.c
@@ -0,0 +1,482 @@
+// 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
+ *
+ * Initialization/cleanup for QUIC protocol support.
+ *
+ * Written or modified by:
+ *    Xin Long <lucien.xin@gmail.com>
+ */
+
+#include <net/inet_common.h>
+#include <net/tls.h>
+
+#include "socket.h"
+
+static DEFINE_PER_CPU(int, quic_memory_per_cpu_fw_alloc);
+static unsigned long quic_memory_pressure;
+static atomic_long_t quic_memory_allocated;
+
+static void quic_enter_memory_pressure(struct sock *sk)
+{
+	WRITE_ONCE(quic_memory_pressure, 1);
+}
+
+/* Lookup a connected QUIC socket based on address and dest connection ID.
+ *
+ * This function searches the established (non-listening) QUIC socket table for
+ * a socket that matches the source and dest addresses and, optionally, the
+ * dest connection ID (DCID). The value returned by quic_path_orig_dcid() might
+ * be the original dest connection ID from the ClientHello or the Source
+ * Connection ID from a Retry packet before.
+ *
+ * The DCID is provided from a handshake packet when searching by source
+ * connection ID fails, such as when the peer has not yet received server's
+ * response and updated the DCID.
+ *
+ * Return: A pointer to the matching connected socket, or NULL if no match is
+ * found.
+ */
+struct sock *quic_sock_lookup(struct sk_buff *skb, union quic_addr *sa,
+			      union quic_addr *da, struct sock *usk,
+			      struct quic_conn_id *dcid)
+{
+	struct net *net = sock_net(usk);
+	struct quic_path_group *paths;
+	struct hlist_nulls_node *node;
+	struct quic_shash_head *head;
+	struct sock *sk = NULL, *tmp;
+	struct quic_conn_id *odcid;
+	unsigned int hash;
+
+	hash = quic_sock_hash(net, sa, da);
+	head = quic_sock_head(hash);
+
+	rcu_read_lock();
+begin:
+	sk_nulls_for_each_rcu(tmp, node, &head->head) {
+		if (net != sock_net(tmp))
+			continue;
+		paths = quic_paths(tmp);
+		odcid = quic_path_orig_dcid(paths);
+		if (quic_cmp_sk_addr(tmp, quic_path_saddr(paths, 0), sa) &&
+		    quic_cmp_sk_addr(tmp, quic_path_daddr(paths, 0), da) &&
+		    quic_path_usock(paths, 0) == usk &&
+		    (!dcid || !quic_conn_id_cmp(odcid, dcid))) {
+			sk = tmp;
+			break;
+		}
+	}
+	/* If the final nulls value differs from the expected one, restart the
+	 * lookup as the node may have been rehashed (e.g., due to connection
+	 * migration).
+	 */
+	if (!sk && get_nulls_value(node) != hash)
+		goto begin;
+
+	if (sk && unlikely(!refcount_inc_not_zero(&sk->sk_refcnt)))
+		sk = NULL;
+	rcu_read_unlock();
+	return sk;
+}
+
+/* Find the listening QUIC socket for an incoming packet.
+ *
+ * This function searches the QUIC socket table for a listening socket that
+ * matches the dest address and port, and the ALPN(s) if presented in the
+ * ClientHello.  If multiple listening sockets are bound to the same address,
+ * port, and ALPN(s) (e.g., via SO_REUSEPORT), this function selects a socket
+ * from the reuseport group.
+ *
+ * Return: A pointer to the matching listening socket, or NULL if no match is
+ * found.
+ */
+struct sock *quic_listen_sock_lookup(struct sk_buff *skb, union quic_addr *sa,
+				     union quic_addr *da,
+				     struct quic_data *alpns)
+{
+	struct net *net = sock_net(skb->sk);
+	struct hlist_nulls_node *node;
+	struct sock *sk = NULL, *tmp;
+	struct quic_shash_head *head;
+	struct quic_data alpn;
+	union quic_addr *a;
+	u32 hash, len;
+	u64 length;
+	u8 *p;
+
+	hash = quic_listen_sock_hash(net, ntohs(sa->v4.sin_port));
+	head = quic_listen_sock_head(hash);
+
+	rcu_read_lock();
+	if (!alpns->len) { /* No ALPNs or parse failed */
+		sk_nulls_for_each_rcu(tmp, node, &head->head) {
+			/* If alpns->data != NULL, TLS parsing succeeded but no
+			 * ALPN was found.  In this case, only match sockets
+			 * that have no ALPN set.
+			 */
+			a = quic_path_saddr(quic_paths(tmp), 0);
+			if (net == sock_net(tmp) &&
+			    quic_cmp_sk_addr(tmp, a, sa) &&
+			    quic_path_usock(quic_paths(tmp), 0) == skb->sk &&
+			    (!alpns->data || !quic_alpn(tmp)->len)) {
+				if (!quic_is_any_addr(a)) {
+					sk = tmp;
+					break; /* Prefer specific addr match. */
+				}
+				/* Prefer ipv4 ANY over ipv6 ANY for v4 addr. */
+				if (!sk || a->sa.sa_family == sa->sa.sa_family)
+					sk = tmp;
+			}
+		}
+		/* No need to check get_nulls_value(node) != hash for !sk, as
+		 * hashtable size is fixed and a listen sk can not rehashed.
+		 */
+		goto out;
+	}
+
+	/* ALPN present: loop through each ALPN entry. */
+	for (p = alpns->data, len = alpns->len; len;
+	     len -= length, p += length) {
+		quic_get_int(&p, &len, &length, 1);
+		quic_data(&alpn, p, length);
+		sk_nulls_for_each_rcu(tmp, node, &head->head) {
+			a = quic_path_saddr(quic_paths(tmp), 0);
+			if (net == sock_net(tmp) &&
+			    quic_cmp_sk_addr(tmp, a, sa) &&
+			    quic_path_usock(quic_paths(tmp), 0) == skb->sk &&
+			    quic_data_has(quic_alpn(tmp), &alpn)) {
+				if (!quic_is_any_addr(a)) {
+					sk = tmp;
+					break;
+				}
+				if (!sk || a->sa.sa_family == sa->sa.sa_family)
+					sk = tmp;
+			}
+		}
+		/* No need to check get_nulls_value(node) != hash for !sk, as
+		 * hashtable size is fixed and a listen sk can not rehashed.
+		 */
+		if (sk)
+			break;
+	}
+out:
+	if (sk && sk->sk_reuseport)
+		sk = reuseport_select_sock(sk, quic_addr_hash(net, da), skb, 1);
+
+	if (sk && unlikely(!refcount_inc_not_zero(&sk->sk_refcnt)))
+		sk = NULL;
+	rcu_read_unlock();
+	return sk;
+}
+
+static void quic_write_space(struct sock *sk)
+{
+	__poll_t mask = EPOLLOUT | EPOLLWRNORM | EPOLLWRBAND;
+	struct socket_wq *wq;
+
+	/* Do not check sock_writeable(). Also wakes stream-open waiters
+	 * blocked on stream limits, where sock_writeable() may be false.
+	 */
+	rcu_read_lock();
+	wq = rcu_dereference(sk->sk_wq);
+	if (skwq_has_sleeper(wq))
+		wake_up_interruptible_sync_poll(&wq->wait, mask);
+	sk_wake_async_rcu(sk, SOCK_WAKE_SPACE, POLL_OUT);
+	rcu_read_unlock();
+}
+
+static void quic_sock_destruct(struct sock *sk)
+{
+	u8 i;
+
+	/* Deferred crypto free for async encryption/decryption. */
+	for (i = 0; i < QUIC_CRYPTO_MAX; i++)
+		quic_crypto_free(quic_crypto(sk, i));
+
+	/* Deferred ALPN free for RCU readers in quic_listen_sock_lookup(). */
+	quic_data_free(quic_alpn(sk));
+
+	quic_sk_destruct(sk);
+}
+
+static int quic_init_sock(struct sock *sk)
+{
+	u8 i;
+
+	sk->sk_destruct = quic_sock_destruct;
+	sk->sk_write_space = quic_write_space;
+	sock_set_flag(sk, SOCK_USE_WRITE_QUEUE);
+
+	sk_sockets_allocated_inc(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);
+	quic_cong_init(quic_cong(sk));
+
+	quic_timer_init(sk);
+	quic_packet_init(sk);
+
+	if (quic_stream_init(quic_streams(sk)))
+		return -ENOMEM;
+
+	for (i = 0; i < QUIC_PNSPACE_MAX; i++) {
+		if (quic_pnspace_init(quic_pnspace(sk, i)))
+			return -ENOMEM;
+	}
+
+	return 0;
+}
+
+static void quic_destroy_sock(struct sock *sk)
+{
+	u8 i;
+
+	quic_timer_free(sk);
+
+	for (i = 0; i < QUIC_PNSPACE_MAX; i++)
+		quic_pnspace_free(quic_pnspace(sk, i));
+
+	quic_path_unbind(sk, quic_paths(sk), 0);
+	quic_path_unbind(sk, quic_paths(sk), 1);
+
+	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));
+	quic_data_free(quic_token(sk));
+
+	sk_sockets_allocated_dec(sk);
+	sock_prot_inuse_add(sock_net(sk), sk->sk_prot, -1);
+}
+
+static int quic_bind(struct sock *sk, struct sockaddr_unsized *addr,
+		     int addr_len)
+{
+	return -EOPNOTSUPP;
+}
+
+static int quic_connect(struct sock *sk, struct sockaddr_unsized *addr,
+			int addr_len)
+{
+	return -EOPNOTSUPP;
+}
+
+static int quic_hash(struct sock *sk)
+{
+	return 0;
+}
+
+static void quic_unhash(struct sock *sk)
+{
+}
+
+static int quic_sendmsg(struct sock *sk, struct msghdr *msg, size_t msg_len)
+{
+	return -EOPNOTSUPP;
+}
+
+static int quic_recvmsg(struct sock *sk, struct msghdr *msg, size_t len,
+			int flags)
+{
+	return -EOPNOTSUPP;
+}
+
+static struct sock *quic_accept(struct sock *sk, struct proto_accept_arg *arg)
+{
+	arg->err = -EOPNOTSUPP;
+	return NULL;
+}
+
+static void quic_close(struct sock *sk, long timeout)
+{
+	lock_sock(sk);
+
+	quic_set_state(sk, QUIC_SS_CLOSED);
+
+	release_sock(sk);
+
+	sk_common_release(sk);
+}
+
+/**
+ * quic_do_setsockopt - set a QUIC socket option
+ * @sk: socket to configure
+ * @optname: option name (QUIC-level)
+ * @optval: user buffer containing the option value
+ * @optlen: size of the option value
+ *
+ * Sets a QUIC socket option on a given socket.
+ *
+ * Return:
+ * - On success, 0 is returned.
+ * - On error, a negative error value is returned.
+ */
+int quic_do_setsockopt(struct sock *sk, int optname, sockptr_t optval,
+		       unsigned int optlen)
+{
+	return -EOPNOTSUPP;
+}
+EXPORT_SYMBOL_GPL(quic_do_setsockopt);
+
+static int quic_setsockopt(struct sock *sk, int level, int optname,
+			   sockptr_t optval, unsigned int optlen)
+{
+	if (level != SOL_QUIC)
+		return quic_common_setsockopt(sk, level, optname, optval,
+					      optlen);
+
+	return quic_do_setsockopt(sk, optname, optval, optlen);
+}
+
+/**
+ * quic_do_getsockopt - get a QUIC socket option
+ * @sk: socket to query
+ * @optname: option name (QUIC-level)
+ * @optval: user buffer to receive the option value
+ * @optlen: pointer to buffer size; updated with actual size on return
+ *
+ * Gets a QUIC socket option from a given socket.
+ *
+ * Return:
+ * - On success, 0 is returned.
+ * - On error, a negative error value is returned.
+ */
+int quic_do_getsockopt(struct sock *sk, int optname, sockptr_t optval,
+		       sockptr_t optlen)
+{
+	return -EOPNOTSUPP;
+}
+EXPORT_SYMBOL_GPL(quic_do_getsockopt);
+
+static int quic_getsockopt(struct sock *sk, int level, int optname,
+			   char __user *optval, int __user *optlen)
+{
+	if (level != SOL_QUIC)
+		return quic_common_getsockopt(sk, level, optname, optval,
+					      optlen);
+
+	return quic_do_getsockopt(sk, optname, USER_SOCKPTR(optval),
+				  USER_SOCKPTR(optlen));
+}
+
+static void quic_release_cb(struct sock *sk)
+{
+	/* Similar to tcp_release_cb(). */
+	unsigned long nflags, flags = smp_load_acquire(&sk->sk_tsq_flags);
+
+	do {
+		if (!(flags & QUIC_DEFERRED_ALL))
+			return;
+		nflags = flags & ~QUIC_DEFERRED_ALL;
+	} while (!try_cmpxchg(&sk->sk_tsq_flags, &flags, nflags));
+
+	if (flags & QUIC_F_MTU_REDUCED_DEFERRED) {
+		quic_packet_rcv_err_pmtu(sk);
+		__sock_put(sk);
+	}
+	if (flags & QUIC_F_LOSS_DEFERRED) {
+		quic_timer_loss_handler(sk);
+		__sock_put(sk);
+	}
+	if (flags & QUIC_F_SACK_DEFERRED) {
+		quic_timer_sack_handler(sk);
+		__sock_put(sk);
+	}
+	if (flags & QUIC_F_PATH_DEFERRED) {
+		quic_timer_path_handler(sk);
+		__sock_put(sk);
+	}
+	if (flags & QUIC_F_PMTU_DEFERRED) {
+		quic_timer_pmtu_handler(sk);
+		__sock_put(sk);
+	}
+	if (flags & QUIC_F_PACE_DEFERRED) {
+		quic_timer_pace_handler(sk);
+		__sock_put(sk);
+	}
+}
+
+static int quic_disconnect(struct sock *sk, int flags)
+{
+	return -EOPNOTSUPP;
+}
+
+static void quic_shutdown(struct sock *sk, int how)
+{
+	quic_set_state(sk, QUIC_SS_CLOSED);
+}
+
+static int quic_backlog_rcv(struct sock *sk, struct sk_buff *skb)
+{
+	return quic_packet_process(sk, skb, GFP_ATOMIC);
+}
+
+struct proto quic_prot = {
+	.name		=  "QUIC",
+	.owner		=  THIS_MODULE,
+	.init		=  quic_init_sock,
+	.destroy	=  quic_destroy_sock,
+	.shutdown	=  quic_shutdown,
+	.setsockopt	=  quic_setsockopt,
+	.getsockopt	=  quic_getsockopt,
+	.connect	=  quic_connect,
+	.bind		=  quic_bind,
+	.close		=  quic_close,
+	.disconnect	=  quic_disconnect,
+	.sendmsg	=  quic_sendmsg,
+	.recvmsg	=  quic_recvmsg,
+	.accept		=  quic_accept,
+	.hash		=  quic_hash,
+	.unhash		=  quic_unhash,
+	.backlog_rcv	=  quic_backlog_rcv,
+	.release_cb	=  quic_release_cb,
+	.no_autobind	=  true,
+	.obj_size	=  sizeof(struct quic_sock),
+	.sysctl_mem		=  sysctl_quic_mem,
+	.sysctl_rmem		=  sysctl_quic_rmem,
+	.sysctl_wmem		=  sysctl_quic_wmem,
+	.memory_pressure	=  &quic_memory_pressure,
+	.enter_memory_pressure	=  quic_enter_memory_pressure,
+	.memory_allocated	=  &quic_memory_allocated,
+	.per_cpu_fw_alloc	=  &quic_memory_per_cpu_fw_alloc,
+	.sockets_allocated	=  &quic_sockets_allocated,
+};
+
+struct proto quicv6_prot = {
+	.name		=  "QUICv6",
+	.owner		=  THIS_MODULE,
+	.init		=  quic_init_sock,
+	.destroy	=  quic_destroy_sock,
+	.shutdown	=  quic_shutdown,
+	.setsockopt	=  quic_setsockopt,
+	.getsockopt	=  quic_getsockopt,
+	.connect	=  quic_connect,
+	.bind		=  quic_bind,
+	.close		=  quic_close,
+	.disconnect	=  quic_disconnect,
+	.sendmsg	=  quic_sendmsg,
+	.recvmsg	=  quic_recvmsg,
+	.accept		=  quic_accept,
+	.hash		=  quic_hash,
+	.unhash		=  quic_unhash,
+	.backlog_rcv	=  quic_backlog_rcv,
+	.release_cb	=  quic_release_cb,
+	.no_autobind	=  true,
+	.obj_size	= sizeof(struct quic6_sock),
+	.ipv6_pinfo_offset	=  offsetof(struct quic6_sock, inet6),
+	.sysctl_mem		=  sysctl_quic_mem,
+	.sysctl_rmem		=  sysctl_quic_rmem,
+	.sysctl_wmem		=  sysctl_quic_wmem,
+	.memory_pressure	=  &quic_memory_pressure,
+	.enter_memory_pressure	=  quic_enter_memory_pressure,
+	.memory_allocated	=  &quic_memory_allocated,
+	.per_cpu_fw_alloc	=  &quic_memory_per_cpu_fw_alloc,
+	.sockets_allocated	=  &quic_sockets_allocated,
+};
diff --git a/net/quic/socket.h b/net/quic/socket.h
new file mode 100644
index 00000000000000..3c1bea767be9c8
--- /dev/null
+++ b/net/quic/socket.h
@@ -0,0 +1,209 @@
+/* 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>
+ */
+
+#include <net/udp_tunnel.h>
+#include <linux/quic.h>
+
+#include "common.h"
+#include "pnspace.h"
+#include "family.h"
+#include "stream.h"
+#include "connid.h"
+#include "crypto.h"
+#include "path.h"
+#include "cong.h"
+
+#include "packet.h"
+
+#include "protocol.h"
+#include "timer.h"
+
+extern struct proto quic_prot;
+extern struct proto quicv6_prot;
+
+enum quic_state {
+	QUIC_SS_CLOSED		= TCP_CLOSE,
+	QUIC_SS_LISTENING	= TCP_LISTEN,
+	QUIC_SS_ESTABLISHING	= TCP_SYN_RECV,
+	QUIC_SS_ESTABLISHED	= TCP_ESTABLISHED,
+};
+
+enum quic_tsq_enum {
+	QUIC_MTU_REDUCED_DEFERRED,
+	QUIC_LOSS_DEFERRED,
+	QUIC_SACK_DEFERRED,
+	QUIC_PATH_DEFERRED,
+	QUIC_PMTU_DEFERRED,
+	QUIC_PACE_DEFERRED,
+};
+
+enum quic_tsq_flags {
+	QUIC_F_MTU_REDUCED_DEFERRED	= BIT(QUIC_MTU_REDUCED_DEFERRED),
+	QUIC_F_LOSS_DEFERRED		= BIT(QUIC_LOSS_DEFERRED),
+	QUIC_F_SACK_DEFERRED		= BIT(QUIC_SACK_DEFERRED),
+	QUIC_F_PATH_DEFERRED		= BIT(QUIC_PATH_DEFERRED),
+	QUIC_F_PMTU_DEFERRED		= BIT(QUIC_PMTU_DEFERRED),
+	QUIC_F_PACE_DEFERRED		= BIT(QUIC_PACE_DEFERRED),
+};
+
+#define QUIC_DEFERRED_ALL (QUIC_F_MTU_REDUCED_DEFERRED |	\
+			   QUIC_F_LOSS_DEFERRED |		\
+			   QUIC_F_SACK_DEFERRED |		\
+			   QUIC_F_PATH_DEFERRED |		\
+			   QUIC_F_PMTU_DEFERRED |		\
+			   QUIC_F_PACE_DEFERRED)
+
+struct quic_sock {
+	struct inet_sock		inet;
+	struct list_head		reqs;
+
+	struct quic_data		ticket;
+	struct quic_data		token;
+	struct quic_data		alpn;
+
+	struct quic_stream_table	streams;
+	struct quic_conn_id_set		source;
+	struct quic_conn_id_set		dest;
+	struct quic_path_group		paths;
+	struct quic_cong		cong;
+	struct quic_pnspace		space[QUIC_PNSPACE_MAX];
+	struct quic_crypto		crypto[QUIC_CRYPTO_MAX];
+
+	struct quic_packet		packet;
+	struct quic_timer		timers[QUIC_TIMER_MAX];
+};
+
+struct quic6_sock {
+	struct quic_sock	quic;
+	struct ipv6_pinfo	inet6;
+};
+
+static inline struct quic_sock *quic_sk(const struct sock *sk)
+{
+	return (struct quic_sock *)sk;
+}
+
+static inline struct list_head *quic_reqs(const struct sock *sk)
+{
+	return &quic_sk(sk)->reqs;
+}
+
+static inline struct quic_data *quic_token(const struct sock *sk)
+{
+	return &quic_sk(sk)->token;
+}
+
+static inline struct quic_data *quic_ticket(const struct sock *sk)
+{
+	return &quic_sk(sk)->ticket;
+}
+
+static inline struct quic_data *quic_alpn(const struct sock *sk)
+{
+	return &quic_sk(sk)->alpn;
+}
+
+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 struct quic_path_group *quic_paths(const struct sock *sk)
+{
+	return &quic_sk(sk)->paths;
+}
+
+static inline bool quic_is_serv(const struct sock *sk)
+{
+	return !!sk->sk_max_ack_backlog;
+}
+
+static inline struct quic_cong *quic_cong(const struct sock *sk)
+{
+	return &quic_sk(sk)->cong;
+}
+
+static inline struct quic_pnspace *quic_pnspace(const struct sock *sk, u8 level)
+{
+	return &quic_sk(sk)->space[level % QUIC_CRYPTO_EARLY];
+}
+
+static inline struct quic_crypto *quic_crypto(const struct sock *sk, u8 level)
+{
+	return &quic_sk(sk)->crypto[level];
+}
+
+static inline struct quic_packet *quic_packet(const struct sock *sk)
+{
+	return &quic_sk(sk)->packet;
+}
+
+static inline void *quic_timer(const struct sock *sk, u8 type)
+{
+	return (void *)&quic_sk(sk)->timers[type];
+}
+
+static inline bool quic_is_establishing(struct sock *sk)
+{
+	return sk->sk_state == QUIC_SS_ESTABLISHING;
+}
+
+static inline bool quic_is_established(struct sock *sk)
+{
+	return sk->sk_state == QUIC_SS_ESTABLISHED;
+}
+
+static inline bool quic_is_listen(struct sock *sk)
+{
+	return sk->sk_state == QUIC_SS_LISTENING;
+}
+
+static inline bool quic_is_closed(struct sock *sk)
+{
+	return sk->sk_state == QUIC_SS_CLOSED;
+}
+
+static inline void quic_set_state(struct sock *sk, int state)
+{
+	struct net *net = sock_net(sk);
+	int mib;
+
+	if (sk->sk_state == state)
+		return;
+
+	if (state == QUIC_SS_ESTABLISHED) {
+		mib = quic_is_serv(sk) ? QUIC_MIB_CONN_PASSIVEESTABS :
+					 QUIC_MIB_CONN_ACTIVEESTABS;
+		QUIC_INC_STATS(net, mib);
+		QUIC_INC_STATS(net, QUIC_MIB_CONN_CURRENTESTABS);
+	} else if (quic_is_established(sk)) {
+		QUIC_DEC_STATS(net, QUIC_MIB_CONN_CURRENTESTABS);
+	}
+
+	inet_sk_set_state(sk, state);
+	sk->sk_state_change(sk);
+}
+
+struct sock *quic_listen_sock_lookup(struct sk_buff *skb, union quic_addr *sa,
+				     union quic_addr *da,
+				     struct quic_data *alpns);
+struct sock *quic_sock_lookup(struct sk_buff *skb, union quic_addr *sa,
+			      union quic_addr *da, struct sock *usk,
+			      struct quic_conn_id *dcid);
diff --git a/net/quic/stream.c b/net/quic/stream.c
new file mode 100644
index 00000000000000..6b34b117103a8e
--- /dev/null
+++ b/net/quic/stream.c
@@ -0,0 +1,416 @@
+// 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
+ *
+ * Initialization/cleanup for QUIC protocol support.
+ *
+ * Written or modified by:
+ *    Xin Long <lucien.xin@gmail.com>
+ */
+
+#include <linux/quic.h>
+
+#include "common.h"
+#include "stream.h"
+
+/* Check if a stream ID is valid for sending or receiving. */
+static bool quic_stream_id_valid(s64 stream_id, bool is_serv, bool send)
+{
+	u8 type = (stream_id & QUIC_STREAM_TYPE_MASK);
+
+	if (send) {
+		if (is_serv)
+			return type != QUIC_STREAM_TYPE_CLIENT_UNI;
+		return type != QUIC_STREAM_TYPE_SERVER_UNI;
+	}
+	if (is_serv)
+		return type != QUIC_STREAM_TYPE_SERVER_UNI;
+	return type != QUIC_STREAM_TYPE_CLIENT_UNI;
+}
+
+/* Check if a stream ID was initiated locally. */
+static bool quic_stream_id_local(s64 stream_id, bool is_serv)
+{
+	return is_serv ^ !(stream_id & QUIC_STREAM_TYPE_SERVER_MASK);
+}
+
+/* Check if a stream ID represents a unidirectional stream. */
+static bool quic_stream_id_uni(s64 stream_id)
+{
+	return stream_id & QUIC_STREAM_TYPE_UNI_MASK;
+}
+
+#define QUIC_STREAM_HT_SIZE	64
+
+static struct hlist_head *quic_stream_head(struct quic_stream_table *streams,
+					   s64 stream_id)
+{
+	/* Skip the SERVER initiator bit, which is constant per endpoint. */
+	return &streams->head[(stream_id >> 1) & (QUIC_STREAM_HT_SIZE - 1)];
+}
+
+struct quic_stream *quic_stream_find(struct quic_stream_table *streams,
+				     s64 stream_id)
+{
+	struct hlist_head *head = quic_stream_head(streams, stream_id);
+	struct quic_stream *stream;
+
+	hlist_for_each_entry(stream, head, node) {
+		if (stream->id == stream_id)
+			break;
+	}
+	return stream;
+}
+
+static void quic_stream_add(struct quic_stream_table *streams,
+			    struct quic_stream *stream)
+{
+	struct hlist_head *head;
+
+	head = quic_stream_head(streams, stream->id);
+	hlist_add_head(&stream->node, head);
+}
+
+static void quic_stream_delete(struct quic_stream *stream)
+{
+	hlist_del_init(&stream->node);
+	kfree(stream);
+}
+
+/* Create and register new streams for sending or receiving. */
+static struct quic_stream *quic_stream_create(struct quic_stream_table *streams,
+					      s64 max_stream_id, bool send,
+					      bool is_serv, gfp_t gfp)
+{
+	struct quic_stream *pos, *stream = NULL;
+	struct quic_stream_limits *limits;
+	struct hlist_node *tmp;
+	HLIST_HEAD(head);
+	s64 stream_id;
+	u32 count = 0;
+
+	gfp |= __GFP_ACCOUNT;
+	limits = send ? &streams->send : &streams->recv;
+	stream_id = limits->next_bidi_stream_id;
+	if (quic_stream_id_uni(max_stream_id))
+		stream_id = limits->next_uni_stream_id;
+
+	/* rfc9000#section-2.1: A stream ID that is used out of order results in
+	 * all streams of that type with lower-numbered stream IDs also being
+	 * opened.
+	 */
+	while (stream_id <= max_stream_id) {
+		stream = kzalloc_obj(*stream, gfp);
+		if (!stream)
+			goto free;
+
+		stream->id = stream_id;
+		if (quic_stream_id_uni(stream_id)) {
+			if (send) {
+				stream->send.max_bytes =
+					limits->max_stream_data_uni;
+			} else {
+				stream->recv.max_bytes =
+					limits->max_stream_data_uni;
+				stream->recv.window = stream->recv.max_bytes;
+			}
+			hlist_add_head(&stream->node, &head);
+			stream_id += QUIC_STREAM_ID_STEP;
+			continue;
+		}
+
+		if (quic_stream_id_local(stream_id, is_serv)) {
+			stream->send.max_bytes =
+				streams->send.max_stream_data_bidi_remote;
+			stream->recv.max_bytes =
+				streams->recv.max_stream_data_bidi_local;
+		} else {
+			stream->send.max_bytes =
+				streams->send.max_stream_data_bidi_local;
+			stream->recv.max_bytes =
+				streams->recv.max_stream_data_bidi_remote;
+		}
+		stream->recv.window = stream->recv.max_bytes;
+		hlist_add_head(&stream->node, &head);
+		stream_id += QUIC_STREAM_ID_STEP;
+	}
+
+	hlist_for_each_entry_safe(pos, tmp, &head, node) {
+		hlist_del_init(&pos->node);
+		quic_stream_add(streams, pos);
+		count++;
+	}
+
+	/* Streams must be opened sequentially. Update the next stream ID so the
+	 * correct starting point is known if an out-of-order open is requested.
+	 * Note overflow of next_uni/bidi_stream_id is impossible with s64.
+	 */
+	if (quic_stream_id_uni(stream_id)) {
+		limits->next_uni_stream_id = stream_id;
+		limits->streams_uni += count;
+		return stream;
+	}
+
+	limits->next_bidi_stream_id = stream_id;
+	limits->streams_bidi += count;
+	return stream;
+
+free:
+	hlist_for_each_entry_safe(pos, tmp, &head, node) {
+		hlist_del_init(&pos->node);
+		kfree(pos);
+	}
+	return NULL;
+}
+
+/* Check if a send or receive stream ID is already closed. */
+static bool quic_stream_id_closed(struct quic_stream_table *streams,
+				  s64 stream_id, bool send)
+{
+	struct quic_stream_limits *limits = send ? &streams->send :
+						   &streams->recv;
+
+	if (quic_stream_id_uni(stream_id))
+		return stream_id < limits->next_uni_stream_id;
+	return stream_id < limits->next_bidi_stream_id;
+}
+
+/* Check if a stream ID would exceed local (recv) or peer (send) limits. */
+bool quic_stream_id_exceeds(struct quic_stream_table *streams, s64 stream_id,
+			    bool send)
+{
+	u64 nstreams;
+
+	if (!send) {
+		/* recv.max_uni/bidi_stream_id is updated in
+		 * quic_stream_max_streams_update() already based on
+		 * next_uni/bidi_stream_id, max_streams_uni/bidi, and
+		 * streams_uni/bidi, so only recv.max_uni/bidi_stream_id needs
+		 * to be checked.
+		 */
+		if (quic_stream_id_uni(stream_id))
+			return stream_id > streams->recv.max_uni_stream_id;
+
+		return stream_id > streams->recv.max_bidi_stream_id;
+	}
+
+	if (quic_stream_id_uni(stream_id)) {
+		if (stream_id > streams->send.max_uni_stream_id)
+			return true;
+		stream_id -= streams->send.next_uni_stream_id;
+		nstreams = quic_stream_id_to_streams(stream_id);
+
+		return nstreams + streams->send.streams_uni >
+		       streams->send.max_streams_uni;
+	}
+
+	if (stream_id > streams->send.max_bidi_stream_id)
+		return true;
+	stream_id -= streams->send.next_bidi_stream_id;
+	nstreams = quic_stream_id_to_streams(stream_id);
+
+	return nstreams + streams->send.streams_bidi >
+	       streams->send.max_streams_bidi;
+}
+
+/* Get or create a send or recv stream by ID. Requires sock lock held. */
+struct quic_stream *quic_stream_get(struct quic_stream_table *streams,
+				    s64 stream_id, u32 flags, bool is_serv,
+				    bool send, gfp_t gfp)
+{
+	struct quic_stream *stream;
+
+	if (!quic_stream_id_valid(stream_id, is_serv, send))
+		return ERR_PTR(-EINVAL);
+
+	stream = quic_stream_find(streams, stream_id);
+	if (stream)
+		return stream;
+
+	if (!send && quic_stream_id_local(stream_id, is_serv)) {
+		if (quic_stream_id_closed(streams, stream_id, !send))
+			return ERR_PTR(-ENOSTR);
+		return ERR_PTR(-EINVAL);
+	}
+	if (send && !quic_stream_id_local(stream_id, is_serv))
+		return ERR_PTR(-EINVAL);
+
+	if (quic_stream_id_closed(streams, stream_id, send))
+		return ERR_PTR(-ENOSTR);
+
+	if (!(flags & MSG_QUIC_STREAM_NEW))
+		return ERR_PTR(-EINVAL);
+
+	if (quic_stream_id_exceeds(streams, stream_id, send))
+		return ERR_PTR(-EAGAIN);
+
+	stream = quic_stream_create(streams, stream_id, send, is_serv, gfp);
+	if (!stream)
+		return ERR_PTR(-ENOMEM);
+
+	if (send || quic_stream_id_valid(stream_id, is_serv, !send))
+		streams->send.active_stream_id = stream_id;
+
+	return stream;
+}
+
+/* Release or clean up a send or recv stream. This function updates stream
+ * counters and state when a send stream has either successfully sent all data
+ * or has been reset, or when a recv stream has either received all data or has
+ * been reset. Requires sock lock held.
+ */
+void quic_stream_put(struct quic_stream_table *streams,
+		     struct quic_stream *stream, bool is_serv, bool send)
+{
+	if (quic_stream_id_uni(stream->id)) {
+		/* For uni streams, decrement uni count and delete stream. */
+		if (send) {
+			streams->send.streams_uni--;
+			quic_stream_delete(stream);
+			return;
+		}
+		streams->recv.streams_uni--;
+		streams->recv.uni_pending = 1;
+		quic_stream_delete(stream);
+		return;
+	}
+
+	/* For bidi streams, proceed only if both send and receive in a final
+	 * state.
+	 */
+	if (send) {
+		if (stream->recv.state < QUIC_STREAM_RECV_STATE_RECVD)
+			return;
+	} else {
+		if (stream->send.state != QUIC_STREAM_SEND_STATE_RECVD &&
+		    stream->send.state != QUIC_STREAM_SEND_STATE_RESET_RECVD)
+			return;
+	}
+	if (quic_stream_id_local(stream->id, is_serv)) {
+		/* Local-initiated stream: decrement send.bidi count. */
+		streams->send.streams_bidi--;
+	} else {
+		/* Remote-initiated stream: decrement recv.bidi count. */
+		streams->recv.streams_bidi--;
+		streams->recv.bidi_pending = 1;
+	}
+	quic_stream_delete(stream);
+}
+
+/* Updates the maximum allowed incoming stream IDs if any streams were recently
+ * closed.  Recalculates the max_uni and max_bidi stream ID limits based on the
+ * number of open streams and whether any were marked for deletion.
+ *
+ * Returns true if either max_uni or max_bidi was updated, indicating that a
+ * MAX_STREAMS_UNI or MAX_STREAMS_BIDI frame should be sent to the peer.
+ */
+bool quic_stream_max_streams_update(struct quic_stream_table *streams,
+				    s64 *max_uni, s64 *max_bidi)
+{
+	s64 max, rem;
+
+	*max_uni = 0;
+	*max_bidi = 0;
+	if (streams->recv.uni_pending) {
+		rem = streams->recv.max_streams_uni - streams->recv.streams_uni;
+		max = streams->recv.next_uni_stream_id - QUIC_STREAM_ID_STEP +
+		      (rem << QUIC_STREAM_TYPE_BITS);
+
+		streams->recv.max_uni_stream_id = max;
+		*max_uni = quic_stream_id_to_streams(max);
+		streams->recv.uni_pending = 0;
+	}
+	if (streams->recv.bidi_pending) {
+		rem = streams->recv.max_streams_bidi -
+		      streams->recv.streams_bidi;
+		max = streams->recv.next_bidi_stream_id - QUIC_STREAM_ID_STEP +
+			(rem << QUIC_STREAM_TYPE_BITS);
+
+		streams->recv.max_bidi_stream_id = max;
+		*max_bidi = quic_stream_id_to_streams(max);
+		streams->recv.bidi_pending = 0;
+	}
+
+	return *max_uni || *max_bidi;
+}
+
+int quic_stream_init(struct quic_stream_table *streams)
+{
+	struct hlist_head *head;
+	int i;
+
+	head = kmalloc_objs(*head, QUIC_STREAM_HT_SIZE);
+	if (!head)
+		return -ENOMEM;
+	for (i = 0; i < QUIC_STREAM_HT_SIZE; i++)
+		INIT_HLIST_HEAD(&head[i]);
+	streams->head = head;
+	return 0;
+}
+
+void quic_stream_free(struct quic_stream_table *streams)
+{
+	struct quic_stream *stream;
+	struct hlist_head *head;
+	struct hlist_node *tmp;
+	int i;
+
+	if (!streams->head)
+		return;
+
+	for (i = 0; i < QUIC_STREAM_HT_SIZE; i++) {
+		head = &streams->head[i];
+		hlist_for_each_entry_safe(stream, tmp, head, node)
+			quic_stream_delete(stream);
+	}
+	kfree(streams->head);
+	streams->head = NULL;
+}
+
+/* Populate transport parameters from stream hash table. */
+void quic_stream_get_param(struct quic_stream_table *streams,
+			   struct quic_transport_param *p)
+{
+	struct quic_stream_limits *limits = p->remote ? &streams->send :
+							&streams->recv;
+
+	p->max_stream_data_bidi_remote = limits->max_stream_data_bidi_remote;
+	p->max_stream_data_bidi_local = limits->max_stream_data_bidi_local;
+	p->max_stream_data_uni = limits->max_stream_data_uni;
+	p->max_streams_bidi = limits->max_streams_bidi;
+	p->max_streams_uni = limits->max_streams_uni;
+}
+
+/* Configure stream hashtable from transport parameters. */
+void quic_stream_set_param(struct quic_stream_table *streams,
+			   struct quic_transport_param *p, bool is_serv)
+{
+	struct quic_stream_limits *limits = p->remote ? &streams->send :
+							&streams->recv;
+	u8 bidi_type, uni_type;
+
+	limits->max_stream_data_bidi_local = p->max_stream_data_bidi_local;
+	limits->max_stream_data_bidi_remote = p->max_stream_data_bidi_remote;
+	limits->max_stream_data_uni = p->max_stream_data_uni;
+	limits->max_streams_bidi = p->max_streams_bidi;
+	limits->max_streams_uni = p->max_streams_uni;
+	limits->active_stream_id = -1;
+
+	if (p->remote ^ is_serv) {
+		bidi_type = QUIC_STREAM_TYPE_CLIENT_BIDI;
+		uni_type = QUIC_STREAM_TYPE_CLIENT_UNI;
+	} else {
+		bidi_type = QUIC_STREAM_TYPE_SERVER_BIDI;
+		uni_type = QUIC_STREAM_TYPE_SERVER_UNI;
+	}
+
+	limits->max_bidi_stream_id =
+		quic_stream_streams_to_id(p->max_streams_bidi, bidi_type);
+	limits->next_bidi_stream_id = bidi_type;
+
+	limits->max_uni_stream_id =
+		quic_stream_streams_to_id(p->max_streams_uni, uni_type);
+	limits->next_uni_stream_id = uni_type;
+}
diff --git a/net/quic/stream.h b/net/quic/stream.h
new file mode 100644
index 00000000000000..d915712f3d5fb1
--- /dev/null
+++ b/net/quic/stream.h
@@ -0,0 +1,133 @@
+/* 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_DEF_STREAMS	100
+#define QUIC_MAX_STREAMS	4096ULL
+
+/*
+ * rfc9000#section-2.1:
+ *
+ *   The least significant bit (0x01) of the stream ID identifies the initiator
+ *   of the stream.  Client-initiated streams have even-numbered stream IDs
+ *   (with the bit set to 0), and server-initiated streams have odd-numbered
+ *   stream IDs (with the bit set to 1).
+ *
+ *   The second least significant bit (0x02) of the stream ID distinguishes
+ *   between bidirectional streams (with the bit set to 0) and unidirectional
+ *   streams (with the bit set to 1).
+ */
+#define QUIC_STREAM_TYPE_BITS	2
+#define QUIC_STREAM_ID_STEP	BIT(QUIC_STREAM_TYPE_BITS)
+
+#define QUIC_STREAM_TYPE_CLIENT_BIDI	0x00
+#define QUIC_STREAM_TYPE_SERVER_BIDI	0x01
+#define QUIC_STREAM_TYPE_CLIENT_UNI	0x02
+#define QUIC_STREAM_TYPE_SERVER_UNI	0x03
+
+struct quic_stream {
+	struct hlist_node node;
+	s64 id; /* Stream ID as defined in RFC 9000 Section 2.1 */
+	struct {
+		/* Sending-side stream level flow control */
+		u64 last_max_bytes; /* Max send offset advertised by peer */
+		u64 max_bytes;      /* Max offset allowed to send */
+		u64 bytes;          /* Bytes already sent to peer */
+
+		u32 errcode; /* App error code for RESET_STREAM */
+		u32 frags;   /* STREAM frames sent but not yet acked */
+		u8 state;    /* Send stream state, per rfc9000#section-3.1 */
+
+		u8 data_blocked; /* True if flow control blocks sending */
+	} send;
+	struct {
+		/* Receiving-side stream level flow control */
+		u64 max_bytes; /* Max offset peer can send */
+		u64 window;    /* Remaining receive window */
+		u64 bytes;     /* Bytes consumed by app */
+
+		u64 highest; /* Highest received offset */
+		u64 offset;  /* Data buffered or consumed */
+		u64 finalsz; /* Final stream size if FIN received */
+
+		u32 frags; /* STREAM frames pending reassembly */
+		u8 state;  /* Receive stream state, per rfc9000#section-3.2 */
+
+		u8 stop_sent; /* True if STOP_SENDING has been sent */
+	} recv;
+};
+
+struct quic_stream_limits {
+	/* Stream limit parameters defined in rfc9000#section-18.2:
+	 *
+	 * - initial_max_stream_data_bidi_remote
+	 * - initial_max_stream_data_bidi_local
+	 * - initial_max_stream_data_uni
+	 * - initial_max_streams_bidi
+	 * - initial_max_streams_uni
+	 */
+	u64 max_stream_data_bidi_remote;
+	u64 max_stream_data_bidi_local;
+	u64 max_stream_data_uni;
+	u64 max_streams_bidi;
+	u64 max_streams_uni;
+
+	s64 next_bidi_stream_id; /* Next bidi stream ID to open or accept */
+	s64 next_uni_stream_id;  /* Next uni stream ID to open or accept */
+	s64 max_bidi_stream_id;  /* Highest allowed bidi stream ID */
+	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_pending;  /* MAX_STREAMS_BIDI needs to be sent */
+	u8 uni_pending;   /* MAX_STREAMS_UNI needs to be sent */
+
+	u16 streams_bidi; /* Number of open bidi streams */
+	u16 streams_uni;  /* Number of open uni streams */
+};
+
+struct quic_stream_table {
+	struct hlist_head *head; /* Hash table storing all active streams */
+
+	struct quic_stream_limits send; /* Limits advertised by peer */
+	struct quic_stream_limits recv; /* Limits we advertise to peer */
+};
+
+static inline u64 quic_stream_id_to_streams(s64 stream_id)
+{
+	return (u64)(stream_id >> QUIC_STREAM_TYPE_BITS) + 1;
+}
+
+static inline s64 quic_stream_streams_to_id(u64 streams, u8 type)
+{
+	if (!streams)
+		return -1;
+	return (s64)((streams - 1) << QUIC_STREAM_TYPE_BITS) | type;
+}
+
+struct quic_stream *quic_stream_get(struct quic_stream_table *streams,
+				    s64 stream_id, u32 flags, bool is_serv,
+				    bool send, gfp_t gfp);
+void quic_stream_put(struct quic_stream_table *streams,
+		     struct quic_stream *stream, bool is_serv, bool send);
+
+bool quic_stream_max_streams_update(struct quic_stream_table *streams,
+				    s64 *max_uni, s64 *max_bidi);
+bool quic_stream_id_exceeds(struct quic_stream_table *streams,
+			    s64 stream_id, bool send);
+struct quic_stream *quic_stream_find(struct quic_stream_table *streams,
+				     s64 stream_id);
+
+void quic_stream_get_param(struct quic_stream_table *streams,
+			   struct quic_transport_param *p);
+void quic_stream_set_param(struct quic_stream_table *streams,
+			   struct quic_transport_param *p, bool is_serv);
+void quic_stream_free(struct quic_stream_table *streams);
+int quic_stream_init(struct quic_stream_table *streams);
diff --git a/net/quic/timer.c b/net/quic/timer.c
new file mode 100644
index 00000000000000..0dd6d6580bbd93
--- /dev/null
+++ b/net/quic/timer.c
@@ -0,0 +1,154 @@
+// 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
+ *
+ * Initialization/cleanup for QUIC protocol support.
+ *
+ * Written or modified by:
+ *    Xin Long <lucien.xin@gmail.com>
+ */
+
+#include "socket.h"
+
+static void quic_timer_timeout(struct quic_timer *t, int type, int defer_bit,
+			       void (*handler)(struct sock *sk))
+{
+	struct quic_sock *qs = container_of(t, struct quic_sock, timers[type]);
+	struct sock *sk = &qs->inet.sk;
+
+	bh_lock_sock(sk);
+	if (sock_owned_by_user(sk)) {
+		if (!test_and_set_bit(defer_bit, &sk->sk_tsq_flags))
+			sock_hold(sk);
+		goto out;
+	}
+
+	handler(sk);
+out:
+	bh_unlock_sock(sk);
+	sock_put(sk);
+}
+
+void quic_timer_sack_handler(struct sock *sk)
+{
+}
+
+static void quic_timer_sack_timeout(struct timer_list *t)
+{
+	quic_timer_timeout((struct quic_timer *)t, QUIC_TIMER_SACK,
+			   QUIC_SACK_DEFERRED, quic_timer_sack_handler);
+}
+
+void quic_timer_loss_handler(struct sock *sk)
+{
+}
+
+static void quic_timer_loss_timeout(struct timer_list *t)
+{
+	quic_timer_timeout((struct quic_timer *)t, QUIC_TIMER_LOSS,
+			   QUIC_LOSS_DEFERRED, quic_timer_loss_handler);
+}
+
+void quic_timer_path_handler(struct sock *sk)
+{
+}
+
+static void quic_timer_path_timeout(struct timer_list *t)
+{
+	quic_timer_timeout((struct quic_timer *)t, QUIC_TIMER_PATH,
+			   QUIC_PATH_DEFERRED, quic_timer_path_handler);
+}
+
+void quic_timer_pmtu_handler(struct sock *sk)
+{
+}
+
+static void quic_timer_pmtu_timeout(struct timer_list *t)
+{
+	quic_timer_timeout((struct quic_timer *)t, QUIC_TIMER_PMTU,
+			   QUIC_PMTU_DEFERRED, quic_timer_pmtu_handler);
+}
+
+void quic_timer_pace_handler(struct sock *sk)
+{
+}
+
+static enum hrtimer_restart quic_timer_pace_timeout(struct hrtimer *hr)
+{
+	quic_timer_timeout((struct quic_timer *)hr, QUIC_TIMER_PACE,
+			   QUIC_PACE_DEFERRED, quic_timer_pace_handler);
+	return HRTIMER_NORESTART;
+}
+
+void quic_timer_reset(struct sock *sk, u8 type, u64 timeout)
+{
+	struct timer_list *t = quic_timer(sk, type);
+
+	/* Note that type must never be QUIC_TIMER_PACE for this helper. */
+	if (WARN_ON_ONCE(type == QUIC_TIMER_PACE))
+		return;
+	if (timeout && !mod_timer(t, jiffies + usecs_to_jiffies(timeout)))
+		sock_hold(sk);
+}
+
+void quic_timer_start(struct sock *sk, u8 type, u64 timeout)
+{
+	struct timer_list *t;
+	struct hrtimer *hr;
+
+	if (type == QUIC_TIMER_PACE) {
+		hr = quic_timer(sk, type);
+
+		if (!hrtimer_is_queued(hr)) {
+			hrtimer_start(hr, ns_to_ktime(timeout),
+				      HRTIMER_MODE_ABS_PINNED_SOFT);
+			sock_hold(sk);
+		}
+		return;
+	}
+
+	t = quic_timer(sk, type);
+	if (timeout && !timer_pending(t)) {
+		if (!mod_timer(t, jiffies + usecs_to_jiffies(timeout)))
+			sock_hold(sk);
+	}
+}
+
+void quic_timer_stop(struct sock *sk, u8 type)
+{
+	if (type == QUIC_TIMER_PACE) {
+		if (hrtimer_try_to_cancel(quic_timer(sk, type)) == 1)
+			sock_put(sk);
+		return;
+	}
+	if (timer_delete(quic_timer(sk, type)))
+		sock_put(sk);
+}
+
+void quic_timer_init(struct sock *sk)
+{
+	timer_setup(quic_timer(sk, QUIC_TIMER_LOSS), quic_timer_loss_timeout,
+		    0);
+	timer_setup(quic_timer(sk, QUIC_TIMER_SACK), quic_timer_sack_timeout,
+		    0);
+	timer_setup(quic_timer(sk, QUIC_TIMER_PATH), quic_timer_path_timeout,
+		    0);
+	timer_setup(quic_timer(sk, QUIC_TIMER_PMTU), quic_timer_pmtu_timeout,
+		    0);
+	/* Use hrtimer for pace timer, ensuring precise control over send
+	 * timing.
+	 */
+	hrtimer_setup(quic_timer(sk, QUIC_TIMER_PACE), quic_timer_pace_timeout,
+		      CLOCK_MONOTONIC, HRTIMER_MODE_ABS_PINNED_SOFT);
+}
+
+void quic_timer_free(struct sock *sk)
+{
+	quic_timer_stop(sk, QUIC_TIMER_LOSS);
+	quic_timer_stop(sk, QUIC_TIMER_SACK);
+	quic_timer_stop(sk, QUIC_TIMER_PATH);
+	quic_timer_stop(sk, QUIC_TIMER_PMTU);
+	quic_timer_stop(sk, QUIC_TIMER_PACE);
+}
diff --git a/net/quic/timer.h b/net/quic/timer.h
new file mode 100644
index 00000000000000..4f63660376027a
--- /dev/null
+++ b/net/quic/timer.h
@@ -0,0 +1,45 @@
+/* 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>
+ */
+
+enum {
+	QUIC_TIMER_LOSS, /* Loss detection timer: retransmit on packet loss */
+	QUIC_TIMER_SACK, /* ACK delay timer, also used as idle timer alias */
+	QUIC_TIMER_PATH, /* Path validation timer: verifies path connectivity */
+	QUIC_TIMER_PMTU, /* PLPMTUD probing timer */
+	QUIC_TIMER_PACE, /* Pacing timer: controls packet transmission pacing */
+	QUIC_TIMER_MAX,
+	QUIC_TIMER_IDLE = QUIC_TIMER_SACK,
+};
+
+struct quic_timer {
+	union {
+		struct timer_list t;
+		struct hrtimer hr;
+	};
+};
+
+#define QUIC_MIN_PROBE_TIMEOUT	5000000
+
+#define QUIC_MIN_PATH_TIMEOUT	1500000
+
+#define QUIC_MIN_IDLE_TIMEOUT	1000000
+#define QUIC_DEF_IDLE_TIMEOUT	30000000
+
+void quic_timer_reset(struct sock *sk, u8 type, u64 timeout);
+void quic_timer_start(struct sock *sk, u8 type, u64 timeout);
+void quic_timer_stop(struct sock *sk, u8 type);
+void quic_timer_init(struct sock *sk);
+void quic_timer_free(struct sock *sk);
+
+void quic_timer_loss_handler(struct sock *sk);
+void quic_timer_pace_handler(struct sock *sk);
+void quic_timer_path_handler(struct sock *sk);
+void quic_timer_sack_handler(struct sock *sk);
+void quic_timer_pmtu_handler(struct sock *sk);
diff --git a/usr/include/Makefile b/usr/include/Makefile
index ee69dd9d970e53..8b4133d38da2d6 100644
--- a/usr/include/Makefile
+++ b/usr/include/Makefile
@@ -31,6 +31,7 @@ no-header-test += linux/omap3isp.h
 no-header-test += linux/omapfb.h
 no-header-test += linux/patchkey.h
 no-header-test += linux/phonet.h
+no-header-test += linux/quic.h
 no-header-test += linux/sctp.h
 no-header-test += linux/sysctl.h
 no-header-test += linux/usb/audio.h

3/2 2026/07/16 03:53 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 102667
							output: 20
							thoughts: 2475
						

Thoughts:
**QUIC Protocol Implementation Overview**

Okay, so I'm looking at this patch introducing QUIC. It's a significant undertaking, a brand new transport protocol! It seems pretty structured, with a dedicated `net/quic/` directory containing all the core components: Kconfig, Makefile, and modules for connection management, congestion control, crypto, and packet handling. My initial impression is that this is a solid, modular design, which is what I'd expect.

I need to confirm the entry points:  From what I can see, it registers itself within the kernel with `IPPROTO_QUIC`, so we can create sockets using `socket(AF_INET, SOCK_STREAM, IPPROTO_QUIC)` or `SOCK_DGRAM`.  The Kconfig entry (`IP_QUIC`) looks straightforward; it's got the usual dependencies.

I'm trying to get a feel for the core functionality. I'm focusing on the socket API operations and how packets flow through the stack. `quic_setsockopt`, `quic_getsockopt`, `quic_bind`, all seem stubbed out for now, which, looking at the code, appears to be the case. They are all returning `-EOPNOTSUPP`. So this is definitely a skeleton implementation.

However, the packet reception path, `quic_packet_rcv`, is implemented. This is important.  It looks like this function is where the magic begins.  It calls `quic_packet_process` which handles different packet types. `quic_packet_process` then calls other functions depending on if it's a listen socket, handshake, or app data. And, even with these being stubbed, the key is what happens *before* these functions are called.

The packet reception seems to be triggered by `quic_udp_rcv` in `net/quic/path.c`.  It's calling `quic_packet_rcv`. This is important. `quic_udp_rcv` appears to be the *encap_rcv* for a UDP tunnel socket. `quic_udp_sock_create` creates the socket and `quic_path_bind` seems to be responsible for setting this up. So, the reception path goes: UDP socket -> `quic_udp_rcv` -> `quic_packet_rcv` -> `quic_packet_process`.

That call to `quic_packet_get_sock` does a bunch of parsing. I can see that `quic_crypto_initial_keys_install` and `quic_crypto_decrypt` is happening within `quic_packet_parse_alpn`. So, even if the higher-level functions are stubs, there is some serious crypto happening right away when packets arrive. I'd need to dive deeper to see *when* and *how* those initial keys get set up.

So, while the patch looks like a significant work-in-progress with many essential socket operations stubbed, it has implemented the packet reception path with quite a bit of parsing and crypto functionality. This gives me a good starting point for analysis. I'll need to trace the binding and socket creation logic to understand how packets get directed to this path.



4/2 2026/07/16 03:53 tool grepper
0m Args:
{
  "Expression": "quic_path_bind"
}

Results:
{
  "Output": "net/quic/path.c=149=static void quic_path_set_udp_sk(struct quic_path *path,\n--\nnet/quic/path.c-165-/* Binds a QUIC path to a local port and sets up a UDP socket. */\nnet/quic/path.c:166:int quic_path_bind(struct sock *sk, struct quic_path_group *paths, u8 path)\nnet/quic/path.c-167-{\n--\nnet/quic/path.h=172=bool quic_path_detect_alt(struct quic_path_group *paths, union quic_addr *sa,\nnet/quic/path.h-173-\t\t\t  union quic_addr *da, struct sock *sk);\nnet/quic/path.h:174:int quic_path_bind(struct sock *sk, struct quic_path_group *paths, u8 path);\nnet/quic/path.h-175-void quic_path_unbind(struct sock *sk, struct quic_path_group *paths, u8 path);\n"
}

5/2 2026/07/16 03:53 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 106152
							output: 22
							thoughts: 39
						

Thoughts:
Okay, here's my take on those thoughts, framed for an expert audience:

**Investigating Unused Function Calls in this QUIC Patch**

Alright, so I'm looking at this patch and immediately notice something odd: `quic_path_bind` isn't called *at all* within this code. That's a red flag – usually, if a function is being introduced or altered, it's used somewhere! I need to understand why. Before I start hunting for the bug, I want to rule out the most obvious scenario. The absence of `quic_path_bind` suggests it may be dead code. 

To confirm, the next step is to see if `quic_udp_sock_create` is being used elsewhere. This is the constructor for the socket the `quic_path_bind` would be used for. Perhaps the binding is done in a follow-up patch? Or maybe the socket creation itself is misplaced and `quic_path_bind` is used there. The goal here is to establish the current call graph and look for either a logical gap or unintended consequences. Let's see what's happening.



6/2 2026/07/16 03:53 tool grepper
0m Args:
{
  "Expression": "quic_udp_sock_create"
}

Results:
{
  "Output": "net/quic/path.c=40=static void quic_udp_sock_put_work(struct work_struct *work)\n--\nnet/quic/path.c-66-\nnet/quic/path.c:67:static struct quic_udp_sock *quic_udp_sock_create(struct sock *sk,\nnet/quic/path.c-68-\t\t\t\t\t\t  union quic_addr *a)\n--\nnet/quic/path.c=166=int quic_path_bind(struct sock *sk, struct quic_path_group *paths, u8 path)\n--\nnet/quic/path.c-191-\t\t} else {\nnet/quic/path.c:192:\t\t\tus = quic_udp_sock_create(sk, a);\nnet/quic/path.c-193-\t\t\tif (IS_ERR(us)) {\n--\nnet/quic/path.c-221-\t\ta-\u003ev4.sin_port = htons(port);\nnet/quic/path.c:222:\t\tus = quic_udp_sock_create(sk, a);\nnet/quic/path.c-223-\t\tif (IS_ERR(us)) {\n"
}

7/2 2026/07/16 03:53 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 106779
							output: 20
							thoughts: 340
						

Thoughts:
**My Analysis of the QUIC UDP Socket Creation**

Okay, so I'm looking at this `quic_path_bind` function. It seems it's the *only* caller of `quic_udp_sock_create`. But wait... digging deeper, `quic_path_bind` isn't called *anywhere*! That immediately raises a flag. If `quic_path_bind` is never invoked, then no UDP tunnel sockets are ever being created, which is... concerning. And if no sockets exist, then `quic_udp_rcv` isn't registered as `encap_rcv` for *any* UDP socket. This completely breaks the expected data flow. Because if `quic_udp_rcv` isn't registered, `quic_packet_rcv` is never getting called from the network stack. So, the incoming QUIC packets from the network stack are never reaching our QUIC handling.

Right, let me consider the `IPPROTO_QUIC` sockets. We can create them via `socket(AF_INET, SOCK_STREAM, IPPROTO_QUIC)`, that's straightforward. But, hold on... `quic_bind`, `quic_connect`, `quic_sendmsg`, `quic_recvmsg`, and `quic_accept` all return `-EOPNOTSUPP`. Effectively, the socket is just a dummy. I can create it, but I can't actually *do* anything with it. No binding, no connecting, no sending or receiving data. Not very useful. Let's see about `setsockopt` and `getsockopt`. Aha, `quic_setsockopt` and `quic_getsockopt` are both calling `quic_do_setsockopt` and `quic_do_getsockopt`, and, predictably, they also return `-EOPNOTSUPP`. Okay, so those are out as well.

Let me take another look to ensure I haven't missed a path into this code. `quic_init_sock` is called when the socket is created. That's the only entry point it seems.



8/2 2026/07/16 03:53 tool grepper
0m Args:
{
  "Expression": "quic_init_sock"
}

Results:
{
  "Output": "net/quic/socket.c=191=static void quic_sock_destruct(struct sock *sk)\n--\nnet/quic/socket.c-204-\nnet/quic/socket.c:205:static int quic_init_sock(struct sock *sk)\nnet/quic/socket.c-206-{\n--\nnet/quic/socket.c=421=struct proto quic_prot = {\n--\nnet/quic/socket.c-423-\t.owner\t\t=  THIS_MODULE,\nnet/quic/socket.c:424:\t.init\t\t=  quic_init_sock,\nnet/quic/socket.c-425-\t.destroy\t=  quic_destroy_sock,\n--\nnet/quic/socket.c=452=struct proto quicv6_prot = {\n--\nnet/quic/socket.c-454-\t.owner\t\t=  THIS_MODULE,\nnet/quic/socket.c:455:\t.init\t\t=  quic_init_sock,\nnet/quic/socket.c-456-\t.destroy\t=  quic_destroy_sock,\n"
}

9/2 2026/07/16 03:53 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 107914
							output: 25
							thoughts: 704
						

Thoughts:
**Analyzing QUIC Socket Initialization and Destruction**

Alright, let's break down how this QUIC implementation handles socket creation and destruction. My initial thought is to examine `quic_init_sock` and `quic_destroy_sock` to get a handle on the socket lifecycle. From what I see, `quic_init_sock` seems responsible for setting up the initial state of the socket, and `quic_destroy_sock` cleans it up when it's no longer needed.

`quic_init_sock` does quite a bit. It sets up the standard `sock` structure, hooking into the destruction and write space functions. I notice it allocates resources like connection IDs, congestion control structures, timers, packet handling, streams, and various packet number spaces. Several functions like `quic_cong_init`, `quic_timer_init`, `quic_packet_init`, `quic_stream_init`, and `quic_pnspace_init` are called as part of the process.  The socket counter and protocol usage are also updated, which is crucial.

`quic_destroy_sock` on the other hand performs the reverse operation. It frees the resources previously allocated by `quic_init_sock`. Specifically, it tears down timers, packet number spaces, paths, connection IDs, streams, and other data structures like tickets and tokens.  It also decrements the socket counter and the protocol in-use count, properly releasing the allocated resources.

This gives me a good overview of the socket's lifecycle, which is important. The question now is, how do we get packets into this system?  Since this is QUIC, I assume there's a packet reception path, or an obvious entry point. I need to figure out what function or mechanism is responsible for receiving packets, and if it's exported and usable. Is `quic_packet_rcv` perhaps that entry point? I should investigate whether it's accessible or if it's called somewhere else.



10/2 2026/07/16 03:53 tool grepper
0m Error:
no matches

Args:
{
  "Expression": "EXPORT_SYMBOL.*quic_packet_rcv"
}

Results:
{
  "error": "no matches"
}

11/2 2026/07/16 03:54 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 109083
							output: 21
							thoughts: 18
						

Thoughts:
Okay, here's my attempt at summarizing that thought process, keeping in mind I'm speaking to a fellow expert:

**Investigating `quic_packet_rcv` Usage**

Alright, the initial instinct is a flat "No" - meaning I'm not immediately thinking of a clear answer. That's a good starting point. Now, to dig deeper, the next question immediately pops up: is `quic_packet_rcv` only ever called from one specific point in the codebase, or does it have other call paths? I need to quickly establish the scope of its usage to understand its impact and potential complexities. Understanding its call graph is key to understanding its place in the grand scheme of things.



12/2 2026/07/16 03:54 tool grepper
0m Args:
{
  "Expression": "quic_packet_rcv"
}

Results:
{
  "Output": "net/quic/packet.c=105=static int quic_packet_get_token(struct quic_data *token, u8 **pp, u32 *plen)\n--\nnet/quic/packet.c-117-/* Process PMTU reduction event on a QUIC socket. */\nnet/quic/packet.c:118:void quic_packet_rcv_err_pmtu(struct sock *sk)\nnet/quic/packet.c-119-{\n--\nnet/quic/packet.c-155-/* Handle ICMP Toobig packet and update QUIC socket path MTU. */\nnet/quic/packet.c:156:static int quic_packet_rcv_err(struct sock *sk, struct sk_buff *skb)\nnet/quic/packet.c-157-{\n--\nnet/quic/packet.c-188-\t/* Otherwise, process the MTU reduction now. */\nnet/quic/packet.c:189:\tquic_packet_rcv_err_pmtu(sk);\nnet/quic/packet.c-190-out:\n--\nnet/quic/packet.c=442=static struct sock *quic_packet_get_sock(struct sk_buff *skb)\n--\nnet/quic/packet.c-518-/* Entry point for processing received QUIC packets. */\nnet/quic/packet.c:519:int quic_packet_rcv(struct sock *sk, struct sk_buff *skb, bool icmp)\nnet/quic/packet.c-520-{\n--\nnet/quic/packet.c-524-\tif (unlikely(icmp))\nnet/quic/packet.c:525:\t\treturn quic_packet_rcv_err(sk, skb);\nnet/quic/packet.c-526-\n--\nnet/quic/packet.h=119=void quic_packet_backlog_work(struct work_struct *work);\nnet/quic/packet.h:120:void quic_packet_rcv_err_pmtu(struct sock *sk);\n--\nnet/quic/path.c=20=static int quic_udp_rcv(struct sock *sk, struct sk_buff *skb)\n--\nnet/quic/path.c-29-\tskb_dst_force(skb);\nnet/quic/path.c:30:\tquic_packet_rcv(sk, skb, false);\nnet/quic/path.c-31-\t/* .encap_rcv must return 0 if skb was either consumed or dropped. */\n--\nnet/quic/path.c=35=static int quic_udp_err(struct sock *sk, struct sk_buff *skb)\nnet/quic/path.c-36-{\nnet/quic/path.c:37:\treturn quic_packet_rcv(sk, skb, true);\nnet/quic/path.c-38-}\n--\nnet/quic/path.h=165=quic_path_orig_dcid(struct quic_path_group *paths)\n--\nnet/quic/path.h-169-\nnet/quic/path.h:170:int quic_packet_rcv(struct sock *sk, struct sk_buff *skb, bool icmp);\nnet/quic/path.h-171-\n--\nnet/quic/socket.c=369=static void quic_release_cb(struct sock *sk)\n--\nnet/quic/socket.c-380-\tif (flags \u0026 QUIC_F_MTU_REDUCED_DEFERRED) {\nnet/quic/socket.c:381:\t\tquic_packet_rcv_err_pmtu(sk);\nnet/quic/socket.c-382-\t\t__sock_put(sk);\n"
}

13/2 2026/07/16 03:54 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 110266
							output: 20
							thoughts: 121
						

Thoughts:
Okay, let's break this down.

**Investigating the QUIC Packet Reception Path**

Alright, so I'm tracing the incoming packet flow in this QUIC implementation. From what I'm seeing, `quic_packet_rcv` is the core function for receiving a packet, but it's only called from `quic_udp_rcv` and `quic_udp_err`. That makes sense; it's the UDP layer dealing with the raw data. Now, those two UDP functions, `quic_udp_rcv` and `quic_udp_err`, are registered, and it looks like that registration only happens in `quic_udp_sock_create`. Digging deeper, it appears `quic_udp_sock_create` itself is only invoked from `quic_path_bind`.

Hold on... I'm missing something. This whole packet reception path hinges on `quic_path_bind`, and I can't find a single place where it's being called. That's a huge red flag. Is `quic_path_bind` actually being used? This could be a critical flaw, a dead code path, or a subtle bug in the connection establishment. I need to verify that this is connected to any other function, or to any other path to verify if this code is actually working. I need to re-examine all the function calls to ensure it's not being called indirectly. This could mean the whole reception process is broken, or a specific part of the flow isn't being used. I need to confirm this.



14/2 2026/07/16 03:54 tool grepper
0m Args:
{
  "Expression": "quic_path_bind"
}

Results:
{
  "Output": "net/quic/path.c=149=static void quic_path_set_udp_sk(struct quic_path *path,\n--\nnet/quic/path.c-165-/* Binds a QUIC path to a local port and sets up a UDP socket. */\nnet/quic/path.c:166:int quic_path_bind(struct sock *sk, struct quic_path_group *paths, u8 path)\nnet/quic/path.c-167-{\n--\nnet/quic/path.h=172=bool quic_path_detect_alt(struct quic_path_group *paths, union quic_addr *sa,\nnet/quic/path.h-173-\t\t\t  union quic_addr *da, struct sock *sk);\nnet/quic/path.h:174:int quic_path_bind(struct sock *sk, struct quic_path_group *paths, u8 path);\nnet/quic/path.h-175-void quic_path_unbind(struct sock *sk, struct quic_path_group *paths, u8 path);\n"
}

15/2 2026/07/16 03:54 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 111039
							output: 25
							thoughts: 62
						

Thoughts:
Okay, here's my interpretation of those thoughts, framed as my own reasoning:

**Problem Diagnosis: UDP Tunnel Socket**

Right, so the core issue seems to be a failure to establish the UDP tunnel. I'm thinking, "Wait a minute, it's not actually being *created* anywhere, is it?" And if there's no socket, then the whole receiving process is dead in the water. No socket, no packets received. It's elementary, really.

But then I thought, "What about transmission?" I have to verify the outbound process. I remember that the `quic_packet_create_and_xmit` function handles packet creation and transmission. I know I saw the definition of `quic_packet_create_and_xmit` is in `net/quic/packet.c`. Now, I have to figure out if it is ever being *called* anywhere. Is this part of the solution? I need to trace its usage to understand the complete picture of how QUIC is working.



16/2 2026/07/16 03:54 tool grepper
0m Args:
{
  "Expression": "quic_packet_create_and_xmit"
}

Results:
{
  "Output": "net/quic/packet.c=800=static int quic_packet_xmit(struct sock *sk, struct sk_buff *skb, gfp_t gfp)\n--\nnet/quic/packet.c-835-/* Create and transmit a new QUIC packet. */\nnet/quic/packet.c:836:int quic_packet_create_and_xmit(struct sock *sk, gfp_t gfp)\nnet/quic/packet.c-837-{\n--\nnet/quic/packet.h=108=int quic_packet_config(struct sock *sk, u8 level, u8 path);\nnet/quic/packet.h-109-\nnet/quic/packet.h:110:int quic_packet_create_and_xmit(struct sock *sk, gfp_t gfp);\nnet/quic/packet.h-111-int quic_packet_route(struct sock *sk);\n"
}

17/2 2026/07/16 03:54 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 111598
							output: 20
							thoughts: 18
						

Thoughts:
Okay, here's my summary, presented as a stream of thought:

**Cryptographic Function Name Inquiry**

Well, I'm trying to recall the exact name, and it's just not coming to me immediately. Hmm, I can't seem to find the specific identifier anywhere in my mental repository. I need to make sure I am thinking of the correct function and not conflating it with another.

I wonder... what about `quic_crypto_encrypt`? That name feels familiar, and it's in the ballpark of what I'm looking for. It could be the one I am after. Time to consider the context to see if it fits.



18/2 2026/07/16 03:54 tool grepper
0m Args:
{
  "Expression": "quic_crypto_encrypt"
}

Results:
{
  "Output": "net/quic/crypto.c=469=static int quic_crypto_payload_protect(struct quic_crypto *crypto,\n--\nnet/quic/crypto.c-574- */\nnet/quic/crypto.c:575:int quic_crypto_encrypt(struct quic_crypto *crypto, struct sk_buff *skb,\nnet/quic/crypto.c-576-\t\t\tgfp_t gfp)\n--\nnet/quic/crypto.h=65=int quic_crypto_key_update(struct quic_crypto *crypto);\nnet/quic/crypto.h-66-\nnet/quic/crypto.h:67:int quic_crypto_encrypt(struct quic_crypto *crypto, struct sk_buff *skb,\nnet/quic/crypto.h-68-\t\t\tgfp_t gfp);\n--\nnet/quic/packet.c=800=static int quic_packet_xmit(struct sock *sk, struct sk_buff *skb, gfp_t gfp)\n--\nnet/quic/packet.c-815-\tWARN_ON_ONCE(!skb_set_owner_sk_safe(skb, sk));\nnet/quic/packet.c:816:\terr = quic_crypto_encrypt(quic_crypto(sk, packet-\u003elevel), skb, gfp);\nnet/quic/packet.c-817-\tif (err) {\n"
}

19/2 2026/07/16 03:54 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 112150
							output: 20
							thoughts: 50
						

Thoughts:
Okay, here's my interpretation of those thoughts, presented as my own thinking process:

**Deductive Reasoning on Code Flow**

Right, let's see. This function I'm looking at, let's call it `quic_packet_xmit`, apparently it's only ever called from one other place: `quic_packet_create_and_xmit`. Okay, that's not too surprising. But, now the really interesting bit: `quic_packet_create_and_xmit` *itself* isn't called anywhere else in the code base. That seems…unusual, and likely significant, or, at least, worth investigating. If this is accurate, and it seems the code is telling me this is true, then we've got a dead end. Meaning, this isn't getting hit at all. That raises questions. Are we missing something in the call graph? A potential bug? Or is it intentionally unused, perhaps a remnant of previous development? It demands further digging.

Now, hold on...that might be too hasty. What about `quic_crypto_decrypt`? I wonder how it may interact with the rest of this whole process... Does this relate to it? Need to check that out, as the encryption or decryption might use this call.



20/2 2026/07/16 03:54 tool grepper
0m Args:
{
  "Expression": "quic_crypto_decrypt"
}

Results:
{
  "Output": "net/quic/crypto.c=575=int quic_crypto_encrypt(struct quic_crypto *crypto, struct sk_buff *skb,\n--\nnet/quic/crypto.c-589-\t * new key, save the current time. Later used to clear old keys after\nnet/quic/crypto.c:590:\t * some time has passed (see quic_crypto_decrypt()).\nnet/quic/crypto.c-591-\t */\n--\nnet/quic/crypto.c-607- */\nnet/quic/crypto.c:608:int quic_crypto_decrypt(struct quic_crypto *crypto, struct sk_buff *skb,\nnet/quic/crypto.c-609-\t\t\tgfp_t gfp)\n--\nnet/quic/crypto.h=67=int quic_crypto_encrypt(struct quic_crypto *crypto, struct sk_buff *skb,\nnet/quic/crypto.h-68-\t\t\tgfp_t gfp);\nnet/quic/crypto.h:69:int quic_crypto_decrypt(struct quic_crypto *crypto, struct sk_buff *skb,\nnet/quic/crypto.h-70-\t\t\tgfp_t gfp);\n--\nnet/quic/packet.c=357=static int quic_packet_parse_alpn(struct sk_buff *skb, struct quic_data *alpn)\n--\nnet/quic/packet.c-395-\tcb-\u003esync = 1;\nnet/quic/packet.c:396:\terr = quic_crypto_decrypt(crypto, skb, GFP_KERNEL);\nnet/quic/packet.c-397-\tif (err) {\n"
}

21/2 2026/07/16 03:54 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 112943
							output: 20
							thoughts: 961
						

Thoughts:
**Analyzing the QUIC Patch's Reachability**

Okay, so I'm digging into this QUIC patch, and the call stack here is pretty straightforward, I think. `quic_packet_parse_alpn` is the entry point, called by both `quic_packet_get_listen_sock` and `quic_packet_get_sock`.  But `quic_packet_get_sock` is called from `quic_packet_rcv`, which itself is only called from `quic_udp_rcv`.  And *that* function, `quic_udp_rcv`, is never actually registered anywhere.  That's a bit of a red flag, right?

So, this patch introduces QUIC protocol structures, the crypto bits, the packet parsing, everything.  However, it's essentially unreachable from the user or the network. Why? Well, it looks like:

1.  All the standard socket operations (`bind`, `connect`, `sendmsg`, `recvmsg`, `setsockopt`, `getsockopt`) return `-EOPNOTSUPP`.  That's a dead end.
2.  The UDP tunnel socket that would actually receive packets is never created because `quic_path_bind` is never called.  And I'm pretty sure that's how it's supposed to work.

Hang on... is there *any* other possible way that `quic_packet_rcv` *could* be called?  Nope, it's not exported, so that's a hard no.

Okay, let's look at `quic_path_bind` then. Is there any way that is called? Nothing. We grepped, and it looks like no other callers exist.

Let's do a quick check to see if *any* other socket operations are actually implemented. We have `quic_init_sock`, which, as the name implies, seems to allocate memory and initialize some timers and structures.  And `quic_destroy_sock` frees those.  So, if we were to create a QUIC socket and close it, we'd hit those functions. Is this even worth fuzzing?

Let's look at `quic_init_sock` more closely: It assigns a destructor and write space function, sets a flag for using the write queue, does some memory management counters, initializes lists, and then initializes the QUIC connection ID, congestion control, timers, and packet and stream management. It calls `quic_pnspace_init` in a loop, and can return an error if there's no memory.

`quic_destroy_sock` seems to free resources initialized by `quic_init_sock` (timers, packet spaces, connection IDs, stream management, tickets/tokens), decrement memory management counters, and calls `quic_path_unbind`. It's basically just a clean-up function.

Based on the code, all it does is basic initialization and destruction.  None of the actual protocol logic is accessible at this point.

So, yeah, my conclusion is that this is a skeleton patch. It's building the foundational structures and functions but it's not actually wiring them up to the socket API or the network stack yet. It's clearly a preliminary step.



22/2 2026/07/16 03:54 tool grepper
0m Args:
{
  "Expression": "quic_path_bind"
}

Results:
{
  "Output": "net/quic/path.c=149=static void quic_path_set_udp_sk(struct quic_path *path,\n--\nnet/quic/path.c-165-/* Binds a QUIC path to a local port and sets up a UDP socket. */\nnet/quic/path.c:166:int quic_path_bind(struct sock *sk, struct quic_path_group *paths, u8 path)\nnet/quic/path.c-167-{\n--\nnet/quic/path.h=172=bool quic_path_detect_alt(struct quic_path_group *paths, union quic_addr *sa,\nnet/quic/path.h-173-\t\t\t  union quic_addr *da, struct sock *sk);\nnet/quic/path.h:174:int quic_path_bind(struct sock *sk, struct quic_path_group *paths, u8 path);\nnet/quic/path.h-175-void quic_path_unbind(struct sock *sk, struct quic_path_group *paths, u8 path);\n"
}

23/2 2026/07/16 03:54 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 114923
							output: 21
							thoughts: 21
						

Thoughts:
Okay, here's my interpretation of those thoughts, tailored for an expert audience, presented in the first person:

**Navigating the Protocol Stack**

Right, so we're looking at a scenario where a particular function isn't being called, which is a fairly common debugging situation. My first thought is to zero in on what *should* be handling this. The absence of a call itself is the clue. Is it not being called *ever*, or just in this specific instance? I have to start somewhere. Since we are in the QUIC space, my mind immediately jumps to a key entry point, as I'm thinking about the receiving path, and considering packet processing, maybe that isn't the function to focus on? I need to consider `quic_packet_rcv`. This is a core function, so tracing backwards from there, and examining the context, seems like a logical next step. I'll need to check the call stack and data flow around this function to understand why it’s not being invoked, or whether there is a conditional path preventing its execution. It will inform my next steps.



24/2 2026/07/16 03:54 tool grepper
0m Args:
{
  "Expression": "quic_packet_rcv"
}

Results:
{
  "Output": "net/quic/packet.c=105=static int quic_packet_get_token(struct quic_data *token, u8 **pp, u32 *plen)\n--\nnet/quic/packet.c-117-/* Process PMTU reduction event on a QUIC socket. */\nnet/quic/packet.c:118:void quic_packet_rcv_err_pmtu(struct sock *sk)\nnet/quic/packet.c-119-{\n--\nnet/quic/packet.c-155-/* Handle ICMP Toobig packet and update QUIC socket path MTU. */\nnet/quic/packet.c:156:static int quic_packet_rcv_err(struct sock *sk, struct sk_buff *skb)\nnet/quic/packet.c-157-{\n--\nnet/quic/packet.c-188-\t/* Otherwise, process the MTU reduction now. */\nnet/quic/packet.c:189:\tquic_packet_rcv_err_pmtu(sk);\nnet/quic/packet.c-190-out:\n--\nnet/quic/packet.c=442=static struct sock *quic_packet_get_sock(struct sk_buff *skb)\n--\nnet/quic/packet.c-518-/* Entry point for processing received QUIC packets. */\nnet/quic/packet.c:519:int quic_packet_rcv(struct sock *sk, struct sk_buff *skb, bool icmp)\nnet/quic/packet.c-520-{\n--\nnet/quic/packet.c-524-\tif (unlikely(icmp))\nnet/quic/packet.c:525:\t\treturn quic_packet_rcv_err(sk, skb);\nnet/quic/packet.c-526-\n--\nnet/quic/packet.h=119=void quic_packet_backlog_work(struct work_struct *work);\nnet/quic/packet.h:120:void quic_packet_rcv_err_pmtu(struct sock *sk);\n--\nnet/quic/path.c=20=static int quic_udp_rcv(struct sock *sk, struct sk_buff *skb)\n--\nnet/quic/path.c-29-\tskb_dst_force(skb);\nnet/quic/path.c:30:\tquic_packet_rcv(sk, skb, false);\nnet/quic/path.c-31-\t/* .encap_rcv must return 0 if skb was either consumed or dropped. */\n--\nnet/quic/path.c=35=static int quic_udp_err(struct sock *sk, struct sk_buff *skb)\nnet/quic/path.c-36-{\nnet/quic/path.c:37:\treturn quic_packet_rcv(sk, skb, true);\nnet/quic/path.c-38-}\n--\nnet/quic/path.h=165=quic_path_orig_dcid(struct quic_path_group *paths)\n--\nnet/quic/path.h-169-\nnet/quic/path.h:170:int quic_packet_rcv(struct sock *sk, struct sk_buff *skb, bool icmp);\nnet/quic/path.h-171-\n--\nnet/quic/socket.c=369=static void quic_release_cb(struct sock *sk)\n--\nnet/quic/socket.c-380-\tif (flags \u0026 QUIC_F_MTU_REDUCED_DEFERRED) {\nnet/quic/socket.c:381:\t\tquic_packet_rcv_err_pmtu(sk);\nnet/quic/socket.c-382-\t\t__sock_put(sk);\n"
}

25/2 2026/07/16 03:54 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 116191
							output: 126
							thoughts: 918
						

Thoughts:
**Unreachable Code Analysis**

Okay, let's break this down. From what I can see, `quic_udp_rcv` and `quic_udp_err` are only called within `quic_udp_sock_create`. And where's that called? Only in `quic_path_bind`. The problem is that `quic_path_bind` is *never* called. It's a dead end, which means any code dependent on this path is fundamentally unreachable.

Let me think… could anything else trigger this code? What about `quic_packet_create_and_xmit`? Nope, not called anywhere. What about if the other socket options are used such as `quic_setsockopt`, `quic_bind`, `quic_connect`, `quic_sendmsg`, `quic_recvmsg` or `quic_accept`? Those return `-EOPNOTSUPP`. Only `quic_close` actually does anything by setting the state and calling `sk_common_release`. So, all that's actually accessible is socket creation and destruction.

Is that worth fuzzing? It's really just memory allocation, structure initialization, and deallocation. Very basic stuff. It's a skeleton patch at best, very preliminary. No complex logic there to exploit. My initial gut feeling is that we should return `WorthFuzzing=false` here since the core logic is completely unreachable.

Wait, let me double-check. Can `quic_udp_rcv` be called directly? Nope, it's static. How about `quic_packet_rcv`? No, it's not exported. `quic_path_bind`? Same deal, it's not exported. `quic_packet_create_and_xmit`? Again, no.

Aha, hold on. `quic_do_setsockopt` and `quic_do_getsockopt` *are* exported, but they just return `-EOPNOTSUPP`.

So, it's confirmed. It *is* a skeleton. The socket operations are deliberately returning `-EOPNOTSUPP`, and there's no path to creating the UDP tunnel socket needed to receive anything. Therefore, I'm going to return `WorthFuzzing=false`. Here’s my reasoning: "The patch introduces the core structures and functions for the QUIC protocol, but it is a skeleton/preliminary patch. The socket operations (bind, connect, sendmsg, recvmsg, setsockopt) all return -EOPNOTSUPP. Furthermore, the UDP tunnel socket required to receive packets is never created because `quic_path_bind` is not called anywhere, making the packet reception and processing logic completely unreachable from user space or the network."

Just to be absolutely sure, let me check for callers of `quic_path_bind`, `quic_packet_rcv`, `quic_udp_sock_create` and `quic_packet_create_and_xmit`. Yep, grepping the code confirms that the path remains unreachable, so my original assessment is still correct.



26/2 2026/07/16 03:55 tool set-results
0m Args:
{
  "EnableConfigs": [],
  "FocusSymbols": [],
  "Reasoning": "The patch introduces the core structures and functions for the QUIC protocol, but it is a skeleton/preliminary patch. The socket operations (bind, connect, sendmsg, recvmsg, setsockopt) all return -EOPNOTSUPP. Furthermore, the UDP tunnel socket required to receive packets is never created because `quic_path_bind` is not called anywhere, making the packet reception and processing logic completely unreachable from user space or the network.",
  "WorthFuzzing": false
}

Results:
{
  "EnableConfigs": null,
  "FocusSymbols": [],
  "Reasoning": "The patch introduces the core structures and functions for the QUIC protocol, but it is a skeleton/preliminary patch. The socket operations (bind, connect, sendmsg, recvmsg, setsockopt) all return -EOPNOTSUPP. Furthermore, the UDP tunnel socket required to receive packets is never created because `quic_path_bind` is not called anywhere, making the packet reception and processing logic completely unreachable from user space or the network.",
  "WorthFuzzing": false
}

LLM Calls Summary:
Total Calls Total Tokens Avg Tokens Total Duration (Seconds) Avg Duration (Seconds)
Tool Calls Summary:
Total Calls Total Duration (Seconds) Avg Duration (Seconds)