Skip to contentSkip to conversation

net: introduce QUIC infrastructure and core subcomponents

PATCH Superseded Xin Long 2 participants 34 messages Feb 25 – Mar 5

Subsystems (8)

v10 of 18 Superseded Latest is v18 →

lore
Share & tools
lore

Xin Long

author
#0 lore raw

v10 cover net: introduce QUIC infrastructure and core subcomponents

Introduction
============

The QUIC protocol, defined in RFC 9000, is a secure, multiplexed transport built on top of UDP. It enables low-latency connection establishment, stream-based communication with flow control, and supports connection migration across network paths, while ensuring confidentiality, integrity, and availability.

This implementation introduces QUIC support in Linux Kernel, offering several key advantages:

- In-Kernel QUIC Support for Subsystems: Enables kernel subsystems such as SMB and NFS to operate over QUIC with minimal changes. Once the handshake is complete via the net/handshake APIs, data exchange proceeds over standard in-kernel transport interfaces.

- Standard Socket API Semantics: Implements core socket operations (listen(), accept(), connect(), sendmsg(), recvmsg(), close(), getsockopt(), setsockopt(), getsockname(), and getpeername()), allowing user space to interact with QUIC sockets in a familiar, POSIX-compliant way.

- ALPN-Based Connection Dispatching: Supports in-kernel ALPN (Application-Layer Protocol Negotiation) routing, allowing demultiplexing of QUIC connections across different user-space processes based on the ALPN identifiers.

- Performance Enhancements: Handles all control messages in-kernel to reduce syscall overhead, incorporates zero-copy mechanisms such as sendfile() to minimize data movement, and is also structured to support future crypto hardware offloads.

This implementation offers fundamental support for the following RFCs:

- RFC9000 - QUIC: A UDP-Based Multiplexed and Secure Transport
- RFC9001 - Using TLS to Secure QUIC
- RFC9002 - QUIC Loss Detection and Congestion Control
- RFC9221 - An Unreliable Datagram Extension to QUIC
- RFC9287 - Greasing the QUIC Bit
- RFC9368 - Compatible Version Negotiation for QUIC
- RFC9369 - QUIC Version 2

The socket APIs for QUIC follow the RFC draft [1]:

- The Sockets API Extensions for In-kernel QUIC Implementations

Implementation
==============

The central design is to implement QUIC within the kernel while delegating the handshake to userspace.

Only the processing and creation of raw TLS Handshake Messages are handled in userspace, facilitated by a TLS library like GnuTLS. These messages are exchanged between kernel and userspace via sendmsg() and recvmsg(), with cryptographic details conveyed through control messages (cmsg).

The entire QUIC protocol, aside from the TLS Handshake Messages processing and creation, is managed in the kernel. Rather than using an Upper Layer Protocol (ULP) layer, this implementation establishes a socket of type IPPROTO_QUIC (similar to IPPROTO_MPTCP), operating over UDP tunnels.

For kernel consumers, they can initiate a handshake request from the kernel to userspace using the existing net/handshake netlink. The userspace component, such as tlshd service [2], then manages the processing of the QUIC handshake request.

- Handshake Architecture:

  ┌──────┐  ┌──────┐
  │ APP1 │  │ APP2 │ ...
  └──────┘  └──────┘
  ┌──────────────────────────────────────────┐
  │     {quic_client/server_handshake()}     │<─────────────┐
  └──────────────────────────────────────────┘       ┌─────────────┐
   {send/recvmsg()}      {set/getsockopt()}          │    tlshd    │
   [CMSG handshake_info] [SOCKOPT_CRYPTO_SECRET]     └─────────────┘
                         [SOCKOPT_TRANSPORT_PARAM_EXT]    │   ^
                │ ^                  │ ^                  │   │
  Userspace     │ │                  │ │                  │   │
  ──────────────│─│──────────────────│─│──────────────────│───│───────
  Kernel        │ │                  │ │                  │   │
                v │                  v │                  v   │
  ┌──────────────────┬───────────────────────┐       ┌─────────────┐
  │ protocol, timer, │ socket (IPPROTO_QUIC) │<──┐   │ handshake   │
  │                  ├───────────────────────┤   │   │netlink APIs │
  │ common, family,  │ outqueue  |  inqueue  │   │   └─────────────┘
  │                  ├───────────────────────┤   │      │       │
  │ stream, connid,  │         frame         │   │   ┌─────┐ ┌─────┐
  │                  ├───────────────────────┤   │   │     │ │     │
  │ path, pnspace,   │         packet        │   │───│ SMB │ │ NFS │...
  │                  ├───────────────────────┤   │   │     │ │     │
  │ cong, crypto     │       UDP tunnels     │   │   └─────┘ └─────┘
  └──────────────────┴───────────────────────┘   └──────┴───────┘

- User Data Architecture:

  ┌──────┐  ┌──────┐
  │ APP1 │  │ APP2 │ ...
  └──────┘  └──────┘
   {send/recvmsg()}   {set/getsockopt()}              {recvmsg()}
   [CMSG stream_info] [SOCKOPT_KEY_UPDATE]            [EVENT conn update]
                      [SOCKOPT_CONNECTION_MIGRATION]  [EVENT stream update]
                      [SOCKOPT_STREAM_OPEN/RESET/STOP]
                │ ^               │ ^                     ^
  Userspace     │ │               │ │                     │
  ──────────────│─│───────────────│─│─────────────────────│───────────
  Kernel        │ │               │ │                     │
                v │               v │  ┌──────────────────┘
  ┌──────────────────┬───────────────────────┐
  │ protocol, timer, │ socket (IPPROTO_QUIC) │<──┐{kernel_send/recvmsg()}
  │                  ├───────────────────────┤   │{kernel_set/getsockopt()}
  │ common, family,  │ outqueue  |  inqueue  │   │{kernel_recvmsg()}
  │                  ├───────────────────────┤   │
  │ stream, connid,  │         frame         │   │   ┌─────┐ ┌─────┐
  │                  ├───────────────────────┤   │   │     │ │     │
  │ path, pnspace,   │         packet        │   │───│ SMB │ │ NFS │...
  │                  ├───────────────────────┤   │   │     │ │     │
  │ cong, crypto     │       UDP tunnels     │   │   └─────┘ └─────┘
  └──────────────────┴───────────────────────┘   └──────┴───────┘

Interface
=========

This implementation supports a mapping of QUIC into sockets APIs. Similar to TCP and SCTP, a typical Server and Client use the following system call sequence to communicate:

Client Server

  ──────────────────────────────────────────────────────────────────────
  sockfd = socket(IPPROTO_QUIC)      listenfd = socket(IPPROTO_QUIC)
  bind(sockfd)                       bind(listenfd)

listen(listenfd)
connect(sockfd)
quic_client_handshake(sockfd)
sockfd = accept(listenfd) quic_server_handshake(sockfd, cert)

sendmsg(sockfd) recvmsg(sockfd)
close(sockfd) close(sockfd)
close(listenfd)

Please note that quic_client_handshake() and quic_server_handshake() functions are currently sourced from libquic [3]. These functions are responsible for receiving and processing the raw TLS handshake messages until the completion of the handshake process.

For utilization by kernel consumers, it is essential to have tlshd service [2] installed and running in userspace. This service receives and manages kernel handshake requests for kernel sockets. In the kernel, the APIs closely resemble those used in userspace:

Client Server

  ────────────────────────────────────────────────────────────────────────
  __sock_create(IPPROTO_QUIC, &sock)  __sock_create(IPPROTO_QUIC, &sock)
  kernel_bind(sock)                   kernel_bind(sock)
                                      kernel_listen(sock)
  kernel_connect(sock)
  tls_client_hello_x509(args:{sock})
                                      kernel_accept(sock, &newsock)
                                      tls_server_hello_x509(args:{newsock})

kernel_sendmsg(sock) kernel_recvmsg(newsock)
sock_release(sock) sock_release(newsock)
sock_release(sock)

Please be aware that tls_client_hello_x509() and tls_server_hello_x509() are APIs from net/handshake/. They are used to dispatch the handshake request to the userspace tlshd service and subsequently block until the handshake process is completed.

Use Cases
=========

- Samba

Stefan Metzmacher has integrated Linux QUIC into Samba for both client and server roles [4].

- tlshd

The tlshd daemon [2] facilitates Linux QUIC handshake requests from kernel sockets. This is essential for enabling protocols like SMB and NFS over QUIC.

- curl

Linux QUIC is being integrated into curl [5] for HTTP/3. Example usage:

  # curl --http3-only https://nghttp2.org:4433/
  # curl --http3-only https://www.google.com/
  # curl --http3-only https://facebook.com/
  # curl --http3-only https://outlook.office.com/
  # curl --http3-only https://cloudflare-quic.com/

- httpd-portable

Moritz Buhl has deployed an HTTP/3 server over Linux QUIC [6] that is accessible via Firefox and curl:

https://d.moritzbuhl.de/pub

- NetPerfMeter

The latest NetPerfMeter release supports Linux QUIC and can be used to run performance evaluations [10].

Test Coverage
=============

The Coverage (gcov) of Functional and Interop Tests:

https://d.moritzbuhl.de/lcov

- Functional Tests

The libquic self-tests (make check) pass on all major architectures:
x86_64, i386, s390x, aarch64, ppc64le.

- Interop tests

Interoperability was validated using the QUIC Interop Runner [7] against all major userland QUIC stacks. Results are available at:

https://d.moritzbuhl.de/

- Fuzzing via Syzkaller

Syzkaller has been running kernel fuzzing with QUIC for weeks using tests/syzkaller/ in libquic [3].

- Performance Testing

Performance was benchmarked using iperf [8] over a 100G NIC using various MTUs and packet sizes:

- QUIC vs. kTLS:

    UNIT        size:1024      size:4096      size:16384     size:65536
    Gbits/sec   QUIC | kTLS    QUIC | kTLS    QUIC | kTLS    QUIC | kTLS
    ────────────────────────────────────────────────────────────────────
    mtu:1500    2.27 | 3.26    3.02 | 6.97    3.36 | 9.74    3.48 | 10.8
    ────────────────────────────────────────────────────────────────────
    mtu:9000    3.66 | 3.72    5.87 | 8.92    7.03 | 11.2    8.04 | 11.4

- QUIC(disable_1rtt_encryption) vs. TCP:

    UNIT        size:1024      size:4096      size:16384     size:65536
    Gbits/sec   QUIC | TCP     QUIC | TCP     QUIC | TCP     QUIC | TCP
    ────────────────────────────────────────────────────────────────────
    mtu:1500    3.09 | 4.59    4.46 | 14.2    5.07 | 21.3    5.18 | 23.9
    ────────────────────────────────────────────────────────────────────
    mtu:9000    4.60 | 4.65    8.41 | 14.0    11.3 | 28.9    13.5 | 39.2

The performance gap between QUIC and kTLS may be attributed to:

- The absence of Generic Segmentation Offload (GSO) for QUIC.
- An additional data copy on the transmission (TX) path.
- Extra encryption required for header protection in QUIC.
- A longer header length for the stream data in QUIC.

Patches
=======

Note: This implementation is organized into five parts and submitted across two patchsets for review. This patchset includes Parts 1–2, while Parts 3–5 will be submitted in a subsequent patchset. For complete series, see [9].

1. Infrastructure (2):

net: define IPPROTO_QUIC and SOL_QUIC constants
net: build socket infrastructure for QUIC protocol

2. Subcomponents (13):

quic: provide common utilities and data structures
quic: provide family ops for address and protocol
quic: provide quic.h header files for kernel and userspace
quic: add stream management
quic: add connection id management
quic: add path management
quic: add congestion control
quic: add packet number space
quic: add crypto key derivation and installation
quic: add crypto packet encryption and decryption
quic: add timer management
quic: add packet builder base
quic: add packet parser base

3. Data Processing (8):

quic: add frame encoder and decoder base
quic: implement outqueue transmission and flow control
quic: implement outqueue sack and retransmission
quic: implement inqueue receiving and flow control
quic: implement frame creation functions
quic: implement frame processing functions
quic: implement packet creation functions
quic: implement packet processing functions

4. Socket APIs (6):

quic: support bind/listen/connect/accept/close()
quic: support sendmsg() and recvmsg()
quic: support socket options related to interaction after handshake
quic: support socket options related to settings prior to handshake
quic: support socket options related to setup during handshake
quic: support socket ioctls and socket dump via procfs

5. Documentation and Selftests (3):

Documentation: describe QUIC protocol interface in quic.rst
quic: create sample test using handshake APIs for kernel consumers
selftests: net: add tests for QUIC protocol

Notice: The QUIC module is currently labeled as "EXPERIMENTAL".

All contributors are recognized in the respective patches with the tag of 'Signed-off-by:'. Special thanks to Moritz Buhl and Stefan Metzmacher whose practical use cases and insightful feedback have been instrumental in shaping the design and advancing the development.

References
==========

[1]  https://datatracker.ietf.org/doc/html/draft-lxin-quic-socket-apis
[2]  https://github.com/oracle/ktls-utils
[3]  https://github.com/lxin/quic
[4]  https://gitlab.com/samba-team/samba/-/merge_requests/4019
[5]  https://github.com/moritzbuhl/curl/tree/linux_curl
[6]  https://github.com/moritzbuhl/httpd-portable
[7]  https://github.com/quic-interop/quic-interop-runner
[8]  https://github.com/lxin/iperf
[9]  https://github.com/lxin/net-next/commits/quic/

[10] https://www.nntb.no/~dreibh/netperfmeter/

Changes in v2-v10: See individual patch changelogs for details.

Xin Long (15):
net: define IPPROTO_QUIC and SOL_QUIC constants
net: build socket infrastructure for QUIC protocol
quic: provide common utilities and data structures
quic: provide family ops for address and protocol
quic: provide quic.h header files for kernel and userspace
quic: add stream management
quic: add connection id management
quic: add path management
quic: add congestion control
quic: add packet number space
quic: add crypto key derivation and installation
quic: add crypto packet encryption and decryption
quic: add timer management
quic: add packet builder base
quic: add packet parser base

 Documentation/networking/ip-sysctl.rst |   39 +
 MAINTAINERS                            |    9 +
 include/linux/quic.h                   |   20 +
 include/linux/socket.h                 |    1 +
 include/uapi/linux/in.h                |    2 +
 include/uapi/linux/quic.h              |  242 +++++
 net/Kconfig                            |    1 +
 net/Makefile                           |    1 +
 net/quic/Kconfig                       |   36 +
 net/quic/Makefile                      |    9 +
 net/quic/common.c                      |  550 +++++++++++
 net/quic/common.h                      |  205 ++++
 net/quic/cong.c                        |  307 ++++++
 net/quic/cong.h                        |  123 +++
 net/quic/connid.c                      |  227 +++++
 net/quic/connid.h                      |  163 ++++
 net/quic/crypto.c                      | 1226 ++++++++++++++++++++++++
 net/quic/crypto.h                      |   83 ++
 net/quic/family.c                      |  372 +++++++
 net/quic/family.h                      |   33 +
 net/quic/packet.c                      |  832 ++++++++++++++++
 net/quic/packet.h                      |  119 +++
 net/quic/path.c                        |  524 ++++++++++
 net/quic/path.h                        |  172 ++++
 net/quic/pnspace.c                     |  225 +++++
 net/quic/pnspace.h                     |  150 +++
 net/quic/protocol.c                    |  405 ++++++++
 net/quic/protocol.h                    |   62 ++
 net/quic/socket.c                      |  433 +++++++++
 net/quic/socket.h                      |  207 ++++
 net/quic/stream.c                      |  400 ++++++++
 net/quic/stream.h                      |  119 +++
 net/quic/timer.c                       |  196 ++++
 net/quic/timer.h                       |   47 +
 usr/include/Makefile                   |    1 +
 35 files changed, 7541 insertions(+)
 create mode 100644 include/linux/quic.h
 create mode 100644 include/uapi/linux/quic.h
 create mode 100644 net/quic/Kconfig
 create mode 100644 net/quic/Makefile
 create mode 100644 net/quic/common.c
 create mode 100644 net/quic/common.h
 create mode 100644 net/quic/cong.c
 create mode 100644 net/quic/cong.h
 create mode 100644 net/quic/connid.c
 create mode 100644 net/quic/connid.h
 create mode 100644 net/quic/crypto.c
 create mode 100644 net/quic/crypto.h
 create mode 100644 net/quic/family.c
 create mode 100644 net/quic/family.h
 create mode 100644 net/quic/packet.c
 create mode 100644 net/quic/packet.h
 create mode 100644 net/quic/path.c
 create mode 100644 net/quic/path.h
 create mode 100644 net/quic/pnspace.c
 create mode 100644 net/quic/pnspace.h
 create mode 100644 net/quic/protocol.c
 create mode 100644 net/quic/protocol.h
 create mode 100644 net/quic/socket.c
 create mode 100644 net/quic/socket.h
 create mode 100644 net/quic/stream.c
 create mode 100644 net/quic/stream.h
 create mode 100644 net/quic/timer.c
 create mode 100644 net/quic/timer.h
Signature · 1 line
2.47.1
author
#1 lore raw

PATCH v10 1/15 net: define IPPROTO_QUIC and SOL_QUIC constants

This patch adds IPPROTO_QUIC and SOL_QUIC constants to the networking subsystem. These definitions are essential for applications to set socket options and protocol identifiers related to the QUIC protocol.

QUIC does not possess a protocol number allocated from IANA, and like IPPROTO_MPTCP, IPPROTO_QUIC is merely a value used when opening a QUIC socket with:

  socket(AF_INET, SOCK_STREAM, IPPROTO_QUIC);

Note we did not opt for UDP ULP for QUIC implementation due to several considerations:

- QUIC's connection Migration requires at least 2 UDP sockets for one QUIC connection at the same time, not to mention the multipath feature in one of its draft RFCs.

- In-Kernel QUIC, as a Transport Protocol, wants to provide users with the TCP or SCTP like Socket APIs, like connect()/listen()/accept()... Note that a single UDP socket might even be used for multiple QUIC connections.

The use of IPPROTO_QUIC type sockets over UDP tunnel will effectively address these challenges and provides a more flexible and scalable solution.

---
 include/linux/socket.h  | 1 +
 include/uapi/linux/in.h | 2 ++
 2 files changed, 3 insertions(+)
include/linux/socket.h +1 -0
index ec715ad4bf25..dc80de8d8c50 100644
@@ -401,6 +401,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
include/uapi/linux/in.h +2 -0
index ced0fc3c3aa5..34becd90d3a6 100644
@@ -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 and Secure Transport	*/
+#define IPPROTO_QUIC		IPPROTO_QUIC
   IPPROTO_MPTCP = 262,		/* Multipath TCP connection		*/
 #define IPPROTO_MPTCP		IPPROTO_MPTCP
   IPPROTO_MAX
Signature · 1 line
2.47.1
author
#2 lore raw

PATCH v10 2/15 net: build socket infrastructure for QUIC protocol

In reply to Xin Long

This patch lays the groundwork for QUIC socket support in the kernel. It defines the core structures and protocol hooks needed to create QUIC sockets, without implementing any protocol behavior at this stage.

Basic integration is included to allow building the module via CONFIG_IP_QUIC=m.

This provides the scaffolding necessary for adding actual QUIC socket behavior in follow-up patches.

---
v3:
- Kconfig: add 'default n' for IP_QUIC (reported by Paolo).
- quic_disconnect(): return -EOPNOTSUPP (suggested by Paolo).
- quic_init/destroy_sock(): drop local_bh_disable/enable() calls (noted by Paolo).
- sysctl: add alpn_demux option to en/disable ALPN-based demux.
- SNMP: remove SNMP_MIB_SENTINEL, switch to snmp_get_cpu_field_batch_cnt() to align with latest net-next changes.
v4:
- Remove unnecessary READ_ONCE() in quic_inet_connect() (reported by

    Paolo).

v5:
- Update the type of the parameter 'addr' in quic_inet_connect(), quic_connect(), and quic_bind() to match the latest net-next changes.
- Define quic_is_serv() to reuse sk->sk_max_ack_backlog for server-side

    detection; path->serv will be deleted in a later patch.
  - Use MODULE_ALIAS_NET_PF_PROTO instead of MODULE_ALIAS (suggested by
    Stefan).
  - Add the missing Documentation entry for the new sysctl options (noted
    by Paolo).
  - Add the missing MAINTAINERS entry for the QUIC PROTOCOL (noted by
    Jakub).

v6:
- Relocate the QUIC PROTOCOL MAINTAINERS entry to its proper section (noted by Jakub).
v7:
- Replace #ifdef CONFIG_XXX with #if IS_ENABLED(CONFIG_XXX) (noted by

    Paolo).

v8:
- Fix an issue where an uninitialized value could be returned from quic_net_init() by initializing err to 0 (reported by AI review).
- Replace the global ALPN demultiplexing sysctl with a static key that will be enabled only when ALPN is configured on a listening socket (noted by Stefan).
v10:
- Note for AI reviews: inet6_register_protosw() never fails for QUIC, so checking its return value is unnecessary.
- Remove sk_sndbuf/sk_rcvbuf initialization in quic_init_sock(), as it's already done in sock_init_data_uid() with sysctl_w/rmem_default.

---
 Documentation/networking/ip-sysctl.rst |  39 +++
 MAINTAINERS                            |   7 +
 net/Kconfig                            |   1 +
 net/Makefile                           |   1 +
 net/quic/Kconfig                       |  36 +++
 net/quic/Makefile                      |   8 +
 net/quic/protocol.c                    | 369 +++++++++++++++++++++++++
 net/quic/protocol.h                    |  57 ++++
 net/quic/socket.c                      | 204 ++++++++++++++
 net/quic/socket.h                      |  89 ++++++
 10 files changed, 811 insertions(+)
 create mode 100644 net/quic/Kconfig
 create mode 100644 net/quic/Makefile
 create mode 100644 net/quic/protocol.c
 create mode 100644 net/quic/protocol.h
 create mode 100644 net/quic/socket.c
 create mode 100644 net/quic/socket.h
Documentation/networking/ip-sysctl.rst +39 -0
index 6921d8594b84..635443980aae 100644
@@ -3771,6 +3771,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/*``
 ========================
 
MAINTAINERS +7 -0
index b8d8a5c41597..f1dfc2239978 100644
@@ -21915,6 +21915,13 @@ L:	[email protected]
 S:	Maintained
 F:	drivers/net/wireless/quantenna/
 
+QUIC PROTOCOL
+M:	Xin Long <[email protected]>
+L:	[email protected]
+S:	Maintained
+W:	https://github.com/lxin/quic
+F:	net/quic/
+
 RADEON and AMDGPU DRM DRIVERS
 M:	Alex Deucher <[email protected]>
 M:	Christian König <[email protected]>
net/Kconfig +1 -0
index 62266eaf0e95..dd2ed8420102 100644
@@ -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"
net/Makefile +1 -0
index 90e3d72bf58b..cd43d03907cd 100644
@@ -43,6 +43,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/
net/quic/Kconfig new file +36 -0
new file mode 100644
index 000000000000..1f10a452b3a1
@@ -0,0 +1,36 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# QUIC configuration
+#
+
+menuconfig IP_QUIC
+	tristate "QUIC: A UDP-Based Multiplexed and Secure Transport (Experimental)"
+	depends on INET
+	depends on IPV6
+	select CRYPTO
+	select CRYPTO_HMAC
+	select CRYPTO_HKDF
+	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.
net/quic/Makefile new file +8 -0
new file mode 100644
index 000000000000..020e4dd133d8
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# Makefile for QUIC support code.
+#
+
+obj-$(CONFIG_IP_QUIC) += quic.o
+
+quic-y := protocol.o socket.o
net/quic/protocol.c new file +369 -0

369 lines not on this page · raw message

net/quic/protocol.h new file +57 -0

57 lines not on this page · raw message

net/quic/socket.c new file +204 -0

204 lines not on this page · raw message

net/quic/socket.h new file +89 -0

89 lines not on this page · raw message

Signature · 1 line
2.47.1
author
#3 lore raw

PATCH v10 3/15 quic: provide common utilities and data structures

In reply to Xin Long

This patch provides foundational data structures and utilities used throughout the QUIC stack.

It introduces packet header types, connection ID support, and address handling. Hash tables are added to manage socket lookup and connection ID mapping.

A flexible binary data type is provided, along with helpers for parsing, matching, and memory management. Helpers for encoding and decoding transport parameters and frames are also included.

---
v3:
- Rework hashtables: split into two types and size them based on totalram_pages(), similar to SCTP (reported by Paolo).
- struct quic_shash_table: use rwlock instead of spinlock.
- quic_data_from/to_string(): add safety and common-case checks (noted by Paolo).
v4:
- Handle the error returned by quic_hash_tables_init() properly (reported by Simon).
- Use vmalloc() to simplify hashtable allocations (suggested by Paolo).
- Replace rwlock_t with spinlock_t and use hlist_nulls_head in quic_shash_head for lockless lookup/access (suggested by Paolo).
- Define QUIC_PN_BITS to replace a magical number in quic_get_num() (reported by Paolo)
- Rename several hash-related functions:
* quic_(listen_)sock_hash() → quic_(listen_)sock_head()
* quic_(listen_)sock_head() → quic_(listen_)sock_hash()
* quic_shash() → quic_addr_hash()
* quic_ahash() → call its code directly in quic_sock_hash().
- Include net in the hash calculations in quic_listen_sock_hash() and quic_udp_sock_head(), and include len in quic_source_conn_id_head().
v5:
- Use u64 for skb_cb->time and u32 for skb_cb->seqno, and introduce quic_ktime_get_us() to obtain timestamps in microseconds.
- Remove skb_cb->number_max and reuse number as the largest previously

    seen, and add skb_cb->crypto_ctx for async crypto context freeing.

- Add case 8 to quic_put_int(), which will be used to pack a u64 timestamp into the token in a later patch.
v6:
- Since transport_header is no longer set for QUIC, use skb->data instead in quic_hdr() and quic_hshdr(). As the UDP header can now be accessed via udp_hdr(), drop udph_offset from struct quic_skb_cb.
- Note for AI reviews: although ticket/token/alpn are not initialized in quic_init_sock(), it is safe to kfree() them in quic_destroy_sock() because they are always set to NULL (via sk_alloc() in inet/6_create() or memset(0) in quic_accept() in a later patchset).
v7:
- Add the missing check len < length in quic_data_has/match() (reported by AI review).
v9:
- Add BUILD_BUG_ON() to check size of struct quic_skb_cb in quic_init() (suggested by Paolo).
v10:
- Add a comment to quic_conn_id_update() to clarify that the caller is responsible for ensuring the connection ID length does not exceed QUIC_CONN_ID_MAX_LEN.
- Ensure quic_get_param() validates that the decoded parameter value consumes exactly the expected valuelen in quic_get_param() (noted by AI review).
- Replace manual memcpy() + endian conversion and temporary union usage with get_unaligned_beNN() and put_unaligned_beNN() helpers for reading and writing integers in network byte order.
- Replace vmalloc(size * sizeof(type)) with vmalloc_array() in hash table allocations.
- Move *plen update to after successful parse in quic_get_int().

---
 net/quic/Makefile   |   2 +-
 net/quic/common.c   | 550 ++++++++++++++++++++++++++++++++++++++++++++
 net/quic/common.h   | 205 +++++++++++++++++
 net/quic/protocol.c |   9 +
 net/quic/socket.c   |   4 +
 net/quic/socket.h   |  21 ++
 6 files changed, 790 insertions(+), 1 deletion(-)
 create mode 100644 net/quic/common.c
 create mode 100644 net/quic/common.h
net/quic/Makefile +1 -1
index 020e4dd133d8..e0067272de7d 100644
@@ -5,4 +5,4 @@
 
 obj-$(CONFIG_IP_QUIC) += quic.o
 
-quic-y := protocol.o socket.o
+quic-y := common.o protocol.o socket.o
net/quic/common.c new file +550 -0

550 lines not on this page · raw message

net/quic/common.h new file +205 -0

205 lines not on this page · raw message

net/quic/protocol.c +9 -0
index 6ab81f1c50cc..f20e4aef1fa5 100644
@@ -304,6 +304,8 @@ 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);
@@ -326,6 +328,10 @@ static __init int quic_init(void)
 	if (err)
 		goto err_percpu_counter;
 
+	err = quic_hash_tables_init();
+	if (err)
+		goto err_hash;
+
 	err = register_pernet_subsys(&quic_net_ops);
 	if (err)
 		goto err_def_ops;
@@ -343,6 +349,8 @@ static __init int quic_init(void)
 err_protosw:
 	unregister_pernet_subsys(&quic_net_ops);
 err_def_ops:
+	quic_hash_tables_destroy();
+err_hash:
 	percpu_counter_destroy(&quic_sockets_allocated);
 err_percpu_counter:
 	return err;
@@ -355,6 +363,7 @@ static __exit void quic_exit(void)
 #endif
 	quic_protosw_exit();
 	unregister_pernet_subsys(&quic_net_ops);
+	quic_hash_tables_destroy();
 	percpu_counter_destroy(&quic_sockets_allocated);
 	pr_info("quic: exit\n");
 }
net/quic/socket.c +4 -0
index d65236482ad3..6e1999f171b1 100644
@@ -49,6 +49,10 @@ static int quic_init_sock(struct sock *sk)
 
 static void quic_destroy_sock(struct sock *sk)
 {
+	quic_data_free(quic_ticket(sk));
+	quic_data_free(quic_token(sk));
+	quic_data_free(quic_alpn(sk));
+
 	sk_sockets_allocated_dec(sk);
 	sock_prot_inuse_add(sock_net(sk), sk->sk_prot, -1);
 }
net/quic/socket.h +21 -0
index 98d3f738e909..9a2f4b851676 100644
@@ -10,6 +10,8 @@
 
 #include <net/udp_tunnel.h>
 
+#include "common.h"
+
 #include "protocol.h"
 
 extern struct proto quic_prot;
@@ -25,6 +27,10 @@ enum quic_state {
 struct quic_sock {
 	struct inet_sock		inet;
 	struct list_head		reqs;
+
+	struct quic_data		ticket;
+	struct quic_data		token;
+	struct quic_data		alpn;
 };
 
 struct quic6_sock {
@@ -42,6 +48,21 @@ 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 bool quic_is_serv(const struct sock *sk)
 {
 	return !!sk->sk_max_ack_backlog;
Signature · 1 line
2.47.1
author
#4 lore raw

PATCH v10 4/15 quic: provide family ops for address and protocol

In reply to Xin Long

Introduce QUIC address and protocol family operations to handle IPv4/IPv6 specifics consistently, similar to SCTP. The new quic_family.{c,h} provide helpers for routing, skb transmit handling, address parsing and comparison and UDP socket config initializing etc.

This consolidates protocol-family logic and enables cleaner dual-stack support in the QUIC socket implementation.

---
v2:
- Add more checks for addrs in .get_user_addr() and .get_pref_addr().
- Consider sk_bound_dev_if in .udp_conf_init() and .flow_route() to support vrf.
v3:
- Remove quic_addr_family/proto_ops abstraction; use if statements to reduce indirect call overhead (suggested by Paolo).
- quic_v6_set_sk_addr(): add quic_v6_copy_sk_addr() helper to avoid duplicate code (noted by Paolo).
- quic_v4_flow_route(): use flowi4_dscp per latest net-next changes.
v4:
- Remove unnecessary _fl variable from flow_route() functions (noted by Paolo).
- Fix coding style of ?: operator (noted by Paolo).
v5:
- Remove several unused functions from this patch series (suggested by Paolo):
* quic_seq_dump_addr()
* quic_get_msg_ecn()
* quic_get_user_addr()
* quic_get_pref_addr()
* quic_set_pref_addr()
* quic_set_sk_addr()
* quic_set_sk_ecn()
- Replace the sa->v4/v6.sin_family checks with quic_v4/v6_is_any_addr() in quic_v4/v6_flow_route() (suggested by Paolo).
- Introduce quic_v4_match_v6_addr() to simplify family-mismatch checks between sk and addr in quic_v6_cmp_sk_addr() (notied by Paolo).
v6:
- Use udp_hdr(skb) to access UDP header in quic_v4/6_get_msg_addrs(), as transport_header is no longer reset for QUIC.
v10:
- Fix argument types passed to ip6_dst_store() in quic_v6_flow_route().

---
 net/quic/Makefile   |   2 +-
 net/quic/family.c   | 372 ++++++++++++++++++++++++++++++++++++++++++++
 net/quic/family.h   |  33 ++++
 net/quic/protocol.c |   2 +-
 net/quic/socket.c   |   4 +-
 net/quic/socket.h   |   1 +
 6 files changed, 410 insertions(+), 4 deletions(-)
 create mode 100644 net/quic/family.c
 create mode 100644 net/quic/family.h
net/quic/Makefile +1 -1
index e0067272de7d..13bf4a4e5442 100644
@@ -5,4 +5,4 @@
 
 obj-$(CONFIG_IP_QUIC) += quic.o
 
-quic-y := common.o protocol.o socket.o
+quic-y := common.o family.o protocol.o socket.o
net/quic/family.c new file +372 -0

372 lines not on this page · raw message

net/quic/family.h new file +33 -0
new file mode 100644
index 000000000000..624a5d7a8471
@@ -0,0 +1,33 @@
+/* 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 <[email protected]>
+ */
+
+#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)
+
+int 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, bool peer);
+
+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);
net/quic/protocol.c +1 -1
index f20e4aef1fa5..1fecbd3e3d9d 100644
@@ -46,7 +46,7 @@ static int quic_inet_listen(struct socket *sock, int backlog)
 
 static int quic_inet_getname(struct socket *sock, struct sockaddr *uaddr, int peer)
 {
-	return -EOPNOTSUPP;
+	return quic_get_sk_addr(sock, uaddr, peer);
 }
 
 static __poll_t quic_inet_poll(struct file *file, struct socket *sock, poll_table *wait)
net/quic/socket.c +2 -2
index 6e1999f171b1..1ef8a7932932 100644
@@ -113,7 +113,7 @@ static int quic_setsockopt(struct sock *sk, int level, int optname,
 			   sockptr_t optval, unsigned int optlen)
 {
 	if (level != SOL_QUIC)
-		return -EOPNOTSUPP;
+		return quic_common_setsockopt(sk, level, optname, optval, optlen);
 
 	return quic_do_setsockopt(sk, optname, optval, optlen);
 }
@@ -127,7 +127,7 @@ static int quic_getsockopt(struct sock *sk, int level, int optname,
 			   char __user *optval, int __user *optlen)
 {
 	if (level != SOL_QUIC)
-		return -EOPNOTSUPP;
+		return quic_common_getsockopt(sk, level, optname, optval, optlen);
 
 	return quic_do_getsockopt(sk, optname, USER_SOCKPTR(optval), USER_SOCKPTR(optlen));
 }
net/quic/socket.h +1 -0
index 9a2f4b851676..0aa642e3b0ae 100644
@@ -11,6 +11,7 @@
 #include <net/udp_tunnel.h>
 
 #include "common.h"
+#include "family.h"
 
 #include "protocol.h"
 
Signature · 1 line
2.47.1
author
#5 lore raw

PATCH v10 5/15 quic: provide quic.h header files for kernel and userspace

In reply to Xin Long

This commit adds quic.h to include/uapi/linux, providing the necessary definitions for the QUIC socket API. Exporting this header allows both user space applications and kernel subsystems to access QUIC-related control messages, socket options, and event/notification interfaces.

Since kernel_get/setsockopt() is no longer available to kernel consumers, a corresponding internal header, include/linux/quic.h, is added. This exposes quic_do_get/setsockopt() to handle QUIC socket options directly for kernel subsystems.

Detailed descriptions of these structures are available in [1], and will be also provided when adding corresponding socket interfaces in the later patches.

[1] https://datatracker.ietf.org/doc/html/draft-lxin-quic-socket-apis

---
v2:
- Fix a kernel API description warning, found by Jakub.
- Replace uintN_t with __uN, capitalize _UAPI_LINUX_QUIC_H, and assign explicit values for QUIC_TRANSPORT_ERROR_ enum in UAPI quic.h, suggested by David Howells.
v4:
- Use MSG_QUIC_ prefix for MSG_* flags to avoid conflicts with other protocols, such as MSG_NOTIFICATION in SCTP (reported by Thomas).
- Remove QUIC_CONG_ALG_CUBIC; only NEW RENO congestion control is supported in this version.
v5:
- Add include/linux/quic.h and include/uapi/linux/quic.h to the QUIC PROTOCOL entry in MAINTAINERS.
v6:
- Fix the copy/pasted the uAPI path for SCTP to the QUIC entry (noted by Jakub).
v7:
- Expose quic_do_get/setsockopt() instead of quic_kernel_get/setsockopt() (suggested by Paolo).
v10:
- Fix typo: 'extented' -> 'extended' (noted by AI review).
- Add comment for inclusion of sys/socket.h in uapi quic.h.
- Add uses-libc += linux/quic.h in usr/include/Makefile to fix the new build error.
- Delete config from struct quic_sock, its members will be split into other subcomponents in the future patches.
- Add explicit reserved fields to multiple structs to account for implicit padding and ensure UAPI stability.
- Expand reserved fields in struct transport_param and config, handshake and stream_info to allow future extensions without breaking the UAPI.

---
 MAINTAINERS               |   2 +
 include/linux/quic.h      |  20 ++++
 include/uapi/linux/quic.h | 242 ++++++++++++++++++++++++++++++++++++++
 net/quic/socket.c         |  32 ++++-
 net/quic/socket.h         |   1 +
 usr/include/Makefile      |   1 +
 6 files changed, 296 insertions(+), 2 deletions(-)
 create mode 100644 include/linux/quic.h
 create mode 100644 include/uapi/linux/quic.h
MAINTAINERS +2 -0
index f1dfc2239978..36aa61f29aab 100644
@@ -21920,6 +21920,8 @@ M:	Xin Long <[email protected]>
 L:	[email protected]
 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
include/linux/quic.h new file +20 -0
new file mode 100644
index 000000000000..c246f6349f9c
@@ -0,0 +1,20 @@
+/* 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 <[email protected]>
+ */
+
+#ifndef _LINUX_QUIC_H
+#define _LINUX_QUIC_H
+
+#include <linux/sockptr.h>
+#include <uapi/linux/quic.h>
+
+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
include/uapi/linux/quic.h new file +242 -0

242 lines not on this page · raw message

net/quic/socket.c +30 -2
index 1ef8a7932932..72483b561820 100644
@@ -104,10 +104,24 @@ static void quic_close(struct sock *sk, long timeout)
 	sk_common_release(sk);
 }
 
-static int quic_do_setsockopt(struct sock *sk, int optname, sockptr_t optval, unsigned int optlen)
+/**
+ * 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)
@@ -118,10 +132,24 @@ static int quic_setsockopt(struct sock *sk, int level, int optname,
 	return quic_do_setsockopt(sk, optname, optval, optlen);
 }
 
-static int quic_do_getsockopt(struct sock *sk, int optname, sockptr_t optval, sockptr_t 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: in/out parameter for buffer size; updated with actual length 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)
net/quic/socket.h +1 -0
index 0aa642e3b0ae..61df0c5867be 100644
@@ -9,6 +9,7 @@
  */
 
 #include <net/udp_tunnel.h>
+#include <linux/quic.h>
 
 #include "common.h"
 #include "family.h"
usr/include/Makefile +1 -0
index 6d86a53c6f0a..a0442e4d77da 100644
@@ -120,6 +120,7 @@ uses-libc += linux/netfilter_ipv4.h
 uses-libc += linux/netfilter_ipv4/ip_tables.h
 uses-libc += linux/netfilter_ipv6.h
 uses-libc += linux/netfilter_ipv6/ip6_tables.h
+uses-libc += linux/quic.h
 uses-libc += linux/route.h
 uses-libc += linux/shm.h
 uses-libc += linux/soundcard.h
Signature · 1 line
2.47.1
author
#6 lore raw

PATCH v10 6/15 quic: add stream management

In reply to Xin Long

This patch introduces 'struct quic_stream_table' for managing QUIC streams, each represented by 'struct quic_stream'.

It implements mechanisms for acquiring and releasing streams on both the send and receive paths, ensuring efficient lifecycle management during transmission and reception.

- quic_stream_get(): Acquire a send-side stream by ID and flags during TX path, or a receive-side stream by ID during RX path.

- quic_stream_put(): Release a send-side stream when sending is done, or a receive-side stream when receiving is done.

It includes logic to detect when stream ID limits are reached and when control frames should be sent to update or request limits from the peer.

- quic_stream_id_exceeds(): Check a stream ID would exceed local (recv) or peer (send) limits.

- quic_stream_max_streams_update(): Determines whether a MAX_STREAMS_UNI/BIDI frame should be sent to the peer.

Note stream hash table is per socket, the operations on it are always protected by the sock lock.

---
v3:
- Merge send/recv stream helpers into unified functions to reduce code:
* quic_stream_id_send/recv() → quic_stream_id_valid()
* quic_stream_id_send/recv_closed() → quic_stream_id_closed()
* quic_stream_id_send/recv_exceeds() → quic_stream_id_exceeds() (pointed out by Paolo).
- Clarify in changelog that stream hash table is always protected by sock lock (suggested by Paolo).
- quic_stream_init/free(): adjust for new hashtable type; call quic_stream_delete() in quic_stream_free() to avoid open-coded logic.
- Receiving streams: delete stream only when fully read or reset, instead of when no data was received. Prevents freeing a stream while a FIN with no data is still queued.
v4:
- Replace struct quic_shash_table with struct hlist_head for the stream hashtable. Since they are protected by the socket lock, no per-chain lock is needed.
- Initialize stream to NULL in stream creation functions to avoid warnings from Smatch (reported by Simon).
- Allocate send streams with GFP_KERNEL_ACCOUNT and receive streams with GFP_ATOMIC | __GFP_ACCOUNT for memory accounting (suggested by Paolo).
v5:
- Introduce struct quic_stream_limits to merge quic_stream_send_create() and quic_stream_recv_create(), and to simplify quic_stream_get_param() (suggested by Paolo).
- Annotate the sock-lock requirement for quic_stream_send/recv_get() and quic_stream_send/recv_put() (notied by Paolo).
- Add quic_stream_bidi_put() to deduplicate the common logic between quic_stream_send_put() and quic_stream_recv_put().
- Remove the unnecessary check when incrementing

    streams->send.next_bidi/uni_stream_id in quic_stream_create().

- Remove the unused 'is_serv' parameter from quic_stream_get_param().
v7:
- Free the allocated streams on error path in quic_stream_create() (noted by Paolo).
- Merge quic_stream_send_get/put() and quic_stream_recv_get/put() helpers to quic_stream_get/put() (suggested by Paolo).
- Add more comments in quic_stream_id_exceeds() and quic_stream_create().
v8:
- Replace bitfields with plain u8 in struct quic_stream_limits and struct quic_stream (suggested by Paolo).
v9:
- Fix grammar in the comment for quic_stream::send.window.
v10:
- Move quic_stream_init() to after sock_prot_inuse_add() ensure counters are incremented before any early return paths in quic_init_sock(), preventing underflow in quic_destroy_sock() (noted by AI review).
- Initialize the output parameters '*max_uni' and '*max_bidi' to 0 at the start of quic_stream_max_streams_update()
- Use 'stream->recv.state > QUIC_STREAM_RECV_STATE_RECVD' instead of '!=' for clearer intent.
- Simplify some state checks in quic_stream_put() by using range

    comparisons (> or <) instead of multiple != conditions.
  - streams_uni/bidi are u16 type, and their overflow is already prevented
    by QUIC_MAX_STREAMS indirectly. Update comment in quic_stream_create().
  - Replace open-coded kzalloc(sizeof(*stream)) with kzalloc_obj(*stream)
    in quic_stream_create().
---
 net/quic/Makefile |   2 +-
 net/quic/socket.c |   5 +
 net/quic/socket.h |   8 +
 net/quic/stream.c | 400 ++++++++++++++++++++++++++++++++++++++++++++++
 net/quic/stream.h | 119 ++++++++++++++
 5 files changed, 533 insertions(+), 1 deletion(-)
 create mode 100644 net/quic/stream.c
 create mode 100644 net/quic/stream.h
net/quic/Makefile +1 -1
index 13bf4a4e5442..094e9da5d739 100644
@@ -5,4 +5,4 @@
 
 obj-$(CONFIG_IP_QUIC) += quic.o
 
-quic-y := common.o family.o protocol.o socket.o
+quic-y := common.o family.o protocol.o socket.o stream.o
net/quic/socket.c +5 -0
index 72483b561820..8102361404de 100644
@@ -44,11 +44,16 @@ static int quic_init_sock(struct sock *sk)
 	sk_sockets_allocated_inc(sk);
 	sock_prot_inuse_add(sock_net(sk), sk->sk_prot, 1);
 
+	if (quic_stream_init(quic_streams(sk)))
+		return -ENOMEM;
+
 	return 0;
 }
 
 static void quic_destroy_sock(struct sock *sk)
 {
+	quic_stream_free(quic_streams(sk));
+
 	quic_data_free(quic_ticket(sk));
 	quic_data_free(quic_token(sk));
 	quic_data_free(quic_alpn(sk));
net/quic/socket.h +8 -0
index 61df0c5867be..e76737b9b74b 100644
@@ -13,6 +13,7 @@
 
 #include "common.h"
 #include "family.h"
+#include "stream.h"
 
 #include "protocol.h"
 
@@ -33,6 +34,8 @@ struct quic_sock {
 	struct quic_data		ticket;
 	struct quic_data		token;
 	struct quic_data		alpn;
+
+	struct quic_stream_table	streams;
 };
 
 struct quic6_sock {
@@ -65,6 +68,11 @@ 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 bool quic_is_serv(const struct sock *sk)
 {
 	return !!sk->sk_max_ack_backlog;
net/quic/stream.c new file +400 -0

400 lines not on this page · raw message

net/quic/stream.h new file +119 -0

119 lines not on this page · raw message

Signature · 1 line
2.47.1
author
#7 lore raw

PATCH v10 7/15 quic: add connection id management

In reply to Xin Long

This patch introduces 'struct quic_conn_id_set' for managing Connection IDs (CIDs), which are represented by 'struct quic_source_conn_id' and 'struct quic_dest_conn_id'.

It provides helpers to add and remove CIDs from the set, and handles insertion of source CIDs into the global connection ID hash table when necessary.

- quic_conn_id_add(): Add a new Connection ID to the set, and inserts it to conn_id hash table if it is a source conn_id.

- quic_conn_id_remove(): Remove connection IDs the set with sequence numbers less than or equal to a number.

It also adds utilities to look up CIDs by value or sequence number, search the global hash table for incoming packets, and check for stateless reset tokens among destination CIDs. These functions are essential for RX path connection lookup and stateless reset processing.

- quic_conn_id_find(): Find a Connection ID in the set by seq number.

- quic_conn_id_lookup(): Lookup a Connection ID from global hash table using the ID value, typically used for socket lookup on the RX path.

- quic_conn_id_token_exists(): Check if a stateless reset token exists in any dest Connection ID (used during stateless reset processing).

Note source/dest conn_id set is per socket, the operations on it are always protected by the sock lock.

---
v3:
- Clarify in changelog that conn_id set is always protected by sock lock (suggested by Paolo).
- Adjust global source conn_id hashtable operations for the new hashtable type.
v4:
- Replace struct hlist_node with hlist_nulls_node for the node in struct quic_source_conn_id to support lockless lookup.
v7:
- Break the loop earlier if common->number > number in quic_conn_id_remove/find() (suggested by Paolo).
- Add a comment in quic_conn_id_first_number().
v8:
- Add a comment to quic_conn_id_remove() clarifying that the ID number must be smaller than the sequence number of the last ID in the set.

---
 net/quic/Makefile |   2 +-
 net/quic/connid.c | 227 ++++++++++++++++++++++++++++++++++++++++++++++
 net/quic/connid.h | 163 +++++++++++++++++++++++++++++++++
 net/quic/socket.c |   6 ++
 net/quic/socket.h |  13 +++
 5 files changed, 410 insertions(+), 1 deletion(-)
 create mode 100644 net/quic/connid.c
 create mode 100644 net/quic/connid.h
net/quic/Makefile +1 -1
index 094e9da5d739..eee7501588d3 100644
@@ -5,4 +5,4 @@
 
 obj-$(CONFIG_IP_QUIC) += quic.o
 
-quic-y := common.o family.o protocol.o socket.o stream.o
+quic-y := common.o family.o protocol.o socket.o stream.o connid.o
net/quic/connid.c new file +227 -0

227 lines not on this page · raw message

net/quic/connid.h new file +163 -0

163 lines not on this page · raw message

net/quic/socket.c +6 -0
index 8102361404de..fdbe80b4690d 100644
@@ -44,6 +44,9 @@ static int quic_init_sock(struct sock *sk)
 	sk_sockets_allocated_inc(sk);
 	sock_prot_inuse_add(sock_net(sk), sk->sk_prot, 1);
 
+	quic_conn_id_set_init(quic_source(sk), 1);
+	quic_conn_id_set_init(quic_dest(sk), 0);
+
 	if (quic_stream_init(quic_streams(sk)))
 		return -ENOMEM;
 
@@ -52,6 +55,9 @@ static int quic_init_sock(struct sock *sk)
 
 static void quic_destroy_sock(struct sock *sk)
 {
+	quic_conn_id_set_free(quic_source(sk));
+	quic_conn_id_set_free(quic_dest(sk));
+
 	quic_stream_free(quic_streams(sk));
 
 	quic_data_free(quic_ticket(sk));
net/quic/socket.h +13 -0
index e76737b9b74b..68a58f0016cc 100644
@@ -14,6 +14,7 @@
 #include "common.h"
 #include "family.h"
 #include "stream.h"
+#include "connid.h"
 
 #include "protocol.h"
 
@@ -36,6 +37,8 @@ struct quic_sock {
 	struct quic_data		alpn;
 
 	struct quic_stream_table	streams;
+	struct quic_conn_id_set		source;
+	struct quic_conn_id_set		dest;
 };
 
 struct quic6_sock {
@@ -73,6 +76,16 @@ static inline struct quic_stream_table *quic_streams(const struct sock *sk)
 	return &quic_sk(sk)->streams;
 }
 
+static inline struct quic_conn_id_set *quic_source(const struct sock *sk)
+{
+	return &quic_sk(sk)->source;
+}
+
+static inline struct quic_conn_id_set *quic_dest(const struct sock *sk)
+{
+	return &quic_sk(sk)->dest;
+}
+
 static inline bool quic_is_serv(const struct sock *sk)
 {
 	return !!sk->sk_max_ack_backlog;
Signature · 1 line
2.47.1
author
#8 lore raw

PATCH v10 8/15 quic: add path management

In reply to Xin Long

This patch introduces 'quic_path_group' for managing paths, represented by 'struct quic_path'. A connection may use two paths simultaneously for connection migration.

Each path is associated with a UDP tunnel socket (sk), and a single UDP tunnel socket can be related to multiple paths from different sockets. These UDP tunnel sockets are wrapped in 'quic_udp_sock' structures and stored in a hash table.

It includes mechanisms to bind and unbind paths, detect alternative paths for migration, and swap paths to support seamless transition between networks.

- quic_path_bind(): Bind a path to a port and associate it with a UDP sk.

- quic_path_unbind(): Unbind a path from a port and disassociate it from a UDP sk.

- quic_path_swap(): Swap two paths to facilitate connection migration.

- quic_path_detect_alt(): Determine if a packet is using an alternative path, used for connection migration.

It also integrates basic support for Packetization Layer Path MTU
Discovery (PLPMTUD), using PING frames and ICMP feedback to adjust path MTU and handle probe confirmation or resets during routing changes.

- quic_path_pl_recv(): state transition and pmtu update after the probe packet is acked.

- quic_path_pl_toobig(): state transition and pmtu update after receiving a toobig or needfrag icmp packet.

- quic_path_pl_send(): state transition and pmtu update after sending a probe packet.

- quic_path_pl_reset(): restart the probing when path routing changes.

- quic_path_pl_confirm(): check if probe packet gets acked.

---
v3:
- Fix annotation in quic_udp_sock_lookup() (noted by Paolo).
- Use inet_sk_get_local_port_range() instead of inet_get_local_port_range() (suggested by Paolo).
- Adjust global UDP tunnel socket hashtable operations for the new hashtable type.
- Delete quic_workqueue; use system_wq for UDP tunnel socket destroy.
v4:
- Cache UDP tunnel socket pointer and its source address in struct quic_path for RCU-protected lookup/access.
- Return -EAGAIN instead of -EINVAL in quic_path_bind() when UDP socket is being released in workqueue.
- Move udp_tunnel_sock_release() out of the mutex_lock to avoid a warning of lockdep in quic_udp_sock_put_work().
- Introduce quic_wq for UDP socket release work, so all pending works can be flushed before destroying the hashtable in quic_exit().
v5:
- Rename quic_path_free() to quic_path_unbind() (suggested by Paolo).
- Remove the 'serv' member from struct quic_path_group, since quic_is_serv() defined in a previous patch now uses sk->sk_max_ack_backlog for server-side detection.
- Use quic_ktime_get_us() to set skb_cb->time, as RTT is measured in microseconds and jiffies_to_usecs() is not accurate enough.
v6:
- Do not reset transport_header for QUIC in quic_udp_rcv(), allowing

    removal of udph_offset and enabling access to the UDP header via
    udp_hdr(); Pull skb->data in quic_udp_rcv() to allow access to the
    QUIC header via skb->data.

v7:
- Pass udp sk to quic_path_rcv() and move the call to skb_linearize() and skb_set_owner_sk_safe() to .quic_path_rcv().
- Delete the call to skb_linearize() and skb_set_owner_sk_safe() from quic_udp_err(), as it should not change skb in .encap_err_lookup() (noted by AI review).
v8:
- Remove indirect quic_path_rcv and late call quic_packet_rcv() directly via extern (noted by Paolo).
- Add a comment in quic_udp_rcv() clarifying it must return 0.
- Add a comment in quic_udp_sock_put() clarifying the UDP socket may be freed in atomic RX context during connection migration.
- Reorder some quic_path_group members to reduce struct size.
v10:
- Replace open-coded kzalloc(sizeof(*us)) with kzalloc_obj(*us) in quic_stream_create().
- Use get_random_u32_below() for ephemeral port selection instead of manual scaling of get_random_u32() in quic_path_bind().
- Reset additional PLPMTUD probe state (probe_high, probe_count) in quic_path_pl_reset() to ensure a clean probe restart.
- Add plpmtud_interval to struct quic_path_group to store the PLPMTUD probe timer interval, previously kept in struct quic_sock.config.

---
 net/quic/Makefile   |   2 +-
 net/quic/path.c     | 522 ++++++++++++++++++++++++++++++++++++++++++++
 net/quic/path.h     | 172 +++++++++++++++
 net/quic/protocol.c |  11 +
 net/quic/socket.c   |   3 +
 net/quic/socket.h   |   7 +
 6 files changed, 716 insertions(+), 1 deletion(-)
 create mode 100644 net/quic/path.c
 create mode 100644 net/quic/path.h
net/quic/Makefile +1 -1
index eee7501588d3..1565fb5cef9d 100644
@@ -5,4 +5,4 @@
 
 obj-$(CONFIG_IP_QUIC) += quic.o
 
-quic-y := common.o family.o protocol.o socket.o stream.o connid.o
+quic-y := common.o family.o protocol.o socket.o stream.o connid.o path.o
net/quic/path.c new file +522 -0

522 lines not on this page · raw message

net/quic/path.h new file +172 -0

172 lines not on this page · raw message

net/quic/protocol.c +11 -0
index 1fecbd3e3d9d..0d9679f02ebd 100644
@@ -21,6 +21,7 @@
 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);
 
@@ -332,6 +333,12 @@ static __init int quic_init(void)
 	if (err)
 		goto err_hash;
 
+	quic_wq = create_workqueue("quic_workqueue");
+	if (!quic_wq) {
+		err = -ENOMEM;
+		goto err_wq;
+	}
+
 	err = register_pernet_subsys(&quic_net_ops);
 	if (err)
 		goto err_def_ops;
@@ -349,6 +356,8 @@ static __init int quic_init(void)
 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);
@@ -363,6 +372,8 @@ static __exit void quic_exit(void)
 #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);
 	pr_info("quic: exit\n");
net/quic/socket.c +3 -0
index fdbe80b4690d..dc99d1bdec6d 100644
@@ -55,6 +55,9 @@ static int quic_init_sock(struct sock *sk)
 
 static void quic_destroy_sock(struct sock *sk)
 {
+	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));
 
net/quic/socket.h +7 -0
index 68a58f0016cc..91338601905e 100644
@@ -15,6 +15,7 @@
 #include "family.h"
 #include "stream.h"
 #include "connid.h"
+#include "path.h"
 
 #include "protocol.h"
 
@@ -39,6 +40,7 @@ struct quic_sock {
 	struct quic_stream_table	streams;
 	struct quic_conn_id_set		source;
 	struct quic_conn_id_set		dest;
+	struct quic_path_group		paths;
 };
 
 struct quic6_sock {
@@ -86,6 +88,11 @@ 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;
Signature · 1 line
2.47.1
maintainer
#9 lore raw

Re: [PATCH net-next v10 08/15] quic: add path management

Quoted 6 lines · Xin Long
net/quic/path.c new file +6 -0
+/* 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);

net/quic/path.c line 171

Quoted 10 lines
net/quic/path.c new file +10 -0
+     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) {

When the quick socket is already bound to a local port, reusing an existing udp tunnel sock is allowed, but when the quick socket is not bound, UDP tunnel sock reused is prevented. This looks confusing and not documented, please clarify the behavior and/or make it consistent.

net/quic/path.c line 174

Quoted 3 lines
net/quic/path.c new file +3 -0
+                     if (!quic_udp_sock_get(us)) { /* Releasing in workqueue; retry later. */
+                             mutex_unlock(&head->lock);
+                             return -EAGAIN;

Why not -EADDRINUSE here?

net/quic/path.c line 180

Quoted 6 lines
net/quic/path.c new file +6 -0
+                     }
+             } else {
+                     us = quic_udp_sock_create(sk, a);
+                     if (!us) {
+                             mutex_unlock(&head->lock);
+                             return -EINVAL;

It's probably better to propagate an error code (PTR_ERR) from quic_udp_sock_create(), or use -ENOMEM

[...]

net/quic/protocol.c static __init int quic_init(void) · line 340

Quoted 9 lines
net/quic/protocol.c +5 -0
@@ -332,6 +333,12 @@ static __init int quic_init(void)
      if (err)
              goto err_hash;
 
+     quic_wq = create_workqueue("quic_workqueue");
+     if (!quic_wq) {
+             err = -ENOMEM;
+             goto err_wq;
+     }

AI review noted that:

This isn't a bug, but create_workqueue() is a legacy API marked with __WQ_LEGACY in include/linux/workqueue.h. Should new subsystem code use alloc_workqueue() with explicit flags instead?

Looking at include/linux/workqueue.h, create_workqueue() implicitly sets WQ_PERCPU, creating per-CPU worker threads. Since quic_wq only handles infrequent UDP socket cleanup via quic_udp_sock_put_work() in path.c, is per-CPU allocation necessary here? Would alloc_workqueue("quic_workqueue", WQ_MEM_RECLAIM, 0) be more appropriate, or could this simply use system_wq if memory reclaim safety is not required?

/P

author
#10 lore raw

Re: [PATCH net-next v10 08/15] quic: add path management

Quoted 15 lines · Paolo Abeni
Quoted 13 lines · Xin Long
net/quic/path.c new file +13 -0
+/* 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);
Quoted 9 lines

net/quic/path.c line 171

Quoted 3 lines
net/quic/path.c new file +3 -0
+             mutex_lock(&head->lock);
+             us = quic_udp_sock_lookup(sk, a, port);
+             if (us) {

When the quick socket is already bound to a local port, reusing an existing udp tunnel sock is allowed, but when the quick socket is not bound, UDP tunnel sock reused is prevented. This looks confusing and not documented, please clarify the behavior and/or make it consistent.

Yes,

/* Reuse of an existing UDP tunnel socket is allowed,
* but if it is currently being freed asynchronously by the workqueue,
* it cannot be used now — retry later.
*/

Let me know if it's still not clear.

Quoted 6 lines

net/quic/path.c line 174

Quoted 4 lines
net/quic/path.c new file +3 -0
+                     if (!quic_udp_sock_get(us)) { /* Releasing in workqueue; retry later. */
+                             mutex_unlock(&head->lock);
+                             return -EAGAIN;

Why not -EADDRINUSE here?

Because in this case, the us is being released in workqueue, a retry will likely succeed.

Quoted 11 lines

net/quic/path.c line 180

Quoted 7 lines
net/quic/path.c new file +6 -0
+                     }
+             } else {
+                     us = quic_udp_sock_create(sk, a);
+                     if (!us) {
+                             mutex_unlock(&head->lock);
+                             return -EINVAL;

It's probably better to propagate an error code (PTR_ERR) from quic_udp_sock_create(), or use -ENOMEM

Changing to PTR_ERR looks better.

Quoted 17 lines

[...]

net/quic/protocol.c static __init int quic_init(void) · line 340

Quoted 9 lines
net/quic/protocol.c +5 -0
@@ -332,6 +333,12 @@ static __init int quic_init(void)
      if (err)
              goto err_hash;
 
+     quic_wq = create_workqueue("quic_workqueue");
+     if (!quic_wq) {
+             err = -ENOMEM;
+             goto err_wq;
+     }

AI review noted that:

This isn't a bug, but create_workqueue() is a legacy API marked with __WQ_LEGACY in include/linux/workqueue.h. Should new subsystem code use alloc_workqueue() with explicit flags instead?

Quoted 7 lines

Looking at include/linux/workqueue.h, create_workqueue() implicitly sets WQ_PERCPU, creating per-CPU worker threads. Since quic_wq only handles infrequent UDP socket cleanup via quic_udp_sock_put_work() in path.c, is per-CPU allocation necessary here? Would alloc_workqueue("quic_workqueue", WQ_MEM_RECLAIM, 0) be more appropriate, or could this simply use system_wq if memory reclaim safety is not required?

This workqueue is also used for processing some backlog packets, which requires process context.

I will move the infrequent UDP socket cleanup to system_wq as the AI suggests, and leave this workqueue for the backlog packets processing only. Then the allocation becomes:

quic_wq = alloc_workqueue("quic_workqueue", WQ_PERCPU, 0);

Thanks.

author
#11 lore raw

PATCH v10 9/15 quic: add congestion control

In reply to Xin Long

This patch introduces 'quic_cong' for RTT measurement and congestion control. The 'quic_cong_ops' is added to define the congestion control algorithm.

It implements a congestion control state machine with slow start, congestion avoidance, and recovery phases, and currently introduces the New Reno algorithm only.

The implementation updates RTT estimates when packets are acknowledged, reacts to loss and ECN signals, and adjusts the congestion window accordingly during packet transmission and acknowledgment processing.

- quic_cong_rtt_update(): Performs RTT measurement, invoked when a packet is acknowledged by the largest number in the ACK frame.

- quic_cong_on_packet_acked(): Invoked when a packet is acknowledged.

- quic_cong_on_packet_lost(): Invoked when a packet is marked as lost.

- quic_cong_on_process_ecn(): Invoked when an ACK_ECN frame is received.

- quic_cong_on_packet_sent(): Invoked when a packet is transmitted.

- quic_cong_on_ack_recv(): Invoked when an ACK frame is received.

---
v4:
- Remove the CUBIC congestion algorithm support for this version (suggested by Paolo).
v5:
- Do not update the pacing rate when !cong->smoothed_rtt in quic_cong_pace_update() (suggested by Paolo).
- Change timestamp variables from u32 to u64, as RTT is measured in microseconds and u64 provides sufficient precision for timestamps in microsecond.
v8:
- Add a comment in quic_reno_on_packet_acked() clarifying cong->window is never zero (noted by AI review).
v9:
- Use abs_diff() to simplify RTT variance calculation (noted by AI review).
- Fix a small typo in the comment for struct quic_cong::time (noted by AI review).
- Fix another small typo in quic_cong_check_persistent_congestion().
v10:
- Note for AI reviews: cong->time will always be set before processing the packet on the RX path in the future patch series.
- Add initial_srtt and algo to struct quic_cong to store the initial smoothed RTT and congestion control algorithm, previously kept in quic_sock.config.

---
 net/quic/Makefile |   3 +-
 net/quic/cong.c   | 307 ++++++++++++++++++++++++++++++++++++++++++++++
 net/quic/cong.h   | 123 +++++++++++++++++++
 net/quic/socket.c |   1 +
 net/quic/socket.h |   7 ++
 5 files changed, 440 insertions(+), 1 deletion(-)
 create mode 100644 net/quic/cong.c
 create mode 100644 net/quic/cong.h
net/quic/Makefile +2 -1
index 1565fb5cef9d..4d4a42c6d565 100644
@@ -5,4 +5,5 @@
 
 obj-$(CONFIG_IP_QUIC) += quic.o
 
-quic-y := common.o family.o protocol.o socket.o stream.o connid.o path.o
+quic-y := common.o family.o protocol.o socket.o stream.o connid.o path.o \
+	  cong.o
net/quic/cong.c new file +307 -0

307 lines not on this page · raw message

net/quic/cong.h new file +123 -0

123 lines not on this page · raw message

net/quic/socket.c +1 -0
index dc99d1bdec6d..063418c77a03 100644
@@ -46,6 +46,7 @@ static int quic_init_sock(struct sock *sk)
 
 	quic_conn_id_set_init(quic_source(sk), 1);
 	quic_conn_id_set_init(quic_dest(sk), 0);
+	quic_cong_init(quic_cong(sk));
 
 	if (quic_stream_init(quic_streams(sk)))
 		return -ENOMEM;
net/quic/socket.h +7 -0
index 91338601905e..9201ca3edad0 100644
@@ -16,6 +16,7 @@
 #include "stream.h"
 #include "connid.h"
 #include "path.h"
+#include "cong.h"
 
 #include "protocol.h"
 
@@ -41,6 +42,7 @@ struct quic_sock {
 	struct quic_conn_id_set		source;
 	struct quic_conn_id_set		dest;
 	struct quic_path_group		paths;
+	struct quic_cong		cong;
 };
 
 struct quic6_sock {
@@ -98,6 +100,11 @@ 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 bool quic_is_establishing(struct sock *sk)
 {
 	return sk->sk_state == QUIC_SS_ESTABLISHING;
Signature · 1 line
2.47.1
maintainer
#12 lore raw

Re: [net-next,v10,09/15] quic: add congestion control

This is an AI-generated review of your patch. The human sending this email has considered the AI review valid, or at least plausible.

For local reproduction steps see https://netdev-ai.bots.linux.dev/ai-local.html
---
quic: add congestion control

This patch introduces quic_cong for RTT measurement and congestion control, implementing a state machine with slow start, congestion avoidance, and recovery phases using the New Reno algorithm.

Quoted 16 lines
net/quic/Makefile +2 -1
index 1565fb5cef9d..4d4a42c6d565 100644
@@ -5,4 +5,5 @@
 
 obj-$(CONFIG_IP_QUIC) += quic.o
 
-quic-y := common.o family.o protocol.o socket.o stream.o connid.o path.o
+quic-y := common.o family.o protocol.o socket.o stream.o connid.o path.o \
+	  cong.o
net/quic/cong.c new file +0 -0
new file mode 100644
index 000000000000..e0f78ecb00e4

[ ... ]

Quoted 26 lines
net/quic/cong.c new file +26 -0
+static void quic_reno_on_packet_lost(struct quic_cong *cong, u64 time, u32 bytes, s64 number)
+{
+	if (quic_cong_check_persistent_congestion(cong, time))
+		return;
+
+	switch (cong->state) {
+	case QUIC_CONG_SLOW_START:
+		pr_debug("%s: slow_start -> recovery, cwnd: %u, ssthresh: %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, ssthresh: %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;
+}

[ ... ]

Quoted 13 lines
net/quic/cong.c new file +13 -0
+static void quic_reno_on_process_ecn(struct quic_cong *cong)
+{
+	switch (cong->state) {
+	case QUIC_CONG_SLOW_START:
+		pr_debug("%s: slow_start -> recovery, cwnd: %u, ssthresh: %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, ssthresh: %u\n",
+			 __func__, cong->window, cong->ssthresh);
+		break;

net/quic/cong.c line 119

Quoted 10 lines
net/quic/cong.c new file +10 -0
+	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;
+}

This isn't a bug, but these two functions share almost identical logic for entering the recovery state. The switch statement has the same three cases with identical handling in both functions, and the four-line recovery-entry block at the end is exactly the same:

    cong->recovery_time = cong->time;
    cong->state = QUIC_CONG_RECOVERY_PERIOD;
    cong->ssthresh = max(cong->window >> 1U, cong->min_window);
    cong->window = cong->ssthresh;

The only difference is quic_reno_on_packet_lost() checks for persistent congestion first. Would factoring this shared logic into a helper function make future maintenance easier and ensure the recovery path stays consistent across both ECN and loss events?

[ ... ]

net/quic/cong.c line 286

Quoted 9 lines
net/quic/cong.c new file +9 -0
+void quic_cong_set_algo(struct quic_cong *cong, u8 algo)
+{
+	/* The caller must ensure algo < QUIC_CONG_ALG_MAX. */
+	cong->algo = algo;
+	cong->state = QUIC_CONG_SLOW_START;
+	cong->ssthresh = U32_MAX;
+	cong->ops = &quic_congs[algo];
+	cong->ops->on_init(cong);
+}

Should this function validate the algo parameter? The comment says the caller must ensure algo is less than QUIC_CONG_ALG_MAX, but there's no runtime check. Since QUIC_CONG_ALG_MAX is defined in the UAPI and this function is exported in cong.h, could a future caller (for example, a setsockopt path accepting user-supplied algorithm choices) pass an out-of-range value and cause an out-of-bounds array access into quic_congs[]?

[ ... ]

author
#13 lore raw

Re: [net-next,v10,09/15] quic: add congestion control

Quoted 96 lines · Paolo Abeni

This is an AI-generated review of your patch. The human sending this email has considered the AI review valid, or at least plausible.

For local reproduction steps see https://netdev-ai.bots.linux.dev/ai-local.html
---
quic: add congestion control

This patch introduces quic_cong for RTT measurement and congestion control, implementing a state machine with slow start, congestion avoidance, and recovery phases using the New Reno algorithm.

Quoted 16 lines
net/quic/Makefile +2 -1
index 1565fb5cef9d..4d4a42c6d565 100644
@@ -5,4 +5,5 @@
 
 obj-$(CONFIG_IP_QUIC) += quic.o
 
-quic-y := common.o family.o protocol.o socket.o stream.o connid.o path.o
+quic-y := common.o family.o protocol.o socket.o stream.o connid.o path.o \
+       cong.o
net/quic/cong.c new file +0 -0
new file mode 100644
index 000000000000..e0f78ecb00e4

[ ... ]

Quoted 26 lines
net/quic/cong.c new file +26 -0
+static void quic_reno_on_packet_lost(struct quic_cong *cong, u64 time, u32 bytes, s64 number)
+{
+     if (quic_cong_check_persistent_congestion(cong, time))
+             return;
+
+     switch (cong->state) {
+     case QUIC_CONG_SLOW_START:
+             pr_debug("%s: slow_start -> recovery, cwnd: %u, ssthresh: %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, ssthresh: %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;
+}

[ ... ]

Quoted 13 lines
net/quic/cong.c new file +13 -0
+static void quic_reno_on_process_ecn(struct quic_cong *cong)
+{
+     switch (cong->state) {
+     case QUIC_CONG_SLOW_START:
+             pr_debug("%s: slow_start -> recovery, cwnd: %u, ssthresh: %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, ssthresh: %u\n",
+                      __func__, cong->window, cong->ssthresh);
+             break;

net/quic/cong.c line 119

Quoted 10 lines
net/quic/cong.c new file +10 -0
+     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;
+}

This isn't a bug, but these two functions share almost identical logic for entering the recovery state. The switch statement has the same three cases with identical handling in both functions, and the four-line recovery-entry block at the end is exactly the same:

    cong->recovery_time = cong->time;
    cong->state = QUIC_CONG_RECOVERY_PERIOD;
    cong->ssthresh = max(cong->window >> 1U, cong->min_window);
    cong->window = cong->ssthresh;
Quoted 5 lines

The only difference is quic_reno_on_packet_lost() checks for persistent congestion first. Would factoring this shared logic into a helper function make future maintenance easier and ensure the recovery path stays consistent across both ECN and loss events?

I will add a helper quic_reno_handle_packet_lost() for this.

Quoted 12 lines

[ ... ]

net/quic/cong.c line 286

Quoted 9 lines
net/quic/cong.c new file +9 -0
+void quic_cong_set_algo(struct quic_cong *cong, u8 algo)
+{
+     /* The caller must ensure algo < QUIC_CONG_ALG_MAX. */
+     cong->algo = algo;
+     cong->state = QUIC_CONG_SLOW_START;
+     cong->ssthresh = U32_MAX;
+     cong->ops = &quic_congs[algo];
+     cong->ops->on_init(cong);
+}
Quoted 8 lines

Should this function validate the algo parameter? The comment says the caller must ensure algo is less than QUIC_CONG_ALG_MAX, but there's no runtime check. Since QUIC_CONG_ALG_MAX is defined in the UAPI and this function is exported in cong.h, could a future caller (for example, a setsockopt path accepting user-supplied algorithm choices) pass an out-of-range value and cause an out-of-bounds array access into quic_congs[]?

The callers will do the validation as the comment said, hopefully AI will not flag this again.

Thanks.

author
#14 lore raw

PATCH v10 10/15 quic: add packet number space

In reply to Xin Long

This patch introduces 'quic_pnspace', which manages per packet number space members.

It maintains the next packet number to assign, tracks the total length of frames currently in flight, and records the time when the next packet may be considered lost. It also keeps track of the largest acknowledged packet number, the time it was acknowledged, and when the most recent ack eliciting packet was sent. These fields are useful for loss detection, RTT estimation, and congestion control.

To support ACK frame generation, quic_pnspace includes a packet number acknowledgment map (pn_ack_map) that tracks received packet numbers. Supporting functions are provided to validate and mark received packet numbers and compute the number of gap blocks needed during ACK frame construction.

- quic_pnspace_check(): Validates a received packet number.

- quic_pnspace_mark(): Marks a received packet number in the ACK map.

- quic_pnspace_num_gabs(): Returns the gap ACK blocks for constructing ACK frames.

Note QUIC uses separate packet number spaces for each encryption level (APP, INITIAL, HANDSHAKE, EARLY) except EARLY and all generations of APP keys use the same packet number space, as describe in rfc9002#section-4.1.

---
v5:
- Change timestamp variables from u32 to u64 and use quic_ktime_get_us() to set max_pn_acked_time, as jiffies_to_usecs() is not accurate enough.
- Reorder some members in quic_pnspace to reduce 32-bit holes (noted by Paolo).
v6:
- Note for AI reviews: it's safe to do cast (u16)(pn - space->base_pn) in quic_pnspace_mark(), as the pn < base_pn + QUIC_PN_MAP_SIZE (4096) validation is always done in quic_pnspace_check(), which will always be called before quic_pnspace_mark() in a later patchset.
- Note for AI reviews: failures in quic_pnspace_init() do not result in a pn_map leak in quic_init_sock(), because quic_destroy_sock() is always called to free it in err path, either via inet/6_create() or through quic_accept() in a later patchset.
v8:
- Replace bitfields with plain u8 in struct quic_pnspace.
v10:
- Fix a grammar error in the comment of quic_pnspace_check().

---
 net/quic/Makefile  |   2 +-
 net/quic/pnspace.c | 225 +++++++++++++++++++++++++++++++++++++++++++++
 net/quic/pnspace.h | 150 ++++++++++++++++++++++++++++++
 net/quic/socket.c  |  12 +++
 net/quic/socket.h  |   7 ++
 5 files changed, 395 insertions(+), 1 deletion(-)
 create mode 100644 net/quic/pnspace.c
 create mode 100644 net/quic/pnspace.h
net/quic/Makefile +1 -1
index 4d4a42c6d565..9d8e18297911 100644
@@ -6,4 +6,4 @@
 obj-$(CONFIG_IP_QUIC) += quic.o
 
 quic-y := common.o family.o protocol.o socket.o stream.o connid.o path.o \
-	  cong.o
+	  cong.o pnspace.o
net/quic/pnspace.c new file +225 -0

225 lines not on this page · raw message

net/quic/pnspace.h new file +150 -0

150 lines not on this page · raw message

net/quic/socket.c +12 -0
index 063418c77a03..1830a9726238 100644
@@ -37,6 +37,8 @@ static void quic_write_space(struct sock *sk)
 
 static int quic_init_sock(struct sock *sk)
 {
+	u8 i;
+
 	sk->sk_destruct = inet_sock_destruct;
 	sk->sk_write_space = quic_write_space;
 	sock_set_flag(sk, SOCK_USE_WRITE_QUEUE);
@@ -51,11 +53,21 @@ static int quic_init_sock(struct sock *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;
+
+	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);
 
net/quic/socket.h +7 -0
index 9201ca3edad0..68c7b22d1e88 100644
@@ -12,6 +12,7 @@
 #include <linux/quic.h>
 
 #include "common.h"
+#include "pnspace.h"
 #include "family.h"
 #include "stream.h"
 #include "connid.h"
@@ -43,6 +44,7 @@ struct quic_sock {
 	struct quic_conn_id_set		dest;
 	struct quic_path_group		paths;
 	struct quic_cong		cong;
+	struct quic_pnspace		space[QUIC_PNSPACE_MAX];
 };
 
 struct quic6_sock {
@@ -105,6 +107,11 @@ 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 bool quic_is_establishing(struct sock *sk)
 {
 	return sk->sk_state == QUIC_SS_ESTABLISHING;
Signature · 1 line
2.47.1
author
#15 lore raw

PATCH v10 11/15 quic: add crypto key derivation and installation

In reply to Xin Long

This patch introduces 'quic_crypto', a component responsible for QUIC encryption key derivation and installation across the various key
levels: Initial, Handshake, 0-RTT (Early), and 1-RTT (Application).

It provides helpers to derive and install initial secrets, set traffic secrets and install the corresponding keys, and perform key updates to enable forward secrecy. Additionally, it implements stateless reset token generation, used to support connection reset without state.

- quic_crypto_initial_keys_install(): Derive and install initial keys.

- quic_crypto_set_cipher(): Allocate all transforms based on the cipher type provided.

- quic_crypto_set_secret(): Set the traffic secret and install derived keys.

- quic_crypto_key_update(): Rekey and install new keys to the !phase side.

- quic_crypto_generate_stateless_reset_token(): Generate token for stateless reset.

These mechanisms are essential for establishing and maintaining secure communication throughout the QUIC connection lifecycle.

---
v3:
- Remove lock from quic_net, since Initial packet decryption for ALPN will be handled serially in a workqueue when ALPN demux is enabled.
v4:
- Use local cipher pointer in quic_crypto_set_secret() to avoid a warning from Smatch.
v5:
- Change the timestamp variables from u32 to u64, which provides sufficient precision for timestamps in microsecond.
v8:
- Remove the redundant err initialization in quic_net_init(), since err is now assigned from quic_crypto_set_cipher().
v10:
- Fix header protection key passed to pr_debug().
- Fix typo: "For example,to ..." -> "For example, to ..." in comment.
- Remove redundant initialization of err in quic_crypto_set_cipher().

---
 net/quic/Makefile   |   2 +-
 net/quic/crypto.c   | 560 ++++++++++++++++++++++++++++++++++++++++++++
 net/quic/crypto.h   |  73 ++++++
 net/quic/protocol.c |  13 +-
 net/quic/protocol.h |   1 +
 net/quic/socket.c   |   2 +
 net/quic/socket.h   |   7 +
 7 files changed, 656 insertions(+), 2 deletions(-)
 create mode 100644 net/quic/crypto.c
 create mode 100644 net/quic/crypto.h
net/quic/Makefile +1 -1
index 9d8e18297911..58bb18f7926d 100644
@@ -6,4 +6,4 @@
 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
+	  cong.o pnspace.o crypto.o
net/quic/crypto.c new file +560 -0

560 lines not on this page · raw message

net/quic/crypto.h new file +73 -0

73 lines not on this page · raw message

net/quic/protocol.c +12 -1
index 0d9679f02ebd..11a122d84ccb 100644
@@ -252,15 +252,23 @@ static void quic_protosw_exit(void)
 static int __net_init quic_net_init(struct net *net)
 {
 	struct quic_net *qn = quic_net(net);
-	int err = 0;
+	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, CRYPTO_ALG_ASYNC);
+	if (err) {
+		free_percpu(qn->stat);
+		qn->stat = NULL;
+		return err;
+	}
+
 #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;
 	}
@@ -275,6 +283,7 @@ static void __net_exit quic_net_exit(struct net *net)
 #if IS_ENABLED(CONFIG_PROC_FS)
 	quic_net_proc_exit(net);
 #endif
+	quic_crypto_free(&qn->crypto);
 	free_percpu(qn->stat);
 	qn->stat = NULL;
 }
@@ -325,6 +334,8 @@ static __init int quic_init(void)
 	sysctl_quic_wmem[1] = 16 * 1024;
 	sysctl_quic_wmem[2] = max(64 * 1024, max_share);
 
+	quic_crypto_init();
+
 	err = percpu_counter_init(&quic_sockets_allocated, 0, GFP_KERNEL);
 	if (err)
 		goto err_percpu_counter;
net/quic/protocol.h +1 -0
index 010e2cd66dd9..850f43806688 100644
@@ -49,6 +49,7 @@ struct quic_net {
 #if IS_ENABLED(CONFIG_PROC_FS)
 	struct proc_dir_entry *proc_net;	/* procfs entry for dumping QUIC socket stats */
 #endif
+	struct quic_crypto crypto;	/* Context for decrypting Initial packets for ALPN */
 };
 
 struct quic_net *quic_net(struct net *net);
net/quic/socket.c +2 -0
index 1830a9726238..74e3e3939c61 100644
@@ -67,6 +67,8 @@ static void quic_destroy_sock(struct sock *sk)
 
 	for (i = 0; i < QUIC_PNSPACE_MAX; i++)
 		quic_pnspace_free(quic_pnspace(sk, i));
+	for (i = 0; i < QUIC_CRYPTO_MAX; i++)
+		quic_crypto_free(quic_crypto(sk, i));
 
 	quic_path_unbind(sk, quic_paths(sk), 0);
 	quic_path_unbind(sk, quic_paths(sk), 1);
net/quic/socket.h +7 -0
index 68c7b22d1e88..d7811391cc8b 100644
@@ -16,6 +16,7 @@
 #include "family.h"
 #include "stream.h"
 #include "connid.h"
+#include "crypto.h"
 #include "path.h"
 #include "cong.h"
 
@@ -45,6 +46,7 @@ struct quic_sock {
 	struct quic_path_group		paths;
 	struct quic_cong		cong;
 	struct quic_pnspace		space[QUIC_PNSPACE_MAX];
+	struct quic_crypto		crypto[QUIC_CRYPTO_MAX];
 };
 
 struct quic6_sock {
@@ -112,6 +114,11 @@ 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 bool quic_is_establishing(struct sock *sk)
 {
 	return sk->sk_state == QUIC_SS_ESTABLISHING;
Signature · 1 line
2.47.1
maintainer
#16 lore raw

Re: [net-next,v10,11/15] quic: add crypto key derivation and installation

This is an AI-generated review of your patch. The human sending this email has considered the AI review valid, or at least plausible.

For local reproduction steps see https://netdev-ai.bots.linux.dev/ai-local.html
---
quic: add crypto key derivation and installation

This patch introduces 'quic_crypto', a component responsible for QUIC encryption key derivation and installation across the various key
levels: Initial, Handshake, 0-RTT (Early), and 1-RTT (Application). It provides helpers to derive and install initial secrets, set traffic secrets and install the corresponding keys, and perform key updates to enable forward secrecy.

Quoted 15 lines
net/quic/Makefile +1 -1
index 9d8e18297911..58bb18f7926d 100644
@@ -6,4 +6,4 @@
 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
+	  cong.o pnspace.o crypto.o
net/quic/crypto.c new file +0 -0
new file mode 100644
index 000000000000..a9de418e604e

[ ... ]

Quoted 14 lines
net/quic/crypto.c new file +14 -0
+/* 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, z = {};
+	struct quic_crypto_secret srt = {};
+	char *tl, *rl, *sal;
+	int err;
+
+	if (is_serv) {
+		rl = "client in";
+		tl = "server in";
+	} else {

net/quic/crypto.c line 490

Quoted 10 lines
net/quic/crypto.c new file +10 -0
+		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);

Is crypto->secret_tfm guaranteed to be initialized at this point?

Looking at the function flow, this line uses crypto->secret_tfm before quic_crypto_set_secret() below gets a chance to initialize the cipher:

quic_crypto_initial_keys_install() {
	err = quic_crypto_hkdf_extract(crypto->secret_tfm, ...); /* line 490 */
	...
	err = quic_crypto_set_secret(crypto, &srt, version, 0);  /* line 497 */
}

If quic_crypto_set_cipher() hasn't been called on this struct quic_crypto yet, crypto->secret_tfm is NULL.

This contrasts with quic_crypto_set_secret(), which has lazy initialization:

quic_crypto_set_secret() {
	if (!crypto->cipher) {
		err = quic_crypto_set_cipher(crypto, srt->type, flag);
		if (err)
			return err;
	}
	...
}

Without this initialization, quic_crypto_hkdf_extract() flows through hkdf_extract() -> crypto_shash_setkey(NULL, ...), causing a NULL pointer dereference.

Would it make sense to call quic_crypto_set_cipher() before using secret_tfm, or to reorder the function so quic_crypto_set_secret() is called first?

Quoted 27 lines
net/quic/crypto.c new file +27 -0
+	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, &z, &k);
+	if (err)
+		goto out;
+	err = quic_crypto_set_secret(crypto, &srt, version, 0);
+	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, &z, &k);
+	if (err)
+		goto out;
+	err = quic_crypto_set_secret(crypto, &srt, version, 0);
+out:
+	memzero_explicit(secret, sizeof(secret));
+	memzero_explicit(&srt, sizeof(srt));
+	return err;
+}

[ ... ]

Quoted 65 lines
net/quic/crypto.c new file +65 -0
+/* Derive and install transmission (TX) packet protection keys for the current key phase.
+ * This involves generating AEAD encryption key, IV, and optionally header protection key.
+ */
+static int quic_crypto_tx_keys_derive_and_install(struct quic_crypto *crypto)
+{
+	struct quic_data srt = {}, k, iv, hp_k = {}, *hp = NULL;
+	u8 tx_key[QUIC_KEY_LEN], tx_hp_key[QUIC_KEY_LEN] = {};
+	int err, phase = crypto->key_phase;
+	u32 keylen, ivlen = QUIC_IV_LEN;
+
+	keylen = crypto->cipher->keylen;
+	quic_data(&srt, crypto->tx_secret, crypto->cipher->secretlen);
+	quic_data(&k, tx_key, keylen);
+	quic_data(&iv, crypto->tx_iv[phase], ivlen);
+	/* Only derive header protection key when not in key update. */
+	if (!crypto->key_pending)
+		hp = quic_data(&hp_k, tx_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(crypto->tx_tfm[phase], QUIC_TAG_LEN);
+	if (err)
+		goto out;
+	err = crypto_aead_setkey(crypto->tx_tfm[phase], tx_key, keylen);
+	if (err)
+		goto out;
+	if (hp) {
+		err = crypto_skcipher_setkey(crypto->tx_hp_tfm, tx_hp_key, keylen);
+		if (err)
+			goto out;
+	}
+	pr_debug("%s: k: %16phN, iv: %12phN, hp_k:%16phN\n", __func__, k.data, iv.data, tx_hp_key);
+out:
+	memzero_explicit(tx_key, sizeof(tx_key));
+	memzero_explicit(tx_hp_key, sizeof(tx_hp_key));
+	return err;
+}
+
+/* Derive and install reception (RX) packet protection keys for the current key phase.
+ * This installs AEAD decryption key, IV, and optionally header protection key.
+ */
+static int quic_crypto_rx_keys_derive_and_install(struct quic_crypto *crypto)
+{
+	struct quic_data srt = {}, k, iv, hp_k = {}, *hp = NULL;
+	u8 rx_key[QUIC_KEY_LEN], rx_hp_key[QUIC_KEY_LEN] = {};
+	int err, phase = crypto->key_phase;
+	u32 keylen, ivlen = QUIC_IV_LEN;
+
+	keylen = crypto->cipher->keylen;
+	quic_data(&srt, crypto->rx_secret, crypto->cipher->secretlen);
+	quic_data(&k, rx_key, keylen);
+	quic_data(&iv, crypto->rx_iv[phase], ivlen);
+	/* Only derive header protection key when not in key update. */
+	if (!crypto->key_pending)
+		hp = quic_data(&hp_k, rx_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(crypto->rx_tfm[phase], QUIC_TAG_LEN);
+	if (err)
+		goto out;
+	err = crypto_aead_setkey(crypto->rx_tfm[phase], rx_key, keylen);
+	if (err)
+		goto out;
+	if (hp) {

net/quic/crypto.c line 208

Quoted 10 lines
net/quic/crypto.c new file +10 -0
+		err = crypto_skcipher_setkey(crypto->rx_hp_tfm, rx_hp_key, keylen);
+		if (err)
+			goto out;
+	}
+	pr_debug("%s: k: %16phN, iv: %12phN, hp_k:%16phN\n", __func__, k.data, iv.data, rx_hp_key);
+out:
+	memzero_explicit(rx_key, sizeof(rx_key));
+	memzero_explicit(rx_hp_key, sizeof(rx_hp_key));
+	return err;
+}

This isn't a bug, but these two functions are nearly identical.

quic_crypto_tx_keys_derive_and_install() and quic_crypto_rx_keys_derive_and_install() share the same structure: derive keys via quic_crypto_keys_derive(), set AEAD auth size and key, conditionally set the HP skcipher key, pr_debug, and memzero_explicit the temporaries.

The only differences are the member names accessed (tx_secret vs rx_secret, tx_iv vs rx_iv, tx_tfm vs rx_tfm, tx_hp_tfm vs rx_hp_tfm).

Could these be unified into a single static helper that takes direction-specific pointers as parameters?

This would eliminate the duplication and reduce the risk of the two copies diverging in future maintenance.

[ ... ]

author
#17 lore raw

Re: [net-next,v10,11/15] quic: add crypto key derivation and installation

Quoted 83 lines · Paolo Abeni

This is an AI-generated review of your patch. The human sending this email has considered the AI review valid, or at least plausible.

For local reproduction steps see https://netdev-ai.bots.linux.dev/ai-local.html
---
quic: add crypto key derivation and installation

This patch introduces 'quic_crypto', a component responsible for QUIC encryption key derivation and installation across the various key
levels: Initial, Handshake, 0-RTT (Early), and 1-RTT (Application). It provides helpers to derive and install initial secrets, set traffic secrets and install the corresponding keys, and perform key updates to enable forward secrecy.

Quoted 15 lines
net/quic/Makefile +1 -1
index 9d8e18297911..58bb18f7926d 100644
@@ -6,4 +6,4 @@
 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
+       cong.o pnspace.o crypto.o
net/quic/crypto.c new file +0 -0
new file mode 100644
index 000000000000..a9de418e604e

[ ... ]

Quoted 14 lines
net/quic/crypto.c new file +14 -0
+/* 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, z = {};
+     struct quic_crypto_secret srt = {};
+     char *tl, *rl, *sal;
+     int err;
+
+     if (is_serv) {
+             rl = "client in";
+             tl = "server in";
+     } else {

net/quic/crypto.c line 490

Quoted 10 lines
net/quic/crypto.c new file +10 -0
+             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);

Is crypto->secret_tfm guaranteed to be initialized at this point?

Looking at the function flow, this line uses crypto->secret_tfm before quic_crypto_set_secret() below gets a chance to initialize the cipher:

quic_crypto_initial_keys_install() {
        err = quic_crypto_hkdf_extract(crypto->secret_tfm, ...); /* line 490 */
        ...
        err = quic_crypto_set_secret(crypto, &srt, version, 0);  /* line 497 */
}

If quic_crypto_set_cipher() hasn't been called on this struct quic_crypto yet, crypto->secret_tfm is NULL.

This contrasts with quic_crypto_set_secret(), which has lazy initialization:

quic_crypto_set_secret() {
        if (!crypto->cipher) {
                err = quic_crypto_set_cipher(crypto, srt->type, flag);
                if (err)
                        return err;
        }
        ...
}
Quoted 8 lines

Without this initialization, quic_crypto_hkdf_extract() flows through hkdf_extract() -> crypto_shash_setkey(NULL, ...), causing a NULL pointer dereference.

Would it make sense to call quic_crypto_set_cipher() before using secret_tfm, or to reorder the function so quic_crypto_set_secret() is called first?

secret_tfm will always set in quic_connect(), quic_accept() and quic_inet_listen(),
quic_crypto_initial_keys_install() will be only called after one of these 3 functions
is called.

The patchset-2 (the following series) will give you this completeness.

Quoted 114 lines
Quoted 27 lines
net/quic/crypto.c new file +27 -0
+     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, &z, &k);
+     if (err)
+             goto out;
+     err = quic_crypto_set_secret(crypto, &srt, version, 0);
+     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, &z, &k);
+     if (err)
+             goto out;
+     err = quic_crypto_set_secret(crypto, &srt, version, 0);
+out:
+     memzero_explicit(secret, sizeof(secret));
+     memzero_explicit(&srt, sizeof(srt));
+     return err;
+}

[ ... ]

Quoted 65 lines
net/quic/crypto.c new file +65 -0
+/* Derive and install transmission (TX) packet protection keys for the current key phase.
+ * This involves generating AEAD encryption key, IV, and optionally header protection key.
+ */
+static int quic_crypto_tx_keys_derive_and_install(struct quic_crypto *crypto)
+{
+     struct quic_data srt = {}, k, iv, hp_k = {}, *hp = NULL;
+     u8 tx_key[QUIC_KEY_LEN], tx_hp_key[QUIC_KEY_LEN] = {};
+     int err, phase = crypto->key_phase;
+     u32 keylen, ivlen = QUIC_IV_LEN;
+
+     keylen = crypto->cipher->keylen;
+     quic_data(&srt, crypto->tx_secret, crypto->cipher->secretlen);
+     quic_data(&k, tx_key, keylen);
+     quic_data(&iv, crypto->tx_iv[phase], ivlen);
+     /* Only derive header protection key when not in key update. */
+     if (!crypto->key_pending)
+             hp = quic_data(&hp_k, tx_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(crypto->tx_tfm[phase], QUIC_TAG_LEN);
+     if (err)
+             goto out;
+     err = crypto_aead_setkey(crypto->tx_tfm[phase], tx_key, keylen);
+     if (err)
+             goto out;
+     if (hp) {
+             err = crypto_skcipher_setkey(crypto->tx_hp_tfm, tx_hp_key, keylen);
+             if (err)
+                     goto out;
+     }
+     pr_debug("%s: k: %16phN, iv: %12phN, hp_k:%16phN\n", __func__, k.data, iv.data, tx_hp_key);
+out:
+     memzero_explicit(tx_key, sizeof(tx_key));
+     memzero_explicit(tx_hp_key, sizeof(tx_hp_key));
+     return err;
+}
+
+/* Derive and install reception (RX) packet protection keys for the current key phase.
+ * This installs AEAD decryption key, IV, and optionally header protection key.
+ */
+static int quic_crypto_rx_keys_derive_and_install(struct quic_crypto *crypto)
+{
+     struct quic_data srt = {}, k, iv, hp_k = {}, *hp = NULL;
+     u8 rx_key[QUIC_KEY_LEN], rx_hp_key[QUIC_KEY_LEN] = {};
+     int err, phase = crypto->key_phase;
+     u32 keylen, ivlen = QUIC_IV_LEN;
+
+     keylen = crypto->cipher->keylen;
+     quic_data(&srt, crypto->rx_secret, crypto->cipher->secretlen);
+     quic_data(&k, rx_key, keylen);
+     quic_data(&iv, crypto->rx_iv[phase], ivlen);
+     /* Only derive header protection key when not in key update. */
+     if (!crypto->key_pending)
+             hp = quic_data(&hp_k, rx_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(crypto->rx_tfm[phase], QUIC_TAG_LEN);
+     if (err)
+             goto out;
+     err = crypto_aead_setkey(crypto->rx_tfm[phase], rx_key, keylen);
+     if (err)
+             goto out;
+     if (hp) {

net/quic/crypto.c line 208

Quoted 10 lines
net/quic/crypto.c new file +10 -0
+             err = crypto_skcipher_setkey(crypto->rx_hp_tfm, rx_hp_key, keylen);
+             if (err)
+                     goto out;
+     }
+     pr_debug("%s: k: %16phN, iv: %12phN, hp_k:%16phN\n", __func__, k.data, iv.data, rx_hp_key);
+out:
+     memzero_explicit(rx_key, sizeof(rx_key));
+     memzero_explicit(rx_hp_key, sizeof(rx_hp_key));
+     return err;
+}

This isn't a bug, but these two functions are nearly identical.

quic_crypto_tx_keys_derive_and_install() and quic_crypto_rx_keys_derive_and_install() share the same structure: derive keys via quic_crypto_keys_derive(), set AEAD auth size and key, conditionally set the HP skcipher key, pr_debug, and memzero_explicit the temporaries.

Quoted 6 lines

The only differences are the member names accessed (tx_secret vs rx_secret, tx_iv vs rx_iv, tx_tfm vs rx_tfm, tx_hp_tfm vs rx_hp_tfm).

Could these be unified into a single static helper that takes direction-specific pointers as parameters?

Looks possible, and I will give it a try.

Thanks.

author
#18 lore raw

PATCH v10 12/15 quic: add crypto packet encryption and decryption

In reply to Xin Long

This patch adds core support for packet-level encryption and decryption using AEAD, including both payload protection and QUIC header protection. It introduces helpers to encrypt packets before transmission and to remove header protection and decrypt payloads upon reception, in line with QUIC's cryptographic requirements.

- quic_crypto_encrypt(): Perform header protection and payload encryption (TX).

- quic_crypto_decrypt(): Perform header protection removal and payload decryption (RX).

The patch also includes support for Retry token handling. It provides helpers to compute the Retry integrity tag, generate tokens for address validation, and verify tokens received from clients during the handshake phase.

- quic_crypto_get_retry_tag(): Compute tag for Retry packets.

- quic_crypto_generate_token(): Generate retry token.

- quic_crypto_verify_token(): Verify retry token.

These additions establish the cryptographic primitives necessary for secure QUIC packet exchange and address validation.

---
v3:
- quic_crypto_decrypt(): return -EKEYREVOKED to defer key updates to the workqueue when the packet is not marked backlog, since quic_crypto_key_update()/crypto_aead_setkey() must run in process context.
- Only perform header decryption if !cb->number_len to avoid double decryption when a key-update packet (with flipped key_phase) re-enters quic_crypto_decrypt() from the workqueue.
v4:
- skb_dst_force() is already called in in quic_udp_rcv() on recv path, so remove its call from quic_crypto_payload_decrypt(), which may be called without RCU protection.
- Remove the incorrect (void *) cast to quic_crypto_done.
v5:
- Use skb_cb->crypto_ctx for async crypto context freeing, which is

    safer than using skb_shinfo(skb)->destructor_arg.

- skb_cb->number_max is removed and number is reused as the largest previously seen and update quic_crypto_get_header() accordingly.
- Change timestamp variables from u32 to u64 and use quic_ktime_get_us() for current timestamps, as jiffies_to_usecs() is not accurate enough.
v6:
- Rename quic_crypto_get_header() to quic_crypto_get_number(), move key_phase parsing out of it, check cb->length when parsing packet number, and update all callers.
- Use hdr->pnl + 1 instead of (*p & QUIC_PN_LEN_BITS_MASK) + 1 to get packet number length, and remove the unnecessary the len variable and QUIC_PN_LEN_BITS_MASK macro from quic_crypto_header_decrypt().
v8:
- Move skb_cow_data() from quic_crypto_payload_decrypt() to quic_crypto_header_decrypt(), as header decryption also writes to the skb and is invoked earlier than payload decryption on RX path.
v10:
- Fix double period at end of the comment for quic_crypto_encrypt().

---
 net/quic/crypto.c | 666 ++++++++++++++++++++++++++++++++++++++++++++++
 net/quic/crypto.h |  10 +
 2 files changed, 676 insertions(+)
net/quic/crypto.c +666 -0
index a9de418e604e..a84fd8d7bf93 100644
@@ -207,6 +207,337 @@ static int quic_crypto_rx_keys_derive_and_install(struct quic_crypto *crypto)
 	return err;
 }
 
+static void *quic_crypto_skcipher_mem_alloc(struct crypto_skcipher *tfm, u32 mask_size,
+					    u8 **iv, struct skcipher_request **req)
+{
+	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_ATOMIC);
+	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;
+}
+
+#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_encrypt(struct crypto_skcipher *tfm, struct sk_buff *skb, bool chacha)
+{
+	struct quic_skb_cb *cb = QUIC_SKB_CB(skb);
+	struct skcipher_request *req;
+	struct scatterlist sg;
+	u8 *mask, *iv, *p;
+	int err, i;
+
+	mask = quic_crypto_skcipher_mem_alloc(tfm, QUIC_SAMPLE_LEN, &iv, &req);
+	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})
+	 */
+	memcpy((chacha ? iv : mask), skb->data + cb->number_offset + QUIC_PN_MAX_LEN,
+	       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 err;
+
+	/* 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;
+	*p = (u8)(*p ^ (mask[0] & (((*p & QUIC_HEADER_FORM_BIT) == QUIC_HEADER_FORM_BIT) ?
+				   QUIC_LONG_HEADER_MASK : QUIC_SHORT_HEADER_MASK)));
+	p = skb->data + cb->number_offset;
+	for (i = 1; i <= cb->number_len; i++)
+		*p++ ^= mask[i];
+err:
+	kfree_sensitive(mask);
+	return err;
+}
+
+/* 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;
+}
+
+static int quic_crypto_header_decrypt(struct crypto_skcipher *tfm, struct sk_buff *skb, bool chacha)
+{
+	struct quic_skb_cb *cb = QUIC_SKB_CB(skb);
+	struct skcipher_request *req;
+	struct sk_buff *trailer;
+	struct scatterlist sg;
+	struct quichdr *hdr;
+	u8 *mask, *iv, *p;
+	int err, i;
+
+	err = skb_cow_data(skb, 0, &trailer);
+	if (err < 0)
+		return err;
+
+	mask = quic_crypto_skcipher_mem_alloc(tfm, QUIC_SAMPLE_LEN, &iv, &req);
+	if (!mask)
+		return -ENOMEM;
+
+	if (cb->length < QUIC_PN_MAX_LEN + QUIC_SAMPLE_LEN) {
+		err = -EINVAL;
+		goto err;
+	}
+
+	/* Similar logic to quic_crypto_header_encrypt(). */
+	hdr = quic_hdr(skb);
+	p = (u8 *)hdr + cb->number_offset;
+	memcpy((chacha ? iv : mask), p + QUIC_PN_MAX_LEN, 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 err;
+
+	p = (u8 *)hdr;
+	*p = (u8)(*p ^ (mask[0] & (((*p & QUIC_HEADER_FORM_BIT) == QUIC_HEADER_FORM_BIT) ?
+				   QUIC_LONG_HEADER_MASK : QUIC_SHORT_HEADER_MASK)));
+	cb->number_len = hdr->pnl + 1;
+	cb->key_phase = hdr->key;
+	p += cb->number_offset;
+	for (i = 0; i < cb->number_len; ++i)
+		*(p + i) = *((u8 *)hdr + cb->number_offset + i) ^ mask[i + 1];
+	err = quic_crypto_get_number(skb);
+
+err:
+	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)
+{
+	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_ATOMIC);
+	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;
+
+	kfree_sensitive(QUIC_SKB_CB(skb)->crypto_ctx);
+	QUIC_SKB_CB(skb)->crypto_done(skb, err);
+}
+
+/* AEAD Usage. */
+static int quic_crypto_payload_encrypt(struct crypto_aead *tfm, struct sk_buff *skb,
+				       u8 *tx_iv, bool ccm)
+{
+	struct quic_skb_cb *cb = QUIC_SKB_CB(skb);
+	u8 *iv, i, nonce[QUIC_IV_LEN];
+	struct aead_request *req;
+	struct sk_buff *trailer;
+	struct scatterlist *sg;
+	struct quichdr *hdr;
+	u32 nsg, hlen, len;
+	void *ctx;
+	__be64 n;
+	int err;
+
+	len = skb->len;
+	err = skb_cow_data(skb, QUIC_TAG_LEN, &trailer);
+	if (err < 0)
+		return err;
+	nsg = (u32)err;
+	pskb_put(skb, trailer, QUIC_TAG_LEN);
+	hdr = quic_hdr(skb);
+	hdr->key = cb->key_phase;
+
+	ctx = quic_crypto_aead_mem_alloc(tfm, 0, &iv, &req, &sg, nsg);
+	if (!ctx)
+		return -ENOMEM;
+
+	sg_init_table(sg, nsg);
+	err = skb_to_sgvec(skb, sg, 0, (int)skb->len);
+	if (err < 0)
+		goto err;
+
+	/* 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.
+	 */
+	hlen = cb->number_offset + cb->number_len;
+	memcpy(nonce, tx_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);
+	aead_request_set_callback(req, CRYPTO_TFM_REQ_MAY_BACKLOG, quic_crypto_done, skb);
+
+	cb->crypto_ctx = ctx; /* Set crypto_ctx for async free in quic_crypto_done(). */
+	err = crypto_aead_encrypt(req);
+	if (err == -EINPROGRESS) {
+		memzero_explicit(nonce, sizeof(nonce));
+		return err;
+	}
+
+err:
+	kfree_sensitive(ctx);
+	memzero_explicit(nonce, sizeof(nonce));
+	return err;
+}
+
+static int quic_crypto_payload_decrypt(struct crypto_aead *tfm, struct sk_buff *skb,
+				       u8 *rx_iv, bool ccm)
+{
+	struct quic_skb_cb *cb = QUIC_SKB_CB(skb);
+	u8 *iv, i, nonce[QUIC_IV_LEN];
+	struct aead_request *req;
+	int nsg, hlen, len, err;
+	struct scatterlist *sg;
+	void *ctx;
+	__be64 n;
+
+	len = cb->length + cb->number_offset;
+	hlen = cb->number_offset + cb->number_len;
+	if (len - hlen < QUIC_TAG_LEN)
+		return -EINVAL;
+	nsg = 1; /* skb is already linearized in quic_packet_rcv(). */
+	ctx = quic_crypto_aead_mem_alloc(tfm, 0, &iv, &req, &sg, nsg);
+	if (!ctx)
+		return -ENOMEM;
+
+	sg_init_table(sg, nsg);
+	err = skb_to_sgvec(skb, sg, 0, len);
+	if (err < 0)
+		goto err;
+
+	/* Similar logic to quic_crypto_payload_encrypt(). */
+	memcpy(nonce, rx_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];
+
+	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);
+	aead_request_set_callback(req, CRYPTO_TFM_REQ_MAY_BACKLOG, quic_crypto_done, skb);
+
+	cb->crypto_ctx = ctx;
+	err = crypto_aead_decrypt(req);
+	if (err == -EINPROGRESS) {
+		memzero_explicit(nonce, sizeof(nonce));
+		return err;
+	}
+err:
+	kfree_sensitive(ctx);
+	memzero_explicit(nonce, sizeof(nonce));
+	return err;
+}
+
 #define QUIC_CIPHER_MIN TLS_CIPHER_AES_GCM_128
 #define QUIC_CIPHER_MAX TLS_CIPHER_CHACHA20_POLY1305
 
@@ -231,6 +562,137 @@ static struct quic_cipher ciphers[QUIC_CIPHER_MAX + 1 - QUIC_CIPHER_MIN] = {
 		    "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;
+}
+
+/* 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)
+{
+	u8 *iv, cha, ccm, phase = crypto->key_phase;
+	struct quic_skb_cb *cb = QUIC_SKB_CB(skb);
+	int err;
+
+	cb->key_phase = phase;
+	iv = crypto->tx_iv[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();
+
+	ccm = quic_crypto_is_cipher_ccm(crypto);
+	err = quic_crypto_payload_encrypt(crypto->tx_tfm[phase], skb, iv, ccm);
+	if (err)
+		return err;
+out:
+	cha = quic_crypto_is_cipher_chacha(crypto);
+	return quic_crypto_header_encrypt(crypto->tx_hp_tfm, skb, cha);
+}
+
+/* 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)
+{
+	struct quic_skb_cb *cb = QUIC_SKB_CB(skb);
+	u8 *iv, cha, ccm, phase;
+	int err = 0;
+	u64 time;
+
+	/* 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. */
+		cha = quic_crypto_is_cipher_chacha(crypto);
+		err = quic_crypto_header_decrypt(crypto->rx_hp_tfm, skb, cha);
+		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.
+	 */
+	if (cb->key_phase != crypto->key_phase && !crypto->key_pending) {
+		if (!crypto->send_ready) /* Not ready for key update. */
+			return -EINVAL;
+		if (!cb->backlog) /* Key update must be done in process context. */
+			return -EKEYREVOKED;
+		err = quic_crypto_key_update(crypto); /* Perform a key update. */
+		if (err) {
+			cb->errcode = QUIC_TRANSPORT_ERROR_KEY_UPDATE;
+			return err;
+		}
+		cb->key_update = 1; /* Mark packet as triggering key update. */
+	}
+
+	phase = cb->key_phase;
+	iv = crypto->rx_iv[phase];
+	ccm = quic_crypto_is_cipher_ccm(crypto);
+	err = quic_crypto_payload_decrypt(crypto->rx_tfm[phase], skb, iv, ccm);
+	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 && cb->key_phase != crypto->key_phase) {
+			crypto->key_pending = 0;
+			crypto->key_update_time = 0;
+		}
+		return err;
+	}
+
+out:
+	/* 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;
+		}
+	}
+	return err;
+}
+
 int quic_crypto_set_cipher(struct quic_crypto *crypto, u32 type, u8 flag)
 {
 	struct quic_cipher *cipher;
@@ -516,6 +978,210 @@ int quic_crypto_initial_keys_install(struct quic_crypto *crypto, struct quic_con
 	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)
+{
+	struct crypto_aead *tfm = crypto->tag_tfm;
+	u8 *pseudo_retry, *p, *iv, *key;
+	struct aead_request *req;
+	struct scatterlist *sg;
+	u32 plen;
+	int err;
+
+	/* 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);
+	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);
+	err = crypto_aead_encrypt(req);
+	if (!err)
+		memcpy(tag, p, QUIC_TAG_LEN);
+	kfree_sensitive(pseudo_retry);
+	return err;
+}
+
+/* Generate a token for Retry or address validation.
+ *
+ * Builds a token with the format: [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 key[TLS_CIPHER_AES_GCM_128_KEY_SIZE], iv[QUIC_IV_LEN];
+	struct crypto_aead *tfm = crypto->tag_tfm;
+	u8 *retry_token = NULL, *tx_iv, *p;
+	struct quic_data srt = {}, k, i;
+	u64 ts = quic_ktime_get_us();
+	struct aead_request *req;
+	struct scatterlist *sg;
+	int err, len;
+
+	quic_data(&srt, quic_random_data, QUIC_RANDOM_DATA_LEN);
+	quic_data(&k, key, TLS_CIPHER_AES_GCM_128_KEY_SIZE);
+	quic_data(&i, iv, QUIC_IV_LEN);
+	err = quic_crypto_keys_derive(crypto->secret_tfm, &srt, &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);
+	if (err)
+		goto out;
+	token++;
+	len = addrlen + sizeof(ts) + conn_id->len + QUIC_TAG_LEN;
+	retry_token = quic_crypto_aead_mem_alloc(tfm, len, &tx_iv, &req, &sg, 1);
+	if (!retry_token) {
+		err = -ENOMEM;
+		goto out;
+	}
+
+	p = retry_token;
+	p = quic_put_data(p, addr, addrlen);
+	p = quic_put_int(p, ts, sizeof(ts));
+	quic_put_data(p, conn_id->data, conn_id->len);
+	sg_init_one(sg, retry_token, len);
+	aead_request_set_tfm(req, tfm);
+	aead_request_set_ad(req, addrlen);
+	aead_request_set_crypt(req, sg, sg, len - addrlen - QUIC_TAG_LEN, iv);
+	err = crypto_aead_encrypt(req);
+	if (err)
+		goto out;
+	memcpy(token, retry_token, len);
+	*tlen = len + 1;
+out:
+	kfree_sensitive(retry_token);
+	memzero_explicit(key, sizeof(key));
+	memzero_explicit(iv, sizeof(iv));
+	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 key[TLS_CIPHER_AES_GCM_128_KEY_SIZE], iv[QUIC_IV_LEN];
+	u8 *retry_token = NULL, *rx_iv, *p, flag = *token;
+	struct crypto_aead *tfm = crypto->tag_tfm;
+	struct quic_data srt = {}, k, i;
+	struct aead_request *req;
+	struct scatterlist *sg;
+	int err;
+
+	if (len < sizeof(flag) + addrlen + sizeof(ts) + QUIC_TAG_LEN)
+		return -EINVAL;
+	quic_data(&srt, quic_random_data, QUIC_RANDOM_DATA_LEN);
+	quic_data(&k, key, TLS_CIPHER_AES_GCM_128_KEY_SIZE);
+	quic_data(&i, iv, QUIC_IV_LEN);
+	err = quic_crypto_keys_derive(crypto->secret_tfm, &srt, &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);
+	if (err)
+		goto out;
+	len--;
+	token++;
+	retry_token = quic_crypto_aead_mem_alloc(tfm, len, &rx_iv, &req, &sg, 1);
+	if (!retry_token) {
+		err = -ENOMEM;
+		goto out;
+	}
+
+	memcpy(retry_token, token, len);
+	sg_init_one(sg, retry_token, len);
+	aead_request_set_tfm(req, tfm);
+	aead_request_set_ad(req, addrlen);
+	aead_request_set_crypt(req, sg, sg, len - addrlen, iv);
+	err = crypto_aead_decrypt(req);
+	if (err)
+		goto out;
+
+	err = -EINVAL;
+	p = retry_token;
+	if (memcmp(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, sizeof(ts)) || t + timeout < ts)
+		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_sensitive(retry_token);
+	memzero_explicit(key, sizeof(key));
+	memzero_explicit(iv, sizeof(iv));
+	return err;
+}
+
 /* Generate a derived key using HKDF-Extract and HKDF-Expand with a given label. */
 static int quic_crypto_generate_key(struct quic_crypto *crypto, void *data, u32 len,
 				    char *label, u8 *token, u32 key_len)
net/quic/crypto.h +10 -0
index fc562035271c..51891ae9852e 100644
@@ -62,6 +62,9 @@ int quic_crypto_get_secret(struct quic_crypto *crypto, struct quic_crypto_secret
 int quic_crypto_set_cipher(struct quic_crypto *crypto, u32 type, u8 flag);
 int quic_crypto_key_update(struct quic_crypto *crypto);
 
+int quic_crypto_encrypt(struct quic_crypto *crypto, struct sk_buff *skb);
+int quic_crypto_decrypt(struct quic_crypto *crypto, struct sk_buff *skb);
+
 int quic_crypto_initial_keys_install(struct quic_crypto *crypto, struct quic_conn_id *conn_id,
 				     u32 version, bool is_serv);
 int quic_crypto_generate_session_ticket_key(struct quic_crypto *crypto, void *data,
@@ -69,5 +72,12 @@ int quic_crypto_generate_session_ticket_key(struct quic_crypto *crypto, void *da
 int quic_crypto_generate_stateless_reset_token(struct quic_crypto *crypto, void *data,
 					       u32 len, u8 *key, u32 key_len);
 
+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);
 void quic_crypto_init(void);
Signature · 1 line
2.47.1
maintainer
#19 lore raw

Re: [net-next,v10,12/15] quic: add crypto packet encryption and decryption

This is an AI-generated review of your patch. The human sending this email has considered the AI review valid, or at least plausible.

For local reproduction steps see https://netdev-ai.bots.linux.dev/ai-local.html
---
quic: add crypto packet encryption and decryption

This patch adds core support for packet-level encryption and decryption using AEAD, including both payload protection and QUIC header protection. It introduces helpers to encrypt and decrypt packets for secure QUIC packet exchange and address validation.

Quoted 4 lines
net/quic/crypto.c +0 -0
index a9de418e604e..a84fd8d7bf93 100644

[ ... ]

Quoted 1 line
net/quic/crypto.c +0 -0
@@ -231,6 +562,137 @@ static struct quic_cipher ciphers[QUIC_CIPHER_MAX + 1 - QUIC_CIPHER_MIN] = {

[ ... ]

Quoted 25 lines
net/quic/crypto.c +25 -0
+/* 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)
+{
+	u8 *iv, cha, ccm, phase = crypto->key_phase;
+	struct quic_skb_cb *cb = QUIC_SKB_CB(skb);
+	int err;
+
+	cb->key_phase = phase;
+	iv = crypto->tx_iv[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)

net/quic/crypto.c static struct quic_cipher ciphers[QUIC_CIPHER_MAX + 1 - QUIC_CIPHER_MIN] = { · line 609

Quoted 10 lines
net/quic/crypto.c +10 -0
+		crypto->key_update_send_time = quic_ktime_get_us();
+
+	ccm = quic_crypto_is_cipher_ccm(crypto);
+	err = quic_crypto_payload_encrypt(crypto->tx_tfm[phase], skb, iv, ccm);
+	if (err)
+		return err;
+out:
+	cha = quic_crypto_is_cipher_chacha(crypto);
+	return quic_crypto_header_encrypt(crypto->tx_hp_tfm, skb, cha);
+}

This function calls quic_crypto_payload_encrypt(). Looking at that function:

Quoted 17 lines
net/quic/crypto.c +17 -0
+/* AEAD Usage. */
+static int quic_crypto_payload_encrypt(struct crypto_aead *tfm, struct sk_buff *skb,
+				       u8 *tx_iv, bool ccm)
+{
+	struct quic_skb_cb *cb = QUIC_SKB_CB(skb);
+	u8 *iv, i, nonce[QUIC_IV_LEN];
+	struct aead_request *req;
+	struct sk_buff *trailer;
+	struct scatterlist *sg;
+	struct quichdr *hdr;
+	u32 nsg, hlen, len;
+	void *ctx;
+	__be64 n;
+	int err;
+
+	len = skb->len;
+	err = skb_cow_data(skb, QUIC_TAG_LEN, &trailer);

net/quic/crypto.c static int quic_crypto_rx_keys_derive_and_install(struct quic_crypto *crypto) · line 446

Quoted 10 lines
net/quic/crypto.c +10 -0
+	if (err < 0)
+		return err;
+	nsg = (u32)err;
+	pskb_put(skb, trailer, QUIC_TAG_LEN);
+	hdr = quic_hdr(skb);
+	hdr->key = cb->key_phase;
+
+	ctx = quic_crypto_aead_mem_alloc(tfm, 0, &iv, &req, &sg, nsg);
+	if (!ctx)
+		return -ENOMEM;

If the kzalloc(GFP_ATOMIC) inside quic_crypto_aead_mem_alloc() fails, this returns -ENOMEM without rolling back the SKB modifications. The SKB is now permanently enlarged by QUIC_TAG_LEN (16 bytes) with a modified hdr->key.

Compare with quic_crypto_payload_decrypt():

Quoted 12 lines
net/quic/crypto.c +12 -0
+static int quic_crypto_payload_decrypt(struct crypto_aead *tfm, struct sk_buff *skb,
+				       u8 *rx_iv, bool ccm)
+{
+	struct quic_skb_cb *cb = QUIC_SKB_CB(skb);
+	u8 *iv, i, nonce[QUIC_IV_LEN];
+	struct aead_request *req;
+	int nsg, hlen, len, err;
+	struct scatterlist *sg;
+	void *ctx;
+	__be64 n;
+
+	len = cb->length + cb->number_offset;

net/quic/crypto.c static int quic_crypto_rx_keys_derive_and_install(struct quic_crypto *crypto) · line 512

Quoted 10 lines
net/quic/crypto.c +10 -0
+	hlen = cb->number_offset + cb->number_len;
+	if (len - hlen < QUIC_TAG_LEN)
+		return -EINVAL;
+	nsg = 1; /* skb is already linearized in quic_packet_rcv(). */
+	ctx = quic_crypto_aead_mem_alloc(tfm, 0, &iv, &req, &sg, nsg);
+	if (!ctx)
+		return -ENOMEM;
+
+	sg_init_table(sg, nsg);
+	err = skb_to_sgvec(skb, sg, 0, len);

In the decrypt path, quic_crypto_aead_mem_alloc() is called before any SKB modifications. Should quic_crypto_payload_encrypt() do the same to avoid leaving the SKB in a modified state if allocation fails?

[ ... ]

net/quic/crypto.c static int quic_crypto_rx_keys_derive_and_install(struct quic_crypto *crypto) · line 520

Quoted 5 lines
net/quic/crypto.c +5 -0
+	/* Similar logic to quic_crypto_payload_encrypt(). */
+	memcpy(nonce, rx_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];

This isn't a bug, but there's duplicated logic between quic_crypto_payload_encrypt() and quic_crypto_payload_decrypt(). The AEAD nonce construction sequence (memcpy nonce from IV, XOR with be64 packet number, set CCM byte) appears in both functions.

In quic_crypto_payload_encrypt():

net/quic/crypto.c static int quic_crypto_rx_keys_derive_and_install(struct quic_crypto *crypto) · line 472

Quoted 9 lines
net/quic/crypto.c +9 -0
+	hlen = cb->number_offset + cb->number_len;
+	memcpy(nonce, tx_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);

And in quic_crypto_payload_decrypt():

net/quic/crypto.c static int quic_crypto_rx_keys_derive_and_install(struct quic_crypto *crypto) · line 523

Quoted 8 lines
net/quic/crypto.c +8 -0
+	/* Similar logic to quic_crypto_payload_encrypt(). */
+	memcpy(nonce, rx_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];
+
+	iv[0] = TLS_AES_CCM_IV_B0_BYTE;
+	memcpy(&iv[ccm], nonce, QUIC_IV_LEN);

The AEAD request teardown pattern (EINPROGRESS check, kfree_sensitive, memzero_explicit) is also duplicated. Would a shared static helper for nonce construction reduce the risk of the two copies diverging during maintenance?

[ ... ]

author
#20 lore raw

Re: [net-next,v10,12/15] quic: add crypto packet encryption and decryption

Quoted 116 lines · Paolo Abeni

This is an AI-generated review of your patch. The human sending this email has considered the AI review valid, or at least plausible.

For local reproduction steps see https://netdev-ai.bots.linux.dev/ai-local.html
---
quic: add crypto packet encryption and decryption

This patch adds core support for packet-level encryption and decryption using AEAD, including both payload protection and QUIC header protection. It introduces helpers to encrypt and decrypt packets for secure QUIC packet exchange and address validation.

Quoted 4 lines
net/quic/crypto.c +0 -0
index a9de418e604e..a84fd8d7bf93 100644

[ ... ]

Quoted 1 line
net/quic/crypto.c +0 -0
@@ -231,6 +562,137 @@ static struct quic_cipher ciphers[QUIC_CIPHER_MAX + 1 - QUIC_CIPHER_MIN] = {

[ ... ]

Quoted 25 lines
net/quic/crypto.c +25 -0
+/* 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)
+{
+     u8 *iv, cha, ccm, phase = crypto->key_phase;
+     struct quic_skb_cb *cb = QUIC_SKB_CB(skb);
+     int err;
+
+     cb->key_phase = phase;
+     iv = crypto->tx_iv[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)

net/quic/crypto.c static struct quic_cipher ciphers[QUIC_CIPHER_MAX + 1 - QUIC_CIPHER_MIN] = { · line 609

Quoted 10 lines
net/quic/crypto.c +10 -0
+             crypto->key_update_send_time = quic_ktime_get_us();
+
+     ccm = quic_crypto_is_cipher_ccm(crypto);
+     err = quic_crypto_payload_encrypt(crypto->tx_tfm[phase], skb, iv, ccm);
+     if (err)
+             return err;
+out:
+     cha = quic_crypto_is_cipher_chacha(crypto);
+     return quic_crypto_header_encrypt(crypto->tx_hp_tfm, skb, cha);
+}

This function calls quic_crypto_payload_encrypt(). Looking at that function:

Quoted 17 lines
net/quic/crypto.c +17 -0
+/* AEAD Usage. */
+static int quic_crypto_payload_encrypt(struct crypto_aead *tfm, struct sk_buff *skb,
+                                    u8 *tx_iv, bool ccm)
+{
+     struct quic_skb_cb *cb = QUIC_SKB_CB(skb);
+     u8 *iv, i, nonce[QUIC_IV_LEN];
+     struct aead_request *req;
+     struct sk_buff *trailer;
+     struct scatterlist *sg;
+     struct quichdr *hdr;
+     u32 nsg, hlen, len;
+     void *ctx;
+     __be64 n;
+     int err;
+
+     len = skb->len;
+     err = skb_cow_data(skb, QUIC_TAG_LEN, &trailer);

net/quic/crypto.c static int quic_crypto_rx_keys_derive_and_install(struct quic_crypto *crypto) · line 446

Quoted 10 lines
net/quic/crypto.c +10 -0
+     if (err < 0)
+             return err;
+     nsg = (u32)err;
+     pskb_put(skb, trailer, QUIC_TAG_LEN);
+     hdr = quic_hdr(skb);
+     hdr->key = cb->key_phase;
+
+     ctx = quic_crypto_aead_mem_alloc(tfm, 0, &iv, &req, &sg, nsg);
+     if (!ctx)
+             return -ENOMEM;

If the kzalloc(GFP_ATOMIC) inside quic_crypto_aead_mem_alloc() fails, this returns -ENOMEM without rolling back the SKB modifications. The SKB is now permanently enlarged by QUIC_TAG_LEN (16 bytes) with a modified hdr->key.

Compare with quic_crypto_payload_decrypt():

Quoted 18 lines
net/quic/crypto.c +18 -0
+static int quic_crypto_payload_decrypt(struct crypto_aead *tfm, struct sk_buff *skb,
+                                    u8 *rx_iv, bool ccm)
+{
+     struct quic_skb_cb *cb = QUIC_SKB_CB(skb);
+     u8 *iv, i, nonce[QUIC_IV_LEN];
+     struct aead_request *req;
+     int nsg, hlen, len, err;
+     struct scatterlist *sg;
+     void *ctx;
+     __be64 n;
+
+     len = cb->length + cb->number_offset;
+     hlen = cb->number_offset + cb->number_len;
+     if (len - hlen < QUIC_TAG_LEN)
+             return -EINVAL;
+     nsg = 1; /* skb is already linearized in quic_packet_rcv(). */
+     ctx = quic_crypto_aead_mem_alloc(tfm, 0, &iv, &req, &sg, nsg);
+     if (!ctx)
Quoted 9 lines

net/quic/crypto.c static int quic_crypto_rx_keys_derive_and_install(struct quic_crypto *crypto) · line 512

Quoted 4 lines
net/quic/crypto.c +4 -0
+             return -ENOMEM;
+
+     sg_init_table(sg, nsg);
+     err = skb_to_sgvec(skb, sg, 0, len);

In the decrypt path, quic_crypto_aead_mem_alloc() is called before any SKB modifications. Should quic_crypto_payload_encrypt() do the same to avoid leaving the SKB in a modified state if allocation fails?

This is not true.

- firstly, on RX or Decrypt path, skb_cow_data() is called in quic_crypto_header_decrypt(), which is called before quic_crypto_payload_decrypt() in quic_crypto_decrypt(), so skb_cow_data() is called before mem_alloc for both places.

- secondly, even if the mem_alloc is failed, the skb will be dropped, no issue could be caused.

Quoted 36 lines

[ ... ]

net/quic/crypto.c static int quic_crypto_rx_keys_derive_and_install(struct quic_crypto *crypto) · line 520

Quoted 5 lines
net/quic/crypto.c +5 -0
+     /* Similar logic to quic_crypto_payload_encrypt(). */
+     memcpy(nonce, rx_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];

This isn't a bug, but there's duplicated logic between quic_crypto_payload_encrypt() and quic_crypto_payload_decrypt(). The AEAD nonce construction sequence (memcpy nonce from IV, XOR with be64 packet number, set CCM byte) appears in both functions.

In quic_crypto_payload_encrypt():

net/quic/crypto.c static int quic_crypto_rx_keys_derive_and_install(struct quic_crypto *crypto) · line 472

Quoted 9 lines
net/quic/crypto.c +9 -0
+     hlen = cb->number_offset + cb->number_len;
+     memcpy(nonce, tx_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);

And in quic_crypto_payload_decrypt():

net/quic/crypto.c static int quic_crypto_rx_keys_derive_and_install(struct quic_crypto *crypto) · line 523

Quoted 8 lines
net/quic/crypto.c +8 -0
+     /* Similar logic to quic_crypto_payload_encrypt(). */
+     memcpy(nonce, rx_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];
+
+     iv[0] = TLS_AES_CCM_IV_B0_BYTE;
+     memcpy(&iv[ccm], nonce, QUIC_IV_LEN);
Quoted 5 lines

The AEAD request teardown pattern (EINPROGRESS check, kfree_sensitive, memzero_explicit) is also duplicated. Would a shared static helper for nonce construction reduce the risk of the two copies diverging during maintenance?

I will see what I can do to dedup a bit for this.

Thanks.

author
#21 lore raw

PATCH v10 13/15 quic: add timer management

In reply to Xin Long

This patch introduces 'quic_timer' to unify and manage the five main timers used in QUIC: loss detection, delayed ACK, path validation, PMTU probing, and pacing. These timers are critical for driving retransmissions, connection liveness, and flow control.

Each timer type is initialized, started, reset, or stopped using a common set of operations.

- quic_timer_reset(): Reset a timer with type and timeout

- quic_timer_start(): Start a timer with type and timeout

- quic_timer_stop(): Stop a timer with type

Although handler functions for each timer are defined, they are currently placeholders; their logic will be implemented in upcoming patches for packet transmission and outqueue handling.

Deferred timer actions are also integrated through quic_release_cb(), which dispatches to the appropriate handler when timers expire.

---
v5:
- Rename QUIC_TSQ_DEFERRED to QUIC_PACE_DEFERRED.

---
 net/quic/Makefile |   2 +-
 net/quic/socket.c |  33 ++++++++
 net/quic/socket.h |  33 ++++++++
 net/quic/timer.c  | 196 ++++++++++++++++++++++++++++++++++++++++++++++
 net/quic/timer.h  |  47 +++++++++++
 5 files changed, 310 insertions(+), 1 deletion(-)
 create mode 100644 net/quic/timer.c
 create mode 100644 net/quic/timer.h
net/quic/Makefile +1 -1
index 58bb18f7926d..2ccf01ad9e22 100644
@@ -6,4 +6,4 @@
 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
+	  cong.o pnspace.o crypto.o timer.o
net/quic/socket.c +33 -0
index 74e3e3939c61..93a7abefc226 100644
@@ -50,6 +50,8 @@ static int quic_init_sock(struct sock *sk)
 	quic_conn_id_set_init(quic_dest(sk), 0);
 	quic_cong_init(quic_cong(sk));
 
+	quic_timer_init(sk);
+
 	if (quic_stream_init(quic_streams(sk)))
 		return -ENOMEM;
 
@@ -65,6 +67,8 @@ 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));
 	for (i = 0; i < QUIC_CRYPTO_MAX; i++)
@@ -191,6 +195,35 @@ static int quic_getsockopt(struct sock *sk, int level, int optname,
 
 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_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)
net/quic/socket.h +33 -0
index d7811391cc8b..c5654fdc06b5 100644
@@ -21,6 +21,7 @@
 #include "cong.h"
 
 #include "protocol.h"
+#include "timer.h"
 
 extern struct proto quic_prot;
 extern struct proto quicv6_prot;
@@ -32,6 +33,31 @@ enum quic_state {
 	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;
@@ -47,6 +73,8 @@ struct quic_sock {
 	struct quic_cong		cong;
 	struct quic_pnspace		space[QUIC_PNSPACE_MAX];
 	struct quic_crypto		crypto[QUIC_CRYPTO_MAX];
+
+	struct quic_timer		timers[QUIC_TIMER_MAX];
 };
 
 struct quic6_sock {
@@ -119,6 +147,11 @@ static inline struct quic_crypto *quic_crypto(const struct sock *sk, u8 level)
 	return &quic_sk(sk)->crypto[level];
 }
 
+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;
net/quic/timer.c new file +196 -0

196 lines not on this page · raw message

net/quic/timer.h new file +47 -0

47 lines not on this page · raw message

Signature · 1 line
2.47.1
maintainer
#22 lore raw

Re: [net-next,v10,13/15] quic: add timer management

This is an AI-generated review of your patch. The human sending this email has considered the AI review valid, or at least plausible.

For local reproduction steps see https://netdev-ai.bots.linux.dev/ai-local.html
---
quic: add timer management

This patch introduces 'quic_timer' to unify and manage the five main timers used in QUIC: loss detection, delayed ACK, path validation, PMTU probing, and pacing. These timers are critical for driving retransmissions, connection liveness, and flow control.

Quoted 4 lines
net/quic/Makefile +0 -0
index 58bb18f7926d..2ccf01ad9e22 100644

[ ... ]

Quoted 4 lines
net/quic/socket.c +0 -0
index 74e3e3939c61..93a7abefc226 100644

[ ... ]

Quoted 4 lines
net/quic/socket.h +0 -0
index d7811391cc8b..c5654fdc06b5 100644

[ ... ]

Quoted 19 lines
net/quic/timer.c new file +13 -0
new file mode 100644
index 000000000000..6f957385a341
@@ -0,0 +1,196 @@
+// 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 <[email protected]>
+ */
+
+#include "socket.h"

[ ... ]

Quoted 17 lines
net/quic/timer.c new file +17 -0
+static void quic_timer_sack_timeout(struct timer_list *t)
+{
+	struct quic_sock *qs = container_of(t, struct quic_sock, timers[QUIC_TIMER_SACK].t);
+	struct sock *sk = &qs->inet.sk;
+
+	bh_lock_sock(sk);
+	if (sock_owned_by_user(sk)) {
+		if (!test_and_set_bit(QUIC_SACK_DEFERRED, &sk->sk_tsq_flags))
+			sock_hold(sk);
+		goto out;
+	}
+
+	quic_timer_sack_handler(sk);
+out:
+	bh_unlock_sock(sk);
+	sock_put(sk);
+}

[ ... ]

Quoted 17 lines
net/quic/timer.c new file +17 -0
+static void quic_timer_loss_timeout(struct timer_list *t)
+{
+	struct quic_sock *qs = container_of(t, struct quic_sock, timers[QUIC_TIMER_LOSS].t);
+	struct sock *sk = &qs->inet.sk;
+
+	bh_lock_sock(sk);
+	if (sock_owned_by_user(sk)) {
+		if (!test_and_set_bit(QUIC_LOSS_DEFERRED, &sk->sk_tsq_flags))
+			sock_hold(sk);
+		goto out;
+	}
+
+	quic_timer_loss_handler(sk);
+out:
+	bh_unlock_sock(sk);
+	sock_put(sk);
+}

[ ... ]

Quoted 17 lines
net/quic/timer.c new file +17 -0
+static void quic_timer_path_timeout(struct timer_list *t)
+{
+	struct quic_sock *qs = container_of(t, struct quic_sock, timers[QUIC_TIMER_PATH].t);
+	struct sock *sk = &qs->inet.sk;
+
+	bh_lock_sock(sk);
+	if (sock_owned_by_user(sk)) {
+		if (!test_and_set_bit(QUIC_PATH_DEFERRED, &sk->sk_tsq_flags))
+			sock_hold(sk);
+		goto out;
+	}
+
+	quic_timer_path_handler(sk);
+out:
+	bh_unlock_sock(sk);
+	sock_put(sk);
+}

[ ... ]

Quoted 7 lines
net/quic/timer.c new file +7 -0
+static void quic_timer_pmtu_timeout(struct timer_list *t)
+{
+	struct quic_sock *qs = container_of(t, struct quic_sock, timers[QUIC_TIMER_PMTU].t);
+	struct sock *sk = &qs->inet.sk;
+
+	bh_lock_sock(sk);
+	if (sock_owned_by_user(sk)) {

net/quic/timer.c line 112

Quoted 10 lines
net/quic/timer.c new file +10 -0
+		if (!test_and_set_bit(QUIC_PMTU_DEFERRED, &sk->sk_tsq_flags))
+			sock_hold(sk);
+		goto out;
+	}
+
+	quic_timer_pmtu_handler(sk);
+out:
+	bh_unlock_sock(sk);
+	sock_put(sk);
+}

This isn't a bug, but the four timer_list-based timeout callbacks (quic_timer_sack_timeout, quic_timer_loss_timeout, quic_timer_path_timeout, quic_timer_pmtu_timeout) are structurally identical.

Each extracts the sock via container_of, takes bh_lock_sock, checks sock_owned_by_user to conditionally defer via test_and_set_bit/sock_hold, otherwise calls the handler directly, then bh_unlock_sock/sock_put. They differ only in the timer index constant, the deferred bit name, and the handler function.

This pattern could be consolidated into a single parameterized timeout callback using a data table that maps timer type to deferred bit and handler function pointer, reducing around 60 lines of near-duplicate code to roughly 15 lines. The TCP subsystem uses a similar approach for some of its timer infrastructure.

That said, this follows the existing per-timer-callback pattern used elsewhere in the kernel networking stack (for example, tcp_write_timer, tcp_delack_timer are also separate functions), so this is a minor style observation rather than a defect.

net/quic/timer.c line 143

Quoted 7 lines
net/quic/timer.c new file +7 -0
+void quic_timer_reset(struct sock *sk, u8 type, u64 timeout)
+{
+	struct timer_list *t = quic_timer(sk, type);
+
+	if (timeout && !mod_timer(t, jiffies + usecs_to_jiffies(timeout)))
+		sock_hold(sk);
+}

Can this function handle QUIC_TIMER_PACE safely? It unconditionally casts the result of quic_timer() to 'struct timer_list *' and calls mod_timer().

Looking at the timer API in this patch:

Both quic_timer_start() and quic_timer_stop() have explicit QUIC_TIMER_PACE guards that dispatch to hrtimer APIs:

quic_timer_start():

	if (type == QUIC_TIMER_PACE) {
		hr = quic_timer(sk, type);
		if (!hrtimer_is_queued(hr)) {
			hrtimer_start(hr, ns_to_ktime(timeout), ...);
			sock_hold(sk);
		}
		return;
	}

quic_timer_stop():

	if (type == QUIC_TIMER_PACE) {
		if (hrtimer_try_to_cancel(quic_timer(sk, type)) == 1)
			sock_put(sk);
		return;
	}

However, quic_timer_reset() does not have a similar guard. If a future caller invokes quic_timer_reset(sk, QUIC_TIMER_PACE, timeout), mod_timer() will operate on memory that is actually a 'struct hrtimer', potentially causing memory corruption.

Should quic_timer_reset() either add a QUIC_TIMER_PACE guard consistent with quic_timer_start()/quic_timer_stop(), or document that this function must only be used with timer_list-based timers?

Quoted 32 lines
net/quic/timer.c new file +32 -0
+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);
+}

[ ... ]

Quoted 5 lines
net/quic/timer.h new file +0 -0
new file mode 100644
index 000000000000..61b094325334

[ ... ]

author
#23 lore raw

Re: [net-next,v10,13/15] quic: add timer management

Quoted 150 lines · Paolo Abeni

This is an AI-generated review of your patch. The human sending this email has considered the AI review valid, or at least plausible.

For local reproduction steps see https://netdev-ai.bots.linux.dev/ai-local.html
---
quic: add timer management

This patch introduces 'quic_timer' to unify and manage the five main timers used in QUIC: loss detection, delayed ACK, path validation, PMTU probing, and pacing. These timers are critical for driving retransmissions, connection liveness, and flow control.

Quoted 4 lines
net/quic/Makefile +0 -0
index 58bb18f7926d..2ccf01ad9e22 100644

[ ... ]

Quoted 4 lines
net/quic/socket.c +0 -0
index 74e3e3939c61..93a7abefc226 100644

[ ... ]

Quoted 4 lines
net/quic/socket.h +0 -0
index d7811391cc8b..c5654fdc06b5 100644

[ ... ]

Quoted 19 lines
net/quic/timer.c new file +13 -0
new file mode 100644
index 000000000000..6f957385a341
@@ -0,0 +1,196 @@
+// 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 <[email protected]>
+ */
+
+#include "socket.h"

[ ... ]

Quoted 17 lines
net/quic/timer.c new file +17 -0
+static void quic_timer_sack_timeout(struct timer_list *t)
+{
+     struct quic_sock *qs = container_of(t, struct quic_sock, timers[QUIC_TIMER_SACK].t);
+     struct sock *sk = &qs->inet.sk;
+
+     bh_lock_sock(sk);
+     if (sock_owned_by_user(sk)) {
+             if (!test_and_set_bit(QUIC_SACK_DEFERRED, &sk->sk_tsq_flags))
+                     sock_hold(sk);
+             goto out;
+     }
+
+     quic_timer_sack_handler(sk);
+out:
+     bh_unlock_sock(sk);
+     sock_put(sk);
+}

[ ... ]

Quoted 17 lines
net/quic/timer.c new file +17 -0
+static void quic_timer_loss_timeout(struct timer_list *t)
+{
+     struct quic_sock *qs = container_of(t, struct quic_sock, timers[QUIC_TIMER_LOSS].t);
+     struct sock *sk = &qs->inet.sk;
+
+     bh_lock_sock(sk);
+     if (sock_owned_by_user(sk)) {
+             if (!test_and_set_bit(QUIC_LOSS_DEFERRED, &sk->sk_tsq_flags))
+                     sock_hold(sk);
+             goto out;
+     }
+
+     quic_timer_loss_handler(sk);
+out:
+     bh_unlock_sock(sk);
+     sock_put(sk);
+}

[ ... ]

Quoted 17 lines
net/quic/timer.c new file +17 -0
+static void quic_timer_path_timeout(struct timer_list *t)
+{
+     struct quic_sock *qs = container_of(t, struct quic_sock, timers[QUIC_TIMER_PATH].t);
+     struct sock *sk = &qs->inet.sk;
+
+     bh_lock_sock(sk);
+     if (sock_owned_by_user(sk)) {
+             if (!test_and_set_bit(QUIC_PATH_DEFERRED, &sk->sk_tsq_flags))
+                     sock_hold(sk);
+             goto out;
+     }
+
+     quic_timer_path_handler(sk);
+out:
+     bh_unlock_sock(sk);
+     sock_put(sk);
+}

[ ... ]

Quoted 7 lines
net/quic/timer.c new file +7 -0
+static void quic_timer_pmtu_timeout(struct timer_list *t)
+{
+     struct quic_sock *qs = container_of(t, struct quic_sock, timers[QUIC_TIMER_PMTU].t);
+     struct sock *sk = &qs->inet.sk;
+
+     bh_lock_sock(sk);
+     if (sock_owned_by_user(sk)) {

net/quic/timer.c line 112

Quoted 10 lines
net/quic/timer.c new file +10 -0
+             if (!test_and_set_bit(QUIC_PMTU_DEFERRED, &sk->sk_tsq_flags))
+                     sock_hold(sk);
+             goto out;
+     }
+
+     quic_timer_pmtu_handler(sk);
+out:
+     bh_unlock_sock(sk);
+     sock_put(sk);
+}

This isn't a bug, but the four timer_list-based timeout callbacks (quic_timer_sack_timeout, quic_timer_loss_timeout, quic_timer_path_timeout, quic_timer_pmtu_timeout) are structurally identical.

Each extracts the sock via container_of, takes bh_lock_sock, checks sock_owned_by_user to conditionally defer via test_and_set_bit/sock_hold, otherwise calls the handler directly, then bh_unlock_sock/sock_put. They differ only in the timer index constant, the deferred bit name, and the handler function.

This pattern could be consolidated into a single parameterized timeout callback using a data table that maps timer type to deferred bit and handler function pointer, reducing around 60 lines of near-duplicate code to roughly 15 lines. The TCP subsystem uses a similar approach for some of its timer infrastructure.

Quoted 5 lines

That said, this follows the existing per-timer-callback pattern used elsewhere in the kernel networking stack (for example, tcp_write_timer, tcp_delack_timer are also separate functions), so this is a minor style observation rather than a defect.

This is a good one. May not add a data table, but a simple helper like this:

static void quic_timer_timeout(struct timer_list *t, int type, int defer,
void (*handler)(struct sock *sk))
{
struct quic_sock *qs = container_of(t, struct quic_sock,

timers[type].t);
        struct sock *sk = &qs->inet.sk;

        bh_lock_sock(sk);
        if (sock_owned_by_user(sk)) {
                if (!test_and_set_bit(defer, &sk->sk_tsq_flags))
                        sock_hold(sk);
                goto out;
        }

        handler(sk);
out:
        bh_unlock_sock(sk);
        sock_put(sk);
}

will reduce quite some dup code.

Quoted 33 lines

net/quic/timer.c line 143

Quoted 7 lines
net/quic/timer.c new file +7 -0
+void quic_timer_reset(struct sock *sk, u8 type, u64 timeout)
+{
+     struct timer_list *t = quic_timer(sk, type);
+
+     if (timeout && !mod_timer(t, jiffies + usecs_to_jiffies(timeout)))
+             sock_hold(sk);
+}

Can this function handle QUIC_TIMER_PACE safely? It unconditionally casts the result of quic_timer() to 'struct timer_list *' and calls mod_timer().

Looking at the timer API in this patch:

Both quic_timer_start() and quic_timer_stop() have explicit QUIC_TIMER_PACE guards that dispatch to hrtimer APIs:

quic_timer_start():

        if (type == QUIC_TIMER_PACE) {
                hr = quic_timer(sk, type);
                if (!hrtimer_is_queued(hr)) {
                        hrtimer_start(hr, ns_to_ktime(timeout), ...);
                        sock_hold(sk);
                }
                return;
        }

quic_timer_stop():

        if (type == QUIC_TIMER_PACE) {
                if (hrtimer_try_to_cancel(quic_timer(sk, type)) == 1)
                        sock_put(sk);
                return;
        }
Quoted 9 lines

However, quic_timer_reset() does not have a similar guard. If a future caller invokes quic_timer_reset(sk, QUIC_TIMER_PACE, timeout), mod_timer() will operate on memory that is actually a 'struct hrtimer', potentially causing memory corruption.

Should quic_timer_reset() either add a QUIC_TIMER_PACE guard consistent with quic_timer_start()/quic_timer_stop(), or document that this function must only be used with timer_list-based timers?

There will be no callers to quic_timer_reset(QUIC_TIMER_PACE), so no need to add a check for QUIC_TIMER_PACE, I will leave a comment there for this.

Thanks.

Quoted 43 lines
Quoted 32 lines
net/quic/timer.c new file +32 -0
+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);
+}

[ ... ]

Quoted 5 lines
net/quic/timer.h new file +0 -0
new file mode 100644
index 000000000000..61b094325334

[ ... ]

author
#24 lore raw

PATCH v10 14/15 quic: add packet builder base

In reply to Xin Long

This patch introduces 'quic_packet' to handle packing of QUIC packets on the transmit (TX) path.

It provides functionality for frame packing and packet construction. The packet configuration includes setting the path, calculating overhead, and verifying routing. Frames are appended to the packet before it is created with the queued frames.

Once assembled, the packet is encrypted, bundled, and sent out. There is also support to flush the packet when no additional frames remain. Functions to create application (short) and handshake (long) packets are currently placeholders for future implementation.

- quic_packet_config(): Set the path, compute overhead, and verify routing.

- quic_packet_create_and_xmit(): Create and send the packet with the queued frames.

- quic_packet_xmit(): Encrypt, bundle, and send out the packet.

- quic_packet_flush(): Send the packet if there's nothing left to bundle.

---
v3:
- Adjust global connection and listen socket hashtable operations based on the new hashtable type.
- Introduce quic_packet_backlog_schedule() to enqueue Initial packets to quic_net.backlog_list and defer their decryption for ALPN demux to quic_packet_backlog_work() on quic_net.work, since quic_crypto_initial_keys_install()/crypto_aead_setkey() must run in process context.
v4:
- Update quic_(listen_)sock_lookup() to support lockless socket lookup using hlist_nulls_node APIs.
- Use quic_wq for QUIC packet backlog processing work.
v5:
- Rename quic_packet_create() to quic_packet_create_and_xmit() (suggested by Paolo).
- Move the packet parser base code to a separate patch, keeping only the packet builder base in this patch (suggested by Paolo).
- Change sent_time timestamp from u32 to u64 to improve accuracy.
v8:
- Remove the dependency on struct quic_frame by returning NULL in quic_packet_handshake/app_create() and dropping quic_packet_tail() and struct quic_packet_sent. This effectively strips out patch 14 (suggested by Paolo).
v9:
- Warn on oversized header length in quic_packet_config() (suggested by

    Paolo).

- Factor bundle initialization into a common 'init' goto label in quic_packet_bundle() (suggested by Paolo).
- Clarify comment for packet->ipfragok in quic_packet_config().
v10:
- Set MSS to QUIC_MIN_UDP_PAYLOAD in quic_packet_init(); it serves only as a default for procfs dumps before a connection exists.
- Introduce QUIC_PACKET_INVALID as a return value for invalid packet types used in the later patch.
- quic_sock.config.plpmtud_probe_interval has been moved to quic_path_group.plpmtud_interval, so update its usage in quic_packet_route() and quic_packet_config() accordingly.

---
 net/quic/Makefile |   2 +-
 net/quic/packet.c | 255 ++++++++++++++++++++++++++++++++++++++++++++++
 net/quic/packet.h | 109 ++++++++++++++++++++
 net/quic/socket.c |   1 +
 net/quic/socket.h |   8 ++
 5 files changed, 374 insertions(+), 1 deletion(-)
 create mode 100644 net/quic/packet.c
 create mode 100644 net/quic/packet.h
net/quic/Makefile +1 -1
index 2ccf01ad9e22..0f903f4a7ff1 100644
@@ -6,4 +6,4 @@
 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
+	  cong.o pnspace.o crypto.o timer.o packet.o
net/quic/packet.c new file +255 -0

255 lines not on this page · raw message

net/quic/packet.h new file +109 -0

109 lines not on this page · raw message

net/quic/socket.c +1 -0
index 93a7abefc226..4607f3ae8a7b 100644
@@ -51,6 +51,7 @@ static int quic_init_sock(struct sock *sk)
 	quic_cong_init(quic_cong(sk));
 
 	quic_timer_init(sk);
+	quic_packet_init(sk);
 
 	if (quic_stream_init(quic_streams(sk)))
 		return -ENOMEM;
net/quic/socket.h +8 -0
index c5654fdc06b5..1efc76ec2033 100644
@@ -20,6 +20,8 @@
 #include "path.h"
 #include "cong.h"
 
+#include "packet.h"
+
 #include "protocol.h"
 #include "timer.h"
 
@@ -74,6 +76,7 @@ struct quic_sock {
 	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];
 };
 
@@ -147,6 +150,11 @@ 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];
Signature · 1 line
2.47.1
maintainer
#25 lore raw

Re: [net-next,v10,14/15] quic: add packet builder base

This is an AI-generated review of your patch. The human sending this email has considered the AI review valid, or at least plausible.

For local reproduction steps see https://netdev-ai.bots.linux.dev/ai-local.html
---
quic: add packet builder base

This patch introduces 'quic_packet' to handle packing of QUIC packets on the transmit path, including frame packing, packet construction, encryption, bundling, and transmission logic.

Quoted 15 lines
net/quic/Makefile +1 -1
index 2ccf01ad9e22..0f903f4a7ff1 100644
@@ -6,4 +6,4 @@
 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
+	  cong.o pnspace.o crypto.o timer.o packet.o
net/quic/packet.c new file +0 -0
new file mode 100644
index 000000000000..a56edc745bb1

[ ... ]

Quoted 29 lines
net/quic/packet.c new file +29 -0
+/* 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;
+
+	packet->hlen = quic_encap_len(da);
+	pmtu = min_t(u32, dst_mtu(__sk_dst_get(sk)), 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;
+}
+
+/* Configure the QUIC packet header and routing based on encryption level and path. */
+int quic_packet_config(struct sock *sk, u8 level, u8 path)
+{

[ ... ]

net/quic/packet.c line 116

Quoted 6 lines
net/quic/packet.c new file +6 -0
+
+	/* Perform routing and MSS update for the configured packet. */
+	if (quic_packet_route(sk) < 0)
+		return -1;
+	return 0;
+}

Should quic_packet_config() propagate the error code from quic_packet_route() instead of returning -1?

The function quic_packet_route() returns proper negative errno values (via quic_flow_route()), but quic_packet_config() replaces any error with -1 (which is -EPERM). This could be misleading when diagnosing routing failures.

Quoted 5 lines
net/quic/packet.h new file +0 -0
new file mode 100644
index 000000000000..8c23be386207

[ ... ]

Quoted 14 lines
net/quic/packet.h new file +14 -0
+static inline u8 quic_packet_taglen(struct quic_packet *packet)
+{
+	return packet->taglen[!!packet->level];
+}
+
+static inline void quic_packet_set_taglen(struct quic_packet *packet, u8 taglen)
+{
+	packet->taglen[0] = taglen;
+}
+
+static inline u32 quic_packet_mss(struct quic_packet *packet)
+{
+	return packet->mss[0] - packet->taglen[!!packet->level];
+}

net/quic/packet.h line 83

Quoted 10 lines
net/quic/packet.h new file +10 -0
+
+static inline u32 quic_packet_max_payload(struct quic_packet *packet)
+{
+	return packet->mss[0] - packet->overhead - packet->taglen[!!packet->level];
+}
+
+static inline u32 quic_packet_max_payload_dgram(struct quic_packet *packet)
+{
+	return packet->mss[1] - packet->overhead - packet->taglen[!!packet->level];
+}

This isn't a bug, but quic_packet_mss(), quic_packet_max_payload(), and quic_packet_max_payload_dgram() all repeat the expression 'packet->taglen[!!packet->level]'.

Could these use quic_packet_taglen(packet) instead for consistency?

[ ... ]

author
#26 lore raw

Re: [net-next,v10,14/15] quic: add packet builder base

Quoted 72 lines · Paolo Abeni

This is an AI-generated review of your patch. The human sending this email has considered the AI review valid, or at least plausible.

For local reproduction steps see https://netdev-ai.bots.linux.dev/ai-local.html
---
quic: add packet builder base

This patch introduces 'quic_packet' to handle packing of QUIC packets on the transmit path, including frame packing, packet construction, encryption, bundling, and transmission logic.

Quoted 15 lines
net/quic/Makefile +1 -1
index 2ccf01ad9e22..0f903f4a7ff1 100644
@@ -6,4 +6,4 @@
 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
+       cong.o pnspace.o crypto.o timer.o packet.o
net/quic/packet.c new file +0 -0
new file mode 100644
index 000000000000..a56edc745bb1

[ ... ]

Quoted 29 lines
net/quic/packet.c new file +29 -0
+/* 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;
+
+     packet->hlen = quic_encap_len(da);
+     pmtu = min_t(u32, dst_mtu(__sk_dst_get(sk)), 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;
+}
+
+/* Configure the QUIC packet header and routing based on encryption level and path. */
+int quic_packet_config(struct sock *sk, u8 level, u8 path)
+{

[ ... ]

net/quic/packet.c line 116

Quoted 6 lines
net/quic/packet.c new file +6 -0
+
+     /* Perform routing and MSS update for the configured packet. */
+     if (quic_packet_route(sk) < 0)
+             return -1;
+     return 0;
+}

Should quic_packet_config() propagate the error code from quic_packet_route() instead of returning -1?

Quoted 4 lines

The function quic_packet_route() returns proper negative errno values (via quic_flow_route()), but quic_packet_config() replaces any error with -1 (which is -EPERM). This could be misleading when diagnosing routing failures.

Currently quic_packet_config() callers will either skip the process or return -ENETUNREACH on the failures. But maybe it's better to use the errno from quic_packet_route(). I will try to improve this.

Quoted 38 lines
Quoted 6 lines
net/quic/packet.h new file +0 -0
new file mode 100644
index 000000000000..8c23be386207

[ ... ]

Quoted 14 lines
net/quic/packet.h new file +14 -0
+static inline u8 quic_packet_taglen(struct quic_packet *packet)
+{
+     return packet->taglen[!!packet->level];
+}
+
+static inline void quic_packet_set_taglen(struct quic_packet *packet, u8 taglen)
+{
+     packet->taglen[0] = taglen;
+}
+
+static inline u32 quic_packet_mss(struct quic_packet *packet)
+{
+     return packet->mss[0] - packet->taglen[!!packet->level];
+}

net/quic/packet.h line 83

Quoted 10 lines
net/quic/packet.h new file +10 -0
+
+static inline u32 quic_packet_max_payload(struct quic_packet *packet)
+{
+     return packet->mss[0] - packet->overhead - packet->taglen[!!packet->level];
+}
+
+static inline u32 quic_packet_max_payload_dgram(struct quic_packet *packet)
+{
+     return packet->mss[1] - packet->overhead - packet->taglen[!!packet->level];
+}

This isn't a bug, but quic_packet_mss(), quic_packet_max_payload(), and quic_packet_max_payload_dgram() all repeat the expression 'packet->taglen[!!packet->level]'.

Quoted 2 lines

Could these use quic_packet_taglen(packet) instead for consistency?

OK.

Thanks.

maintainer
#27 lore raw

Re: [PATCH net-next v10 14/15] quic: add packet builder base

In reply to Xin Long
Quoted 5 lines · Xin Long
net/quic/packet.c new file +5 -0
+/* Transmit a QUIC packet, possibly encrypting and bundling it. */
+int quic_packet_xmit(struct sock *sk, struct sk_buff *skb)
+{
+     struct quic_packet *packet = quic_packet(sk);
+     struct quic_skb_cb *cb = QUIC_SKB_CB(skb);

net/quic/packet.c line 180

Quoted 10 lines
net/quic/packet.c new file +10 -0
+     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(!skb_set_owner_sk_safe(skb, sk));

This is the TX path, how can sk refcout be 0 here? Possibly use skb_set_owner_r() directly? At least use the WARN_ON_ONCE() variant and add a comment documenting why is needed.

Quoted 62 lines
net/quic/packet.c new file +62 -0
+     err = quic_crypto_encrypt(quic_crypto(sk, packet->level), skb);
+     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)
+{
+     struct quic_packet *packet = quic_packet(sk);
+     struct sk_buff *skb;
+     int err;
+
+     err = quic_packet_number_check(sk);
+     if (err)
+             goto err;
+
+     if (packet->level)
+             skb = quic_packet_handshake_create(sk);
+     else
+             skb = quic_packet_app_create(sk);
+     if (!skb) {
+             err = -ENOMEM;
+             goto err;
+     }
+
+     err = quic_packet_xmit(sk, skb);
+     if (err && err != -EINPROGRESS)
+             goto err;
+
+     /* Return 1 if at least one ACK-eliciting (non-PING) frame was sent. */
+     return !!packet->frames;
+err:
+     pr_debug("%s: err: %d\n", __func__, err);
+     return 0;
+}
+
+/* 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;
+     }
+}

net/quic/packet.c line 252

Quoted 10 lines
net/quic/packet.c new file +10 -0
+
+void quic_packet_init(struct sock *sk)
+{
+     struct quic_packet *packet = quic_packet(sk);
+
+     INIT_LIST_HEAD(&packet->frame_list);
+     packet->taglen[0] = QUIC_TAG_LEN;
+     packet->taglen[1] = QUIC_TAG_LEN;
+     packet->mss[0] = QUIC_MIN_UDP_PAYLOAD;
+     packet->mss[1] = QUIC_MIN_UDP_PAYLOAD;

The magic number above looks quite obscure, and AFAICS looking at struct quick_packet comments have different meaning. Please use some macro instead.

/P

author
#28 lore raw

Re: [PATCH net-next v10 14/15] quic: add packet builder base

Quoted 13 lines · Paolo Abeni
Quoted 11 lines · Xin Long
net/quic/packet.c new file +11 -0
+/* Transmit a QUIC packet, possibly encrypting and bundling it. */
+int quic_packet_xmit(struct sock *sk, struct sk_buff *skb)
+{
+     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;
Quoted 9 lines

net/quic/packet.c line 180

Quoted 4 lines
net/quic/packet.c new file +4 -0
+
+     cb->crypto_done = quic_packet_encrypt_done;
+     /* Associate skb with sk to ensure sk is valid during async encryption completion. */
+     WARN_ON(!skb_set_owner_sk_safe(skb, sk));

This is the TX path, how can sk refcout be 0 here? Possibly use skb_set_owner_r() directly? At least use the WARN_ON_ONCE() variant and add a comment documenting why is needed,

skb_set_owner_r() will do memory account with the skb->truesize, which is not what it wants here. skb_set_owner_sk_safe() is used to keep the sk not released during async encryption completion, as the comment above says.

I don't see another set_owner_sk helper for this. Please let me know if you have a better way for this.

For now I will change from WARN_ON() to WARN_ON_ONCE(), but still keep skb_set_owner_sk_safe().

Quoted 67 lines
Quoted 67 lines
net/quic/packet.c new file +67 -0
+     err = quic_crypto_encrypt(quic_crypto(sk, packet->level), skb);
+     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)
+{
+     struct quic_packet *packet = quic_packet(sk);
+     struct sk_buff *skb;
+     int err;
+
+     err = quic_packet_number_check(sk);
+     if (err)
+             goto err;
+
+     if (packet->level)
+             skb = quic_packet_handshake_create(sk);
+     else
+             skb = quic_packet_app_create(sk);
+     if (!skb) {
+             err = -ENOMEM;
+             goto err;
+     }
+
+     err = quic_packet_xmit(sk, skb);
+     if (err && err != -EINPROGRESS)
+             goto err;
+
+     /* Return 1 if at least one ACK-eliciting (non-PING) frame was sent. */
+     return !!packet->frames;
+err:
+     pr_debug("%s: err: %d\n", __func__, err);
+     return 0;
+}
+
+/* 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);
+
Quoted 9 lines

net/quic/packet.c line 252

Quoted 5 lines
net/quic/packet.c new file +5 -0
+     INIT_LIST_HEAD(&packet->frame_list);
+     packet->taglen[0] = QUIC_TAG_LEN;
+     packet->taglen[1] = QUIC_TAG_LEN;
+     packet->mss[0] = QUIC_MIN_UDP_PAYLOAD;
+     packet->mss[1] = QUIC_MIN_UDP_PAYLOAD;

The magic number above looks quite obscure, and AFAICS looking at struct quick_packet comments have different meaning. Please use some macro instead.

Sure.

author
#29 lore raw

PATCH v10 15/15 quic: add packet parser base

In reply to Xin Long

This patch uses 'quic_packet' to handle packing of QUIC packets on the receive (RX) path.

It introduces mechanisms to parse the ALPN from client Initial packets to determine the correct listener socket. Received packets are then routed and processed accordingly. Similar to the TX path, handling for application and handshake packets is not yet implemented.

- quic_packet_parse_alpn()`: Parse the ALPN from a client Initial packet, then locate the appropriate listener using the ALPN.

- quic_packet_rcv(): Locate the appropriate socket to handle the packet via quic_packet_process().

- quic_packet_process()`: Process the received packet.

In addition to packet flow, this patch adds support for ICMP-based MTU updates by locating the relevant socket and updating the stored PMTU accordingly.

- quic_packet_rcv_err_pmtu(): Find the socket and update the PMTU via quic_packet_mss_update().

---
v5:
- In quic_packet_rcv_err(), remove the unnecessary quic_is_listen() check and move quic_get_mtu_info() out of sock lock (suggested by Paolo).
- Replace cancel_work_sync() to disable_work_sync() (suggested by

    Paolo).

v6:
- Fix the loop using skb_dequeue() in quic_packet_backlog_work(), and kfree_skb() when sk is not found (reported by AI Reviews).
- Remove skb_pull() from quic_packet_rcv(), since it is now handled

    in quic_path_rcv().

- Note for AI reviews: add if (dst) check in quic_packet_rcv_err_pmtu(), although quic_packet_route() >= 0 already guarantees it is not NULL.
- Note for AI reviews: it is safe to do *plen -= QUIC_HLEN in quic_packet_get_version_and_connid(), since quic_packet_get_sock() already checks if (skb->len < QUIC_HLEN).
- Note for AI reviews: cb->length - cb->number_len - QUIC_TAG_LEN cannot underflow, because quic_crypto_header_decrypt() already checks if (cb->length < QUIC_PN_MAX_LEN + QUIC_SAMPLE_LEN).
- Note for AI reviews: the cast (u16)length in quic_packet_parse_alpn() is safe, as there is a prior check if (length > (u16)len); len is skb->len, which cannot exceed U16_MAX.
- Note for AI reviews: it's correct to do if (flags & QUIC_F_MTU_REDUCED_DEFERRED) in quic_release_cb(), since QUIC_MTU_REDUCED_DEFERRED is the bit used with test_and_set_bit().
- Note for AI reviews: move skb_cb->backlog = 1 before adding skb to backlog, although it's safe to write skb_cb after adding to backlog with sk_lock.slock, as skb dequeue from backlog requires sk_lock.slock.
v7:
- Pass udp sk to quic_packet_rcv(), quic_packet_rcv_err() and

    quic_sock_lookup().
  - Move the call to skb_linearize() and skb_set_owner_sk_safe() to
    .quic_path_rcv()/quic_packet_rcv().

v8:
- Replace the global ALPN demultiplexing sysctl with the static key in quic_packet_parse_alpn() (noted by Stefan).
- Refetch skb->data after decrypt in ALPN parsing, as skb_cow_data() may reallocate the skb data buffer (reported by Syzkaller).
- The indirect quic_path_rcv has been removed and call quic_packet_rcv() directly via extern.
- Do not restore skb data when QUIC Initial decryption fails, as the caller will free the skb for this failure anyway.
- With patch 14 removed, define a temporary QUIC_FRAME_CRYPTO ID when parsing the ALPN.
v9:
- Remove local_bh_disable() in quic_packet_get_listen_sock() as it's now using rcu_read_lock instead of spin_sock in quic_listen_sock_lookup() (noted by Paolo).
v10:
- Return QUIC_PACKET_INVALID (instead of -1) for invalid packet types in quic_packet_version_get_type().
- Update the comment to clarify in quic_packet_rcv_err() that ICMP errors embed the original QUIC packet, reversing src/dst addrs when parsed.
- Use qn->backlog_list.lock in quic_packet_backlog_schedule() to prevent TOCTOU.
- Add check 'len < TLS_CH_RANDOM_LEN + TLS_CH_VERSION_LEN' before parsing ClientHello in quic_packet_get_alpn().
- Add more limits in quic_packet_get_alpn() to improve robustness against malformed TLS ClientHello messages.
- Move skb_queue_purge() to after disable_work_sync() in quic_net_exit() for clarity and to satisfy AI review.
- quic_sock.config.plpmtud_probe_interval has been moved to quic_path_group.plpmtud_interval, so update its usage in quic_packet_rcv_err_pmtu() accordingly.
- Remove quic_packet_select_version() and quic_packet_version_change(); they will be reintroduced later when needed in the next patch series.

---
 net/quic/packet.c   | 577 ++++++++++++++++++++++++++++++++++++++++++++
 net/quic/packet.h   |  10 +
 net/quic/path.c     |   6 +-
 net/quic/protocol.c |   5 +
 net/quic/protocol.h |   4 +
 net/quic/socket.c   | 134 ++++++++++
 net/quic/socket.h   |   5 +
 7 files changed, 739 insertions(+), 2 deletions(-)
net/quic/packet.c +577 -0
index a56edc745bb1..a23ce99a6656 100644
@@ -14,6 +14,583 @@
 
 #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;
+	}
+}
+
+/* Parse QUIC version and connection IDs (DCID and SCID) from a Long header packet buffer. */
+static int quic_packet_get_version_and_connid(struct quic_conn_id *dcid, struct quic_conn_id *scid,
+					      u32 *version, u8 **pp, u32 *plen)
+{
+	u64 len, v;
+
+	*pp += QUIC_HLEN;
+	*plen -= QUIC_HLEN;
+
+	if (!quic_get_int(pp, plen, &v, QUIC_VERSION_LEN))
+		return -EINVAL;
+	*version = v;
+
+	if (!quic_get_int(pp, plen, &len, 1) ||
+	    len > *plen || len > QUIC_CONN_ID_MAX_LEN)
+		return -EINVAL;
+	quic_conn_id_update(dcid, *pp, len);
+	*plen -= len;
+	*pp += len;
+
+	if (!quic_get_int(pp, plen, &len, 1) ||
+	    len > *plen || len > QUIC_CONN_ID_MAX_LEN)
+		return -EINVAL;
+	quic_conn_id_update(scid, *pp, len);
+	*plen -= len;
+	*pp += len;
+	return 0;
+}
+
+/* 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 (!ip_sk_accept_pmtu(sk))
+		return;
+
+	info = clamp(paths->mtu_info, QUIC_PATH_MIN_PMTU, QUIC_PATH_MAX_PMTU);
+	/* If PLPMTUD is not enabled, update MSS using the route and ICMP info. */
+	if (!paths->plpmtud_interval) {
+		if (quic_packet_route(sk) < 0)
+			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.
+	 */
+	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)) {
+		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 is in use by userspace context.  Defer MTU processing to later via
+		 * tasklet.  Ensure the socket is not dropped 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(&head->lock);
+	if (head->qlen >= QUIC_PACKET_BACKLOG_MAX) {
+		spin_unlock(&head->lock);
+		QUIC_INC_STATS(net, QUIC_MIB_PKT_RCVDROP);
+		kfree_skb(skb);
+		return -1;
+	}
+	cb->backlog = 1;
+	__skb_queue_tail(head, skb);
+	spin_unlock(&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) /* Limit len to handshake message length if larger. */
+		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;
+
+	if (!quic_get_int(&p, &len, &length, 2)) /* Read TLS extensions length (2 bytes). */
+		return err;
+	if (len > (u32)length) /* Limit len to extensions length if larger. */
+		len = length;
+	while (len > 4) { /* Iterate over extensions to find ALPN (type TLS_EXT_alpn). */
+		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 (exts++ >= TLS_MAX_EXTENSIONS)
+			return err;
+	}
+	if (!found) { /* no ALPN extension found: 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 protocols list in alpn->data. */
+	len = length;
+	while (len) { /* Validate ALPN protocols list format. */
+		if (!quic_get_int(&p, &len, &length, 1) || length > (u64)len) {
+			/* Malformed ALPN entry: set alpn->len = 0 and 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 net *net = sock_net(skb->sk);
+	struct quic_conn_id dcid, scid;
+	u32 len = skb->len, version;
+	struct quic_crypto *crypto;
+	u8 *p = skb->data, type;
+	struct quic_data token;
+	u64 offset, length;
+	int err = -EINVAL;
+
+	if (!static_branch_unlikely(&quic_alpn_demux_key))
+		return 0;
+	if (quic_packet_get_version_and_connid(&dcid, &scid, &version, &p, &len))
+		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;
+	if (quic_packet_get_token(&token, &p, &len))
+		return err;
+	if (!quic_get_var(&p, &len, &length) || length > (u64)len)
+		return err;
+	if (!cb->backlog) { /* skb_get() needed as caller will free skb on this path. */
+		quic_packet_backlog_schedule(net, skb_get(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);
+	err = quic_crypto_decrypt(crypto, skb);
+	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);
+}
+
+/* Extract the Destination Connection ID (DCID) from a QUIC Long header packet. */
+int quic_packet_get_dcid(struct quic_conn_id *dcid, struct sk_buff *skb)
+{
+	u32 plen = skb->len;
+	u8 *p = skb->data;
+	u64 len;
+
+	if (plen < QUIC_HLEN + QUIC_VERSION_LEN)
+		return -EINVAL;
+	plen -= (QUIC_HLEN + QUIC_VERSION_LEN);
+	p += (QUIC_HLEN + QUIC_VERSION_LEN);
+
+	if (!quic_get_int(&p, &plen, &len, 1) ||
+	    len > plen || len > QUIC_CONN_ID_MAX_LEN)
+		return -EINVAL;
+	quic_conn_id_update(dcid, p, len);
+	return 0;
+}
+
+/* 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 = {};
+
+	quic_get_msg_addrs(skb, &daddr, &saddr);
+
+	if (quic_packet_parse_alpn(skb, &alpns))
+		return NULL;
+
+	return quic_listen_sock_lookup(skb, &daddr, &saddr, &alpns);
+}
+
+/* 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 net *net = sock_net(skb->sk);
+	struct quic_conn_id dcid, *conn_id;
+	union quic_addr daddr, saddr;
+	struct quic_data alpns = {};
+	struct sock *sk = NULL;
+
+	if (skb->len < QUIC_HLEN)
+		return NULL;
+
+	if (!quic_hdr(skb)->form) { /* Short header path. */
+		if (skb->len < QUIC_HLEN + QUIC_CONN_ID_DEF_LEN)
+			return NULL;
+		/* Fast path: look up QUIC connection by fixed-length DCID
+		 * (Currently, only source CIDs of size QUIC_CONN_ID_DEF_LEN 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 quic_conn_id_sk(conn_id); /* Return associated socket. */
+		}
+
+		/* 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).
+		 */
+		return quic_sock_lookup(skb, &daddr, &saddr, skb->sk, NULL);
+	}
+
+	/* Long header path. */
+	if (quic_packet_get_dcid(&dcid, skb))
+		return NULL;
+	/* 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).
+	 */
+	if (quic_packet_parse_alpn(skb, &alpns))
+		return NULL;
+	return quic_listen_sock_lookup(skb, &daddr, &saddr, &alpns);
+}
+
+/* Entry point for processing received QUIC packets. */
+int quic_packet_rcv(struct sock *sk, struct sk_buff *skb, u8 err)
+{
+	struct net *net = sock_net(sk);
+
+	if (unlikely(err))
+		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)) {
+		QUIC_INC_STATS(net, QUIC_MIB_PKT_RCVDROP);
+		goto err;
+	}
+
+	/* Look up socket from socket or connection IDs hash tables. */
+	sk = quic_packet_get_sock(skb);
+	if (!sk)
+		goto err;
+
+	bh_lock_sock(sk);
+	if (sock_owned_by_user(sk)) {
+		/* Socket is busy (owned by user context): queue to backlog. */
+		QUIC_SKB_CB(skb)->backlog = 1;
+		if (sk_add_backlog(sk, skb, READ_ONCE(sk->sk_rcvbuf))) {
+			QUIC_INC_STATS(net, QUIC_MIB_PKT_RCVDROP);
+			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_packet_process(). */
+	}
+	bh_unlock_sock(sk);
+	sock_put(sk);
+	return 0;
+
+err:
+	kfree_skb(skb);
+	return -EINVAL;
+}
+
+static int quic_packet_listen_process(struct sock *sk, struct sk_buff *skb)
+{
+	kfree_skb(skb);
+	return -EOPNOTSUPP;
+}
+
+static int quic_packet_handshake_process(struct sock *sk, struct sk_buff *skb)
+{
+	kfree_skb(skb);
+	return -EOPNOTSUPP;
+}
+
+static int quic_packet_app_process(struct sock *sk, struct sk_buff *skb)
+{
+	kfree_skb(skb);
+	return -EOPNOTSUPP;
+}
+
+int quic_packet_process(struct sock *sk, struct sk_buff *skb)
+{
+	if (quic_is_closed(sk)) {
+		kfree_skb(skb);
+		return 0;
+	}
+
+	if (quic_is_listen(sk))
+		return quic_packet_listen_process(sk, skb);
+
+	if (quic_hdr(skb)->form)
+		return quic_packet_handshake_process(sk, skb);
+
+	return quic_packet_app_process(sk, skb);
+}
+
+/* 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 (!sk) {
+			kfree_skb(skb);
+			continue;
+		}
+
+		lock_sock(sk);
+		quic_packet_process(sk, skb);
+		release_sock(sk);
+		sock_put(sk);
+	}
+}
+
 /* Make these fixed for easy coding. */
 #define QUIC_PACKET_NUMBER_LEN	QUIC_PN_MAX_LEN
 #define QUIC_PACKET_LENGTH_LEN	4
net/quic/packet.h +10 -0
index 8c23be386207..b925e9ab148d 100644
@@ -57,6 +57,8 @@ struct quic_packet {
 
 #define QUIC_VERSION_LEN		4
 
+#define QUIC_ALPN_MAX_LEN		128
+
 static inline u8 quic_packet_taglen(struct quic_packet *packet)
 {
 	return packet->taglen[!!packet->level];
@@ -98,6 +100,7 @@ static inline void quic_packet_reset(struct quic_packet *packet)
 	packet->ack_immediate = 0;
 }
 
+int quic_packet_process(struct sock *sk, struct sk_buff *skb);
 int quic_packet_config(struct sock *sk, u8 level, u8 path);
 
 int quic_packet_xmit(struct sock *sk, struct sk_buff *skb);
@@ -107,3 +110,10 @@ 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);
+
+int quic_packet_get_dcid(struct quic_conn_id *dcid, struct sk_buff *skb);
+u32 *quic_packet_compatible_versions(u32 version);
+
+int quic_packet_rcv(struct sock *sk, struct sk_buff *skb, u8 err);
+void quic_packet_backlog_work(struct work_struct *work);
+void quic_packet_rcv_err_pmtu(struct sock *sk);
net/quic/path.c +4 -2
index 9414c4f813c5..655f38af4107 100644
@@ -17,6 +17,8 @@
 #include "family.h"
 #include "path.h"
 
+extern int quic_packet_rcv(struct sock *sk, struct sk_buff *skb, u8 err);
+
 static int quic_udp_rcv(struct sock *sk, struct sk_buff *skb)
 {
 	memset(skb->cb, 0, sizeof(skb->cb));
@@ -25,13 +27,13 @@ static int quic_udp_rcv(struct sock *sk, struct sk_buff *skb)
 
 	skb_pull(skb, sizeof(struct udphdr));
 	skb_dst_force(skb);
-	kfree_skb(skb);
+	quic_packet_rcv(sk, skb, 0);
 	return 0; /* .encap_rcv must return 0 if skb was either consumed or dropped. */
 }
 
 static int quic_udp_err(struct sock *sk, struct sk_buff *skb)
 {
-	return 0;
+	return quic_packet_rcv(sk, skb, 1);
 }
 
 static void quic_udp_sock_put_work(struct work_struct *work)
net/quic/protocol.c +5 -0
index 11a122d84ccb..f1a754556d8d 100644
@@ -265,6 +265,9 @@ static int __net_init quic_net_init(struct net *net)
 		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) {
@@ -283,6 +286,8 @@ static void __net_exit quic_net_exit(struct 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;
net/quic/protocol.h +4 -0
index 850f43806688..6793255604c6 100644
@@ -50,6 +50,10 @@ struct quic_net {
 	struct proc_dir_entry *proc_net;	/* procfs entry for dumping QUIC socket stats */
 #endif
 	struct quic_crypto crypto;	/* Context for decrypting Initial packets for ALPN */
+
+	/* Queue of packets deferred for processing in process context */
+	struct sk_buff_head backlog_list;
+	struct work_struct work;	/* Work scheduled to drain and process backlog_list */
 };
 
 struct quic_net *quic_net(struct net *net);
net/quic/socket.c +134 -0

158 lines not on this page · raw message

net/quic/socket.h +5 -0
index 1efc76ec2033..34dbdfd2dedc 100644
@@ -200,3 +200,8 @@ static inline void quic_set_state(struct sock *sk, int state)
 	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);
Signature · 1 line
2.47.1
maintainer
#30 lore raw

Re: [net-next,v10,15/15] quic: add packet parser base

This is an AI-generated review of your patch. The human sending this email has considered the AI review valid, or at least plausible.

For local reproduction steps see https://netdev-ai.bots.linux.dev/ai-local.html
---
quic: add packet parser base

This patch uses 'quic_packet' to handle packing of QUIC packets on the receive (RX) path. It introduces mechanisms to parse the ALPN from client Initial packets to determine the correct listener socket. Received packets are then routed and processed accordingly.

Quoted 4 lines
net/quic/packet.c +0 -0
index a56edc745bb1..a23ce99a6656 100644

[ ... ]

Quoted 8 lines
net/quic/packet.c +8 -0
+/* Extract the Destination Connection ID (DCID) from a QUIC Long header packet. */
+int quic_packet_get_dcid(struct quic_conn_id *dcid, struct sk_buff *skb)
+{
+	u32 plen = skb->len;
+	u8 *p = skb->data;
+	u64 len;
+
+	if (plen < QUIC_HLEN + QUIC_VERSION_LEN)

net/quic/packet.c line 414

Quoted 10 lines
net/quic/packet.c +10 -0
+		return -EINVAL;
+	plen -= (QUIC_HLEN + QUIC_VERSION_LEN);
+	p += (QUIC_HLEN + QUIC_VERSION_LEN);
+
+	if (!quic_get_int(&p, &plen, &len, 1) ||
+	    len > plen || len > QUIC_CONN_ID_MAX_LEN)
+		return -EINVAL;
+	quic_conn_id_update(dcid, p, len);
+	return 0;
+}

This parsing logic looks nearly identical to what's already in quic_packet_get_version_and_connid(). Both skip QUIC_HLEN + QUIC_VERSION_LEN, read the length byte, validate against QUIC_CONN_ID_MAX_LEN, and call quic_conn_id_update().

Could quic_packet_get_dcid() call quic_packet_get_version_and_connid() with dummy parameters, or could both share a helper to extract just the DCID portion?

net/quic/path.c line 20

Quoted 9 lines
net/quic/path.c +1 -0
index 9414c4f813c5..655f38af4107 100644
@@ -17,6 +17,8 @@
 #include "family.h"
 #include "path.h"
 
+extern int quic_packet_rcv(struct sock *sk, struct sk_buff *skb, u8 err);

Could this include packet.h instead? The canonical declaration already exists in packet.h (after this patch). Using 'extern' here creates a duplicate signature.

If the signature in packet.h changes later, this extern could silently become stale depending on calling conventions. Since packet.h has no include directives and wouldn't create a circular dependency, including it would keep the declaration in sync.

Quoted 4 lines
net/quic/path.c +1 -0
+
 static int quic_udp_rcv(struct sock *sk, struct sk_buff *skb)
 {
 	memset(skb->cb, 0, sizeof(skb->cb));
author
#31 lore raw

Re: [net-next,v10,15/15] quic: add packet parser base

Quoted 44 lines · Paolo Abeni

This is an AI-generated review of your patch. The human sending this email has considered the AI review valid, or at least plausible.

For local reproduction steps see https://netdev-ai.bots.linux.dev/ai-local.html
---
quic: add packet parser base

This patch uses 'quic_packet' to handle packing of QUIC packets on the receive (RX) path. It introduces mechanisms to parse the ALPN from client Initial packets to determine the correct listener socket. Received packets are then routed and processed accordingly.

Quoted 4 lines
net/quic/packet.c +0 -0
index a56edc745bb1..a23ce99a6656 100644

[ ... ]

Quoted 8 lines
net/quic/packet.c +8 -0
+/* Extract the Destination Connection ID (DCID) from a QUIC Long header packet. */
+int quic_packet_get_dcid(struct quic_conn_id *dcid, struct sk_buff *skb)
+{
+     u32 plen = skb->len;
+     u8 *p = skb->data;
+     u64 len;
+
+     if (plen < QUIC_HLEN + QUIC_VERSION_LEN)

net/quic/packet.c line 414

Quoted 10 lines
net/quic/packet.c +10 -0
+             return -EINVAL;
+     plen -= (QUIC_HLEN + QUIC_VERSION_LEN);
+     p += (QUIC_HLEN + QUIC_VERSION_LEN);
+
+     if (!quic_get_int(&p, &plen, &len, 1) ||
+         len > plen || len > QUIC_CONN_ID_MAX_LEN)
+             return -EINVAL;
+     quic_conn_id_update(dcid, p, len);
+     return 0;
+}

This parsing logic looks nearly identical to what's already in quic_packet_get_version_and_connid(). Both skip QUIC_HLEN + QUIC_VERSION_LEN, read the length byte, validate against QUIC_CONN_ID_MAX_LEN, and call quic_conn_id_update().

Quoted 4 lines

Could quic_packet_get_dcid() call quic_packet_get_version_and_connid() with dummy parameters, or could both share a helper to extract just the DCID portion?

No, they work on different forms of QUIC packet, one for short header and the other for long header packets.

Quoted 14 lines

net/quic/path.c line 20

Quoted 9 lines
net/quic/path.c +1 -0
index 9414c4f813c5..655f38af4107 100644
@@ -17,6 +17,8 @@
 #include "family.h"
 #include "path.h"
 
+extern int quic_packet_rcv(struct sock *sk, struct sk_buff *skb, u8 err);

Could this include packet.h instead? The canonical declaration already exists in packet.h (after this patch). Using 'extern' here creates a duplicate signature.

Quoted 5 lines

If the signature in packet.h changes later, this extern could silently become stale depending on calling conventions. Since packet.h has no include directives and wouldn't create a circular dependency, including it would keep the declaration in sync.

No, let's not have this dependency for now.

Thanks.

Quoted 5 lines
Quoted 4 lines
net/quic/path.c +1 -0
+
 static int quic_udp_rcv(struct sock *sk, struct sk_buff *skb)
 {
      memset(skb->cb, 0, sizeof(skb->cb));
maintainer
#32 lore raw

Re: [PATCH net-next v10 15/15] quic: add packet parser base

In reply to Xin Long
Quoted 63 lines · Xin Long
net/quic/socket.c +63 -0
+/* 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();
+begin:
+     if (!alpns->len) { /* No ALPN entries present or failed to parse the ALPNs. */
+             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)) {
+                             sk = tmp;
+                             if (!quic_is_any_addr(a)) /* Prefer specific address match. */
+                                     break;
+                     }
+             }
+             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)) {
+                             sk = tmp;
+                             if (!quic_is_any_addr(a))
+                                     break;
+                     }
+             }
+             if (sk)
+                     break;
+     }
+out:
+     /* If the nulls value we got at the end of the iteration is different from the expected

net/quic/socket.c static void quic_enter_memory_pressure(struct sock *sk) · line 150

Quoted 10 lines
net/quic/socket.c +10 -0
+      * one, we must restart the lookup as the list was modified concurrently.
+      */
+     if (!sk && get_nulls_value(node) != hash)
+             goto begin;
+
+     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;

Note that you could avoid the refcount if you keep using the sk in an RCU critical section. i.e. plain UDP does that. Same consideration for established lookup.

/P

author
#33 lore raw

Re: [PATCH net-next v10 15/15] quic: add packet parser base

Quoted 71 lines · Paolo Abeni
Quoted 69 lines · Xin Long
net/quic/socket.c +69 -0
+/* 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();
+begin:
+     if (!alpns->len) { /* No ALPN entries present or failed to parse the ALPNs. */
+             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)) {
+                             sk = tmp;
+                             if (!quic_is_any_addr(a)) /* Prefer specific address match. */
+                                     break;
+                     }
+             }
+             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)) {
+                             sk = tmp;
+                             if (!quic_is_any_addr(a))
+                                     break;
+                     }
+             }
+             if (sk)
+                     break;
+     }
+out:
+     /* If the nulls value we got at the end of the iteration is different from the expected
+      * one, we must restart the lookup as the list was modified concurrently.
+      */
+     if (!sk && get_nulls_value(node) != hash)
+             goto begin;
+
+     if (sk && sk->sk_reuseport)
Quoted 9 lines

net/quic/socket.c static void quic_enter_memory_pressure(struct sock *sk) · line 150

Quoted 4 lines
net/quic/socket.c +4 -0
+             sk = reuseport_select_sock(sk, quic_addr_hash(net, da), skb, 1);
+
+     if (sk && unlikely(!refcount_inc_not_zero(&sk->sk_refcnt)))
+             sk = NULL;

Note that you could avoid the refcount if you keep using the sk in an RCU critical section. i.e. plain UDP does that. Same consideration for established lookup.

Doesn't seem easy to adjust the code for this, I will leave it as it is for now.

Thanks.