From: Xin Long <lucien.xin@gmail.com> Date: 2026-02-25 04:06:01
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
--
2.47.1
From: Xin Long <lucien.xin@gmail.com> Date: 2026-02-25 02:35:36
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.
Signed-off-by: Xin Long <lucien.xin@gmail.com>
Acked-by: Paolo Abeni <pabeni@redhat.com>
---
include/linux/socket.h | 1 +
include/uapi/linux/in.h | 2 ++
2 files changed, 3 insertions(+)
From: Xin Long <lucien.xin@gmail.com> Date: 2026-02-25 02:35:43
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.
Signed-off-by: Xin Long <lucien.xin@gmail.com>
Acked-by: Paolo Abeni <pabeni@redhat.com>
---
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
@@ -0,0 +1,400 @@+// SPDX-License-Identifier: GPL-2.0-or-later+/* QUIC kernel implementation+*(C)CopyrightRedHatCorp.2023+*+*ThisfileispartoftheQUICkernelimplementation+*+*Initialization/cleanupforQUICprotocolsupport.+*+*Writtenormodifiedby:+*XinLong<lucien.xin@gmail.com>+*/++#include<linux/quic.h>++#include"common.h"+#include"stream.h"++/* Check if a stream ID is valid for sending or receiving. */+staticboolquic_stream_id_valid(s64stream_id,boolis_serv,boolsend)+{+u8type=(stream_id&QUIC_STREAM_TYPE_MASK);++if(send){+if(is_serv)+returntype!=QUIC_STREAM_TYPE_CLIENT_UNI;+returntype!=QUIC_STREAM_TYPE_SERVER_UNI;+}+if(is_serv)+returntype!=QUIC_STREAM_TYPE_SERVER_UNI;+returntype!=QUIC_STREAM_TYPE_CLIENT_UNI;+}++/* Check if a stream ID was initiated locally. */+staticboolquic_stream_id_local(s64stream_id,u8is_serv)+{+returnis_serv^!(stream_id&QUIC_STREAM_TYPE_SERVER_MASK);+}++/* Check if a stream ID represents a unidirectional stream. */+staticboolquic_stream_id_uni(s64stream_id)+{+returnstream_id&QUIC_STREAM_TYPE_UNI_MASK;+}++#define QUIC_STREAM_HT_SIZE 64++staticstructhlist_head*quic_stream_head(structquic_stream_table*streams,s64stream_id)+{+return&streams->head[stream_id&(QUIC_STREAM_HT_SIZE-1)];+}++structquic_stream*quic_stream_find(structquic_stream_table*streams,s64stream_id)+{+structhlist_head*head=quic_stream_head(streams,stream_id);+structquic_stream*stream;++hlist_for_each_entry(stream,head,node){+if(stream->id==stream_id)+break;+}+returnstream;+}++staticvoidquic_stream_add(structquic_stream_table*streams,structquic_stream*stream)+{+structhlist_head*head;++head=quic_stream_head(streams,stream->id);+hlist_add_head(&stream->node,head);+}++staticvoidquic_stream_delete(structquic_stream*stream)+{+hlist_del_init(&stream->node);+kfree(stream);+}++/* Create and register new streams for sending or receiving. */+staticstructquic_stream*quic_stream_create(structquic_stream_table*streams,+s64max_stream_id,boolsend,boolis_serv)+{+structquic_stream_limits*limits=&streams->send;+structquic_stream*pos,*stream=NULL;+gfp_tgfp=GFP_KERNEL_ACCOUNT;+structhlist_node*tmp;+HLIST_HEAD(head);+s64stream_id;+u32count=0;++if(!send){+limits=&streams->recv;+gfp=GFP_ATOMIC|__GFP_ACCOUNT;+}+stream_id=limits->next_bidi_stream_id;+if(quic_stream_id_uni(max_stream_id))+stream_id=limits->next_uni_stream_id;++/* rfc9000#section-2.1: A stream ID that is used out of order results in all streams+*ofthattypewithlower-numberedstreamIDsalsobeingopened.+*/+while(stream_id<=max_stream_id){+stream=kzalloc_obj(*stream,gfp);+if(!stream)+gotofree;++stream->id=stream_id;+if(quic_stream_id_uni(stream_id)){+if(send){+stream->send.max_bytes=limits->max_stream_data_uni;+}else{+stream->recv.max_bytes=limits->max_stream_data_uni;+stream->recv.window=stream->recv.max_bytes;+}+hlist_add_head(&stream->node,&head);+stream_id+=QUIC_STREAM_ID_STEP;+continue;+}++if(quic_stream_id_local(stream_id,is_serv)){+stream->send.max_bytes=streams->send.max_stream_data_bidi_remote;+stream->recv.max_bytes=streams->recv.max_stream_data_bidi_local;+}else{+stream->send.max_bytes=streams->send.max_stream_data_bidi_local;+stream->recv.max_bytes=streams->recv.max_stream_data_bidi_remote;+}+stream->recv.window=stream->recv.max_bytes;+hlist_add_head(&stream->node,&head);+stream_id+=QUIC_STREAM_ID_STEP;+}++hlist_for_each_entry_safe(pos,tmp,&head,node){+hlist_del_init(&pos->node);+quic_stream_add(streams,pos);+count++;+}++/* Streams must be opened sequentially. Update the next stream ID so the correct+*startingpointisknownifanout-of-orderopenisrequested.Noteoverflow+*ofnext_uni/bidi_stream_idisimpossiblewiths64.+*/+if(quic_stream_id_uni(stream_id)){+limits->next_uni_stream_id=stream_id;+limits->streams_uni+=count;+returnstream;+}++limits->next_bidi_stream_id=stream_id;+limits->streams_bidi+=count;+returnstream;++free:+hlist_for_each_entry_safe(pos,tmp,&head,node){+hlist_del_init(&pos->node);+kfree(pos);+}+returnNULL;+}++/* Check if a send or receive stream ID is already closed. */+staticboolquic_stream_id_closed(structquic_stream_table*streams,s64stream_id,boolsend)+{+structquic_stream_limits*limits=send?&streams->send:&streams->recv;++if(quic_stream_id_uni(stream_id))+returnstream_id<limits->next_uni_stream_id;+returnstream_id<limits->next_bidi_stream_id;+}++/* Check if a stream ID would exceed local (recv) or peer (send) limits. */+boolquic_stream_id_exceeds(structquic_stream_table*streams,s64stream_id,boolsend)+{+u64nstreams;++if(!send){+/* recv.max_uni_stream_id is updated in quic_stream_max_streams_update()+*alreadybasedonnext_uni/bidi_stream_id,max_streams_uni/bidi,and+*streams_uni/bidi,soonlyrecv.max_uni_stream_idneedstobechecked.+*/+if(quic_stream_id_uni(stream_id))+returnstream_id>streams->recv.max_uni_stream_id;+returnstream_id>streams->recv.max_bidi_stream_id;+}++if(quic_stream_id_uni(stream_id)){+if(stream_id>streams->send.max_uni_stream_id)+returntrue;+stream_id-=streams->send.next_uni_stream_id;+nstreams=quic_stream_id_to_streams(stream_id);+returnnstreams+streams->send.streams_uni>streams->send.max_streams_uni;+}++if(stream_id>streams->send.max_bidi_stream_id)+returntrue;+stream_id-=streams->send.next_bidi_stream_id;+nstreams=quic_stream_id_to_streams(stream_id);+returnnstreams+streams->send.streams_bidi>streams->send.max_streams_bidi;+}++/* Get or create a send or recv stream by ID. Requires sock lock held. */+structquic_stream*quic_stream_get(structquic_stream_table*streams,s64stream_id,u32flags,+boolis_serv,boolsend)+{+structquic_stream*stream;++if(!quic_stream_id_valid(stream_id,is_serv,send))+returnERR_PTR(-EINVAL);++stream=quic_stream_find(streams,stream_id);+if(stream){+if(send&&(flags&MSG_QUIC_STREAM_NEW)&&+stream->send.state!=QUIC_STREAM_SEND_STATE_READY)+returnERR_PTR(-EINVAL);+returnstream;+}++if(!send&&quic_stream_id_local(stream_id,is_serv)){+if(quic_stream_id_closed(streams,stream_id,!send))+returnERR_PTR(-ENOSTR);+returnERR_PTR(-EINVAL);+}++if(quic_stream_id_closed(streams,stream_id,send))+returnERR_PTR(-ENOSTR);++if(send&&!(flags&MSG_QUIC_STREAM_NEW))+returnERR_PTR(-EINVAL);++if(quic_stream_id_exceeds(streams,stream_id,send))+returnERR_PTR(-EAGAIN);++stream=quic_stream_create(streams,stream_id,send,is_serv);+if(!stream)+returnERR_PTR(-ENOSTR);++if(send||quic_stream_id_valid(stream_id,is_serv,!send))+streams->send.active_stream_id=stream_id;++returnstream;+}++/* Release or clean up a send or recv stream. This function updates stream counters and state+*whenasendstreamhaseithersuccessfullysentalldataorhasbeenreset,orwhenarecv+*streamhaseitherconsumedalldataorhasbeenreset.Requiressocklockheld.+*/+voidquic_stream_put(structquic_stream_table*streams,structquic_stream*stream,boolis_serv,+boolsend)+{+if(quic_stream_id_uni(stream->id)){+if(send){+/* For uni streams, decrement uni count and delete immediately. */+streams->send.streams_uni--;+quic_stream_delete(stream);+return;+}+/* For uni streams, decrement uni count and mark done. */+if(!stream->recv.done){+stream->recv.done=1;+streams->recv.streams_uni--;+streams->recv.uni_pending=1;+}+/* Delete stream if fully read or reset. */+if(stream->recv.state>QUIC_STREAM_RECV_STATE_RECVD)+quic_stream_delete(stream);+return;+}++if(send){+/* For bidi streams, only proceed if receive side is in a final state. */+if(stream->recv.state<QUIC_STREAM_RECV_STATE_RECVD)+return;+}else{+/* For bidi streams, only proceed if send side is in a final state. */+if(stream->send.state!=QUIC_STREAM_SEND_STATE_RECVD&&+stream->send.state!=QUIC_STREAM_SEND_STATE_RESET_RECVD)+return;+}++if(quic_stream_id_local(stream->id,is_serv)){+/* Local-initiated stream: mark send done and decrement send.bidi count. */+if(!stream->send.done){+stream->send.done=1;+streams->send.streams_bidi--;+}+}else{+/* Remote-initiated stream: mark recv done and decrement recv bidi count. */+if(!stream->recv.done){+stream->recv.done=1;+streams->recv.streams_bidi--;+streams->recv.bidi_pending=1;+}+}++/* Delete stream if fully read or reset. */+if(stream->recv.state>QUIC_STREAM_RECV_STATE_RECVD)+quic_stream_delete(stream);+}++/* Updates the maximum allowed incoming stream IDs if any streams were recently closed.+*Recalculatesthemax_uniandmax_bidistreamIDlimitsbasedonthenumberofopen+*streamsandwhetheranyweremarkedfordeletion.+*+*Returnstrueifeithermax_uniormax_bidiwasupdated,indicatingthata+*MAX_STREAMS_UNIorMAX_STREAMS_BIDIframeshouldbesenttothepeer.+*/+boolquic_stream_max_streams_update(structquic_stream_table*streams,s64*max_uni,s64*max_bidi)+{+*max_uni=0;+*max_bidi=0;+if(streams->recv.uni_pending){+streams->recv.max_uni_stream_id=+streams->recv.next_uni_stream_id-QUIC_STREAM_ID_STEP++((streams->recv.max_streams_uni-streams->recv.streams_uni)<<+QUIC_STREAM_TYPE_BITS);+*max_uni=quic_stream_id_to_streams(streams->recv.max_uni_stream_id);+streams->recv.uni_pending=0;+}+if(streams->recv.bidi_pending){+streams->recv.max_bidi_stream_id=+streams->recv.next_bidi_stream_id-QUIC_STREAM_ID_STEP++((streams->recv.max_streams_bidi-streams->recv.streams_bidi)<<+QUIC_STREAM_TYPE_BITS);+*max_bidi=quic_stream_id_to_streams(streams->recv.max_bidi_stream_id);+streams->recv.bidi_pending=0;+}++return*max_uni||*max_bidi;+}++intquic_stream_init(structquic_stream_table*streams)+{+structhlist_head*head;+inti;++head=kmalloc_array(QUIC_STREAM_HT_SIZE,sizeof(*head),GFP_KERNEL);+if(!head)+return-ENOMEM;+for(i=0;i<QUIC_STREAM_HT_SIZE;i++)+INIT_HLIST_HEAD(&head[i]);+streams->head=head;+return0;+}++voidquic_stream_free(structquic_stream_table*streams)+{+structquic_stream*stream;+structhlist_head*head;+structhlist_node*tmp;+inti;++if(!streams->head)+return;++for(i=0;i<QUIC_STREAM_HT_SIZE;i++){+head=&streams->head[i];+hlist_for_each_entry_safe(stream,tmp,head,node)+quic_stream_delete(stream);+}+kfree(streams->head);+}++/* Populate transport parameters from stream hash table. */+voidquic_stream_get_param(structquic_stream_table*streams,structquic_transport_param*p)+{+structquic_stream_limits*limits=p->remote?&streams->send:&streams->recv;++p->max_stream_data_bidi_remote=limits->max_stream_data_bidi_remote;+p->max_stream_data_bidi_local=limits->max_stream_data_bidi_local;+p->max_stream_data_uni=limits->max_stream_data_uni;+p->max_streams_bidi=limits->max_streams_bidi;+p->max_streams_uni=limits->max_streams_uni;+}++/* Configure stream hashtable from transport parameters. */+voidquic_stream_set_param(structquic_stream_table*streams,structquic_transport_param*p,+boolis_serv)+{+structquic_stream_limits*limits=p->remote?&streams->send:&streams->recv;+u8bidi_type,uni_type;++limits->max_stream_data_bidi_local=p->max_stream_data_bidi_local;+limits->max_stream_data_bidi_remote=p->max_stream_data_bidi_remote;+limits->max_stream_data_uni=p->max_stream_data_uni;+limits->max_streams_bidi=p->max_streams_bidi;+limits->max_streams_uni=p->max_streams_uni;+limits->active_stream_id=-1;++if(p->remote^is_serv){+bidi_type=QUIC_STREAM_TYPE_CLIENT_BIDI;+uni_type=QUIC_STREAM_TYPE_CLIENT_UNI;+}else{+bidi_type=QUIC_STREAM_TYPE_SERVER_BIDI;+uni_type=QUIC_STREAM_TYPE_SERVER_UNI;+}++limits->max_bidi_stream_id=quic_stream_streams_to_id(p->max_streams_bidi,bidi_type);+limits->next_bidi_stream_id=bidi_type;++limits->max_uni_stream_id=quic_stream_streams_to_id(p->max_streams_uni,uni_type);+limits->next_uni_stream_id=uni_type;+}
@@ -0,0 +1,119 @@+/* SPDX-License-Identifier: GPL-2.0-or-later */+/* QUIC kernel implementation+*(C)CopyrightRedHatCorp.2023+*+*ThisfileispartoftheQUICkernelimplementation+*+*Writtenormodifiedby:+*XinLong<lucien.xin@gmail.com>+*/++#define QUIC_DEF_STREAMS 100+#define QUIC_MAX_STREAMS 4096ULL++/*+*rfc9000#section-2.1:+*+*Theleastsignificantbit(0x01)ofthestreamIDidentifiestheinitiatorofthestream.+*Client-initiatedstreamshaveeven-numberedstreamIDs(withthebitsetto0),and+*server-initiatedstreamshaveodd-numberedstreamIDs(withthebitsetto1).+*+*Thesecondleastsignificantbit(0x02)ofthestreamIDdistinguishesbetweenbidirectional+*streams(withthebitsetto0)andunidirectionalstreams(withthebitsetto1).+*/+#define QUIC_STREAM_TYPE_BITS 2+#define QUIC_STREAM_ID_STEP BIT(QUIC_STREAM_TYPE_BITS)++#define QUIC_STREAM_TYPE_CLIENT_BIDI 0x00+#define QUIC_STREAM_TYPE_SERVER_BIDI 0x01+#define QUIC_STREAM_TYPE_CLIENT_UNI 0x02+#define QUIC_STREAM_TYPE_SERVER_UNI 0x03++structquic_stream{+structhlist_nodenode;+s64id;/* Stream ID as defined in RFC 9000 Section 2.1 */+struct{+/* Sending-side stream level flow control */+u64last_max_bytes;/* Maximum send offset advertised by peer at last update */+u64max_bytes;/* Current maximum offset we are allowed to send to */+u64bytes;/* Bytes already sent to peer */++u32errcode;/* Application error code to send in RESET_STREAM */+u32frags;/* Number of sent STREAM frames not yet acknowledged */+u8state;/* Send stream state, per rfc9000#section-3.1 */++u8data_blocked;/* True if flow control blocks sending more data */+u8done;/* True if application indicated end of stream (FIN sent) */+}send;+struct{+/* Receiving-side stream level flow control */+u64max_bytes;/* Maximum offset peer is allowed to send to */+u64window;/* Remaining receive window before advertising new limit */+u64bytes;/* Bytes consumed by application from the stream */++u64highest;/* Highest received offset */+u64offset;/* Offset up to which data is in buffer or consumed */+u64finalsz;/* Final size of the stream if FIN received */++u32frags;/* Number of received STREAM frames pending reassembly */+u8state;/* Receive stream state, per rfc9000#section-3.2 */++u8stop_sent;/* True if STOP_SENDING has been sent */+u8done;/* True if FIN received and final size validated */+}recv;+};++structquic_stream_limits{+/* Stream limit parameters defined in rfc9000#section-18.2 */+u64max_stream_data_bidi_remote;/* initial_max_stream_data_bidi_remote */+u64max_stream_data_bidi_local;/* initial_max_stream_data_bidi_local */+u64max_stream_data_uni;/* initial_max_stream_data_uni */+u64max_streams_bidi;/* initial_max_streams_bidi */+u64max_streams_uni;/* initial_max_streams_uni */++s64next_bidi_stream_id;/* Next bidi stream ID to open or accept */+s64next_uni_stream_id;/* Next uni stream ID to open or accept */+s64max_bidi_stream_id;/* Highest allowed bidi stream ID */+s64max_uni_stream_id;/* Highest allowed uni stream ID */+s64active_stream_id;/* Most recently opened stream ID */++u8bidi_blocked;/* STREAMS_BLOCKED_BIDI sent, awaiting ACK */+u8uni_blocked;/* STREAMS_BLOCKED_UNI sent, awaiting ACK */+u8bidi_pending;/* MAX_STREAMS_BIDI needs to be sent */+u8uni_pending;/* MAX_STREAMS_UNI needs to be sent */++u16streams_bidi;/* Number of open bidi streams */+u16streams_uni;/* Number of open uni streams */+};++structquic_stream_table{+structhlist_head*head;/* Hash table storing all active streams */++structquic_stream_limitssend;/* Limits advertised by peer */+structquic_stream_limitsrecv;/* Limits we advertise to peer */+};++staticinlineu64quic_stream_id_to_streams(s64stream_id)+{+return(u64)(stream_id>>QUIC_STREAM_TYPE_BITS)+1;+}++staticinlines64quic_stream_streams_to_id(u64streams,u8type)+{+return(s64)((streams-1)<<QUIC_STREAM_TYPE_BITS)|type;+}++structquic_stream*quic_stream_get(structquic_stream_table*streams,s64stream_id,u32flags,+boolis_serv,boolsend);+voidquic_stream_put(structquic_stream_table*streams,structquic_stream*stream,boolis_serv,+boolsend);++boolquic_stream_max_streams_update(structquic_stream_table*streams,s64*max_uni,s64*max_bidi);+boolquic_stream_id_exceeds(structquic_stream_table*streams,s64stream_id,boolsend);+structquic_stream*quic_stream_find(structquic_stream_table*streams,s64stream_id);++voidquic_stream_get_param(structquic_stream_table*streams,structquic_transport_param*p);+voidquic_stream_set_param(structquic_stream_table*streams,structquic_transport_param*p,+boolis_serv);+voidquic_stream_free(structquic_stream_table*streams);+intquic_stream_init(structquic_stream_table*streams);
From: Xin Long <lucien.xin@gmail.com> Date: 2026-02-25 02:35:46
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.
Signed-off-by: Tyler Fanelli <redacted>
Signed-off-by: Xin Long <lucien.xin@gmail.com>
---
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
@@ -0,0 +1,522 @@+// SPDX-License-Identifier: GPL-2.0-or-later+/* QUIC kernel implementation+*(C)CopyrightRedHatCorp.2023+*+*ThisfileispartoftheQUICkernelimplementation+*+*Initialization/cleanupforQUICprotocolsupport.+*+*Writtenormodifiedby:+*XinLong<lucien.xin@gmail.com>+*/++#include<net/udp_tunnel.h>+#include<linux/quic.h>++#include"common.h"+#include"family.h"+#include"path.h"++staticintquic_udp_rcv(structsock*sk,structsk_buff*skb)+{+memset(skb->cb,0,sizeof(skb->cb));+QUIC_SKB_CB(skb)->seqno=-1;+QUIC_SKB_CB(skb)->time=quic_ktime_get_us();++skb_pull(skb,sizeof(structudphdr));+skb_dst_force(skb);+kfree_skb(skb);+return0;/* .encap_rcv must return 0 if skb was either consumed or dropped. */+}++staticintquic_udp_err(structsock*sk,structsk_buff*skb)+{+return0;+}++staticvoidquic_udp_sock_put_work(structwork_struct*work)+{+structquic_udp_sock*us=container_of(work,structquic_udp_sock,work);+structquic_uhash_head*head;+structsock*sk=us->sk;++/* Hold the sock to safely access it in quic_udp_sock_lookup() even after+*udp_tunnel_sock_release().Thereleasemustoccurbefore__hlist_del()+*soanewUDPtunnelsocketcanbecreatedforthesameaddressandport+*ifquic_udp_sock_lookup()failstofindone.+*+*Note:udp_tunnel_sock_release()cannotbecalledunderthemutexdueto+*somelockdepwarnings.+*/+sock_hold(sk);+udp_tunnel_sock_release(sk->sk_socket);++head=quic_udp_sock_head(sock_net(sk),ntohs(us->addr.v4.sin_port));+mutex_lock(&head->lock);+__hlist_del(&us->node);+mutex_unlock(&head->lock);++sock_put(sk);+kfree(us);+}++staticstructquic_udp_sock*quic_udp_sock_create(structsock*sk,unionquic_addr*a)+{+structudp_tunnel_sock_cfgtuncfg={};+structudp_port_cfgudp_conf={};+structnet*net=sock_net(sk);+structquic_uhash_head*head;+structquic_udp_sock*us;+structsocket*sock;++us=kzalloc_obj(*us,GFP_KERNEL);+if(!us)+returnNULL;++quic_udp_conf_init(sk,&udp_conf,a);+if(udp_sock_create(net,&udp_conf,&sock)){+pr_debug("%s: failed to create udp sock\n",__func__);+kfree(us);+returnNULL;+}++tuncfg.encap_type=1;+tuncfg.encap_rcv=quic_udp_rcv;+tuncfg.encap_err_lookup=quic_udp_err;+setup_udp_tunnel_sock(net,sock,&tuncfg);++refcount_set(&us->refcnt,1);+us->sk=sock->sk;+memcpy(&us->addr,a,sizeof(*a));+us->bind_ifindex=sk->sk_bound_dev_if;++head=quic_udp_sock_head(net,ntohs(a->v4.sin_port));+hlist_add_head(&us->node,&head->head);+INIT_WORK(&us->work,quic_udp_sock_put_work);++returnus;+}++staticboolquic_udp_sock_get(structquic_udp_sock*us)+{+returnrefcount_inc_not_zero(&us->refcnt);+}++staticvoidquic_udp_sock_put(structquic_udp_sock*us)+{+/* The UDP socket may be freed in atomic RX context during connection migration;+*deferthereleasetoaworkqueue.+*/+if(refcount_dec_and_test(&us->refcnt))+queue_work(quic_wq,&us->work);+}++/* Lookup a quic_udp_sock in the global hash table by port or address. If 'a' is provided, it+*searchesforasocketwhoselocaladdressmatches'a'and,ifapplicable,matchesthedevice+*binding.If'a'isNULL,itsearchesonlybyport.+*/+staticstructquic_udp_sock*quic_udp_sock_lookup(structsock*sk,unionquic_addr*a,u16port)+{+structnet*net=sock_net(sk);+structquic_uhash_head*head;+structquic_udp_sock*us;++head=quic_udp_sock_head(net,port);+hlist_for_each_entry(us,&head->head,node){+if(net!=sock_net(us->sk))+continue;+if(a){+if(quic_cmp_sk_addr(us->sk,&us->addr,a)&&+(!us->bind_ifindex||!sk->sk_bound_dev_if||+us->bind_ifindex==sk->sk_bound_dev_if))+returnus;+continue;+}+if(ntohs(us->addr.v4.sin_port)==port)+returnus;+}+returnNULL;+}++staticvoidquic_path_set_udp_sk(structquic_path*path,structquic_udp_sock*us)+{+if(path->udp_sk)+quic_udp_sock_put(path->udp_sk);++path->udp_sk=us;+if(!us){+path->usk=NULL;+memset(&path->uaddr,0,sizeof(path->uaddr));+return;+}+path->usk=us->sk;+memcpy(&path->uaddr,&us->addr,sizeof(us->addr));+}++/* Binds a QUIC path to a local port and sets up a UDP socket. */+intquic_path_bind(structsock*sk,structquic_path_group*paths,u8path)+{+unionquic_addr*a=quic_path_saddr(paths,path);+introver,low,high,remaining;+structnet*net=sock_net(sk);+structquic_uhash_head*head;+structquic_udp_sock*us;+u16port;++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){+if(!quic_udp_sock_get(us)){/* Releasing in workqueue; retry later. */+mutex_unlock(&head->lock);+return-EAGAIN;+}+}else{+us=quic_udp_sock_create(sk,a);+if(!us){+mutex_unlock(&head->lock);+return-EINVAL;+}+}+mutex_unlock(&head->lock);+quic_path_set_udp_sk(&paths->path[path],us);+return0;+}++inet_sk_get_local_port_range(sk,&low,&high);+remaining=(high-low)+1;+rover=get_random_u32_below(remaining)+low;+do{+rover++;+if(rover<low||rover>high)+rover=low;+port=(u16)rover;+if(inet_is_local_reserved_port(net,port))+continue;++head=quic_udp_sock_head(net,port);+mutex_lock(&head->lock);+if(quic_udp_sock_lookup(sk,NULL,port)){+mutex_unlock(&head->lock);+cond_resched();+continue;+}+a->v4.sin_port=htons(port);+us=quic_udp_sock_create(sk,a);+if(!us){+a->v4.sin_port=0;+mutex_unlock(&head->lock);+return-EINVAL;+}+mutex_unlock(&head->lock);++quic_path_set_udp_sk(&paths->path[path],us);+__sk_dst_reset(sk);+return0;+}while(--remaining>0);++return-EADDRINUSE;+}++/* Swaps the active and alternate QUIC paths.+*+*Promotesthealternatepath(path[1])tobecomethenewactivepath(path[0]).Ifthe+*alternatepathhasavalidUDPsocket,theentirepathisswapped.Otherwise,onlythe+*destinationaddressisexchanged,assumingthesourceaddressisthesameandnorebindis+*needed.+*+*Thisistypicallyusedduringpathmigrationoralternatepathpromotion.+*/+voidquic_path_swap(structquic_path_group*paths)+{+structquic_pathpath=paths->path[0];++paths->alt_probes=0;+paths->alt_state=QUIC_PATH_ALT_SWAPPED;++if(paths->path[1].udp_sk){+paths->path[0]=paths->path[1];+paths->path[1]=path;+return;+}++paths->path[0].daddr=paths->path[1].daddr;+paths->path[1].daddr=path.daddr;+}++/* Frees resources associated with a QUIC path.+*+*Thisisusedforcleanupduringerrorhandlingorwhenthepathisnolongerneeded.+*/+voidquic_path_unbind(structsock*sk,structquic_path_group*paths,u8path)+{+paths->alt_probes=0;+paths->alt_state=QUIC_PATH_ALT_NONE;++quic_path_set_udp_sk(&paths->path[path],NULL);++memset(quic_path_daddr(paths,path),0,sizeof(unionquic_addr));+memset(quic_path_saddr(paths,path),0,sizeof(unionquic_addr));+}++/* Detects and records a potential alternate path.+*+*Ifthenewsourceordestinationaddressdiffersfromtheactivepath,andalternatepath+*detectionisnotdisabled,thefunctionupdatesthealternatepathslot(path[1])withthe+*newaddresses.+*+*Thisistypicallycalledonpacketreceivetodetectnewpossiblenetworkpaths(e.g.,NAT+*rebinding,mobility).+*+*Returns1ifanewalternatepathwasdetectedandupdated,0otherwise.+*/+intquic_path_detect_alt(structquic_path_group*paths,unionquic_addr*sa,unionquic_addr*da,+structsock*sk)+{+if((!quic_cmp_sk_addr(sk,quic_path_saddr(paths,0),sa)&&!paths->disable_saddr_alt)||+(!quic_cmp_sk_addr(sk,quic_path_daddr(paths,0),da)&&!paths->disable_daddr_alt)){+if(!quic_path_saddr(paths,1)->v4.sin_port)+quic_path_set_saddr(paths,1,sa);++if(!quic_cmp_sk_addr(sk,quic_path_saddr(paths,1),sa))+return0;++if(!quic_path_daddr(paths,1)->v4.sin_port)+quic_path_set_daddr(paths,1,da);++returnquic_cmp_sk_addr(sk,quic_path_daddr(paths,1),da);+}+return0;+}++voidquic_path_get_param(structquic_path_group*paths,structquic_transport_param*p)+{+if(p->remote){+p->disable_active_migration=paths->disable_saddr_alt;+return;+}+p->disable_active_migration=paths->disable_daddr_alt;+}++voidquic_path_set_param(structquic_path_group*paths,structquic_transport_param*p)+{+if(p->remote){+paths->disable_saddr_alt=p->disable_active_migration;+return;+}+paths->disable_daddr_alt=p->disable_active_migration;+}++/* State Machine defined in rfc8899#section-5.2 */+enumquic_plpmtud_state{+QUIC_PL_DISABLED,+QUIC_PL_BASE,+QUIC_PL_SEARCH,+QUIC_PL_COMPLETE,+QUIC_PL_ERROR,+};++#define QUIC_BASE_PLPMTU 1200+#define QUIC_MAX_PLPMTU 9000+#define QUIC_MIN_PLPMTU 512++#define QUIC_MAX_PROBES 3++#define QUIC_PL_BIG_STEP 32+#define QUIC_PL_MIN_STEP 4++/* Handle PLPMTUD probe failure on a QUIC path.+*+*CalledimmediatelyaftersendingaprobepacketinQUICPathMTUDiscovery.Tracksprobe+*countandmanagesstatetransitionsbasedonthenumberofprobessentandcurrentPLPMTUD+*state(BASE,SEARCH,COMPLETE,ERROR).Detectsprobefailuresandblackholes,adjusting+*PMTUandprobesizesaccordingly.+*+*Return:NewPMTUvalueifupdated,else0.+*/+u32quic_path_pl_send(structquic_path_group*paths,s64number)+{+u32pathmtu=0;++paths->pl.number=number;+if(paths->pl.probe_count<QUIC_MAX_PROBES)+gotoout;++paths->pl.probe_count=0;+if(paths->pl.state==QUIC_PL_BASE){+if(paths->pl.probe_size==QUIC_BASE_PLPMTU){/* BASE_PLPMTU Confirming Failed */+paths->pl.state=QUIC_PL_ERROR;/* Base -> Error */++paths->pl.pmtu=QUIC_BASE_PLPMTU;+pathmtu=QUIC_BASE_PLPMTU;+}+}elseif(paths->pl.state==QUIC_PL_SEARCH){+if(paths->pl.pmtu==paths->pl.probe_size){/* Black Hole Detected */+paths->pl.state=QUIC_PL_BASE;/* Search -> Base */+paths->pl.probe_size=QUIC_BASE_PLPMTU;+paths->pl.probe_high=0;++paths->pl.pmtu=QUIC_BASE_PLPMTU;+pathmtu=QUIC_BASE_PLPMTU;+}else{/* Normal probe failure. */+paths->pl.probe_high=paths->pl.probe_size;+paths->pl.probe_size=paths->pl.pmtu;+}+}elseif(paths->pl.state==QUIC_PL_COMPLETE){+if(paths->pl.pmtu==paths->pl.probe_size){/* Black Hole Detected */+paths->pl.state=QUIC_PL_BASE;/* Search Complete -> Base */+paths->pl.probe_size=QUIC_BASE_PLPMTU;++paths->pl.pmtu=QUIC_BASE_PLPMTU;+pathmtu=QUIC_BASE_PLPMTU;+}+}++out:+pr_debug("%s: dst: %p, state: %d, pmtu: %d, size: %d, high: %d\n",__func__,paths,+paths->pl.state,paths->pl.pmtu,paths->pl.probe_size,paths->pl.probe_high);+paths->pl.probe_count++;+returnpathmtu;+}++/* Handle successful reception of a PMTU probe.+*+*Calledwhenaprobepacketisacknowledged.Updatesprobesizeandtransitionsstateif+*needed(e.g.,fromSEARCHtoCOMPLETE).ExpandsPMTUusingbinaryorlinearsearch+*dependingonstate.+*+*Return:NewPMTUtoapplyifsearchcompletes,or0ifnochange.+*/+u32quic_path_pl_recv(structquic_path_group*paths,bool*raise_timer,bool*complete)+{+u32pathmtu=0;++pr_debug("%s: dst: %p, state: %d, pmtu: %d, size: %d, high: %d\n",__func__,paths,+paths->pl.state,paths->pl.pmtu,paths->pl.probe_size,paths->pl.probe_high);++*raise_timer=false;+paths->pl.number=0;+paths->pl.pmtu=paths->pl.probe_size;+paths->pl.probe_count=0;+if(paths->pl.state==QUIC_PL_BASE){+paths->pl.state=QUIC_PL_SEARCH;/* Base -> Search */+paths->pl.probe_size+=QUIC_PL_BIG_STEP;+}elseif(paths->pl.state==QUIC_PL_ERROR){+paths->pl.state=QUIC_PL_SEARCH;/* Error -> Search */++paths->pl.pmtu=paths->pl.probe_size;+pathmtu=(u32)paths->pl.pmtu;+paths->pl.probe_size+=QUIC_PL_BIG_STEP;+}elseif(paths->pl.state==QUIC_PL_SEARCH){+if(!paths->pl.probe_high){+if(paths->pl.probe_size<QUIC_MAX_PLPMTU){+paths->pl.probe_size=+(u16)min(paths->pl.probe_size+QUIC_PL_BIG_STEP,+QUIC_MAX_PLPMTU);+*complete=false;+returnpathmtu;+}+paths->pl.probe_high=QUIC_MAX_PLPMTU;+}+paths->pl.probe_size+=QUIC_PL_MIN_STEP;+if(paths->pl.probe_size>=paths->pl.probe_high){+paths->pl.probe_high=0;+paths->pl.state=QUIC_PL_COMPLETE;/* Search -> Search Complete */++paths->pl.probe_size=paths->pl.pmtu;+pathmtu=(u32)paths->pl.pmtu;+*raise_timer=true;+}+}elseif(paths->pl.state==QUIC_PL_COMPLETE){+/* Raise probe_size again after 30 * interval in Search Complete */+paths->pl.state=QUIC_PL_SEARCH;/* Search Complete -> Search */+paths->pl.probe_size=(u16)min(paths->pl.probe_size+QUIC_PL_MIN_STEP,+QUIC_MAX_PLPMTU);+}++*complete=(paths->pl.state==QUIC_PL_COMPLETE);+returnpathmtu;+}++/* Handle ICMP "Packet Too Big" messages.+*+*RespondstoanincomingICMPerrorbyreducingtheprobesizeorfallingbacktoasafe+*baselinePMTUdependingoncurrentstate.AlsohandlescaseswherethePMTUhintlies+*betweenprobeandcurrentPMTU.+*+*Return:NewPMTUtoapplyifstatechanges,or0ifnochange.+*/+u32quic_path_pl_toobig(structquic_path_group*paths,u32pmtu,bool*reset_timer)+{+u32pathmtu=0;++pr_debug("%s: dst: %p, state: %d, pmtu: %d, size: %d, ptb: %d\n",__func__,paths,+paths->pl.state,paths->pl.pmtu,paths->pl.probe_size,pmtu);++*reset_timer=false;+if(pmtu<QUIC_MIN_PLPMTU||pmtu>=(u32)paths->pl.probe_size)+returnpathmtu;++if(paths->pl.state==QUIC_PL_BASE){+if(pmtu>=QUIC_MIN_PLPMTU&&pmtu<QUIC_BASE_PLPMTU){+paths->pl.state=QUIC_PL_ERROR;/* Base -> Error */++paths->pl.pmtu=QUIC_BASE_PLPMTU;+pathmtu=QUIC_BASE_PLPMTU;+}+}elseif(paths->pl.state==QUIC_PL_SEARCH){+if(pmtu>=QUIC_BASE_PLPMTU&&pmtu<(u32)paths->pl.pmtu){+paths->pl.state=QUIC_PL_BASE;/* Search -> Base */+paths->pl.probe_size=QUIC_BASE_PLPMTU;+paths->pl.probe_count=0;++paths->pl.probe_high=0;+paths->pl.pmtu=QUIC_BASE_PLPMTU;+pathmtu=QUIC_BASE_PLPMTU;+}elseif(pmtu>(u32)paths->pl.pmtu&&pmtu<(u32)paths->pl.probe_size){+paths->pl.probe_size=(u16)pmtu;+paths->pl.probe_count=0;+}+}elseif(paths->pl.state==QUIC_PL_COMPLETE){+if(pmtu>=QUIC_BASE_PLPMTU&&pmtu<(u32)paths->pl.pmtu){+paths->pl.state=QUIC_PL_BASE;/* Complete -> Base */+paths->pl.probe_size=QUIC_BASE_PLPMTU;+paths->pl.probe_count=0;++paths->pl.probe_high=0;+paths->pl.pmtu=QUIC_BASE_PLPMTU;+pathmtu=QUIC_BASE_PLPMTU;+*reset_timer=true;+}+}+returnpathmtu;+}++/* Reset PLPMTUD state for a path.+*+*ResetsallPLPMTUD-relatedstatetoitsinitialconfiguration.Calledwhenanewpathis+*initializedorwhenrecoveringfromerrors.+*/+voidquic_path_pl_reset(structquic_path_group*paths)+{+paths->pl.number=0;+paths->pl.probe_high=0;+paths->pl.probe_count=0;+paths->pl.state=QUIC_PL_BASE;+paths->pl.pmtu=QUIC_BASE_PLPMTU;+paths->pl.probe_size=QUIC_BASE_PLPMTU;+}++/* Check if a packet number confirms PLPMTUD probe.+*+*Checkswhetherthelastprobe(trackedby.number)hasbeenacknowledged.Iftheprobe+*numberlieswithintheACKrange,confirmationissuccessful.+*+*Return:trueifprobeisconfirmed,falseotherwise.+*/+boolquic_path_pl_confirm(structquic_path_group*paths,s64largest,s64smallest)+{+returnpaths->pl.number&&paths->pl.number>=smallest&&paths->pl.number<=largest;+}
@@ -0,0 +1,172 @@+/* SPDX-License-Identifier: GPL-2.0-or-later */+/* QUIC kernel implementation+*(C)CopyrightRedHatCorp.2023+*+*ThisfileispartoftheQUICkernelimplementation+*+*Writtenormodifiedby:+*XinLong<lucien.xin@gmail.com>+*/++#define QUIC_PATH_MIN_PMTU 1200U+#define QUIC_PATH_MAX_PMTU 65536U++#define QUIC_MIN_UDP_PAYLOAD 1200+#define QUIC_MAX_UDP_PAYLOAD 65527++#define QUIC_PATH_ENTROPY_LEN 8++externstructworkqueue_struct*quic_wq;++/* Connection Migration State Machine:+*+*+--------+recvnon-probing,freeoldpath+----------++*|NONE|<--------------------------------------|SWAPPED|+*+--------++----------++*|^\^+*|\\|+*|\\newpathdetected,|recv+*|\\hasanotherDCID,|Path+*|\\sndPathChallenge|Response+*|\-------------------------------|+*|-------------------------------\|+*|newpathdetected,Path\\|+*|hasnootherDCID,Challenge\\|+*|requestanewDCIDfailed\\|+*v\v|+*+----------++----------++*|PENDING|------------------------------------>|PROBING|+*+----------+recvanewDCID,sndPathChallenge+----------++*/+enum{+QUIC_PATH_ALT_NONE,/* No alternate path (migration complete or aborted) */+QUIC_PATH_ALT_PENDING,/* Waiting for a new destination CID for migration */+QUIC_PATH_ALT_PROBING,/* Validating the alternate path (PATH_CHALLENGE) */+QUIC_PATH_ALT_SWAPPED,/* Alternate path is now active; roles swapped */+};++structquic_udp_sock{+structwork_structwork;/* Workqueue to destroy UDP tunnel socket */+structhlist_nodenode;/* Entry in address-based UDP socket hash table */+unionquic_addraddr;/* Source address of underlying UDP tunnel socket */+intbind_ifindex;+refcount_trefcnt;+structsock*sk;/* Underlying UDP tunnel socket */+};++structquic_path{+unionquic_addrdaddr;/* Destination address */+unionquic_addrsaddr;/* Source address */++structquic_udp_sock*udp_sk;/* Wrapped UDP socket used to receive QUIC packets */+/* Cached UDP tunnel socket and its source address for RCU-protected lookup/access */+unionquic_addruaddr;+structsock*usk;+};++structquic_path_group{+/* Connection ID validation during handshake (rfc9000#section-7.3) */+structquic_conn_idretry_dcid;/* Source CID from Retry packet */+structquic_conn_idorig_dcid;/* Destination CID from first Initial */++/* Path validation (rfc9000#section-8.2) */+u8entropy[QUIC_PATH_ENTROPY_LEN];/* Entropy for PATH_CHALLENGE */+structquic_pathpath[2];/* Active path (0) and alternate path (1) */+structflowifl;/* Flow info from routing decisions */++/* Anti-amplification limit (rfc9000#section-8) */+u16ampl_sndlen;/* Bytes sent before address is validated */+u16ampl_rcvlen;/* Bytes received to lift amplification limit */++/* MTU discovery handling */+u32mtu_info;/* PMTU value from received ICMP, pending apply */+struct{/* PLPMTUD probing (rfc8899) */+s64number;/* Packet number used for current probe */+u16pmtu;/* Confirmed path MTU */++u16probe_size;/* Current probe packet size */+u16probe_high;/* Highest failed probe size */+u8probe_count;/* Retry count for current probe_size */+u8state;/* Probe state machine (rfc8899#section-5.2) */+}pl;++u32plpmtud_interval;/* Time interval for the PLPMTUD probe timer */++u8ecn_probes;/* ECN probe counter */+u8validated:1;/* Path validated with PATH_RESPONSE */+u8blocked:1;/* Blocked by anti-amplification limit */+u8retry:1;/* Retry used in initial packet */++/* Connection Migration (rfc9000#section-9) */+u8disable_saddr_alt:1;/* Remote disable_active_migration (rfc9000#section-18.2) */+u8disable_daddr_alt:1;/* Local disable_active_migration (rfc9000#section-18.2) */+u8pref_addr:1;/* Preferred address offered (rfc9000#section-18.2) */+u8alt_probes;/* Number of PATH_CHALLENGE probes sent */+u8alt_state;/* State for alternate path migration logic (see above) */+};++staticinlineunionquic_addr*quic_path_saddr(structquic_path_group*paths,u8path)+{+return&paths->path[path].saddr;+}++staticinlinevoidquic_path_set_saddr(structquic_path_group*paths,u8path,+unionquic_addr*addr)+{+memcpy(quic_path_saddr(paths,path),addr,sizeof(*addr));+}++staticinlineunionquic_addr*quic_path_daddr(structquic_path_group*paths,u8path)+{+return&paths->path[path].daddr;+}++staticinlinevoidquic_path_set_daddr(structquic_path_group*paths,u8path,+unionquic_addr*addr)+{+memcpy(quic_path_daddr(paths,path),addr,sizeof(*addr));+}++staticinlineunionquic_addr*quic_path_uaddr(structquic_path_group*paths,u8path)+{+return&paths->path[path].uaddr;+}++staticinlinestructsock*quic_path_usock(structquic_path_group*paths,u8path)+{+returnpaths->path[path].usk;+}++staticinlineboolquic_path_alt_state(structquic_path_group*paths,u8state)+{+returnpaths->alt_state==state;+}++staticinlinevoidquic_path_set_alt_state(structquic_path_group*paths,u8state)+{+paths->alt_state=state;+}++/* Returns the destination Connection ID (DCID) used for identifying the connection.+*Perrfc9000#section-7.3,handshakepacketsareconsideredpartofthesameconnection+*iftheirDCIDmatchestheonereturnedhere.+*/+staticinlinestructquic_conn_id*quic_path_orig_dcid(structquic_path_group*paths)+{+returnpaths->retry?&paths->retry_dcid:&paths->orig_dcid;+}++intquic_path_detect_alt(structquic_path_group*paths,unionquic_addr*sa,unionquic_addr*da,+structsock*sk);+intquic_path_bind(structsock*sk,structquic_path_group*paths,u8path);+voidquic_path_unbind(structsock*sk,structquic_path_group*paths,u8path);+voidquic_path_swap(structquic_path_group*paths);++u32quic_path_pl_recv(structquic_path_group*paths,bool*raise_timer,bool*complete);+u32quic_path_pl_toobig(structquic_path_group*paths,u32pmtu,bool*reset_timer);+u32quic_path_pl_send(structquic_path_group*paths,s64number);++voidquic_path_get_param(structquic_path_group*paths,structquic_transport_param*p);+voidquic_path_set_param(structquic_path_group*paths,structquic_transport_param*p);+boolquic_path_pl_confirm(structquic_path_group*paths,s64largest,s64smallest);+voidquic_path_pl_reset(structquic_path_group*paths);
From: Xin Long <lucien.xin@gmail.com> Date: 2026-02-25 02:35:47
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.
Signed-off-by: Xin Long <lucien.xin@gmail.com>
---
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
From: Xin Long <lucien.xin@gmail.com> Date: 2026-02-25 02:35:49
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.
Signed-off-by: Xin Long <lucien.xin@gmail.com>
Acked-by: Paolo Abeni <pabeni@redhat.com>
---
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
@@ -0,0 +1,225 @@+// SPDX-License-Identifier: GPL-2.0-or-later+/* QUIC kernel implementation+*(C)CopyrightRedHatCorp.2023+*+*ThisfileispartoftheQUICkernelimplementation+*+*Initialization/cleanupforQUICprotocolsupport.+*+*Writtenormodifiedby:+*XinLong<lucien.xin@gmail.com>+*/++#include<linux/slab.h>++#include"common.h"+#include"pnspace.h"++intquic_pnspace_init(structquic_pnspace*space)+{+if(!space->pn_map){+space->pn_map=kzalloc(BITS_TO_BYTES(QUIC_PN_MAP_INITIAL),GFP_KERNEL);+if(!space->pn_map)+return-ENOMEM;+space->pn_map_len=QUIC_PN_MAP_INITIAL;+}else{+bitmap_zero(space->pn_map,space->pn_map_len);+}++space->max_time_limit=QUIC_PNSPACE_TIME_LIMIT;+space->next_pn=QUIC_PNSPACE_NEXT_PN;+space->base_pn=-1;+return0;+}++voidquic_pnspace_free(structquic_pnspace*space)+{+space->pn_map_len=0;+kfree(space->pn_map);+}++/* Expand the bitmap tracking received packet numbers. Ensures the pn_map bitmap can+*coveratleast@sizepacketnumbers.Allocatesalargerbitmap,copiesexisting+*data,andupdatesmetadata.+*+*Returns:1ifthebitmapwassuccessfullygrown,0onfailureoriftherequested+*sizeexceedsQUIC_PN_MAP_SIZE.+*/+staticintquic_pnspace_grow(structquic_pnspace*space,u16size)+{+u16len,inc,offset;+unsignedlong*new;++if(size>QUIC_PN_MAP_SIZE)+return0;++inc=ALIGN((size-space->pn_map_len),BITS_PER_LONG)+QUIC_PN_MAP_INCREMENT;+len=(u16)min(space->pn_map_len+inc,QUIC_PN_MAP_SIZE);++new=kzalloc(BITS_TO_BYTES(len),GFP_ATOMIC);+if(!new)+return0;++offset=(u16)(space->max_pn_seen+1-space->base_pn);+bitmap_copy(new,space->pn_map,offset);+kfree(space->pn_map);+space->pn_map=new;+space->pn_map_len=len;++return1;+}++/* Check if a packet number has been received.+*+*Returns:0ifthepacketnumberhasnotbeenreceived.1ifithasalready+*beenreceived.-1ifthepacketnumberistoooldortoofarinthefuture+*totrack.+*/+intquic_pnspace_check(structquic_pnspace*space,s64pn)+{+if(space->base_pn==-1)/* No packet number received yet. */+return0;++if(pn<space->min_pn_seen||pn>=space->base_pn+QUIC_PN_MAP_SIZE)+return-1;++if(pn<space->base_pn||(pn-space->base_pn<space->pn_map_len&&+test_bit(pn-space->base_pn,space->pn_map)))+return1;++return0;+}++/* Advance base_pn past contiguous received packet numbers. Finds the next gap+*(unreceivedpacket)beyond@pn,shiftsthebitmap,andupdatesbase_pn+*accordingly.+*/+staticvoidquic_pnspace_move(structquic_pnspace*space,s64pn)+{+u16offset;++offset=(u16)(pn+1-space->base_pn);+offset=(u16)find_next_zero_bit(space->pn_map,space->pn_map_len,offset);+space->base_pn+=offset;+bitmap_shift_right(space->pn_map,space->pn_map,offset,space->pn_map_len);+}++/* Mark a packet number as received. Updates the packet number map to record+*receptionof@pn.Advancesbase_pnifpossible,andupdatesmax/min/lastseen+*fieldsasneeded.+*+*Returns:0onsuccessorifthepacketwasalreadymarked.-ENOMEMifbitmap+*allocationfailedduringgrowth.+*/+intquic_pnspace_mark(structquic_pnspace*space,s64pn)+{+s64last_max_pn_seen;+u16gap;++if(space->base_pn==-1){+/* Initialize base_pn based on the peer's first packet number since peer's+*packetnumbersmaystartatanon-zerovalue.+*/+quic_pnspace_set_base_pn(space,pn+1);+return0;+}++/* Ignore packets with number less than current base (already processed). */+if(pn<space->base_pn)+return0;++/* If gap is beyond current map length, try to grow the bitmap to accommodate. */+gap=(u16)(pn-space->base_pn);+if(gap>=space->pn_map_len&&!quic_pnspace_grow(space,gap+1))+return-ENOMEM;++if(space->max_pn_seen<pn){+space->max_pn_seen=pn;+space->max_pn_time=space->time;+}++if(space->base_pn==pn){/* If packet is exactly at base_pn (next expected packet). */+if(quic_pnspace_has_gap(space))/* Advance base_pn to next unacked packet. */+quic_pnspace_move(space,pn);+else/* Fast path: increment base_pn if no gaps. */+space->base_pn++;+}else{/* Mark this packet as received in the bitmap. */+set_bit(gap,space->pn_map);+}++/* Only update min and last_max_pn_seen if this packet is the current max_pn. */+if(space->max_pn_seen!=pn)+return0;++/* Check if enough time has elapsed or enough packets have been received to+*updatetracking.+*/+last_max_pn_seen=min_t(s64,space->last_max_pn_seen,space->base_pn);+if(space->max_pn_time<space->last_max_pn_time+space->max_time_limit&&+space->max_pn_seen<=last_max_pn_seen+QUIC_PN_MAP_LIMIT)+return0;++/* Advance base_pn if last_max_pn_seen is ahead of current base_pn. This is+*neededbecauseQUICdoesn'tretransmitpackets;retransmittedframesare+*carriedinnewpackets,sowemoveforward.+*/+if(space->last_max_pn_seen+1>space->base_pn)+quic_pnspace_move(space,space->last_max_pn_seen);++space->min_pn_seen=space->last_max_pn_seen;+space->last_max_pn_seen=space->max_pn_seen;+space->last_max_pn_time=space->max_pn_time;+return0;+}++/* Find the next gap in received packet numbers. Scans pn_map for a gap starting from+**@iter.Agapisacontiguousblockofunreceivedpacketsbetweenreceivedones.+*+*Returns:1ifagapwasfound,0ifnomoregapsexistorarerelevant.+*/+staticintquic_pnspace_next_gap_ack(conststructquic_pnspace*space,+s64*iter,u16*start,u16*end)+{+u16start_=0,end_=0,offset=(u16)(*iter-space->base_pn);++start_=(u16)find_next_zero_bit(space->pn_map,space->pn_map_len,offset);+if(space->max_pn_seen<=space->base_pn+start_)+return0;++end_=(u16)find_next_bit(space->pn_map,space->pn_map_len,start_);+if(space->max_pn_seen<=space->base_pn+end_-1)+return0;++*start=start_+1;+*end=end_;+*iter=space->base_pn+*end;+return1;+}++/* Generate gap acknowledgment blocks (GABs). GABs describe ranges of unacknowledged+*packetsbetweenreceivedones,andareusedinACKframes.+*+*Returns:NumberofgeneratedGABs(uptoQUIC_PN_MAP_MAX_GABS).+*/+u16quic_pnspace_num_gabs(structquic_pnspace*space,structquic_gap_ack_block*gabs)+{+u16start,end,ngaps=0;+s64iter;++if(!quic_pnspace_has_gap(space))+return0;++iter=space->base_pn;+/* Loop through all gaps until the end of the window or max allowed gaps. */+while(quic_pnspace_next_gap_ack(space,&iter,&start,&end)){+gabs[ngaps].start=start;+if(ngaps==QUIC_PN_MAP_MAX_GABS-1){+gabs[ngaps].end=(u16)(space->max_pn_seen-space->base_pn);+ngaps++;+break;+}+gabs[ngaps].end=end;+ngaps++;+}+returnngaps;+}
@@ -0,0 +1,150 @@+/* SPDX-License-Identifier: GPL-2.0-or-later */+/* QUIC kernel implementation+*(C)CopyrightRedHatCorp.2023+*+*ThisfileispartoftheQUICkernelimplementation+*+*Writtenormodifiedby:+*XinLong<lucien.xin@gmail.com>+*/++#define QUIC_PN_MAP_MAX_GABS 32++#define QUIC_PN_MAP_INITIAL 64+#define QUIC_PN_MAP_INCREMENT QUIC_PN_MAP_INITIAL+#define QUIC_PN_MAP_SIZE 4096+#define QUIC_PN_MAP_LIMIT (QUIC_PN_MAP_SIZE * 3 / 4)++#define QUIC_PNSPACE_MAX (QUIC_CRYPTO_MAX - 1)+#define QUIC_PNSPACE_NEXT_PN 0+#define QUIC_PNSPACE_TIME_LIMIT (333000 * 3)++enum{+QUIC_ECN_ECT1,+QUIC_ECN_ECT0,+QUIC_ECN_CE,+QUIC_ECN_MAX+};++enum{+QUIC_ECN_LOCAL,/* ECN bits from incoming IP headers */+QUIC_ECN_PEER,/* ECN bits reported by peer in ACK frames */+QUIC_ECN_DIR_MAX+};++/* Represents a gap (range of missing packets) in the ACK map. The values are offsets from+*base_pn,withboth'start'and'end'being+1.+*/+structquic_gap_ack_block{+u16start;+u16end;+};++/* Packet Number Map (pn_map) Layout:+*+*min_pn_seen-->++-----------------------+---------------------+---+*base_pn-----^last_max_pn_seen--^max_pn_seen--^+*+*MapAdvancementLogic:+*-min_pn_seen=last_max_pn_seen;+*-base_pn=firstzerobitafterlast_max_pn_seen;+*-last_max_pn_seen=max_pn_seen;+*-last_max_pn_time=currenttime;+*+*ConditionstoAdvancepn_map:+*-(max_pn_time-last_max_pn_time)>=max_time_limit,or+*-(max_pn_seen-last_max_pn_seen)>QUIC_PN_MAP_LIMIT+*+*GapSearchRange:+*-From(base_pn-1)tomax_pn_seen+*/+structquic_pnspace{+/* ECN counters indexed by direction (TX/RX) and ECN codepoint (ECT1, ECT0, CE) */+u64ecn_count[QUIC_ECN_DIR_MAX][QUIC_ECN_MAX];+unsignedlong*pn_map;/* Bit map tracking received packet numbers for ACK generation */+u16pn_map_len;/* Length of the packet number bit map (in bits) */+u8need_sack;/* Flag indicating a SACK frame should be sent for this space */+u8sack_path;/* Path used for sending the SACK frame */++s64last_max_pn_seen;/* Highest packet number seen before pn_map advanced */+u64last_max_pn_time;/* Timestamp when last_max_pn_seen was received */+s64min_pn_seen;/* Smallest packet number received in this space */+s64max_pn_seen;/* Largest packet number received in this space */+u64max_pn_time;/* Timestamp when max_pn_seen was received */+s64base_pn;/* Packet number corresponding to the start of the pn_map */+u64time;/* Cached current timestamp, or latest socket accept timestamp */++s64max_pn_acked_seen;/* Largest packet number acknowledged by the peer */+u64max_pn_acked_time;/* Timestamp when max_pn_acked_seen was acknowledged */+u64last_sent_time;/* Timestamp when the last ack-eliciting packet was sent */+u64loss_time;/* Timestamp after which the next packet can be declared lost */+s64next_pn;/* Next packet number to send in this space */++u32max_time_limit;/* Time threshold to trigger pn_map advancement on packet receipt */+u32inflight;/* Bytes of all ack-eliciting frames in flight in this space */+};++staticinlinevoidquic_pnspace_set_max_pn_acked_seen(structquic_pnspace*space,+s64max_pn_acked_seen)+{+if(space->max_pn_acked_seen>=max_pn_acked_seen)+return;+space->max_pn_acked_seen=max_pn_acked_seen;+space->max_pn_acked_time=quic_ktime_get_us();+}++staticinlinevoidquic_pnspace_set_base_pn(structquic_pnspace*space,s64pn)+{+space->base_pn=pn;+space->max_pn_seen=space->base_pn-1;+space->last_max_pn_seen=space->max_pn_seen;+space->min_pn_seen=space->max_pn_seen;++space->max_pn_time=space->time;+space->last_max_pn_time=space->max_pn_time;+}++staticinlineboolquic_pnspace_has_gap(conststructquic_pnspace*space)+{+returnspace->base_pn!=space->max_pn_seen+1;+}++staticinlinevoidquic_pnspace_inc_ecn_count(structquic_pnspace*space,u8ecn)+{+if(!ecn)+return;+space->ecn_count[QUIC_ECN_LOCAL][ecn-1]++;+}++/* Check if any ECN-marked packets were received. */+staticinlineboolquic_pnspace_has_ecn_count(structquic_pnspace*space)+{+returnspace->ecn_count[QUIC_ECN_LOCAL][QUIC_ECN_ECT0]||+space->ecn_count[QUIC_ECN_LOCAL][QUIC_ECN_ECT1]||+space->ecn_count[QUIC_ECN_LOCAL][QUIC_ECN_CE];+}++/* Updates the stored ECN counters based on values received in the peer's ACK+*frame.Eachcounterisupdatedonlyifthenewvalueishigher.+*+*Returns:1ifCEcountwasincreased(congestionindicated),0otherwise.+*/+staticinlineintquic_pnspace_set_ecn_count(structquic_pnspace*space,u64*ecn_count)+{+if(space->ecn_count[QUIC_ECN_PEER][QUIC_ECN_ECT0]<ecn_count[QUIC_ECN_ECT0])+space->ecn_count[QUIC_ECN_PEER][QUIC_ECN_ECT0]=ecn_count[QUIC_ECN_ECT0];+if(space->ecn_count[QUIC_ECN_PEER][QUIC_ECN_ECT1]<ecn_count[QUIC_ECN_ECT1])+space->ecn_count[QUIC_ECN_PEER][QUIC_ECN_ECT1]=ecn_count[QUIC_ECN_ECT1];+if(space->ecn_count[QUIC_ECN_PEER][QUIC_ECN_CE]<ecn_count[QUIC_ECN_CE]){+space->ecn_count[QUIC_ECN_PEER][QUIC_ECN_CE]=ecn_count[QUIC_ECN_CE];+return1;+}+return0;+}++u16quic_pnspace_num_gabs(structquic_pnspace*space,structquic_gap_ack_block*gabs);+intquic_pnspace_check(structquic_pnspace*space,s64pn);+intquic_pnspace_mark(structquic_pnspace*space,s64pn);++voidquic_pnspace_free(structquic_pnspace*space);+intquic_pnspace_init(structquic_pnspace*space);
From: Xin Long <lucien.xin@gmail.com> Date: 2026-02-25 02:35:51
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.
Signed-off-by: Pengtao He <redacted>
Signed-off-by: Moritz Buhl <redacted>
Signed-off-by: Xin Long <lucien.xin@gmail.com>
---
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
@@ -0,0 +1,73 @@+/* SPDX-License-Identifier: GPL-2.0-or-later */+/* QUIC kernel implementation+*(C)CopyrightRedHatCorp.2023+*+*ThisfileispartoftheQUICkernelimplementation+*+*Writtenormodifiedby:+*XinLong<lucien.xin@gmail.com>+*/++#define QUIC_TAG_LEN 16+#define QUIC_IV_LEN 12+#define QUIC_KEY_LEN 32+#define QUIC_SECRET_LEN 48++#define QUIC_TOKEN_FLAG_REGULAR 0+#define QUIC_TOKEN_FLAG_RETRY 1+#define QUIC_TOKEN_TIMEOUT_RETRY 3000000+#define QUIC_TOKEN_TIMEOUT_REGULAR 600000000++structquic_cipher{+u32secretlen;/* Length of the traffic secret */+u32keylen;/* Length of the AEAD key */++char*shash;/* Name of hash algorithm used for key derivation */+char*aead;/* Name of AEAD algorithm used for payload en/decryption */+char*skc;/* Name of cipher algorithm used for header protection */+};++structquic_crypto{+structcrypto_skcipher*tx_hp_tfm;/* Transform for TX header protection */+structcrypto_skcipher*rx_hp_tfm;/* Transform for RX header protection */+structcrypto_shash*secret_tfm;/* Transform for key derivation (HKDF) */+structcrypto_aead*tx_tfm[2];/* AEAD transform for TX (key phase 0 and 1) */+structcrypto_aead*rx_tfm[2];/* AEAD transform for RX (key phase 0 and 1) */+structcrypto_aead*tag_tfm;/* AEAD transform used for Retry token validation */+structquic_cipher*cipher;/* Cipher information (selected cipher suite) */+u32cipher_type;/* Cipher suite (e.g., AES_GCM_128, etc.) */++u8tx_secret[QUIC_SECRET_LEN];/* TX secret derived or provided by user space */+u8rx_secret[QUIC_SECRET_LEN];/* RX secret derived or provided by user space */+u8tx_iv[2][QUIC_IV_LEN];/* IVs for TX (key phase 0 and 1) */+u8rx_iv[2][QUIC_IV_LEN];/* IVs for RX (key phase 0 and 1) */++u64key_update_send_time;/* Timestamp when 1st packet is sent after key update */+u64key_update_time;/* Timestamp until old keys are retained after key update */+u32version;/* QUIC version in use */++u8ticket_ready:1;/* True if a session ticket is ready to read */+u8key_pending:1;/* A key update is in progress */+u8send_ready:1;/* TX encryption context is initialized */+u8recv_ready:1;/* RX decryption context is initialized */+u8key_phase:1;/* Current key phase being used (0 or 1) */++u64send_offset;/* Number of handshake bytes sent by user at this level */+u64recv_offset;/* Number of handshake bytes read by user at this level */+};++intquic_crypto_set_secret(structquic_crypto*crypto,structquic_crypto_secret*srt,+u32version,u8flag);+intquic_crypto_get_secret(structquic_crypto*crypto,structquic_crypto_secret*srt);+intquic_crypto_set_cipher(structquic_crypto*crypto,u32type,u8flag);+intquic_crypto_key_update(structquic_crypto*crypto);++intquic_crypto_initial_keys_install(structquic_crypto*crypto,structquic_conn_id*conn_id,+u32version,boolis_serv);+intquic_crypto_generate_session_ticket_key(structquic_crypto*crypto,void*data,+u32len,u8*key,u32key_len);+intquic_crypto_generate_stateless_reset_token(structquic_crypto*crypto,void*data,+u32len,u8*key,u32key_len);++voidquic_crypto_free(structquic_crypto*crypto);+voidquic_crypto_init(void);
From: Xin Long <lucien.xin@gmail.com> Date: 2026-02-25 02:35:52
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.
Signed-off-by: Xin Long <lucien.xin@gmail.com>
---
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(+)
@@ -207,6 +207,337 @@ static int quic_crypto_rx_keys_derive_and_install(struct quic_crypto *crypto)returnerr;}+staticvoid*quic_crypto_skcipher_mem_alloc(structcrypto_skcipher*tfm,u32mask_size,+u8**iv,structskcipher_request**req)+{+unsignedintiv_size,req_size;+unsignedintlen;+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)+returnNULL;++*iv=(u8*)PTR_ALIGN(mem+mask_size,crypto_skcipher_alignmask(tfm)+1);+*req=(structskcipher_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. */+staticintquic_crypto_header_encrypt(structcrypto_skcipher*tfm,structsk_buff*skb,boolchacha)+{+structquic_skb_cb*cb=QUIC_SKB_CB(skb);+structskcipher_request*req;+structscatterlistsg;+u8*mask,*iv,*p;+interr,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_offsetisthestartofthePacketNumberfield.+*sample_offset=pn_offset+4+*+*sample=packet[sample_offset..sample_offset+sample_length]+*+*rfc9001#section-5.4.3:AES-BasedHeaderProtection:+*+*header_protection(hp_key,sample):+*mask=AES-ECB(hp_key,sample)+*+*rfc9001#section-5.4.4:ChaCha20-BasedHeaderProtection:+*+*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)+gotoerr;++/* rfc9001#section-5.4.1:+*+*mask=header_protection(hp_key,sample)+*+*pn_length=(packet[0]&0x03)+1+*if(packet[0]&0x80)==0x80:+*#Longheader:4bitsmasked+*packet[0]^=mask[0]&0x0f+*else:+*#Shortheader:5bitsmasked+*packet[0]^=mask[0]&0x1f+*+*#pn_offsetisthestartofthePacketNumberfield.+*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);+returnerr;+}++/* Extracts and reconstructs the packet number from an incoming QUIC packet. */+staticintquic_crypto_get_number(structsk_buff*skb)+{+structquic_skb_cb*cb=QUIC_SKB_CB(skb);+s64number_max=cb->number;+u32len=cb->length;+u8*p;++/* rfc9000#section-17.1:+*+*Onceheaderprotectionisremoved,thepacketnumberisdecodedbyfindingthepacket+*numbervaluethatisclosesttothenextexpectedpacket.Thenextexpectedpacketis+*thehighestreceivedpacketnumberplusone.+*/+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);+return0;+}++staticintquic_crypto_header_decrypt(structcrypto_skcipher*tfm,structsk_buff*skb,boolchacha)+{+structquic_skb_cb*cb=QUIC_SKB_CB(skb);+structskcipher_request*req;+structsk_buff*trailer;+structscatterlistsg;+structquichdr*hdr;+u8*mask,*iv,*p;+interr,i;++err=skb_cow_data(skb,0,&trailer);+if(err<0)+returnerr;++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;+gotoerr;+}++/* 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)+gotoerr;++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);+returnerr;+}++staticvoid*quic_crypto_aead_mem_alloc(structcrypto_aead*tfm,u32ctx_size,+u8**iv,structaead_request**req,+structscatterlist**sg,u32nsg)+{+unsignedintiv_size,req_size;+unsignedintlen;+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__(structscatterlist));+len+=nsg*sizeof(**sg);++mem=kzalloc(len,GFP_ATOMIC);+if(!mem)+returnNULL;++*iv=(u8*)PTR_ALIGN(mem+ctx_size,crypto_aead_alignmask(tfm)+1);+*req=(structaead_request*)PTR_ALIGN(*iv+iv_size,+crypto_tfm_ctx_alignment());+*sg=(structscatterlist*)PTR_ALIGN((u8*)*req+req_size,+__alignof__(structscatterlist));++return(void*)mem;+}++staticvoidquic_crypto_done(void*data,interr)+{+structsk_buff*skb=data;++kfree_sensitive(QUIC_SKB_CB(skb)->crypto_ctx);+QUIC_SKB_CB(skb)->crypto_done(skb,err);+}++/* AEAD Usage. */+staticintquic_crypto_payload_encrypt(structcrypto_aead*tfm,structsk_buff*skb,+u8*tx_iv,boolccm)+{+structquic_skb_cb*cb=QUIC_SKB_CB(skb);+u8*iv,i,nonce[QUIC_IV_LEN];+structaead_request*req;+structsk_buff*trailer;+structscatterlist*sg;+structquichdr*hdr;+u32nsg,hlen,len;+void*ctx;+__be64n;+interr;++len=skb->len;+err=skb_cow_data(skb,QUIC_TAG_LEN,&trailer);+if(err<0)+returnerr;+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)+gotoerr;++/* rfc9001#section-5.3:+*+*Theassociateddata,A,fortheAEADisthecontentsoftheQUICheader,+*startingfromthefirstbyteofeithertheshortorlongheader,uptoand+*includingtheunprotectedpacketnumber.+*+*Thenonce,N,isformedbycombiningthepacketprotectionIVwiththepacket+*number.The62bitsofthereconstructedQUICpacketnumberinnetworkbyte+*orderareleft-paddedwithzerostothesizeoftheIV.TheexclusiveORofthe+*paddedpacketnumberandtheIVformstheAEADnonce.+*/+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));+returnerr;+}++err:+kfree_sensitive(ctx);+memzero_explicit(nonce,sizeof(nonce));+returnerr;+}++staticintquic_crypto_payload_decrypt(structcrypto_aead*tfm,structsk_buff*skb,+u8*rx_iv,boolccm)+{+structquic_skb_cb*cb=QUIC_SKB_CB(skb);+u8*iv,i,nonce[QUIC_IV_LEN];+structaead_request*req;+intnsg,hlen,len,err;+structscatterlist*sg;+void*ctx;+__be64n;++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)+gotoerr;++/* 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));+returnerr;+}+err:+kfree_sensitive(ctx);+memzero_explicit(nonce,sizeof(nonce));+returnerr;+}+#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)"),};+staticboolquic_crypto_is_cipher_ccm(structquic_crypto*crypto)+{+returncrypto->cipher_type==TLS_CIPHER_AES_CCM_128;+}++staticboolquic_crypto_is_cipher_chacha(structquic_crypto*crypto)+{+returncrypto->cipher_type==TLS_CIPHER_CHACHA20_POLY1305;+}++/* Encrypts a QUIC packet before transmission. This function performs AEAD encryption of+*thepacketpayloadandappliesheaderprotection.Ithandleskeyphasetrackingandkey+*updatetiming.+*+*Return:0onsuccess,oranegativeerrorcode.+*/+intquic_crypto_encrypt(structquic_crypto*crypto,structsk_buff*skb)+{+u8*iv,cha,ccm,phase=crypto->key_phase;+structquic_skb_cb*cb=QUIC_SKB_CB(skb);+interr;++cb->key_phase=phase;+iv=crypto->tx_iv[phase];+/* Packet payload is already encrypted (e.g., resumed from async), proceed to header+*protectiononly.+*/+if(cb->resume)+gotoout;++/* If a key update is pending and this is the first packet using the new key, save the+*currenttime.Laterusedtoclearoldkeysaftersometimehaspassed(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)+returnerr;+out:+cha=quic_crypto_is_cipher_chacha(crypto);+returnquic_crypto_header_encrypt(crypto->tx_hp_tfm,skb,cha);+}++/* Decrypts a QUIC packet after reception. This function removes header protection,+*decryptsthepayload,andprocessesanykeyupdatesifthekeyphasebitchanges.+*+*Return:0onsuccess,oranegativeerrorcode.+*/+intquic_crypto_decrypt(structquic_crypto*crypto,structsk_buff*skb)+{+structquic_skb_cb*cb=QUIC_SKB_CB(skb);+u8*iv,cha,ccm,phase;+interr=0;+u64time;++/* Payload was decrypted asynchronously. Proceed with parsing packet number and key+*phase.+*/+if(cb->resume){+err=quic_crypto_get_number(skb);+if(err)+returnerr;+gotoout;+}+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);+returnerr;+}+}++/* rfc9001#section-6:+*+*TheKeyPhasebitallowsarecipienttodetectachangeinkeyingmaterialwithout+*needingtoreceivethefirstpacketthattriggeredthechange.Anendpointthat+*noticesachangedKeyPhasebitupdateskeysanddecryptsthepacketthatcontains+*thechangedvalue.+*/+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;+returnerr;+}+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)+returnerr;+/* When using the old keys can not decrypt the packets, the peer might+*startanotherkey_update.Thus,clearthelastkey_pendingsothat+*nextpacketswilltriggerthenewkey-update.+*/+if(crypto->key_pending&&cb->key_phase!=crypto->key_phase){+crypto->key_pending=0;+crypto->key_update_time=0;+}+returnerr;+}++out:+/* rfc9001#section-6.1:+*+*AnendpointMUSTretainoldkeysuntilithassuccessfullyunprotecteda+*packetsentusingthenewkeys.AnendpointSHOULDretainoldkeysfor+*sometimeafterunprotectingapacketsentusingthenewkeys.+*/+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;+}+}+returnerr;+}+intquic_crypto_set_cipher(structquic_crypto*crypto,u32type,u8flag){structquic_cipher*cipher;
@@ -516,6 +978,210 @@ int quic_crypto_initial_keys_install(struct quic_crypto *crypto, struct quic_conreturnerr;}+#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. */+intquic_crypto_get_retry_tag(structquic_crypto*crypto,structsk_buff*skb,+structquic_conn_id*odcid,u32version,u8*tag)+{+structcrypto_aead*tfm=crypto->tag_tfm;+u8*pseudo_retry,*p,*iv,*key;+structaead_request*req;+structscatterlist*sg;+u32plen;+interr;++/* rfc9001#section-5.8:+*+*TheRetryIntegrityTagisa128-bitfieldthatiscomputedastheoutputof+*AEAD_AES_128_GCMusedwiththefollowinginputs:+*+*-Thesecretkey,K,is128bitsequalto0xbe0c690b9f66575a1d766b54e368c84e.+*-Thenonce,N,is96bitsequalto0x461599d35d632bf2239825bb.+*-Theplaintext,P,isempty.+*-Theassociateddata,A,isthecontentsoftheRetryPseudo-Packet,+*+*TheRetryPseudo-Packetisnotsentoverthewire.Itiscomputedbytakingthe+*transmittedRetrypacket,removingtheRetryIntegrityTag,andprependingthe+*twofollowingfields:ODCIDLength+OriginalDestinationConnectionID(ODCID).+*/+err=crypto_aead_setauthsize(tfm,QUIC_TAG_LEN);+if(err)+returnerr;+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)+returnerr;++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);+returnerr;+}++/* Generate a token for Retry or address validation.+*+*Buildsatokenwiththeformat:[clientaddress][timestamp][originalDCID][authtag]+*+*Encryptsthetoken(excludingthefirstflagbyte)usingAES-GCMwithakeyandIV+*derivedviaHKDF.TheoriginalDCIDisstoredtoberecoveredlaterfromaClient+*Initialpacket.Ensuresthetokenisboundtotheclientaddressandtime,preventing+*reuseortampering.+*+*Returns0onsuccessoranegativeerrorcodeonfailure.+*/+intquic_crypto_generate_token(structquic_crypto*crypto,void*addr,u32addrlen,+structquic_conn_id*conn_id,u8*token,u32*tlen)+{+u8key[TLS_CIPHER_AES_GCM_128_KEY_SIZE],iv[QUIC_IV_LEN];+structcrypto_aead*tfm=crypto->tag_tfm;+u8*retry_token=NULL,*tx_iv,*p;+structquic_datasrt={},k,i;+u64ts=quic_ktime_get_us();+structaead_request*req;+structscatterlist*sg;+interr,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)+gotoout;+err=crypto_aead_setauthsize(tfm,QUIC_TAG_LEN);+if(err)+gotoout;+err=crypto_aead_setkey(tfm,key,TLS_CIPHER_AES_GCM_128_KEY_SIZE);+if(err)+gotoout;+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;+gotoout;+}++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)+gotoout;+memcpy(token,retry_token,len);+*tlen=len+1;+out:+kfree_sensitive(retry_token);+memzero_explicit(key,sizeof(key));+memzero_explicit(iv,sizeof(iv));+returnerr;+}++/* Validate a Retry or address validation token.+*+*DecryptsthetokenusingderivedkeyandIV.Checksthatthedecryptedaddressmatches+*theprovidedaddress,validatestheembeddedtimestampagainstcurrenttimewitha+*version-specifictimeout.Ifapplicable,itextractsandreturnstheoriginal+*destinationconnectionID(ODCID)forRetrypackets.+*+*Returns0ifthetokenisvalid,-EINVALifinvalid,oranothernegativeerrorcode.+*/+intquic_crypto_verify_token(structquic_crypto*crypto,void*addr,u32addrlen,+structquic_conn_id*conn_id,u8*token,u32len)+{+u64t,ts=quic_ktime_get_us(),timeout=QUIC_TOKEN_TIMEOUT_RETRY;+u8key[TLS_CIPHER_AES_GCM_128_KEY_SIZE],iv[QUIC_IV_LEN];+u8*retry_token=NULL,*rx_iv,*p,flag=*token;+structcrypto_aead*tfm=crypto->tag_tfm;+structquic_datasrt={},k,i;+structaead_request*req;+structscatterlist*sg;+interr;++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)+gotoout;+err=crypto_aead_setauthsize(tfm,QUIC_TAG_LEN);+if(err)+gotoout;+err=crypto_aead_setkey(tfm,key,TLS_CIPHER_AES_GCM_128_KEY_SIZE);+if(err)+gotoout;+len--;+token++;+retry_token=quic_crypto_aead_mem_alloc(tfm,len,&rx_iv,&req,&sg,1);+if(!retry_token){+err=-ENOMEM;+gotoout;+}++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)+gotoout;++err=-EINVAL;+p=retry_token;+if(memcmp(p,addr,addrlen))+gotoout;+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)+gotoout;+len-=QUIC_TAG_LEN;+if(len>QUIC_CONN_ID_MAX_LEN)+gotoout;++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));+returnerr;+}+/* Generate a derived key using HKDF-Extract and HKDF-Expand with a given label. */staticintquic_crypto_generate_key(structquic_crypto*crypto,void*data,u32len,char*label,u8*token,u32key_len)
From: Xin Long <lucien.xin@gmail.com> Date: 2026-02-25 02:35:55
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.
Signed-off-by: Xin Long <lucien.xin@gmail.com>
---
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
@@ -0,0 +1,255 @@+// SPDX-License-Identifier: GPL-2.0-or-later+/* QUIC kernel implementation+*(C)CopyrightRedHatCorp.2023+*+*ThisfileispartoftheQUICkernelimplementation+*+*Initialization/cleanupforQUICprotocolsupport.+*+*Writtenormodifiedby:+*XinLong<lucien.xin@gmail.com>+*/++#include"socket.h"++#define QUIC_HLEN 1++/* Make these fixed for easy coding. */+#define QUIC_PACKET_NUMBER_LEN QUIC_PN_MAX_LEN+#define QUIC_PACKET_LENGTH_LEN 4++staticstructsk_buff*quic_packet_handshake_create(structsock*sk)+{+returnNULL;+}++staticintquic_packet_number_check(structsock*sk)+{+return0;+}++staticstructsk_buff*quic_packet_app_create(structsock*sk)+{+returnNULL;+}++/* Update the MSS and inform congestion control. */+voidquic_packet_mss_update(structsock*sk,u32mss)+{+structquic_packet*packet=quic_packet(sk);+structquic_cong*cong=quic_cong(sk);++packet->mss[0]=(u16)mss;+quic_cong_set_mss(cong,packet->mss[0]-packet->taglen[0]);+}++/* Perform routing for the QUIC packet on the specified path, update header length and MSS+*accordingly,resetpathandstartPMTUtimer.+*/+intquic_packet_route(structsock*sk)+{+structquic_path_group*paths=quic_paths(sk);+structquic_packet*packet=quic_packet(sk);+unionquic_addr*sa,*da;+u32pmtu;+interr;++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)+returnerr;++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);+return0;+}++/* Configure the QUIC packet header and routing based on encryption level and path. */+intquic_packet_config(structsock*sk,u8level,u8path)+{+structquic_conn_id_set*dest=quic_dest(sk),*source=quic_source(sk);+structquic_packet*packet=quic_packet(sk);+u32hlen=QUIC_HLEN;++/* If packet already has data, no need to reconfigure. */+if(!quic_packet_empty(packet))+return0;++packet->ack_eliciting=0;+packet->frame_len=0;+packet->ipfragok=0;+packet->padding=0;+packet->frames=0;+hlen+=QUIC_PACKET_NUMBER_LEN;/* Packet number length. */+hlen+=quic_conn_id_choose(dest,path)->len;/* DCID length. */+if(level){+hlen+=1;/* Length byte for DCID. */+hlen+=1+quic_conn_id_active(source)->len;/* Length byte + SCID length. */+if(level==QUIC_CRYPTO_INITIAL)/* Include token for Initial packets. */+hlen+=quic_var_len(quic_token(sk)->len)+quic_token(sk)->len;+hlen+=QUIC_VERSION_LEN;/* Version length. */+hlen+=QUIC_PACKET_LENGTH_LEN;/* Packet length field length. */+/* Allow fragmentation for handshake packets before PLPMTUD probing starts.+*MTUdiscoverydoesnotrelyonICMPPacketTooBigoncePLPMTUDisenabled.+*/+packet->ipfragok=!!quic_paths(sk)->plpmtud_interval;+}+packet->level=level;+packet->len=(u16)hlen;+packet->overhead=(u8)hlen;+DEBUG_NET_WARN_ON_ONCE(hlen>255);++if(packet->path!=path){/* If the path changed, update and reset routing cache. */+packet->path=path;+__sk_dst_reset(sk);+}++/* Perform routing and MSS update for the configured packet. */+if(quic_packet_route(sk)<0)+return-1;+return0;+}++staticvoidquic_packet_encrypt_done(structsk_buff*skb,interr)+{+/* Free it for now, future patches will implement the actual deferred transmission logic. */+kfree_skb(skb);+}++/* Coalescing Packets. */+staticintquic_packet_bundle(structsock*sk,structsk_buff*skb)+{+structquic_skb_cb*head_cb,*cb=QUIC_SKB_CB(skb);+structquic_packet*packet=quic_packet(sk);+structsk_buff*p;++if(!packet->head)/* First packet to bundle: initialize the head. */+gotoinit;++/* If bundling would exceed MSS, flush the current bundle. */+if(packet->head->len+skb->len>=packet->mss[0]){+quic_packet_flush(sk);+gotoinit;+}+/* Bundle it and update metadata for the aggregate skb. */+p=packet->head;+head_cb=QUIC_SKB_CB(p);+if(head_cb->last==p)+skb_shinfo(p)->frag_list=skb;+else+head_cb->last->next=skb;+p->data_len+=skb->len;+p->truesize+=skb->truesize;+p->len+=skb->len;+head_cb->last=skb;+head_cb->ecn|=cb->ecn;/* Merge ECN flags. */++out:+/* rfc9000#section-12.2:+*Packetswithashortheader(Section17.3)donotcontainaLengthfieldandso+*cannotbefollowedbyotherpacketsinthesameUDPdatagram.+*+*soReturn1toflushifitisaShortheaderpacket.+*/+return!cb->level;+init:+packet->head=skb;+cb->last=skb;+gotoout;+}++/* Transmit a QUIC packet, possibly encrypting and bundling it. */+intquic_packet_xmit(structsock*sk,structsk_buff*skb)+{+structquic_packet*packet=quic_packet(sk);+structquic_skb_cb*cb=QUIC_SKB_CB(skb);+structnet*net=sock_net(sk);+interr;++/* Skip encryption if taglen == 0 (e.g., disable_1rtt_encryption). */+if(!packet->taglen[quic_hdr(skb)->form])+gotoxmit;++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));+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);+returnerr;+}+QUIC_INC_STATS(net,QUIC_MIB_PKT_ENCBACKLOGS);+returnerr;+}+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);+return0;+}++/* Create and transmit a new QUIC packet. */+intquic_packet_create_and_xmit(structsock*sk)+{+structquic_packet*packet=quic_packet(sk);+structsk_buff*skb;+interr;++err=quic_packet_number_check(sk);+if(err)+gotoerr;++if(packet->level)+skb=quic_packet_handshake_create(sk);+else+skb=quic_packet_app_create(sk);+if(!skb){+err=-ENOMEM;+gotoerr;+}++err=quic_packet_xmit(sk,skb);+if(err&&err!=-EINPROGRESS)+gotoerr;++/* 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);+return0;+}++/* Flush any coalesced/bundled QUIC packets. */+voidquic_packet_flush(structsock*sk)+{+structquic_path_group*paths=quic_paths(sk);+structquic_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;+}+}++voidquic_packet_init(structsock*sk)+{+structquic_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;++packet->version=QUIC_VERSION_V1;+}
@@ -0,0 +1,109 @@+/* SPDX-License-Identifier: GPL-2.0-or-later */+/* QUIC kernel implementation+*(C)CopyrightRedHatCorp.2023+*+*ThisfileispartoftheQUICkernelimplementation+*+*Writtenormodifiedby:+*XinLong<lucien.xin@gmail.com>+*/++structquic_packet{+structquic_conn_iddcid;/* Dest Connection ID from received packet */+structquic_conn_idscid;/* Source Connection ID from received packet */+unionquic_addrdaddr;/* Dest address from received packet */+unionquic_addrsaddr;/* Source address from received packet */++structlist_headframe_list;/* List of frames to pack into packet for send */+structsk_buff*head;/* Head skb for packet bundling on send */+u16frame_len;/* Length of all ack-eliciting frames excluding PING */+u8taglen[2];/* Tag length for short and long packets */+u32version;/* QUIC version used/selected during handshake */+u8errframe;/* Frame type causing packet processing failure */+u8overhead;/* QUIC header length excluding frames */+u16errcode;/* Error code on packet processing failure */+u16frames;/* Number of ack-eliciting frames excluding PING */+u16mss[2];/* MSS for datagram and non-datagram packets */+u16hlen;/* UDP + IP header length for sending */+u16len;/* QUIC packet length excluding taglen for sending */++u8ack_eliciting:1;/* Packet contains ack-eliciting frames to send */+u8ack_requested:1;/* Packet contains ack-eliciting frames received */+u8ack_immediate:1;/* Send ACK immediately (skip ack_delay timer) */+u8non_probing:1;/* Packet has ack-eliciting frames excluding NEW_CONNECTION_ID */+u8has_sack:1;/* Packet has ACK frames received */+u8ipfragok:1;/* Allow IP fragmentation */+u8padding:1;/* Packet has padding frames */+u8path:1;/* Path identifier used to send this packet */+u8level;/* Encryption level used */+};++#define QUIC_PACKET_INITIAL_V1 0+#define QUIC_PACKET_0RTT_V1 1+#define QUIC_PACKET_HANDSHAKE_V1 2+#define QUIC_PACKET_RETRY_V1 3++#define QUIC_PACKET_INITIAL_V2 1+#define QUIC_PACKET_0RTT_V2 2+#define QUIC_PACKET_HANDSHAKE_V2 3+#define QUIC_PACKET_RETRY_V2 0++#define QUIC_PACKET_INITIAL QUIC_PACKET_INITIAL_V1+#define QUIC_PACKET_0RTT QUIC_PACKET_0RTT_V1+#define QUIC_PACKET_HANDSHAKE QUIC_PACKET_HANDSHAKE_V1+#define QUIC_PACKET_RETRY QUIC_PACKET_RETRY_V1++#define QUIC_PACKET_INVALID 0xff++#define QUIC_VERSION_LEN 4++staticinlineu8quic_packet_taglen(structquic_packet*packet)+{+returnpacket->taglen[!!packet->level];+}++staticinlinevoidquic_packet_set_taglen(structquic_packet*packet,u8taglen)+{+packet->taglen[0]=taglen;+}++staticinlineu32quic_packet_mss(structquic_packet*packet)+{+returnpacket->mss[0]-packet->taglen[!!packet->level];+}++staticinlineu32quic_packet_max_payload(structquic_packet*packet)+{+returnpacket->mss[0]-packet->overhead-packet->taglen[!!packet->level];+}++staticinlineu32quic_packet_max_payload_dgram(structquic_packet*packet)+{+returnpacket->mss[1]-packet->overhead-packet->taglen[!!packet->level];+}++staticinlineintquic_packet_empty(structquic_packet*packet)+{+returnlist_empty(&packet->frame_list);+}++staticinlinevoidquic_packet_reset(structquic_packet*packet)+{+packet->level=0;+packet->errcode=0;+packet->errframe=0;+packet->has_sack=0;+packet->non_probing=0;+packet->ack_requested=0;+packet->ack_immediate=0;+}++intquic_packet_config(structsock*sk,u8level,u8path);++intquic_packet_xmit(structsock*sk,structsk_buff*skb);+intquic_packet_create_and_xmit(structsock*sk);+intquic_packet_route(structsock*sk);++voidquic_packet_mss_update(structsock*sk,u32mss);+voidquic_packet_flush(structsock*sk);+voidquic_packet_init(structsock*sk);
From: Xin Long <lucien.xin@gmail.com> Date: 2026-02-25 02:35:56
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().
Signed-off-by: Xin Long <lucien.xin@gmail.com>
---
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(-)
@@ -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+*Negotiationinrfc9368#section-2.3.+*/+staticu32quic_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(u32version)+{+u8i;++for(i=0;i<QUIC_VERSION_NUM;i++)+if(version==quic_versions[i][0])+returnquic_versions[i];+returnNULL;+}++/* Convert version-specific type to internal standard packet type. */+staticu8quic_packet_version_get_type(u32version,u8type)+{+if(version==QUIC_VERSION_V1)+returntype;++switch(type){+caseQUIC_PACKET_INITIAL_V2:+returnQUIC_PACKET_INITIAL;+caseQUIC_PACKET_0RTT_V2:+returnQUIC_PACKET_0RTT;+caseQUIC_PACKET_HANDSHAKE_V2:+returnQUIC_PACKET_HANDSHAKE;+caseQUIC_PACKET_RETRY_V2:+returnQUIC_PACKET_RETRY;+default:+returnQUIC_PACKET_INVALID;+}+}++/* Parse QUIC version and connection IDs (DCID and SCID) from a Long header packet buffer. */+staticintquic_packet_get_version_and_connid(structquic_conn_id*dcid,structquic_conn_id*scid,+u32*version,u8**pp,u32*plen)+{+u64len,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;+return0;+}++/* Extracts a QUIC token from a buffer in the Client Initial packet. */+staticintquic_packet_get_token(structquic_data*token,u8**pp,u32*plen)+{+u64len;++if(!quic_get_var(pp,plen,&len)||len>*plen)+return-EINVAL;+quic_data(token,*pp,len);+*plen-=len;+*pp+=len;+return0;+}++/* Process PMTU reduction event on a QUIC socket. */+voidquic_packet_rcv_err_pmtu(structsock*sk)+{+structquic_path_group*paths=quic_paths(sk);+structquic_packet*packet=quic_packet(sk);+u32pathmtu,info,taglen;+structdst_entry*dst;+boolreset_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+*notifytheQUICpathlayerforpossiblestatechangesandprobing.+*/+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. */+staticintquic_packet_rcv_err(structsock*sk,structsk_buff*skb)+{+unionquic_addrdaddr,saddr;+u32info;++/* ICMP embeds the original outgoing QUIC packet, so saddr/daddr are reversed when+*parsed.Onlyaddress-basedsocketlookupispossibleinthiscase.+*/+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);+return0;+}++/* 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.Ensurethesocketisnotdroppedbeforedeferral.+*/+if(!test_and_set_bit(QUIC_MTU_REDUCED_DEFERRED,&sk->sk_tsq_flags))+sock_hold(sk);+gotoout;+}+/* Otherwise, process the MTU reduction now. */+quic_packet_rcv_err_pmtu(sk);+out:+bh_unlock_sock(sk);+sock_put(sk);+return1;+}++#define QUIC_PACKET_BACKLOG_MAX 4096++/* Queue a packet for later processing when sleeping is allowed. */+staticintquic_packet_backlog_schedule(structnet*net,structsk_buff*skb)+{+structquic_skb_cb*cb=QUIC_SKB_CB(skb);+structquic_net*qn=quic_net(net);+structsk_buff_head*head;++if(cb->backlog)+return0;++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);+return1;+}++#define TLS_MT_CLIENT_HELLO 1+#define TLS_EXT_alpn 16++/* TLS Client Hello Msg:+*+*uint16ProtocolVersion;+*opaqueRandom[32];+*uint8CipherSuite[2];+*+*struct{+*ExtensionTypeextension_type;+*opaqueextension_data<0..2^16-1>;+*}Extension;+*+*struct{+*ProtocolVersionlegacy_version=0x0303;+*Randomrand;+*opaquelegacy_session_id<0..32>;+*CipherSuitecipher_suites<2..2^16-2>;+*opaquelegacy_compression_methods<1..2^8-1>;+*Extensionextensions<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.+*+*ParsestheTLSClientHellohandshakemessagetofindtheALPN(ApplicationLayerProtocol+*Negotiation)TLSextension.ItvalidatestheTLSClientHellostructure,includingversion,+*random,sessionID,ciphersuites,compressionmethods,andextensions.OncetheALPN+*extensionisfound,theALPNprotocolslistisextractedandstoredin@alpn.+*+*Return:0onsuccessornoALPNfound,anegativeerrorcodeonfailedparsing.+*/+staticintquic_packet_get_alpn(structquic_data*alpn,u8*p,u32len)+{+interr=-EINVAL,found=0,exts=0;+u64length,type;++/* Verify handshake message type (ClientHello) and its length. */+if(!quic_get_int(&p,&len,&type,1)||type!=TLS_MT_CLIENT_HELLO)+returnerr;+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)+returnerr;+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)+returnerr;++/* Skip cipher_suites (2 bytes length + variable data). */+if(!quic_get_int(&p,&len,&length,2)||length>(u64)len)+returnerr;+len-=length;+p+=length;++/* Skip legacy_compression_methods (1 byte length + variable data). */+if(!quic_get_int(&p,&len,&length,1)||length>(u64)len)+returnerr;+len-=length;+p+=length;++if(!quic_get_int(&p,&len,&length,2))/* Read TLS extensions length (2 bytes). */+returnerr;+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. */+return0;+if(type==TLS_EXT_alpn){/* Found ALPN extension. */+if(length>QUIC_ALPN_MAX_LEN)+returnerr;+len=length;+found=1;+break;+}+/* Skip non-ALPN extensions. */+p+=length;+len-=length;+if(exts++>=TLS_MAX_EXTENSIONS)+returnerr;+}+if(!found){/* no ALPN extension found: set alpn->len = 0 and alpn->data = p. */+quic_data(alpn,p,0);+return0;+}++/* Parse ALPN protocols list length (2 bytes). */+if(!quic_get_int(&p,&len,&length,2)||length>(u64)len)+returnerr;+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);+returnerr;+}+len-=length;+p+=length;+}+pr_debug("%s: alpn_len: %d\n",__func__,alpn->len);+return0;+}++#define QUIC_FRAME_CRYPTO 0x06++/* Parse ALPN from a QUIC Initial packet.+*+*ThisfunctionprocessesaQUICInitialpackettoextracttheALPNfromtheTLSClientHello+*messageinsidetheQUICCRYPTOframe.Itverifiespackettype,versioncompatibility,+*decryptsthepacketpayload,andlocatestheCRYPTOframetoparsetheTLSClientHello.+*Finally,itcallsquic_packet_get_alpn()toextracttheALPNextensiondata.+*+*Return:0onsuccessornoALPNfound,anegativeerrorcodeonfailedparsing.+*/+staticintquic_packet_parse_alpn(structsk_buff*skb,structquic_data*alpn)+{+structquic_skb_cb*cb=QUIC_SKB_CB(skb);+structnet*net=sock_net(skb->sk);+structquic_conn_iddcid,scid;+u32len=skb->len,version;+structquic_crypto*crypto;+u8*p=skb->data,type;+structquic_datatoken;+u64offset,length;+interr=-EINVAL;++if(!static_branch_unlikely(&quic_alpn_demux_key))+return0;+if(quic_packet_get_version_and_connid(&dcid,&scid,&version,&p,&len))+returnerr;+if(!quic_packet_compatible_versions(version))+return0;+/* Only parse Initial packets. */+type=quic_packet_version_get_type(version,quic_hshdr(skb)->type);+if(type!=QUIC_PACKET_INITIAL)+return0;+if(quic_packet_get_token(&token,&p,&len))+returnerr;+if(!quic_get_var(&p,&len,&length)||length>(u64)len)+returnerr;+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)+returnerr;+cb->number_offset=(u16)(p-skb->data);+err=quic_crypto_decrypt(crypto,skb);+if(err){+QUIC_INC_STATS(net,QUIC_MIB_PKT_DECDROP);+returnerr;+}++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)+return0;+if(!quic_get_var(&p,&len,&offset)||offset)+return0;+if(!quic_get_var(&p,&len,&length)||length>(u64)len)+return0;++/* Parse the TLS CLIENT_HELLO message. */+returnquic_packet_get_alpn(alpn,p,length);+}++/* Extract the Destination Connection ID (DCID) from a QUIC Long header packet. */+intquic_packet_get_dcid(structquic_conn_id*dcid,structsk_buff*skb)+{+u32plen=skb->len;+u8*p=skb->data;+u64len;++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);+return0;+}++/* Lookup listening socket for Client Initial packet (in process context). */+staticstructsock*quic_packet_get_listen_sock(structsk_buff*skb)+{+unionquic_addrdaddr,saddr;+structquic_dataalpns={};++quic_get_msg_addrs(skb,&daddr,&saddr);++if(quic_packet_parse_alpn(skb,&alpns))+returnNULL;++returnquic_listen_sock_lookup(skb,&daddr,&saddr,&alpns);+}++/* Determine the QUIC socket associated with an incoming packet. */+staticstructsock*quic_packet_get_sock(structsk_buff*skb)+{+structquic_skb_cb*cb=QUIC_SKB_CB(skb);+structnet*net=sock_net(skb->sk);+structquic_conn_iddcid,*conn_id;+unionquic_addrdaddr,saddr;+structquic_dataalpns={};+structsock*sk=NULL;++if(skb->len<QUIC_HLEN)+returnNULL;++if(!quic_hdr(skb)->form){/* Short header path. */+if(skb->len<QUIC_HLEN+QUIC_CONN_ID_DEF_LEN)+returnNULL;+/* Fast path: look up QUIC connection by fixed-length DCID+*(Currently,onlysourceCIDsofsizeQUIC_CONN_ID_DEF_LENareused).+*/+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);+returnquic_conn_id_sk(conn_id);/* Return associated socket. */+}++/* Fallback: listener socket lookup+*(Maybeusedtosendastatelessresetfromalistensocket).+*/+quic_get_msg_addrs(skb,&daddr,&saddr);+sk=quic_listen_sock_lookup(skb,&daddr,&saddr,&alpns);+if(sk)+returnsk;+/* Final fallback: address-based connection lookup+*(Maybeusedtoreceiveastatelessreset).+*/+returnquic_sock_lookup(skb,&daddr,&saddr,skb->sk,NULL);+}++/* Long header path. */+if(quic_packet_get_dcid(&dcid,skb))+returnNULL;+/* 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);+returnquic_conn_id_sk(conn_id);/* Return associated socket. */+}++/* Fallback: address + DCID lookup+*(Maybeusedfor0-RTTorafollow-upClientInitialpacket).+*/+quic_get_msg_addrs(skb,&daddr,&saddr);+sk=quic_sock_lookup(skb,&daddr,&saddr,skb->sk,&dcid);+if(sk)+returnsk;+/* Final fallback: listener socket lookup+*(UsedforreceivingthefirstClientInitialpacket).+*/+if(quic_packet_parse_alpn(skb,&alpns))+returnNULL;+returnquic_listen_sock_lookup(skb,&daddr,&saddr,&alpns);+}++/* Entry point for processing received QUIC packets. */+intquic_packet_rcv(structsock*sk,structsk_buff*skb,u8err)+{+structnet*net=sock_net(sk);++if(unlikely(err))+returnquic_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);+gotoerr;+}++/* Look up socket from socket or connection IDs hash tables. */+sk=quic_packet_get_sock(skb);+if(!sk)+gotoerr;++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);+gotoerr;+}+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);+return0;++err:+kfree_skb(skb);+return-EINVAL;+}++staticintquic_packet_listen_process(structsock*sk,structsk_buff*skb)+{+kfree_skb(skb);+return-EOPNOTSUPP;+}++staticintquic_packet_handshake_process(structsock*sk,structsk_buff*skb)+{+kfree_skb(skb);+return-EOPNOTSUPP;+}++staticintquic_packet_app_process(structsock*sk,structsk_buff*skb)+{+kfree_skb(skb);+return-EOPNOTSUPP;+}++intquic_packet_process(structsock*sk,structsk_buff*skb)+{+if(quic_is_closed(sk)){+kfree_skb(skb);+return0;+}++if(quic_is_listen(sk))+returnquic_packet_listen_process(sk,skb);++if(quic_hdr(skb)->form)+returnquic_packet_handshake_process(sk,skb);++returnquic_packet_app_process(sk,skb);+}++/* Work function to process packets in the backlog queue. */+voidquic_packet_backlog_work(structwork_struct*work)+{+structquic_net*qn=container_of(work,structquic_net,work);+structsk_buff_head*head=&qn->backlog_list;+structsk_buff*skb;+structsock*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
@@ -25,13 +27,13 @@ static int quic_udp_rcv(struct sock *sk, struct sk_buff *skb)skb_pull(skb,sizeof(structudphdr));skb_dst_force(skb);-kfree_skb(skb);+quic_packet_rcv(sk,skb,0);return0;/* .encap_rcv must return 0 if skb was either consumed or dropped. */}staticintquic_udp_err(structsock*sk,structsk_buff*skb){-return0;+returnquic_packet_rcv(sk,skb,1);}staticvoidquic_udp_sock_put_work(structwork_struct*work)
@@ -50,6 +50,10 @@ struct quic_net {structproc_dir_entry*proc_net;/* procfs entry for dumping QUIC socket stats */#endifstructquic_cryptocrypto;/* Context for decrypting Initial packets for ALPN */++/* Queue of packets deferred for processing in process context */+structsk_buff_headbacklog_list;+structwork_structwork;/* Work scheduled to drain and process backlog_list */};structquic_net*quic_net(structnet*net);
@@ -24,6 +24,134 @@ static void quic_enter_memory_pressure(struct sock *sk)WRITE_ONCE(quic_memory_pressure,1);}+/* Lookup a connected QUIC socket based on address and dest connection ID.+*+*Thisfunctionsearchestheestablished(non-listening)QUICsockettableforasocketthat+*matchesthesourceanddestaddressesand,optionally,thedestconnectionID(DCID).The+*valuereturnedbyquic_path_orig_dcid()mightbetheoriginaldestconnectionIDfromthe+*ClientHelloortheSourceConnectionIDfromaRetrypacketbefore.+*+*TheDCIDisprovidedfromahandshakepacketwhensearchingbysourceconnectionIDfails,+*suchaswhenthepeerhasnotyetreceivedserver'sresponseandupdatedtheDCID.+*+*Return:Apointertothematchingconnectedsocket,orNULLifnomatchisfound.+*/+structsock*quic_sock_lookup(structsk_buff*skb,unionquic_addr*sa,unionquic_addr*da,+structsock*usk,structquic_conn_id*dcid)+{+structnet*net=sock_net(usk);+structquic_path_group*paths;+structhlist_nulls_node*node;+structquic_shash_head*head;+structsock*sk=NULL,*tmp;+unsignedinthash;++hash=quic_sock_hash(net,sa,da);+head=quic_sock_head(hash);++rcu_read_lock();+begin:+sk_nulls_for_each_rcu(tmp,node,&head->head){+if(net!=sock_net(tmp))+continue;+paths=quic_paths(tmp);+if(quic_cmp_sk_addr(tmp,quic_path_saddr(paths,0),sa)&&+quic_cmp_sk_addr(tmp,quic_path_daddr(paths,0),da)&&+quic_path_usock(paths,0)==usk&&+(!dcid||!quic_conn_id_cmp(quic_path_orig_dcid(paths),dcid))){+sk=tmp;+break;+}+}+/* If the nulls value we got at the end of the iteration is different from the expected+*one,wemustrestartthelookupasthelistwasmodifiedconcurrently.+*/+if(!sk&&get_nulls_value(node)!=hash)+gotobegin;++if(sk&&unlikely(!refcount_inc_not_zero(&sk->sk_refcnt)))+sk=NULL;+rcu_read_unlock();+returnsk;+}++/* Find the listening QUIC socket for an incoming packet.+*+*ThisfunctionsearchestheQUICsockettableforalisteningsocketthatmatchesthedest+*addressandport,andtheALPN(s)ifpresentedintheClientHello.Ifmultiplelistening+*socketsareboundtothesameaddress,port,andALPN(s)(e.g.,viaSO_REUSEPORT),this+*functionselectsasocketfromthereuseportgroup.+*+*Return:Apointertothematchinglisteningsocket,orNULLifnomatchisfound.+*/+structsock*quic_listen_sock_lookup(structsk_buff*skb,unionquic_addr*sa,unionquic_addr*da,+structquic_data*alpns)+{+structnet*net=sock_net(skb->sk);+structhlist_nulls_node*node;+structsock*sk=NULL,*tmp;+structquic_shash_head*head;+structquic_dataalpn;+unionquic_addr*a;+u32hash,len;+u64length;+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.+*Inthiscase,onlymatchsocketsthathavenoALPNset.+*/+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;+}+}+gotoout;+}++/* 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,wemustrestartthelookupasthelistwasmodifiedconcurrently.+*/+if(!sk&&get_nulls_value(node)!=hash)+gotobegin;++if(sk&&sk->sk_reuseport)+sk=reuseport_select_sock(sk,quic_addr_hash(net,da),skb,1);++if(sk&&unlikely(!refcount_inc_not_zero(&sk->sk_refcnt)))+sk=NULL;+rcu_read_unlock();+returnsk;+}+staticvoidquic_write_space(structsock*sk){structsocket_wq*wq;
From: Xin Long <lucien.xin@gmail.com> Date: 2026-02-25 04:09:58
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
Signed-off-by: Tyler Fanelli <redacted>
Signed-off-by: Stefan Metzmacher <metze@samba.org>
Signed-off-by: Thomas Dreibholz <redacted>
Signed-off-by: Xin Long <lucien.xin@gmail.com>
Acked-by: Paolo Abeni <pabeni@redhat.com>
---
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
@@ -118,10 +132,24 @@ static int quic_setsockopt(struct sock *sk, int level, int optname,returnquic_do_setsockopt(sk,optname,optval,optlen);}-staticintquic_do_getsockopt(structsock*sk,intoptname,sockptr_toptval,sockptr_toptlen)+/**+*quic_do_getsockopt-getaQUICsocketoption+*@sk:sockettoquery+*@optname:optionname(QUIC-level)+*@optval:userbuffertoreceivetheoptionvalue+*@optlen:in/outparameterforbuffersize;updatedwithactuallengthonreturn+*+*GetsaQUICsocketoptionfromagivensocket.+*+*Return:+*-Onsuccess,0isreturned.+*-Onerror,anegativeerrorvalueisreturned.+*/+intquic_do_getsockopt(structsock*sk,intoptname,sockptr_toptval,sockptr_toptlen){return-EOPNOTSUPP;}+EXPORT_SYMBOL_GPL(quic_do_getsockopt);staticintquic_getsockopt(structsock*sk,intlevel,intoptname,char__user*optval,int__user*optlen)
From: Xin Long <lucien.xin@gmail.com> Date: 2026-02-25 04:19:39
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.
Signed-off-by: Xin Long <lucien.xin@gmail.com>
Acked-by: Paolo Abeni <pabeni@redhat.com>
---
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
@@ -0,0 +1,227 @@+// SPDX-License-Identifier: GPL-2.0-or-later+/* QUIC kernel implementation+*(C)CopyrightRedHatCorp.2023+*+*ThisfileispartoftheQUICkernelimplementation+*+*Initialization/cleanupforQUICprotocolsupport.+*+*Writtenormodifiedby:+*XinLong<lucien.xin@gmail.com>+*/++#include<linux/quic.h>+#include<net/sock.h>++#include"common.h"+#include"connid.h"++/* Lookup a source connection ID (scid) in the global source connection ID hash table. */+structquic_conn_id*quic_conn_id_lookup(structnet*net,u8*scid,u32len)+{+structquic_shash_head*head=quic_source_conn_id_head(net,scid,len);+structquic_source_conn_id*s_conn_id;+structquic_conn_id*conn_id=NULL;+structhlist_nulls_node*node;++hlist_nulls_for_each_entry_rcu(s_conn_id,node,&head->head,node){+if(net==sock_net(s_conn_id->sk)&&s_conn_id->common.id.len==len&&+!memcmp(scid,&s_conn_id->common.id.data,s_conn_id->common.id.len)){+if(likely(refcount_inc_not_zero(&s_conn_id->sk->sk_refcnt)))+conn_id=&s_conn_id->common.id;+break;+}+}+returnconn_id;+}++/* Check if a given stateless reset token exists in any connection ID in the connection ID set. */+boolquic_conn_id_token_exists(structquic_conn_id_set*id_set,u8*token)+{+structquic_common_conn_id*common;+structquic_dest_conn_id*dcid;++dcid=(structquic_dest_conn_id*)id_set->active;+if(!memcmp(dcid->token,token,QUIC_CONN_ID_TOKEN_LEN))/* Fast path. */+returntrue;++list_for_each_entry(common,&id_set->head,list){+dcid=(structquic_dest_conn_id*)common;+if(common==id_set->active)+continue;+if(!memcmp(dcid->token,token,QUIC_CONN_ID_TOKEN_LEN))+returntrue;+}+returnfalse;+}++staticvoidquic_source_conn_id_free_rcu(structrcu_head*head)+{+structquic_source_conn_id*s_conn_id;++s_conn_id=container_of(head,structquic_source_conn_id,rcu);+kfree(s_conn_id);+}++staticvoidquic_source_conn_id_free(structquic_source_conn_id*s_conn_id)+{+u8*data=s_conn_id->common.id.data;+u32len=s_conn_id->common.id.len;+structquic_shash_head*head;++if(!hlist_nulls_unhashed(&s_conn_id->node)){+head=quic_source_conn_id_head(sock_net(s_conn_id->sk),data,len);+spin_lock_bh(&head->lock);+hlist_nulls_del_init_rcu(&s_conn_id->node);+spin_unlock_bh(&head->lock);+}++/* Freeing is deferred via RCU to avoid use-after-free during concurrent lookups. */+call_rcu(&s_conn_id->rcu,quic_source_conn_id_free_rcu);+}++staticvoidquic_conn_id_del(structquic_common_conn_id*common)+{+list_del(&common->list);+if(!common->hashed){+kfree(common);+return;+}+quic_source_conn_id_free((structquic_source_conn_id*)common);+}++/* Add a connection ID with sequence number and associated private data to the connection ID set. */+intquic_conn_id_add(structquic_conn_id_set*id_set,+structquic_conn_id*conn_id,u32number,void*data)+{+structquic_source_conn_id*s_conn_id;+structquic_dest_conn_id*d_conn_id;+structquic_common_conn_id*common;+structquic_shash_head*head;+structlist_head*list;++/* Locate insertion point to keep list ordered by number. */+list=&id_set->head;+list_for_each_entry(common,list,list){+if(number==common->number)+return0;/* Ignore if it already exists on the list. */+if(number<common->number){+list=&common->list;+break;+}+}++if(conn_id->len>QUIC_CONN_ID_MAX_LEN)+return-EINVAL;+common=kzalloc(id_set->entry_size,GFP_ATOMIC);+if(!common)+return-ENOMEM;+common->id=*conn_id;+common->number=number;+if(id_set->entry_size==sizeof(structquic_dest_conn_id)){+/* For destination connection IDs, copy the stateless reset token if available. */+if(data){+d_conn_id=(structquic_dest_conn_id*)common;+memcpy(d_conn_id->token,data,QUIC_CONN_ID_TOKEN_LEN);+}+}else{+/* For source connection IDs, mark as hashed and insert into the global source+*connectionIDhashtable.+*/+common->hashed=1;+s_conn_id=(structquic_source_conn_id*)common;+s_conn_id->sk=data;++head=quic_source_conn_id_head(sock_net(s_conn_id->sk),common->id.data,+common->id.len);+spin_lock_bh(&head->lock);+hlist_nulls_add_head_rcu(&s_conn_id->node,&head->head);+spin_unlock_bh(&head->lock);+}+list_add_tail(&common->list,list);++if(number==quic_conn_id_last_number(id_set)+1){+if(!id_set->active)+id_set->active=common;+id_set->count++;++/* Increment count for consecutive following IDs. */+list_for_each_entry_continue(common,&id_set->head,list){+if(common->number!=++number)+break;+id_set->count++;+}+}+return0;+}++/* Remove connection IDs from the set with sequence numbers less than or equal to a number.+*ThenumbermustbesmallerthanthesequencenumberofthelastIDintheset.+*/+voidquic_conn_id_remove(structquic_conn_id_set*id_set,u32number)+{+structquic_common_conn_id*common,*tmp;+structlist_head*list;++list=&id_set->head;+list_for_each_entry_safe(common,tmp,list,list){+if(common->number>number)+break;+if(id_set->active==common)+id_set->active=tmp;+quic_conn_id_del(common);+id_set->count--;+}+}++structquic_conn_id*quic_conn_id_find(structquic_conn_id_set*id_set,u32number)+{+structquic_common_conn_id*common;++list_for_each_entry(common,&id_set->head,list){+if(common->number>number)+break;+if(common->number==number)+return&common->id;+}+returnNULL;+}++voidquic_conn_id_update_active(structquic_conn_id_set*id_set,u32number)+{+structquic_conn_id*conn_id;++if(number==id_set->active->number)+return;+conn_id=quic_conn_id_find(id_set,number);+if(!conn_id)+return;+quic_conn_id_set_active(id_set,conn_id);+}++voidquic_conn_id_set_init(structquic_conn_id_set*id_set,boolsource)+{+id_set->entry_size=source?sizeof(structquic_source_conn_id):+sizeof(structquic_dest_conn_id);+INIT_LIST_HEAD(&id_set->head);+}++voidquic_conn_id_set_free(structquic_conn_id_set*id_set)+{+structquic_common_conn_id*common,*tmp;++list_for_each_entry_safe(common,tmp,&id_set->head,list)+quic_conn_id_del(common);+id_set->count=0;+id_set->active=NULL;+}++voidquic_conn_id_get_param(structquic_conn_id_set*id_set,structquic_transport_param*p)+{+p->active_connection_id_limit=id_set->max_count;+}++voidquic_conn_id_set_param(structquic_conn_id_set*id_set,structquic_transport_param*p)+{+id_set->max_count=p->active_connection_id_limit;+}
@@ -0,0 +1,163 @@+/* SPDX-License-Identifier: GPL-2.0-or-later */+/* QUIC kernel implementation+*(C)CopyrightRedHatCorp.2023+*+*ThisfileispartoftheQUICkernelimplementation+*+*Writtenormodifiedby:+*XinLong<lucien.xin@gmail.com>+*/++#define QUIC_CONN_ID_LIMIT 8+#define QUIC_CONN_ID_DEF 7+#define QUIC_CONN_ID_LEAST 2++#define QUIC_CONN_ID_TOKEN_LEN 16++/* Common fields shared by both source and destination Connection IDs */+structquic_common_conn_id{+structquic_conn_idid;/* The actual Connection ID value and its length */+structlist_headlist;/* Linked list node for conn_id list management */+u32number;/* Sequence number assigned to this Connection ID */+u8hashed;/* Non-zero if this ID is stored in source_conn_id hashtable */+};++structquic_source_conn_id{+structquic_common_conn_idcommon;+structhlist_nulls_nodenode;/* Hash table node for fast lookup by Connection ID */+structrcu_headrcu;/* RCU header for deferred destruction */+structsock*sk;/* Pointer to sk associated with this Connection ID */+};++structquic_dest_conn_id{+structquic_common_conn_idcommon;+u8token[QUIC_CONN_ID_TOKEN_LEN];/* Stateless reset token in rfc9000#section-10.3 */+};++structquic_conn_id_set{+/* Connection ID in use on the current path */+structquic_common_conn_id*active;+/* Connection ID to use for a new path (e.g., after migration) */+structquic_common_conn_id*alt;+structlist_headhead;/* Head of the linked list of available connection IDs */+u8entry_size;/* Size of each connection ID entry (in bytes) in the list */+u8max_count;/* active_connection_id_limit in rfc9000#section-18.2 */+u8count;/* Current number of connection IDs in the list */+};++staticinlineu32quic_conn_id_first_number(structquic_conn_id_set*id_set)+{+structquic_common_conn_id*common;++/* The id_set is guaranteed to be non-empty when called (sk is not in CLOSE state). */+common=list_first_entry(&id_set->head,structquic_common_conn_id,list);+returncommon->number;+}++staticinlineu32quic_conn_id_last_number(structquic_conn_id_set*id_set)+{+returnquic_conn_id_first_number(id_set)+id_set->count-1;+}++staticinlinevoidquic_conn_id_generate(structquic_conn_id*conn_id)+{+get_random_bytes(conn_id->data,QUIC_CONN_ID_DEF_LEN);+conn_id->len=QUIC_CONN_ID_DEF_LEN;+}++/* Select an alternate destination Connection ID for a new path (e.g., after migration). */+staticinlineboolquic_conn_id_select_alt(structquic_conn_id_set*id_set,boolactive)+{+if(id_set->alt)+returntrue;+/* NAT rebinding: peer keeps using the current source conn_id.+*Inthiscase,continueusingthesamedestconn_idforthenewpath.+*/+if(active){+id_set->alt=id_set->active;+returntrue;+}+/* Treat the prev conn_ids as used.+*Tryselectingthenextconn_idinthelist,unlessattheend.+*/+if(id_set->active->number!=quic_conn_id_last_number(id_set)){+id_set->alt=list_next_entry(id_set->active,list);+returntrue;+}+/* If there's only one conn_id in the list, reuse the active one. */+if(id_set->active->number==quic_conn_id_first_number(id_set)){+id_set->alt=id_set->active;+returntrue;+}+/* No alternate conn_id could be selected. Caller should send a+*QUIC_FRAME_RETIRE_CONNECTION_IDframetorequestnewconnectionIDsfromthepeer.+*/+returnfalse;+}++staticinlinevoidquic_conn_id_set_alt(structquic_conn_id_set*id_set,structquic_conn_id*alt)+{+id_set->alt=(structquic_common_conn_id*)alt;+}++/* Swap the active and alternate destination Connection IDs after path migration completes,+*sincethepathhasalreadybeenswitchedaccordingly.+*/+staticinlinevoidquic_conn_id_swap_active(structquic_conn_id_set*id_set)+{+void*active=id_set->active;++id_set->active=id_set->alt;+id_set->alt=active;+}++/* Choose which destination Connection ID to use for a new path migration if alt is true. */+staticinlinestructquic_conn_id*quic_conn_id_choose(structquic_conn_id_set*id_set,u8alt)+{+return(alt&&id_set->alt)?&id_set->alt->id:&id_set->active->id;+}++staticinlinestructquic_conn_id*quic_conn_id_active(structquic_conn_id_set*id_set)+{+return&id_set->active->id;+}++staticinlinevoidquic_conn_id_set_active(structquic_conn_id_set*id_set,+structquic_conn_id*active)+{+id_set->active=(structquic_common_conn_id*)active;+}++staticinlineu32quic_conn_id_number(structquic_conn_id*conn_id)+{+return((structquic_common_conn_id*)conn_id)->number;+}++staticinlinestructsock*quic_conn_id_sk(structquic_conn_id*conn_id)+{+return((structquic_source_conn_id*)conn_id)->sk;+}++staticinlinevoidquic_conn_id_set_token(structquic_conn_id*conn_id,u8*token)+{+memcpy(((structquic_dest_conn_id*)conn_id)->token,token,QUIC_CONN_ID_TOKEN_LEN);+}++staticinlineintquic_conn_id_cmp(structquic_conn_id*a,structquic_conn_id*b)+{+returna->len!=b->len||memcmp(a->data,b->data,a->len);+}++intquic_conn_id_add(structquic_conn_id_set*id_set,structquic_conn_id*conn_id,+u32number,void*data);+boolquic_conn_id_token_exists(structquic_conn_id_set*id_set,u8*token);+voidquic_conn_id_remove(structquic_conn_id_set*id_set,u32number);++structquic_conn_id*quic_conn_id_find(structquic_conn_id_set*id_set,u32number);+structquic_conn_id*quic_conn_id_lookup(structnet*net,u8*scid,u32len);+voidquic_conn_id_update_active(structquic_conn_id_set*id_set,u32number);++voidquic_conn_id_get_param(structquic_conn_id_set*id_set,structquic_transport_param*p);+voidquic_conn_id_set_param(structquic_conn_id_set*id_set,structquic_transport_param*p);+voidquic_conn_id_set_init(structquic_conn_id_set*id_set,boolsource);+voidquic_conn_id_set_free(structquic_conn_id_set*id_set);
From: Xin Long <lucien.xin@gmail.com> Date: 2026-02-25 04:23:16
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.
Signed-off-by: Xin Long <lucien.xin@gmail.com>
Acked-by: Paolo Abeni <pabeni@redhat.com>
---
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
@@ -113,7 +113,7 @@ static int quic_setsockopt(struct sock *sk, int level, int optname,sockptr_toptval,unsignedintoptlen){if(level!=SOL_QUIC)-return-EOPNOTSUPP;+returnquic_common_setsockopt(sk,level,optname,optval,optlen);returnquic_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;+returnquic_common_getsockopt(sk,level,optname,optval,optlen);returnquic_do_getsockopt(sk,optname,USER_SOCKPTR(optval),USER_SOCKPTR(optlen));}
From: Xin Long <lucien.xin@gmail.com> Date: 2026-02-25 04:23:55
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.
Signed-off-by: Pengtao He <redacted>
Signed-off-by: Xin Long <lucien.xin@gmail.com>
Acked-by: Paolo Abeni <pabeni@redhat.com>
---
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
@@ -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/*`` ========================
@@ -0,0 +1,369 @@+// SPDX-License-Identifier: GPL-2.0-or-later+/* QUIC kernel implementation+*(C)CopyrightRedHatCorp.2023+*+*ThisfileispartoftheQUICkernelimplementation+*+*Initialization/cleanupforQUICprotocolsupport.+*+*Writtenormodifiedby:+*XinLong<lucien.xin@gmail.com>+*/++#include<net/inet_common.h>+#include<linux/proc_fs.h>+#include<net/protocol.h>+#include<net/rps.h>+#include<net/tls.h>++#include"socket.h"++staticunsignedintquic_net_id__read_mostly;++structpercpu_counterquic_sockets_allocated;++DEFINE_STATIC_KEY_FALSE(quic_alpn_demux_key);++longsysctl_quic_mem[3];+intsysctl_quic_rmem[3];+intsysctl_quic_wmem[3];++staticintquic_inet_connect(structsocket*sock,structsockaddr_unsized*addr,intaddr_len,+intflags)+{+structsock*sk=sock->sk;++if(addr_len<(int)sizeof(addr->sa_family))+return-EINVAL;++returnsk->sk_prot->connect(sk,addr,addr_len);+}++staticintquic_inet_listen(structsocket*sock,intbacklog)+{+return-EOPNOTSUPP;+}++staticintquic_inet_getname(structsocket*sock,structsockaddr*uaddr,intpeer)+{+return-EOPNOTSUPP;+}++static__poll_tquic_inet_poll(structfile*file,structsocket*sock,poll_table*wait)+{+return0;+}++staticstructctl_tablequic_table[]={+{+.procname="quic_mem",+.data=&sysctl_quic_mem,+.maxlen=sizeof(sysctl_quic_mem),+.mode=0644,+.proc_handler=proc_doulongvec_minmax+},+{+.procname="quic_rmem",+.data=&sysctl_quic_rmem,+.maxlen=sizeof(sysctl_quic_rmem),+.mode=0644,+.proc_handler=proc_dointvec,+},+{+.procname="quic_wmem",+.data=&sysctl_quic_wmem,+.maxlen=sizeof(sysctl_quic_wmem),+.mode=0644,+.proc_handler=proc_dointvec,+},+};++structquic_net*quic_net(structnet*net)+{+returnnet_generic(net,quic_net_id);+}++#if IS_ENABLED(CONFIG_PROC_FS)+staticconststructsnmp_mibquic_snmp_list[]={+SNMP_MIB_ITEM("QuicConnCurrentEstabs",QUIC_MIB_CONN_CURRENTESTABS),+SNMP_MIB_ITEM("QuicConnPassiveEstabs",QUIC_MIB_CONN_PASSIVEESTABS),+SNMP_MIB_ITEM("QuicConnActiveEstabs",QUIC_MIB_CONN_ACTIVEESTABS),+SNMP_MIB_ITEM("QuicPktRcvFastpaths",QUIC_MIB_PKT_RCVFASTPATHS),+SNMP_MIB_ITEM("QuicPktDecFastpaths",QUIC_MIB_PKT_DECFASTPATHS),+SNMP_MIB_ITEM("QuicPktEncFastpaths",QUIC_MIB_PKT_ENCFASTPATHS),+SNMP_MIB_ITEM("QuicPktRcvBacklogs",QUIC_MIB_PKT_RCVBACKLOGS),+SNMP_MIB_ITEM("QuicPktDecBacklogs",QUIC_MIB_PKT_DECBACKLOGS),+SNMP_MIB_ITEM("QuicPktEncBacklogs",QUIC_MIB_PKT_ENCBACKLOGS),+SNMP_MIB_ITEM("QuicPktInvHdrDrop",QUIC_MIB_PKT_INVHDRDROP),+SNMP_MIB_ITEM("QuicPktInvNumDrop",QUIC_MIB_PKT_INVNUMDROP),+SNMP_MIB_ITEM("QuicPktInvFrmDrop",QUIC_MIB_PKT_INVFRMDROP),+SNMP_MIB_ITEM("QuicPktRcvDrop",QUIC_MIB_PKT_RCVDROP),+SNMP_MIB_ITEM("QuicPktDecDrop",QUIC_MIB_PKT_DECDROP),+SNMP_MIB_ITEM("QuicPktEncDrop",QUIC_MIB_PKT_ENCDROP),+SNMP_MIB_ITEM("QuicFrmRcvBufDrop",QUIC_MIB_FRM_RCVBUFDROP),+SNMP_MIB_ITEM("QuicFrmRetrans",QUIC_MIB_FRM_RETRANS),+SNMP_MIB_ITEM("QuicFrmOutCloses",QUIC_MIB_FRM_OUTCLOSES),+SNMP_MIB_ITEM("QuicFrmInCloses",QUIC_MIB_FRM_INCLOSES),+};++staticintquic_snmp_seq_show(structseq_file*seq,void*v)+{+unsignedlongbuff[ARRAY_SIZE(quic_snmp_list)];+constintcnt=ARRAY_SIZE(quic_snmp_list);+structnet*net=seq->private;+u32idx;++memset(buff,0,sizeof(buff));++snmp_get_cpu_field_batch_cnt(buff,quic_snmp_list,cnt,quic_net(net)->stat);+for(idx=0;idx<cnt;idx++)+seq_printf(seq,"%-32s\t%ld\n",quic_snmp_list[idx].name,buff[idx]);++return0;+}++staticintquic_net_proc_init(structnet*net)+{+quic_net(net)->proc_net=proc_net_mkdir(net,"quic",net->proc_net);+if(!quic_net(net)->proc_net)+return-ENOMEM;++if(!proc_create_net_single("snmp",0444,quic_net(net)->proc_net,+quic_snmp_seq_show,NULL))+gotofree;+return0;+free:+remove_proc_subtree("quic",net->proc_net);+quic_net(net)->proc_net=NULL;+return-ENOMEM;+}++staticvoidquic_net_proc_exit(structnet*net)+{+remove_proc_subtree("quic",net->proc_net);+quic_net(net)->proc_net=NULL;+}+#endif++staticconststructproto_opsquic_proto_ops={+.family=PF_INET,+.owner=THIS_MODULE,+.release=inet_release,+.bind=inet_bind,+.connect=quic_inet_connect,+.socketpair=sock_no_socketpair,+.accept=inet_accept,+.getname=quic_inet_getname,+.poll=quic_inet_poll,+.ioctl=inet_ioctl,+.gettstamp=sock_gettstamp,+.listen=quic_inet_listen,+.shutdown=inet_shutdown,+.setsockopt=sock_common_setsockopt,+.getsockopt=sock_common_getsockopt,+.sendmsg=inet_sendmsg,+.recvmsg=inet_recvmsg,+.mmap=sock_no_mmap,+};++staticstructinet_protoswquic_stream_protosw={+.type=SOCK_STREAM,+.protocol=IPPROTO_QUIC,+.prot=&quic_prot,+.ops=&quic_proto_ops,+};++staticstructinet_protoswquic_dgram_protosw={+.type=SOCK_DGRAM,+.protocol=IPPROTO_QUIC,+.prot=&quic_prot,+.ops=&quic_proto_ops,+};++staticconststructproto_opsquicv6_proto_ops={+.family=PF_INET6,+.owner=THIS_MODULE,+.release=inet6_release,+.bind=inet6_bind,+.connect=quic_inet_connect,+.socketpair=sock_no_socketpair,+.accept=inet_accept,+.getname=quic_inet_getname,+.poll=quic_inet_poll,+.ioctl=inet6_ioctl,+.gettstamp=sock_gettstamp,+.listen=quic_inet_listen,+.shutdown=inet_shutdown,+.setsockopt=sock_common_setsockopt,+.getsockopt=sock_common_getsockopt,+.sendmsg=inet_sendmsg,+.recvmsg=inet_recvmsg,+.mmap=sock_no_mmap,+};++staticstructinet_protoswquicv6_stream_protosw={+.type=SOCK_STREAM,+.protocol=IPPROTO_QUIC,+.prot=&quicv6_prot,+.ops=&quicv6_proto_ops,+};++staticstructinet_protoswquicv6_dgram_protosw={+.type=SOCK_DGRAM,+.protocol=IPPROTO_QUIC,+.prot=&quicv6_prot,+.ops=&quicv6_proto_ops,+};++staticintquic_protosw_init(void)+{+interr;++err=proto_register(&quic_prot,1);+if(err)+returnerr;++err=proto_register(&quicv6_prot,1);+if(err){+proto_unregister(&quic_prot);+returnerr;+}++inet_register_protosw(&quic_stream_protosw);+inet_register_protosw(&quic_dgram_protosw);+inet6_register_protosw(&quicv6_stream_protosw);+inet6_register_protosw(&quicv6_dgram_protosw);++return0;+}++staticvoidquic_protosw_exit(void)+{+inet_unregister_protosw(&quic_dgram_protosw);+inet_unregister_protosw(&quic_stream_protosw);+proto_unregister(&quic_prot);++inet6_unregister_protosw(&quicv6_dgram_protosw);+inet6_unregister_protosw(&quicv6_stream_protosw);+proto_unregister(&quicv6_prot);+}++staticint__net_initquic_net_init(structnet*net)+{+structquic_net*qn=quic_net(net);+interr=0;++qn->stat=alloc_percpu(structquic_mib);+if(!qn->stat)+return-ENOMEM;++#if IS_ENABLED(CONFIG_PROC_FS)+err=quic_net_proc_init(net);+if(err){+free_percpu(qn->stat);+qn->stat=NULL;+}+#endif+returnerr;+}++staticvoid__net_exitquic_net_exit(structnet*net)+{+structquic_net*qn=quic_net(net);++#if IS_ENABLED(CONFIG_PROC_FS)+quic_net_proc_exit(net);+#endif+free_percpu(qn->stat);+qn->stat=NULL;+}++staticstructpernet_operationsquic_net_ops={+.init=quic_net_init,+.exit=quic_net_exit,+.id=&quic_net_id,+.size=sizeof(structquic_net),+};++#if IS_ENABLED(CONFIG_SYSCTL)+staticstructctl_table_header*quic_sysctl_header;++staticvoidquic_sysctl_register(void)+{+quic_sysctl_header=register_net_sysctl(&init_net,"net/quic",quic_table);+}++staticvoidquic_sysctl_unregister(void)+{+unregister_net_sysctl_table(quic_sysctl_header);+}+#endif++static__initintquic_init(void)+{+intmax_share,err=-ENOMEM;+unsignedlonglimit;++/* Set QUIC memory limits based on available system memory, similar to sctp_init(). */+limit=nr_free_buffer_pages()/8;+limit=max(limit,128UL);+sysctl_quic_mem[0]=(long)limit/4*3;+sysctl_quic_mem[1]=(long)limit;+sysctl_quic_mem[2]=sysctl_quic_mem[0]*2;++limit=(sysctl_quic_mem[1])<<(PAGE_SHIFT-7);+max_share=min(4UL*1024*1024,limit);++sysctl_quic_rmem[0]=PAGE_SIZE;+sysctl_quic_rmem[1]=1024*1024;+sysctl_quic_rmem[2]=max(sysctl_quic_rmem[1],max_share);++sysctl_quic_wmem[0]=PAGE_SIZE;+sysctl_quic_wmem[1]=16*1024;+sysctl_quic_wmem[2]=max(64*1024,max_share);++err=percpu_counter_init(&quic_sockets_allocated,0,GFP_KERNEL);+if(err)+gotoerr_percpu_counter;++err=register_pernet_subsys(&quic_net_ops);+if(err)+gotoerr_def_ops;++err=quic_protosw_init();+if(err)+gotoerr_protosw;++#if IS_ENABLED(CONFIG_SYSCTL)+quic_sysctl_register();+#endif+pr_info("quic: init\n");+return0;++err_protosw:+unregister_pernet_subsys(&quic_net_ops);+err_def_ops:+percpu_counter_destroy(&quic_sockets_allocated);+err_percpu_counter:+returnerr;+}++static__exitvoidquic_exit(void)+{+#if IS_ENABLED(CONFIG_SYSCTL)+quic_sysctl_unregister();+#endif+quic_protosw_exit();+unregister_pernet_subsys(&quic_net_ops);+percpu_counter_destroy(&quic_sockets_allocated);+pr_info("quic: exit\n");+}++module_init(quic_init);+module_exit(quic_exit);++MODULE_ALIAS_NET_PF_PROTO(PF_INET,261);/* IPPROTO_QUIC == 261 */+MODULE_ALIAS_NET_PF_PROTO(PF_INET6,261);+MODULE_AUTHOR("Xin Long <lucien.xin@gmail.com>");+MODULE_DESCRIPTION("Support for the QUIC protocol (RFC9000)");+MODULE_LICENSE("GPL");
@@ -0,0 +1,57 @@+/* SPDX-License-Identifier: GPL-2.0-or-later */+/* QUIC kernel implementation+*(C)CopyrightRedHatCorp.2023+*+*ThisfileispartoftheQUICkernelimplementation+*+*Writtenormodifiedby:+*XinLong<lucien.xin@gmail.com>+*/++externstructpercpu_counterquic_sockets_allocated;++DECLARE_STATIC_KEY_FALSE(quic_alpn_demux_key);++externlongsysctl_quic_mem[3];+externintsysctl_quic_rmem[3];+externintsysctl_quic_wmem[3];++enum{+QUIC_MIB_NUM=0,+QUIC_MIB_CONN_CURRENTESTABS,/* Currently established connections */+QUIC_MIB_CONN_PASSIVEESTABS,/* Connections established passively (server-side accept) */+QUIC_MIB_CONN_ACTIVEESTABS,/* Connections established actively (client-side connect) */+QUIC_MIB_PKT_RCVFASTPATHS,/* Packets received on the fast path */+QUIC_MIB_PKT_DECFASTPATHS,/* Packets successfully decrypted on the fast path */+QUIC_MIB_PKT_ENCFASTPATHS,/* Packets encrypted on the fast path (for transmission) */+QUIC_MIB_PKT_RCVBACKLOGS,/* Packets received via backlog processing */+QUIC_MIB_PKT_DECBACKLOGS,/* Packets decrypted in backlog handler */+QUIC_MIB_PKT_ENCBACKLOGS,/* Packets encrypted in backlog handler */+QUIC_MIB_PKT_INVHDRDROP,/* Packets dropped due to invalid headers */+QUIC_MIB_PKT_INVNUMDROP,/* Packets dropped due to invalid packet numbers */+QUIC_MIB_PKT_INVFRMDROP,/* Packets dropped due to invalid frames */+QUIC_MIB_PKT_RCVDROP,/* Packets dropped on receive (general errors) */+QUIC_MIB_PKT_DECDROP,/* Packets dropped due to decryption failure */+QUIC_MIB_PKT_ENCDROP,/* Packets dropped due to encryption failure */+QUIC_MIB_FRM_RCVBUFDROP,/* Frames dropped due to receive buffer limits */+QUIC_MIB_FRM_RETRANS,/* Frames retransmitted */+QUIC_MIB_FRM_OUTCLOSES,/* Frames of CONNECTION_CLOSE sent */+QUIC_MIB_FRM_INCLOSES,/* Frames of CONNECTION_CLOSE received */+QUIC_MIB_MAX+};++structquic_mib{+unsignedlongmibs[QUIC_MIB_MAX];/* Array of counters indexed by the enum above */+};++structquic_net{+DEFINE_SNMP_STAT(structquic_mib,stat);/* Per-network namespace MIB statistics */+#if IS_ENABLED(CONFIG_PROC_FS)+structproc_dir_entry*proc_net;/* procfs entry for dumping QUIC socket stats */+#endif+};++structquic_net*quic_net(structnet*net);++#define QUIC_INC_STATS(net, field) SNMP_INC_STATS(quic_net(net)->stat, field)+#define QUIC_DEC_STATS(net, field) SNMP_DEC_STATS(quic_net(net)->stat, field)
From: Xin Long <lucien.xin@gmail.com> Date: 2026-02-25 04:28:13
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.
Signed-off-by: Tyler Fanelli <redacted>
Signed-off-by: Xin Long <lucien.xin@gmail.com>
Acked-by: Paolo Abeni <pabeni@redhat.com>
---
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
@@ -191,6 +195,35 @@ static int quic_getsockopt(struct sock *sk, int level, int optname,staticvoidquic_release_cb(structsock*sk){+/* Similar to tcp_release_cb(). */+unsignedlongnflags,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);+}}staticintquic_disconnect(structsock*sk,intflags)
@@ -0,0 +1,196 @@+// SPDX-License-Identifier: GPL-2.0-or-later+/* QUIC kernel implementation+*(C)CopyrightRedHatCorp.2023+*+*ThisfileispartoftheQUICkernelimplementation+*+*Initialization/cleanupforQUICprotocolsupport.+*+*Writtenormodifiedby:+*XinLong<lucien.xin@gmail.com>+*/++#include"socket.h"++voidquic_timer_sack_handler(structsock*sk)+{+}++staticvoidquic_timer_sack_timeout(structtimer_list*t)+{+structquic_sock*qs=container_of(t,structquic_sock,timers[QUIC_TIMER_SACK].t);+structsock*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);+gotoout;+}++quic_timer_sack_handler(sk);+out:+bh_unlock_sock(sk);+sock_put(sk);+}++voidquic_timer_loss_handler(structsock*sk)+{+}++staticvoidquic_timer_loss_timeout(structtimer_list*t)+{+structquic_sock*qs=container_of(t,structquic_sock,timers[QUIC_TIMER_LOSS].t);+structsock*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);+gotoout;+}++quic_timer_loss_handler(sk);+out:+bh_unlock_sock(sk);+sock_put(sk);+}++voidquic_timer_path_handler(structsock*sk)+{+}++staticvoidquic_timer_path_timeout(structtimer_list*t)+{+structquic_sock*qs=container_of(t,structquic_sock,timers[QUIC_TIMER_PATH].t);+structsock*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);+gotoout;+}++quic_timer_path_handler(sk);+out:+bh_unlock_sock(sk);+sock_put(sk);+}++voidquic_timer_reset_path(structsock*sk)+{+structquic_cong*cong=quic_cong(sk);+u64timeout=cong->pto*2;++/* Calculate timeout based on cong.pto, but enforce a lower bound. */+if(timeout<QUIC_MIN_PATH_TIMEOUT)+timeout=QUIC_MIN_PATH_TIMEOUT;+quic_timer_reset(sk,QUIC_TIMER_PATH,timeout);+}++voidquic_timer_pmtu_handler(structsock*sk)+{+}++staticvoidquic_timer_pmtu_timeout(structtimer_list*t)+{+structquic_sock*qs=container_of(t,structquic_sock,timers[QUIC_TIMER_PMTU].t);+structsock*sk=&qs->inet.sk;++bh_lock_sock(sk);+if(sock_owned_by_user(sk)){+if(!test_and_set_bit(QUIC_PMTU_DEFERRED,&sk->sk_tsq_flags))+sock_hold(sk);+gotoout;+}++quic_timer_pmtu_handler(sk);+out:+bh_unlock_sock(sk);+sock_put(sk);+}++voidquic_timer_pace_handler(structsock*sk)+{+}++staticenumhrtimer_restartquic_timer_pace_timeout(structhrtimer*hr)+{+structquic_sock*qs=container_of(hr,structquic_sock,timers[QUIC_TIMER_PACE].hr);+structsock*sk=&qs->inet.sk;++bh_lock_sock(sk);+if(sock_owned_by_user(sk)){+if(!test_and_set_bit(QUIC_PACE_DEFERRED,&sk->sk_tsq_flags))+sock_hold(sk);+gotoout;+}++quic_timer_pace_handler(sk);+out:+bh_unlock_sock(sk);+sock_put(sk);+returnHRTIMER_NORESTART;+}++voidquic_timer_reset(structsock*sk,u8type,u64timeout)+{+structtimer_list*t=quic_timer(sk,type);++if(timeout&&!mod_timer(t,jiffies+usecs_to_jiffies(timeout)))+sock_hold(sk);+}++voidquic_timer_start(structsock*sk,u8type,u64timeout)+{+structtimer_list*t;+structhrtimer*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);+}+}++voidquic_timer_stop(structsock*sk,u8type)+{+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);+}++voidquic_timer_init(structsock*sk)+{+timer_setup(quic_timer(sk,QUIC_TIMER_LOSS),quic_timer_loss_timeout,0);+timer_setup(quic_timer(sk,QUIC_TIMER_SACK),quic_timer_sack_timeout,0);+timer_setup(quic_timer(sk,QUIC_TIMER_PATH),quic_timer_path_timeout,0);+timer_setup(quic_timer(sk,QUIC_TIMER_PMTU),quic_timer_pmtu_timeout,0);+/* Use hrtimer for pace timer, ensuring precise control over send timing. */+hrtimer_setup(quic_timer(sk,QUIC_TIMER_PACE),quic_timer_pace_timeout,+CLOCK_MONOTONIC,HRTIMER_MODE_ABS_PINNED_SOFT);+}++voidquic_timer_free(structsock*sk)+{+quic_timer_stop(sk,QUIC_TIMER_LOSS);+quic_timer_stop(sk,QUIC_TIMER_SACK);+quic_timer_stop(sk,QUIC_TIMER_PATH);+quic_timer_stop(sk,QUIC_TIMER_PMTU);+quic_timer_stop(sk,QUIC_TIMER_PACE);+}
From: Xin Long <lucien.xin@gmail.com> Date: 2026-02-25 04:30:22
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.
Signed-off-by: Xin Long <lucien.xin@gmail.com>
Acked-by: Paolo Abeni <pabeni@redhat.com>
---
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
@@ -0,0 +1,550 @@+// SPDX-License-Identifier: GPL-2.0-or-later+/* QUIC kernel implementation+*(C)CopyrightRedHatCorp.2023+*+*ThisfileispartoftheQUICkernelimplementation+*+*Initialization/cleanupforQUICprotocolsupport.+*+*Writtenormodifiedby:+*XinLong<lucien.xin@gmail.com>+*/++#include<linux/unaligned.h>+#include<net/netns/hash.h>+#include<linux/vmalloc.h>+#include<linux/jhash.h>++#include"common.h"++#define QUIC_VARINT_1BYTE_MAX 0x3fULL+#define QUIC_VARINT_2BYTE_MAX 0x3fffULL+#define QUIC_VARINT_4BYTE_MAX 0x3fffffffULL+#define QUIC_VARINT_8BYTE_MAX 0x3fffffffffffffffULL++#define QUIC_VARINT_2BYTE_PREFIX 0x40+#define QUIC_VARINT_4BYTE_PREFIX 0x80+#define QUIC_VARINT_8BYTE_PREFIX 0xc0++#define QUIC_VARINT_LENGTH(p) BIT((*(p)) >> 6)++structquic_hashinfo{+structquic_shash_tableshash;/* Source connection ID hashtable */+structquic_shash_tablelhash;/* Listening sock hashtable */+structquic_shash_tablechash;/* Connection sock hashtable */+structquic_uhash_tableuhash;/* UDP sock hashtable */+};++staticstructquic_hashinfoquic_hashinfo;++u32quic_sock_hash_size(void)+{+returnquic_hashinfo.chash.size;+}++u32quic_sock_hash(structnet*net,unionquic_addr*s,unionquic_addr*d)+{+u32ports=((__forceu32)s->v4.sin_port)<<16|(__forceu32)d->v4.sin_port;+u32saddr=(s->sa.sa_family==AF_INET6)?jhash(&s->v6.sin6_addr,16,0):+(__forceu32)s->v4.sin_addr.s_addr;+u32daddr=(d->sa.sa_family==AF_INET6)?jhash(&d->v6.sin6_addr,16,0):+(__forceu32)d->v4.sin_addr.s_addr;++returnjhash_3words(saddr,ports,net_hash_mix(net),daddr)&(quic_sock_hash_size()-1);+}++structquic_shash_head*quic_sock_head(u32hash)+{+return&quic_hashinfo.chash.hash[hash];+}++u32quic_listen_sock_hash_size(void)+{+returnquic_hashinfo.lhash.size;+}++u32quic_listen_sock_hash(structnet*net,u16port)+{+returnjhash_2words((__forceu32)port,net_hash_mix(net),0)&+(quic_listen_sock_hash_size()-1);+}++structquic_shash_head*quic_listen_sock_head(u32hash)+{+return&quic_hashinfo.lhash.hash[hash];+}++structquic_shash_head*quic_source_conn_id_head(structnet*net,u8*scid,u32len)+{+structquic_shash_table*ht=&quic_hashinfo.shash;++return&ht->hash[jhash_2words(jhash(scid,len,0),net_hash_mix(net),0)&(ht->size-1)];+}++structquic_uhash_head*quic_udp_sock_head(structnet*net,u16port)+{+structquic_uhash_table*ht=&quic_hashinfo.uhash;++return&ht->hash[jhash_2words((__forceu32)port,net_hash_mix(net),0)&(ht->size-1)];+}++u32quic_addr_hash(structnet*net,unionquic_addr*a)+{+u32addr=(a->sa.sa_family==AF_INET6)?jhash(&a->v6.sin6_addr,16,0):+(__forceu32)a->v4.sin_addr.s_addr;++returnjhash_3words(addr,(__forceu32)a->v4.sin_port,net_hash_mix(net),0);+}++voidquic_hash_tables_destroy(void)+{+vfree(quic_hashinfo.shash.hash);+vfree(quic_hashinfo.lhash.hash);+vfree(quic_hashinfo.chash.hash);+vfree(quic_hashinfo.uhash.hash);+}++staticintquic_shash_table_init(structquic_shash_table*ht,u32size)+{+inti;++ht->hash=vmalloc_array(size,sizeof(structquic_shash_head));+if(!ht->hash)+return-ENOMEM;++ht->size=size;+for(i=0;i<ht->size;i++){+spin_lock_init(&ht->hash[i].lock);+INIT_HLIST_NULLS_HEAD(&ht->hash[i].head,i);+}+return0;+}++staticintquic_uhash_table_init(structquic_uhash_table*ht,u32size)+{+inti;++ht->hash=vmalloc_array(size,sizeof(structquic_uhash_head));+if(!ht->hash)+return-ENOMEM;++ht->size=size;+for(i=0;i<ht->size;i++){+mutex_init(&ht->hash[i].lock);+INIT_HLIST_HEAD(&ht->hash[i].head);+}+return0;+}++intquic_hash_tables_init(void)+{+unsignedlongnr_pages=totalram_pages();+u32limit,size;+interr;++/* Scale hash table size based on system memory, similar to SCTP. */+if(nr_pages>=(128*1024))+limit=nr_pages>>(22-PAGE_SHIFT);+else+limit=nr_pages>>(24-PAGE_SHIFT);++limit=roundup_pow_of_two(limit);++/* Source connection ID table (fast lookup, larger size) */+size=min(limit,64*1024U);+err=quic_shash_table_init(&quic_hashinfo.shash,size);+if(err)+gotoerr;+size=min(limit,16*1024U);+err=quic_shash_table_init(&quic_hashinfo.lhash,size);+if(err)+gotoerr;+err=quic_shash_table_init(&quic_hashinfo.chash,size);+if(err)+gotoerr;+err=quic_uhash_table_init(&quic_hashinfo.uhash,size);+if(err)+gotoerr;+return0;+err:+quic_hash_tables_destroy();+returnerr;+}++/* Returns the number of bytes required to encode a QUIC variable-length integer. */+u8quic_var_len(u64n)+{+if(n<=QUIC_VARINT_1BYTE_MAX)+return1;+if(n<=QUIC_VARINT_2BYTE_MAX)+return2;+if(n<=QUIC_VARINT_4BYTE_MAX)+return4;+return8;+}++/* Decodes a QUIC variable-length integer from a buffer. */+u8quic_get_var(u8**pp,u32*plen,u64*val)+{+u8*p=*pp,len;+u64v=0;++if(!*plen)+return0;++len=QUIC_VARINT_LENGTH(p);+if(*plen<len)+return0;++switch(len){+case1:+v=*p;+break;+case2:+v=get_unaligned_be16(p)&QUIC_VARINT_2BYTE_MAX;+break;+case4:+v=get_unaligned_be32(p)&QUIC_VARINT_4BYTE_MAX;+break;+case8:+v=get_unaligned_be64(p)&QUIC_VARINT_8BYTE_MAX;+break;+default:+return0;+}++*plen-=len;+*pp=p+len;+*val=v;+returnlen;+}++/* Reads a fixed-length integer from the buffer. */+u32quic_get_int(u8**pp,u32*plen,u64*val,u32len)+{+u8*p=*pp;+u64v=0;++if(*plen<len)+return0;++switch(len){+case1:+v=*p;+break;+case2:+v=get_unaligned_be16(p);+break;+case3:+v=get_unaligned_be24(p);+break;+case4:+v=get_unaligned_be32(p);+break;+case8:+v=get_unaligned_be64(p);+break;+default:+return0;+}+*plen-=len;+*pp=p+len;+*val=v;+returnlen;+}++u32quic_get_data(u8**pp,u32*plen,u8*data,u32len)+{+if(*plen<len)+return0;++memcpy(data,*pp,len);+*pp+=len;+*plen-=len;++returnlen;+}++/* Encodes a value into the QUIC variable-length integer format. */+u8*quic_put_var(u8*p,u64num)+{+if(num<=QUIC_VARINT_1BYTE_MAX){+*p++=(u8)num;+returnp;+}+if(num<=QUIC_VARINT_2BYTE_MAX){+put_unaligned_be16((u16)num,p);+*p|=QUIC_VARINT_2BYTE_PREFIX;+returnp+2;+}+if(num<=QUIC_VARINT_4BYTE_MAX){+put_unaligned_be32((u32)num,p);+*p|=QUIC_VARINT_4BYTE_PREFIX;+returnp+4;+}+put_unaligned_be64(num,p);+*p|=QUIC_VARINT_8BYTE_PREFIX;+returnp+8;+}++/* Writes a fixed-length integer to the buffer in network byte order. */+u8*quic_put_int(u8*p,u64num,u8len)+{+switch(len){+case1:+*p++=(u8)num;+returnp;+case2:+put_unaligned_be16((u16)num,p);+returnp+2;+case4:+put_unaligned_be32((u32)num,p);+returnp+4;+case8:+put_unaligned_be64(num,p);+returnp+8;+default:+returnNULL;+}+}++/* Encodes a value as a variable-length integer with explicit length. */+u8*quic_put_varint(u8*p,u64num,u8len)+{+switch(len){+case1:+*p++=(u8)num;+returnp;+case2:+put_unaligned_be16((u16)num,p);+*p|=QUIC_VARINT_2BYTE_PREFIX;+returnp+2;+case4:+put_unaligned_be32((u32)num,p);+*p|=QUIC_VARINT_4BYTE_PREFIX;+returnp+4;+default:+returnNULL;+}+}++u8*quic_put_data(u8*p,u8*data,u32len)+{+if(!len)+returnp;++memcpy(p,data,len);+returnp+len;+}++/* Writes a transport parameter as two varints: ID and value length, followed by value. */+u8*quic_put_param(u8*p,u16id,u64value)+{+p=quic_put_var(p,id);+p=quic_put_var(p,quic_var_len(value));+returnquic_put_var(p,value);+}++/* Reads a QUIC transport parameter value. */+u8quic_get_param(u64*pdest,u8**pp,u32*plen)+{+u64valuelen;++if(!quic_get_var(pp,plen,&valuelen))+return0;++if(*plen<valuelen)+return0;++if(quic_get_var(pp,plen,pdest)!=valuelen)+return0;++return(u8)valuelen;+}++/* rfc9000#section-a.3: DecodePacketNumber()+*+*Reconstructsthefullpacketnumberfromatruncatedone.+*/+s64quic_get_num(s64max_pkt_num,s64pkt_num,u32n)+{+s64expected=max_pkt_num+1;+s64win=BIT_ULL(n*8);+s64hwin=win/2;+s64mask=win-1;+s64cand;++cand=(expected&~mask)|pkt_num;+if(cand<=expected-hwin&&cand<BIT_ULL(QUIC_PN_BITS)-win)+returncand+win;+if(cand>expected+hwin&&cand>=win)+returncand-win;+returncand;+}++intquic_data_dup(structquic_data*to,u8*data,u32len)+{+if(!len)+return0;++data=kmemdup(data,len,GFP_ATOMIC);+if(!data)+return-ENOMEM;++kfree(to->data);+to->data=data;+to->len=len;+return0;+}++intquic_data_append(structquic_data*to,u8*data,u32len)+{+u8*p;++if(!len)+return0;++p=kzalloc(to->len+len,GFP_ATOMIC);+if(!p)+return-ENOMEM;+p=quic_put_data(p,to->data,to->len);+p=quic_put_data(p,data,len);++kfree(to->data);+to->len=to->len+len;+to->data=p-to->len;+return0;+}++/* Check whether 'd2' is equal to any element inside the list 'd1'.+*+*'d1'isassumedtobeasequenceoflength-prefixedelements.Eachelement+*iscomparedto'd2'using'quic_data_cmp()'.+*+*Returns1ifamatchisfound,0otherwise.+*/+intquic_data_has(structquic_data*d1,structquic_data*d2)+{+structquic_datad;+u64length;+u32len;+u8*p;++for(p=d1->data,len=d1->len;len;len-=length,p+=length){+if(!quic_get_int(&p,&len,&length,1)||len<length)+return0;+quic_data(&d,p,length);+if(!quic_data_cmp(&d,d2))+return1;+}+return0;+}++/* Check if any element of 'd1' is present in the list 'd2'.+*+*Iteratesthrougheachelementin'd1',anduses'quic_data_has()'tocheck+*foritspresencein'd2'.+*+*Returns1ifanymatchisfound,0otherwise.+*/+intquic_data_match(structquic_data*d1,structquic_data*d2)+{+structquic_datad;+u64length;+u32len;+u8*p;++for(p=d1->data,len=d1->len;len;len-=length,p+=length){+if(!quic_get_int(&p,&len,&length,1)||len<length)+return0;+quic_data(&d,p,length);+if(quic_data_has(d2,&d))+return1;+}+return0;+}++/* Serialize a list of 'quic_data' elements into a comma-separated string.+*+*Eachelementin'from'islength-prefixed.Thisfunctioncopiestheirraw+*contentintotheoutputbuffer'to',insertingcommasinbetween.The+*resultingstringlengthiswrittento'*plen'.+*/+intquic_data_to_string(u8*to,u32*plen,structquic_data*from)+{+u32remlen=*plen;+structquic_datad;+u8*data=to,*p;+u64length;+u32len;++p=from->data;+len=from->len;+while(len){+if(!quic_get_int(&p,&len,&length,1)||len<length)+return-EINVAL;++quic_data(&d,p,length);+if(d.len>remlen)+return-EOVERFLOW;++data=quic_put_data(data,d.data,d.len);+remlen-=d.len;+p+=d.len;+len-=d.len;+if(len){+if(!remlen)+return-EOVERFLOW;+data=quic_put_int(data,',',1);+remlen--;+}+}+*plen=data-to;+return0;+}++/* Parse a comma-separated string into a 'quic_data' list format.+*+*Eachcomma-separatedtokenisturnedintoalength-prefixedelement.The+*firstbyteofeachelementstoresthelength.Elementsarestoredin+*'to->data',and'to->len'isupdated.+*/+intquic_data_from_string(structquic_data*to,u8*from,u32len)+{+u32remlen=to->len;+structquic_datad;+u8*p=to->data;++to->len=0;+while(len){+while(len&&*from==' '){+from++;+len--;+}+if(!len)+break;+if(!remlen)+return-EOVERFLOW;+d.data=p++;+d.len=0;+remlen--;+while(len){+if(*from==','){+from++;+len--;+break;+}+if(!remlen)+return-EOVERFLOW;+*p++=*from++;+len--;+d.len++;+remlen--;+}+if(d.len>U8_MAX)+return-EINVAL;+*d.data=(u8)(d.len);+to->len+=d.len+1;+}+return0;+}
@@ -0,0 +1,205 @@+/* SPDX-License-Identifier: GPL-2.0-or-later */+/* QUIC kernel implementation+*(C)CopyrightRedHatCorp.2023+*+*ThisfileispartoftheQUICkernelimplementation+*+*Writtenormodifiedby:+*XinLong<lucien.xin@gmail.com>+*/++#include<net/net_namespace.h>++#define QUIC_MAX_ACK_DELAY (16384 * 1000)+#define QUIC_DEF_ACK_DELAY 25000++#define QUIC_STREAM_BIT_FIN 0x01+#define QUIC_STREAM_BIT_LEN 0x02+#define QUIC_STREAM_BIT_OFF 0x04+#define QUIC_STREAM_BIT_MASK 0x08++#define QUIC_CONN_ID_MAX_LEN 20+#define QUIC_CONN_ID_DEF_LEN 8++#define QUIC_PN_MAX_LEN 4 /* For encoded packet number */+#define QUIC_PN_BITS 62+#define QUIC_PN_MAX (BIT_ULL(QUIC_PN_BITS) - 1)++structquic_conn_id{+u8data[QUIC_CONN_ID_MAX_LEN];+u8len;+};++staticinlinevoidquic_conn_id_update(structquic_conn_id*conn_id,u8*data,u32len)+{+/* Caller must ensure len does not exceed QUIC_CONN_ID_MAX_LEN. */+memcpy(conn_id->data,data,len);+conn_id->len=(u8)len;+}++structquic_skb_cb{+/* Callback and temporary context when encryption/decryption completes in async mode */+void(*crypto_done)(structsk_buff*skb,interr);+void*crypto_ctx;+union{+structsk_buff*last;/* Last packet in bundle on TX */+u64time;/* Arrival timestamp in UDP tunnel on RX */+};+s64number;/* Parsed packet number, or the largest previously seen */+u32seqno;/* Dest connection ID number on RX */+u16errcode;/* Error code if encryption/decryption fails */+u16length;/* Payload length + packet number length */++u16number_offset;/* Offset of packet number field */+u8number_len;/* Length of the packet number field */+u8level;/* Encryption level: Initial, Handshake, App, or Early */++u8key_update:1;/* Key update triggered by this packet */+u8key_phase:1;/* Key phase used (0 or 1) */+u8backlog:1;/* Enqueued into backlog list */+u8resume:1;/* Crypto already processed (encrypted or decrypted) */+u8path:1;/* Packet arrived from a new or migrating path */+u8ecn:2;/* ECN marking used on TX */+};++#define QUIC_SKB_CB(skb) ((struct quic_skb_cb *)&((skb)->cb[0]))++structquichdr{+#if defined(__LITTLE_ENDIAN_BITFIELD)+__u8pnl:2,+key:1,+reserved:2,+spin:1,+fixed:1,+form:1;+#elif defined(__BIG_ENDIAN_BITFIELD)+__u8form:1,+fixed:1,+spin:1,+reserved:2,+key:1,+pnl:2;+#endif+};++staticinlinestructquichdr*quic_hdr(structsk_buff*skb)+{+return(structquichdr*)skb->data;+}++structquichshdr{+#if defined(__LITTLE_ENDIAN_BITFIELD)+__u8pnl:2,+reserved:2,+type:2,+fixed:1,+form:1;+#elif defined(__BIG_ENDIAN_BITFIELD)+__u8form:1,+fixed:1,+type:2,+reserved:2,+pnl:2;+#endif+};++staticinlinestructquichshdr*quic_hshdr(structsk_buff*skb)+{+return(structquichshdr*)skb->data;+}++unionquic_addr{+structsockaddr_in6v6;+structsockaddr_inv4;+structsockaddrsa;+};++staticinlineunionquic_addr*quic_addr(constvoid*addr)+{+return(unionquic_addr*)addr;+}++structquic_shash_head{+structhlist_nulls_headhead;+spinlock_tlock;/* Protects 'head' in atomic context */+};++structquic_shash_table{+structquic_shash_head*hash;+u32size;+};++structquic_uhash_head{+structhlist_headhead;+structmutexlock;/* Protects 'head' in process context */+};++structquic_uhash_table{+structquic_uhash_head*hash;+u32size;+};++structquic_data{+u8*data;+u32len;+};++staticinlinestructquic_data*quic_data(structquic_data*d,u8*data,u32len)+{+d->data=data;+d->len=len;+returnd;+}++staticinlineintquic_data_cmp(structquic_data*d1,structquic_data*d2)+{+returnd1->len!=d2->len||memcmp(d1->data,d2->data,d1->len);+}++staticinlinevoidquic_data_free(structquic_data*d)+{+kfree(d->data);+d->data=NULL;+d->len=0;+}++staticinlineu64quic_ktime_get_us(void)+{+returnktime_to_us(ktime_get());+}++u32quic_sock_hash(structnet*net,unionquic_addr*s,unionquic_addr*d);+structquic_shash_head*quic_sock_head(u32hash);+u32quic_sock_hash_size(void);++u32quic_listen_sock_hash(structnet*net,u16port);+structquic_shash_head*quic_listen_sock_head(u32hash);+u32quic_listen_sock_hash_size(void);++structquic_shash_head*quic_source_conn_id_head(structnet*net,u8*scid,u32len);+structquic_uhash_head*quic_udp_sock_head(structnet*net,u16port);+u32quic_addr_hash(structnet*net,unionquic_addr*a);++voidquic_hash_tables_destroy(void);+intquic_hash_tables_init(void);++u32quic_get_data(u8**pp,u32*plen,u8*data,u32len);+u32quic_get_int(u8**pp,u32*plen,u64*val,u32len);+s64quic_get_num(s64max_pkt_num,s64pkt_num,u32n);+u8quic_get_param(u64*pdest,u8**pp,u32*plen);+u8quic_get_var(u8**pp,u32*plen,u64*val);+u8quic_var_len(u64n);++u8*quic_put_param(u8*p,u16id,u64value);+u8*quic_put_data(u8*p,u8*data,u32len);+u8*quic_put_varint(u8*p,u64num,u8len);+u8*quic_put_int(u8*p,u64num,u8len);+u8*quic_put_var(u8*p,u64num);++intquic_data_from_string(structquic_data*to,u8*from,u32len);+intquic_data_to_string(u8*to,u32*plen,structquic_data*from);++intquic_data_match(structquic_data*d1,structquic_data*d2);+intquic_data_append(structquic_data*to,u8*data,u32len);+intquic_data_has(structquic_data*d1,structquic_data*d2);+intquic_data_dup(structquic_data*to,u8*data,u32len);
@@ -304,6 +304,8 @@ static __init int quic_init(void)intmax_share,err=-ENOMEM;unsignedlonglimit;+BUILD_BUG_ON(sizeof(structquic_skb_cb)>sizeof_field(structsk_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)gotoerr_percpu_counter;+err=quic_hash_tables_init();+if(err)+gotoerr_hash;+err=register_pernet_subsys(&quic_net_ops);if(err)gotoerr_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:returnerr;
From: Paolo Abeni <pabeni@redhat.com> Date: 2026-03-03 08:23:09
On 2/25/26 3:34 AM, Xin Long wrote:
+/* Binds a QUIC path to a local port and sets up a UDP socket. */
+int quic_path_bind(struct sock *sk, struct quic_path_group *paths, u8 path)
+{
+ union quic_addr *a = quic_path_saddr(paths, path);
+ int rover, low, high, remaining;
+ struct net *net = sock_net(sk);
+ struct quic_uhash_head *head;
+ struct quic_udp_sock *us;
+ u16 port;
+
+ port = ntohs(a->v4.sin_port);
+ if (port) {
+ head = quic_udp_sock_head(net, port);
+ mutex_lock(&head->lock);
+ us = quic_udp_sock_lookup(sk, a, port);
+ if (us) {
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.
+ if (!quic_udp_sock_get(us)) { /* Releasing in workqueue; retry later. */
+ mutex_unlock(&head->lock);
+ return -EAGAIN;
Why not -EADDRINUSE here?
+ }
+ } 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
[...]
quoted hunk
@@ -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
From: Paolo Abeni <pabeni@redhat.com> Date: 2026-03-03 08:32:41
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.
Signed-off-by: Xin Long <lucien.xin@gmail.com>
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?
[ ... ]
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[]?
[ ... ]
From: Paolo Abeni <pabeni@redhat.com> Date: 2026-03-03 08:33:03
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.
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?
+/* 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) {
+ 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.
[ ... ]
From: Paolo Abeni <pabeni@redhat.com> Date: 2026-03-03 08:33:32
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.
+/* 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);
+}
This function calls quic_crypto_payload_encrypt(). Looking at that
function:
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():
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?
[ ... ]
+ /* 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():
+ 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():
+ /* 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?
[ ... ]
From: Paolo Abeni <pabeni@redhat.com> Date: 2026-03-03 08:33:46
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.
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.
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?
From: Paolo Abeni <pabeni@redhat.com> Date: 2026-03-03 08:33:59
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.
+/* 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)
+{
[ ... ]
+
+ /* 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.
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?
[ ... ]
From: Paolo Abeni <pabeni@redhat.com> Date: 2026-03-03 08:34:11
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.
+/* 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;
+}
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?
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.
From: Paolo Abeni <pabeni@redhat.com> Date: 2026-03-03 09:16:51
On 2/25/26 3:34 AM, Xin Long wrote:
+/* 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)
+ 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
From: Paolo Abeni <pabeni@redhat.com> Date: 2026-03-03 09:19:03
On 2/25/26 3:34 AM, Xin Long wrote:
+/* 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;
+
+ 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.
The magic number above looks quite obscure, and AFAICS looking at struct
quick_packet comments have different meaning. Please use some macro instead.
/P
From: Xin Long <lucien.xin@gmail.com> Date: 2026-03-04 21:26:01
On Tue, Mar 3, 2026 at 3:23 AM Paolo Abeni [off-list ref] wrote:
On 2/25/26 3:34 AM, Xin Long wrote:
quoted
+/* Binds a QUIC path to a local port and sets up a UDP socket. */
+int quic_path_bind(struct sock *sk, struct quic_path_group *paths, u8 path)
+{
+ union quic_addr *a = quic_path_saddr(paths, path);
+ int rover, low, high, remaining;
+ struct net *net = sock_net(sk);
+ struct quic_uhash_head *head;
+ struct quic_udp_sock *us;
+ u16 port;
+
+ port = ntohs(a->v4.sin_port);
+ if (port) {
+ head = quic_udp_sock_head(net, port);
+ mutex_lock(&head->lock);
+ us = quic_udp_sock_lookup(sk, a, port);
+ if (us) {
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
+ 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
+ }
+ } 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
@@ -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?
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.
From: Xin Long <lucien.xin@gmail.com> Date: 2026-03-04 21:42:13
On Tue, Mar 3, 2026 at 3:32 AM Paolo Abeni [off-list ref] wrote:
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.
Signed-off-by: Xin Long <lucien.xin@gmail.com>
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?
I will add a helper quic_reno_handle_packet_lost() for this.
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.
From: Xin Long <lucien.xin@gmail.com> Date: 2026-03-04 21:58:16
On Tue, Mar 3, 2026 at 3:33 AM Paolo Abeni [off-list ref] wrote:
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.
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?
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.
+/* 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) {
+ 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?
From: Xin Long <lucien.xin@gmail.com> Date: 2026-03-04 22:32:11
On Tue, Mar 3, 2026 at 3:33 AM Paolo Abeni [off-list ref] wrote:
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.
+/* 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);
+}
This function calls quic_crypto_payload_encrypt(). Looking at that
function:
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():
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
+ /* 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():
quoted
+ 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():
quoted
+ /* 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?
I will see what I can do to dedup a bit for this.
Thanks.
From: Xin Long <lucien.xin@gmail.com> Date: 2026-03-04 23:03:27
On Tue, Mar 3, 2026 at 3:33 AM Paolo Abeni [off-list ref] wrote:
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.
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.
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.
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?
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.
From: Xin Long <lucien.xin@gmail.com> Date: 2026-03-04 23:13:29
On Tue, Mar 3, 2026 at 3:33 AM Paolo Abeni [off-list ref] wrote:
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.
+/* 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)
+{
[ ... ]
quoted
+
+ /* 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.
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.
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?
From: Xin Long <lucien.xin@gmail.com> Date: 2026-03-04 23:26:57
On Tue, Mar 3, 2026 at 4:19 AM Paolo Abeni [off-list ref] wrote:
On 2/25/26 3:34 AM, Xin Long wrote:
quoted
+/* 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;
+
+ 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().
From: Xin Long <lucien.xin@gmail.com> Date: 2026-03-04 23:38:00
On Tue, Mar 3, 2026 at 3:34 AM Paolo Abeni [off-list ref] wrote:
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.
+/* 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;
+}
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?
No, they work on different forms of QUIC packet, one for short header and
the other for long header packets.
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.
No, let's not have this dependency for now.
Thanks.
From: Xin Long <lucien.xin@gmail.com> Date: 2026-03-05 00:14:56
On Tue, Mar 3, 2026 at 4:16 AM Paolo Abeni [off-list ref] wrote:
On 2/25/26 3:34 AM, Xin Long wrote:
quoted
+/* 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)
+ 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.