Skip to contentSkip to conversation

net: introduce QUIC infrastructure and core subcomponents

PATCH Superseded Xin Long 7 participants 42 messages Aug 18, 2025 – Sep 10, 2025 1 message not in the archive

Subsystems (6)

v2 of 18 Superseded Latest is v18 →

lore
Share & tools
lore

Xin Long

author
#0 lore raw

v2 cover net: introduce QUIC infrastructure and core subcomponents

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

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

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

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

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

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

- Performance Enhancements: Handles all control messages in-kernel to reduce syscall overhead, incorporates zero-copy mechanisms such as sendfile() 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 within the kernel. Rather than using a 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

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 with 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 frame encoder and decoder base
quic: add packet builder and parser base

3. Data Processing (7):

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. Example and Documentation (2):

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

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, which 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/

v2:
- Fix a kernel API description warning found by Jakub in patch 5/15.
- 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 in in patch 5/15.
- Add more checks for addrs in .get_user_addr() and .get_pref_addr() in patch 4/15.
- Consider sk_bound_dev_if in .udp_conf_init() and .flow_route() to support vrf in patch 4/15.
- Clarify the use of offset field with a union type in struct frame in patch 14/15.

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 frame encoder and decoder base
quic: add packet builder and parser base

 include/linux/quic.h      |   19 +
 include/linux/socket.h    |    1 +
 include/uapi/linux/in.h   |    2 +
 include/uapi/linux/quic.h |  236 ++++++++
 net/Kconfig               |    1 +
 net/Makefile              |    1 +
 net/quic/Kconfig          |   35 ++
 net/quic/Makefile         |    9 +
 net/quic/common.c         |  482 +++++++++++++++
 net/quic/common.h         |  219 +++++++
 net/quic/cong.c           |  700 ++++++++++++++++++++++
 net/quic/cong.h           |  120 ++++
 net/quic/connid.c         |  222 +++++++
 net/quic/connid.h         |  162 +++++
 net/quic/crypto.c         | 1200 +++++++++++++++++++++++++++++++++++++
 net/quic/crypto.h         |   83 +++
 net/quic/family.c         |  686 +++++++++++++++++++++
 net/quic/family.h         |   41 ++
 net/quic/frame.c          |  558 +++++++++++++++++
 net/quic/frame.h          |  195 ++++++
 net/quic/packet.c         |  892 +++++++++++++++++++++++++++
 net/quic/packet.h         |  129 ++++
 net/quic/path.c           |  512 ++++++++++++++++
 net/quic/path.h           |  168 ++++++
 net/quic/pnspace.c        |  224 +++++++
 net/quic/pnspace.h        |  150 +++++
 net/quic/protocol.c       |  404 +++++++++++++
 net/quic/protocol.h       |   58 ++
 net/quic/socket.c         |  431 +++++++++++++
 net/quic/socket.h         |  214 +++++++
 net/quic/stream.c         |  549 +++++++++++++++++
 net/quic/stream.h         |  135 +++++
 net/quic/timer.c          |  196 ++++++
 net/quic/timer.h          |   47 ++
 34 files changed, 9081 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/frame.c
 create mode 100644 net/quic/frame.h
 create mode 100644 net/quic/packet.c
 create mode 100644 net/quic/packet.h
 create mode 100644 net/quic/path.c
 create mode 100644 net/quic/path.h
 create mode 100644 net/quic/pnspace.c
 create mode 100644 net/quic/pnspace.h
 create mode 100644 net/quic/protocol.c
 create mode 100644 net/quic/protocol.h
 create mode 100644 net/quic/socket.c
 create mode 100644 net/quic/socket.h
 create mode 100644 net/quic/stream.c
 create mode 100644 net/quic/stream.h
 create mode 100644 net/quic/timer.c
 create mode 100644 net/quic/timer.h
Signature · 1 line
2.47.1
author
#1 lore raw

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

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

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

  socket(AF_INET, SOCK_STREAM, IPPROTO_QUIC);

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

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

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

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

---
 include/linux/socket.h  | 1 +
 include/uapi/linux/in.h | 2 ++
 2 files changed, 3 insertions(+)
include/linux/socket.h +1 -0
index 3b262487ec06..a7c05b064583 100644
@@ -386,6 +386,7 @@ struct ucred {
 #define SOL_MCTP	285
 #define SOL_SMC		286
 #define SOL_VSOCK	287
+#define SOL_QUIC	288
 
 /* IPX options */
 #define IPX_TYPE	1
include/uapi/linux/in.h +2 -0
index ced0fc3c3aa5..34becd90d3a6 100644
@@ -85,6 +85,8 @@ enum {
 #define IPPROTO_RAW		IPPROTO_RAW
   IPPROTO_SMC = 256,		/* Shared Memory Communications		*/
 #define IPPROTO_SMC		IPPROTO_SMC
+  IPPROTO_QUIC = 261,		/* A UDP-Based Multiplexed and Secure Transport	*/
+#define IPPROTO_QUIC		IPPROTO_QUIC
   IPPROTO_MPTCP = 262,		/* Multipath TCP connection		*/
 #define IPPROTO_MPTCP		IPPROTO_MPTCP
   IPPROTO_MAX
Signature · 1 line
2.47.1
#2 lore raw

Re: [PATCH net-next v2 01/15] net: define IPPROTO_QUIC and SOL_QUIC constants

Hi,

Quoted 17 lines
include/linux/socket.h +1 -0
index 3b262487ec06..a7c05b064583 100644
@@ -386,6 +386,7 @@ struct ucred {
  #define SOL_MCTP	285
  #define SOL_SMC		286
  #define SOL_VSOCK	287
+#define SOL_QUIC	288
 
  /* IPX options */
  #define IPX_TYPE	1
include/uapi/linux/in.h +0 -0
index ced0fc3c3aa5..34becd90d3a6 100644

include/uapi/linux/in.h enum { · line 92

Quoted 8 lines
include/uapi/linux/in.h +2 -0
@@ -85,6 +85,8 @@ enum {
  #define IPPROTO_RAW		IPPROTO_RAW
    IPPROTO_SMC = 256,		/* Shared Memory Communications		*/
  #define IPPROTO_SMC		IPPROTO_SMC
+  IPPROTO_QUIC = 261,		/* A UDP-Based Multiplexed and Secure Transport	*/
+#define IPPROTO_QUIC		IPPROTO_QUIC
    IPPROTO_MPTCP = 262,		/* Multipath TCP connection		*/
  #define IPPROTO_MPTCP		IPPROTO_MPTCP
    IPPROTO_MAX

Can these constants be accepted, soon?

Samba 4.23.0 to be released early September will ship userspace code to use them. It would be good to have them correct when kernel's start to support this...

It would also mean less risk for conflicting projects with the need for such numbers.

I think it's useful to use a value lower than IPPROTO_MAX, because it means the kernel module can also be build against older kernels as out of tree module and still it would be transparent for userspace consumers like samba.
There are hardcoded checks for IPPROTO_MAX in inet_create, inet6_create, inet_diag_register and the value of IPPROTO_MAX is 263 starting with commit d25a92ccae6bed02327b63d138e12e7806830f78 in 6.11.

Thanks!
metze

#3 lore raw

Re: [PATCH net-next v2 01/15] net: define IPPROTO_QUIC and SOL_QUIC constants

Hi Stefan, Xin,

Quoted 37 lines · Stefan Metzmacher

Hi,

Quoted 17 lines
include/linux/socket.h +1 -0
index 3b262487ec06..a7c05b064583 100644
@@ -386,6 +386,7 @@ struct ucred {
   #define SOL_MCTP    285
   #define SOL_SMC        286
   #define SOL_VSOCK    287
+#define SOL_QUIC    288
     /* IPX options */
   #define IPX_TYPE    1
include/uapi/linux/in.h +0 -0
index ced0fc3c3aa5..34becd90d3a6 100644
@@ -85,6 +85,8 @@ enum {
   #define IPPROTO_RAW        IPPROTO_RAW
Quoted 8 lines

    IPPROTO_SMC = 256,        /* Shared Memory Communications        */ #define IPPROTO_SMC        IPPROTO_SMC
+  IPPROTO_QUIC = 261,        /* A UDP-Based Multiplexed and Secure Transport    */
+#define IPPROTO_QUIC        IPPROTO_QUIC
    IPPROTO_MPTCP = 262,        /* Multipath TCP connection        */ #define IPPROTO_MPTCP        IPPROTO_MPTCP
    IPPROTO_MAX

Can these constants be accepted, soon?

Samba 4.23.0 to be released early September will ship userspace code to use them. It would be good to have them correct when kernel's start to support this...

It would also mean less risk for conflicting projects with the need for such numbers.

Quoted 8 lines

I think it's useful to use a value lower than IPPROTO_MAX, because it means the kernel module can also be build against older kernels as out of tree module
and still it would be transparent for userspace consumers like samba. There are hardcoded checks for IPPROTO_MAX in inet_create, inet6_create, inet_diag_register
and the value of IPPROTO_MAX is 263 starting with commit d25a92ccae6bed02327b63d138e12e7806830f78 in 6.11.

I would also recommend not changing IPPROTO_MAX here. When IPPROTO_MAX got increased to 263, this caused some (small) small issues because it was hardcoded in some userspace code if I remember well.

It is unclear why IPPROTO_QUIC is using 261 and not 257, but it should not make any differences I suppose.

Note that for MPTCP, we picked 262, just in case the protocol number was limited to 8 bits, to fallback to IPPROTO_TCP: 262 & 0xFF = 6. At that time, we thought it was important, because we were the first ones to use a value higher than U8_MAX. At the end, it is good for new protocols, not to increase IPPROTO_MAX each time :)

(@Xin: BTW, thank you for working on this!)

Cheers,
Matt

Signature · 1 line
Sponsored by the NGI0 Core fund.
author
#4 lore raw

Re: [PATCH net-next v2 01/15] net: define IPPROTO_QUIC and SOL_QUIC constants

Quoted 50 lines · Matthieu Baerts

Hi Stefan, Xin,

Quoted 37 lines · Stefan Metzmacher

Hi,

Quoted 20 lines
include/linux/socket.h +1 -0
index 3b262487ec06..a7c05b064583 100644
@@ -386,6 +386,7 @@ struct ucred {
  #define SOL_MCTP    285
  #define SOL_SMC        286
  #define SOL_VSOCK    287
+#define SOL_QUIC    288
    /* IPX options */
  #define IPX_TYPE    1
include/uapi/linux/in.h +1 -0
index ced0fc3c3aa5..34becd90d3a6 100644
@@ -85,6 +85,8 @@ enum {
  #define IPPROTO_RAW        IPPROTO_RAW
    IPPROTO_SMC = 256,        /* Shared Memory Communications        */
  #define IPPROTO_SMC        IPPROTO_SMC
+  IPPROTO_QUIC = 261,        /* A UDP-Based Multiplexed and Secure
Quoted 5 lines

Transport */
+#define IPPROTO_QUIC IPPROTO_QUIC
IPPROTO_MPTCP = 262, /* Multipath TCP connection */
#define IPPROTO_MPTCP IPPROTO_MPTCP
IPPROTO_MAX

Can these constants be accepted, soon?

Samba 4.23.0 to be released early September will ship userspace code to use them. It would be good to have them correct when kernel's start to support this...

It would also mean less risk for conflicting projects with the need for such numbers.

Quoted 8 lines

I think it's useful to use a value lower than IPPROTO_MAX, because it means the kernel module can also be build against older kernels as out of tree module
and still it would be transparent for userspace consumers like samba. There are hardcoded checks for IPPROTO_MAX in inet_create, inet6_create, inet_diag_register
and the value of IPPROTO_MAX is 263 starting with commit d25a92ccae6bed02327b63d138e12e7806830f78 in 6.11.

Quoted 7 lines

I would also recommend not changing IPPROTO_MAX here. When IPPROTO_MAX got increased to 263, this caused some (small) small issues because it was hardcoded in some userspace code if I remember well.

It is unclear why IPPROTO_QUIC is using 261 and not 257, but it should not make any differences I suppose.

I agree, it should not.

I wasn’t sure if any other project was using 257, so to minimize the risk of conflicts, I’ve been using this large value from the beginning.

Quoted 6 lines

Note that for MPTCP, we picked 262, just in case the protocol number was limited to 8 bits, to fallback to IPPROTO_TCP: 262 & 0xFF = 6. At that time, we thought it was important, because we were the first ones to use a value higher than U8_MAX. At the end, it is good for new protocols, not to increase IPPROTO_MAX each time :)

Yes, this approach saves a lot of trouble for new protocols!

Thanks.

maintainer
#5 lore raw

Re: [PATCH net-next v2 01/15] net: define IPPROTO_QUIC and SOL_QUIC constants

In reply to Stefan Metzmacher
Quoted 31 lines · Stefan Metzmacher

Hi,

Quoted 17 lines
include/linux/socket.h +1 -0
index 3b262487ec06..a7c05b064583 100644
@@ -386,6 +386,7 @@ struct ucred {
  #define SOL_MCTP    285
  #define SOL_SMC             286
  #define SOL_VSOCK   287
+#define SOL_QUIC     288
 
  /* IPX options */
  #define IPX_TYPE    1
include/uapi/linux/in.h +0 -0
index ced0fc3c3aa5..34becd90d3a6 100644

include/uapi/linux/in.h enum { · line 92

Quoted 8 lines
include/uapi/linux/in.h +2 -0
@@ -85,6 +85,8 @@ enum {
  #define IPPROTO_RAW         IPPROTO_RAW
    IPPROTO_SMC = 256,                /* Shared Memory Communications         */
  #define IPPROTO_SMC         IPPROTO_SMC
+  IPPROTO_QUIC = 261,                /* A UDP-Based Multiplexed and Secure Transport */
+#define IPPROTO_QUIC         IPPROTO_QUIC
    IPPROTO_MPTCP = 262,              /* Multipath TCP connection             */
  #define IPPROTO_MPTCP               IPPROTO_MPTCP
    IPPROTO_MAX

Can these constants be accepted, soon?

Quoted 3 lines

Samba 4.23.0 to be released early September will ship userspace code to use them. It would be good to have them correct when kernel's start to support this...

I'd like to test ksmbd with smbclient of samba, which includes quic support. Which Samba branch should I use? How do I enable quic in Samba? Do I need to update smb.conf?

Thanks.

Quoted 12 lines

It would also mean less risk for conflicting projects with the need for such numbers.

I think it's useful to use a value lower than IPPROTO_MAX, because it means the kernel module can also be build against older kernels as out of tree module and still it would be transparent for userspace consumers like samba.
There are hardcoded checks for IPPROTO_MAX in inet_create, inet6_create, inet_diag_register and the value of IPPROTO_MAX is 263 starting with commit d25a92ccae6bed02327b63d138e12e7806830f78 in 6.11.

Thanks!
metze

#6 lore raw

Re: [PATCH net-next v2 01/15] net: define IPPROTO_QUIC and SOL_QUIC constants

Hi Namjae,

Quoted 28 lines
Quoted 28 lines
Quoted 17 lines
include/linux/socket.h +1 -0
index 3b262487ec06..a7c05b064583 100644
@@ -386,6 +386,7 @@ struct ucred {
   #define SOL_MCTP    285
   #define SOL_SMC             286
   #define SOL_VSOCK   287
+#define SOL_QUIC     288
 
   /* IPX options */
   #define IPX_TYPE    1
include/uapi/linux/in.h +0 -0
index ced0fc3c3aa5..34becd90d3a6 100644

include/uapi/linux/in.h enum { · line 92

Quoted 8 lines
include/uapi/linux/in.h +2 -0
@@ -85,6 +85,8 @@ enum {
   #define IPPROTO_RAW         IPPROTO_RAW
     IPPROTO_SMC = 256,                /* Shared Memory Communications         */
   #define IPPROTO_SMC         IPPROTO_SMC
+  IPPROTO_QUIC = 261,                /* A UDP-Based Multiplexed and Secure Transport */
+#define IPPROTO_QUIC         IPPROTO_QUIC
     IPPROTO_MPTCP = 262,              /* Multipath TCP connection             */
   #define IPPROTO_MPTCP               IPPROTO_MPTCP
     IPPROTO_MAX

Can these constants be accepted, soon?

Quoted 6 lines
Quoted 3 lines

Samba 4.23.0 to be released early September will ship userspace code to use them. It would be good to have them correct when kernel's start to support this...

I'd like to test ksmbd with smbclient of samba, which includes quic support. Which Samba branch should I use? How do I enable quic in Samba? Do I need to update smb.conf?

With master or 4.23 the simplest way would be

smbclient //ksmbd-server/share \
-Uuser%Passw0rd \
--option='client smb transports = quic' \
--option='tls verify peer = no_check' \
-I 10.0.0.1

Note it only works with a name in the unc otherwise quic can't work.

For development you may want to use SSLKEYLOGFILE=/dev/shm/sslkeylogfile.txt smbclient ...

And point wireshark to /dev/shm/sslkeylogfile.txt with

wireshark -o tls.keylog_file:/dev/shm/sslkeylogfile.txt

Or you merge it into a pcapng file like this:

editcap --inject-secrets tls,/dev/shm/sslkeylogfile.txt capture.pcap.gz capture.pcapng.gz

Then 'wireshark capture.pcapng.gz' will have everything to decrypt.

metze

author
#7 lore raw

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

In reply to Xin Long

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

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

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

---
 net/Kconfig         |   1 +
 net/Makefile        |   1 +
 net/quic/Kconfig    |  35 +++++
 net/quic/Makefile   |   8 +
 net/quic/protocol.c | 370 ++++++++++++++++++++++++++++++++++++++++++++
 net/quic/protocol.h |  55 +++++++
 net/quic/socket.c   | 213 +++++++++++++++++++++++++
 net/quic/socket.h   |  79 ++++++++++
 8 files changed, 762 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
net/Kconfig +1 -0
index d5865cf19799..1205f5b7cf59 100644
@@ -249,6 +249,7 @@ source "net/bridge/netfilter/Kconfig"
 
 endif # if NETFILTER
 
+source "net/quic/Kconfig"
 source "net/sctp/Kconfig"
 source "net/rds/Kconfig"
 source "net/tipc/Kconfig"
net/Makefile +1 -0
index aac960c41db6..7c6de28e9aa5 100644
@@ -42,6 +42,7 @@ obj-$(CONFIG_PHONET)		+= phonet/
 ifneq ($(CONFIG_VLAN_8021Q),)
 obj-y				+= 8021q/
 endif
+obj-$(CONFIG_IP_QUIC)		+= quic/
 obj-$(CONFIG_IP_SCTP)		+= sctp/
 obj-$(CONFIG_RDS)		+= rds/
 obj-$(CONFIG_WIRELESS)		+= wireless/
net/quic/Kconfig new file +35 -0
new file mode 100644
index 000000000000..b64fa398750e
@@ -0,0 +1,35 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# QUIC configuration
+#
+
+menuconfig IP_QUIC
+	tristate "QUIC: A UDP-Based Multiplexed and Secure Transport (Experimental)"
+	depends on INET
+	depends on IPV6
+	select CRYPTO
+	select CRYPTO_HMAC
+	select CRYPTO_HKDF
+	select CRYPTO_AES
+	select CRYPTO_GCM
+	select CRYPTO_CCM
+	select CRYPTO_CHACHA20POLY1305
+	select NET_UDP_TUNNEL
+	help
+	  QUIC: A UDP-Based Multiplexed and Secure Transport
+
+	  From rfc9000 <https://www.rfc-editor.org/rfc/rfc9000.html>.
+
+	  QUIC provides applications with flow-controlled streams for structured
+	  communication, low-latency connection establishment, and network path
+	  migration.  QUIC includes security measures that ensure
+	  confidentiality, integrity, and availability in a range of deployment
+	  circumstances.  Accompanying documents describe the integration of
+	  TLS for key negotiation, loss detection, and an exemplary congestion
+	  control algorithm.
+
+	  To compile this protocol support as a module, choose M here: the
+	  module will be called quic. Debug messages are handled by the
+	  kernel's dynamic debugging framework.
+
+	  If in doubt, say N.
net/quic/Makefile new file +8 -0
new file mode 100644
index 000000000000..020e4dd133d8
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# Makefile for QUIC support code.
+#
+
+obj-$(CONFIG_IP_QUIC) += quic.o
+
+quic-y := protocol.o socket.o
net/quic/protocol.c new file +370 -0

370 lines not on this page · raw message

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

55 lines not on this page · raw message

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

213 lines not on this page · raw message

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

79 lines not on this page · raw message

Signature · 1 line
2.47.1
maintainer
#8 lore raw

Re: [PATCH net-next v2 02/15] net: build socket infrastructure for QUIC protocol

Quoted 18 lines · Xin Long
net/Makefile +1 -0
index aac960c41db6..7c6de28e9aa5 100644
@@ -42,6 +42,7 @@ obj-$(CONFIG_PHONET)		+= phonet/
 ifneq ($(CONFIG_VLAN_8021Q),)
 obj-y				+= 8021q/
 endif
+obj-$(CONFIG_IP_QUIC)		+= quic/
 obj-$(CONFIG_IP_SCTP)		+= sctp/
 obj-$(CONFIG_RDS)		+= rds/
 obj-$(CONFIG_WIRELESS)		+= wireless/
net/quic/Kconfig new file +0 -0
new file mode 100644
index 000000000000..b64fa398750e

net/quic/Kconfig line 9

Quoted 9 lines
net/quic/Kconfig new file +9 -0
@@ -0,0 +1,35 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# QUIC configuration
+#
+
+menuconfig IP_QUIC
+	tristate "QUIC: A UDP-Based Multiplexed and Secure Transport (Experimental)"
+	depends on INET
+	depends on IPV6

What if IPV6=m ?

net/quic/Kconfig line 17

Quoted 8 lines
net/quic/Kconfig new file +8 -0
+	select CRYPTO
+	select CRYPTO_HMAC
+	select CRYPTO_HKDF
+	select CRYPTO_AES
+	select CRYPTO_GCM
+	select CRYPTO_CCM
+	select CRYPTO_CHACHA20POLY1305
+	select NET_UDP_TUNNEL

Possibly:
default n

?
[...]

net/quic/Kconfig line 27

Quoted 10 lines
net/quic/Kconfig new file +10 -0
+static int quic_init_sock(struct sock *sk)
+{
+	sk->sk_destruct = inet_sock_destruct;
+	sk->sk_write_space = quic_write_space;
+	sock_set_flag(sk, SOCK_USE_WRITE_QUEUE);
+
+	WRITE_ONCE(sk->sk_sndbuf, READ_ONCE(sysctl_quic_wmem[1]));
+	WRITE_ONCE(sk->sk_rcvbuf, READ_ONCE(sysctl_quic_rmem[1]));
+
+	local_bh_disable();

Why?

net/quic/Kconfig

Quoted 10 lines
net/quic/Kconfig new file +10 -0
+	sk_sockets_allocated_inc(sk);
+	sock_prot_inuse_add(sock_net(sk), sk->sk_prot, 1);
+	local_bh_enable();
+
+	return 0;
+}
+
+static void quic_destroy_sock(struct sock *sk)
+{
+	local_bh_disable();

Same question :)

[...]

net/quic/Kconfig

Quoted 5 lines
net/quic/Kconfig new file +5 -0
+static int quic_disconnect(struct sock *sk, int flags)
+{
+	quic_set_state(sk, QUIC_SS_CLOSED); /* for a listen socket only */
+	return 0;
+}

disconnect() primary use-case is creating a lot of syzkaller reports. Since there should be no legacy/backward compatibility issue, I suggest considering a simple implementation always failing.

/P

author
#9 lore raw

Re: [PATCH net-next v2 02/15] net: build socket infrastructure for QUIC protocol

Quoted 23 lines · Paolo Abeni
Quoted 21 lines · Xin Long
net/Makefile +1 -0
index aac960c41db6..7c6de28e9aa5 100644
@@ -42,6 +42,7 @@ obj-$(CONFIG_PHONET)                += phonet/
 ifneq ($(CONFIG_VLAN_8021Q),)
 obj-y                                += 8021q/
 endif
+obj-$(CONFIG_IP_QUIC)                += quic/
 obj-$(CONFIG_IP_SCTP)                += sctp/
 obj-$(CONFIG_RDS)            += rds/
 obj-$(CONFIG_WIRELESS)               += wireless/
net/quic/Kconfig new file +3 -0
new file mode 100644
index 000000000000..b64fa398750e
@@ -0,0 +1,35 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# QUIC configuration
Quoted 8 lines

net/quic/Kconfig line 9

Quoted 6 lines
net/quic/Kconfig new file +6 -0
+#
+
+menuconfig IP_QUIC
+     tristate "QUIC: A UDP-Based Multiplexed and Secure Transport (Experimental)"
+     depends on INET
+     depends on IPV6

What if IPV6=m ?

I think 'depends on IPV6' will include IPV6=m.

Quoted 10 lines

net/quic/Kconfig line 17

Quoted 9 lines
net/quic/Kconfig new file +8 -0
+     select CRYPTO
+     select CRYPTO_HMAC
+     select CRYPTO_HKDF
+     select CRYPTO_AES
+     select CRYPTO_GCM
+     select CRYPTO_CCM
+     select CRYPTO_CHACHA20POLY1305
+     select NET_UDP_TUNNEL
Quoted 2 lines

Possibly:
default n

I missed that..

Quoted 15 lines

?
[...]

net/quic/Kconfig line 27

Quoted 10 lines
net/quic/Kconfig new file +10 -0
+static int quic_init_sock(struct sock *sk)
+{
+     sk->sk_destruct = inet_sock_destruct;
+     sk->sk_write_space = quic_write_space;
+     sock_set_flag(sk, SOCK_USE_WRITE_QUEUE);
+
+     WRITE_ONCE(sk->sk_sndbuf, READ_ONCE(sysctl_quic_wmem[1]));
+     WRITE_ONCE(sk->sk_rcvbuf, READ_ONCE(sysctl_quic_rmem[1]));
+
+     local_bh_disable();

Why?

Good catch, there was a quic_put_port() before, and I forgot to delete local_bh_disable()f while removing quic_put_port().

Quoted 15 lines

net/quic/Kconfig

Quoted 11 lines
net/quic/Kconfig new file +10 -0
+     sk_sockets_allocated_inc(sk);
+     sock_prot_inuse_add(sock_net(sk), sk->sk_prot, 1);
+     local_bh_enable();
+
+     return 0;
+}
+
+static void quic_destroy_sock(struct sock *sk)
+{
+     local_bh_disable();

Same question :)

[...]

Quoted 10 lines

net/quic/Kconfig

Quoted 5 lines
net/quic/Kconfig new file +5 -0
+static int quic_disconnect(struct sock *sk, int flags)
+{
+     quic_set_state(sk, QUIC_SS_CLOSED); /* for a listen socket only */
+     return 0;
+}

disconnect() primary use-case is creating a lot of syzkaller reports. Since there should be no legacy/backward compatibility issue, I suggest considering a simple implementation always failing.

OK. This means shutdown(listensk) won't work.

Thanks Paolo for all the helpful feedback!

author
#10 lore raw

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

In reply to Xin Long

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

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

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

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

482 lines not on this page · raw message

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

219 lines not on this page · raw message

net/quic/protocol.c +6 -0
index 01a5fdfb5227..522c194d4577 100644
@@ -327,6 +327,9 @@ static __init int quic_init(void)
 	if (err)
 		goto err_percpu_counter;
 
+	if (quic_hash_tables_init())
+		goto err_hash;
+
 	err = register_pernet_subsys(&quic_net_ops);
 	if (err)
 		goto err_def_ops;
@@ -344,6 +347,8 @@ static __init int quic_init(void)
 err_protosw:
 	unregister_pernet_subsys(&quic_net_ops);
 err_def_ops:
+	quic_hash_tables_destroy();
+err_hash:
 	percpu_counter_destroy(&quic_sockets_allocated);
 err_percpu_counter:
 	return err;
@@ -356,6 +361,7 @@ static __exit void quic_exit(void)
 #endif
 	quic_protosw_exit();
 	unregister_pernet_subsys(&quic_net_ops);
+	quic_hash_tables_destroy();
 	percpu_counter_destroy(&quic_sockets_allocated);
 	pr_info("quic: exit\n");
 }
net/quic/socket.c +4 -0
index 320a9a5a3c53..9cab01109db7 100644
@@ -55,6 +55,10 @@ static int quic_init_sock(struct sock *sk)
 
 static void quic_destroy_sock(struct sock *sk)
 {
+	quic_data_free(quic_ticket(sk));
+	quic_data_free(quic_token(sk));
+	quic_data_free(quic_alpn(sk));
+
 	local_bh_disable();
 	sk_sockets_allocated_dec(sk);
 	sock_prot_inuse_add(sock_net(sk), sk->sk_prot, -1);
net/quic/socket.h +21 -0
index ded8eb2e6a9c..6cbf12bcae75 100644
@@ -10,6 +10,8 @@
 
 #include <net/udp_tunnel.h>
 
+#include "common.h"
+
 #include "protocol.h"
 
 extern struct proto quic_prot;
@@ -25,6 +27,10 @@ enum quic_state {
 struct quic_sock {
 	struct inet_sock		inet;
 	struct list_head		reqs;
+
+	struct quic_data		ticket;
+	struct quic_data		token;
+	struct quic_data		alpn;
 };
 
 struct quic6_sock {
@@ -42,6 +48,21 @@ static inline struct list_head *quic_reqs(const struct sock *sk)
 	return &quic_sk(sk)->reqs;
 }
 
+static inline struct quic_data *quic_token(const struct sock *sk)
+{
+	return &quic_sk(sk)->token;
+}
+
+static inline struct quic_data *quic_ticket(const struct sock *sk)
+{
+	return &quic_sk(sk)->ticket;
+}
+
+static inline struct quic_data *quic_alpn(const struct sock *sk)
+{
+	return &quic_sk(sk)->alpn;
+}
+
 static inline bool quic_is_establishing(struct sock *sk)
 {
 	return sk->sk_state == QUIC_SS_ESTABLISHING;
Signature · 1 line
2.47.1
maintainer
#11 lore raw

Re: [PATCH net-next v2 03/15] quic: provide common utilities and data structures

On 8/18/25 4:04 PM, Xin Long wrote:
[...]

net/quic/common.c line 73

Quoted 8 lines
net/quic/common.c new file +8 -0
+void quic_hash_tables_destroy(void)
+{
+	struct quic_hash_table *ht;
+	int table;
+
+	for (table = 0; table < QUIC_HT_MAX_TABLES; table++) {
+		ht = &quic_hash_tables[table];
+		ht->size = QUIC_HT_SIZE;

Why?

net/quic/common.c line 86

Quoted 13 lines
net/quic/common.c new file +13 -0
+		kfree(ht->hash);
+	}
+}
+
+int quic_hash_tables_init(void)
+{
+	struct quic_hash_head *head;
+	struct quic_hash_table *ht;
+	int table, i;
+
+	for (table = 0; table < QUIC_HT_MAX_TABLES; table++) {
+		ht = &quic_hash_tables[table];
+		ht->size = QUIC_HT_SIZE;

AFAICS the hash table size is always QUIC_HT_SIZE, which feels like too small for connection and possibly quick sockets.

Do yoi need to differentiate the size among the different hash types?

net/quic/common.c line 87

Quoted 1 line
net/quic/common.c new file +1 -0
+		head = kmalloc_array(ht->size, sizeof(*head), GFP_KERNEL);

If so, possibly you should resort to kvmalloc_array here.

net/quic/common.c line 98

Quoted 11 lines
net/quic/common.c new file +11 -0
+		if (!head) {
+			quic_hash_tables_destroy();
+			return -ENOMEM;
+		}
+		for (i = 0; i < ht->size; i++) {
+			INIT_HLIST_HEAD(&head[i].head);
+			if (table == QUIC_HT_UDP_SOCK) {
+				mutex_init(&head[i].m_lock);
+				continue;
+			}
+			spin_lock_init(&head[i].s_lock);

Doh, I missed the union mutex/spinlock. IMHO it would be cleaner to use separate hash types.

[...]

Quoted 22 lines
net/quic/common.c new file +22 -0
+/* Parse a comma-separated string into a 'quic_data' list format.
+ *
+ * Each comma-separated token is turned into a length-prefixed element. The
+ * first byte of each element stores the length (minus one). Elements are
+ * stored in 'to->data', and 'to->len' is updated.
+ */
+void quic_data_from_string(struct quic_data *to, u8 *from, u32 len)
+{
+	struct quic_data d;
+	u8 *p = to->data;
+
+	to->len = 0;
+	while (len) {
+		d.data = p++;
+		d.len  = 1;
+		while (len && *from == ' ') {
+			from++;
+			len--;
+		}
+		while (len) {
+			if (*from == ',') {
+				from++;

net/quic/common.c line 481

Quoted 10 lines
net/quic/common.c new file +10 -0
+				len--;
+				break;
+			}
+			*p++ = *from++;
+			len--;
+			d.len++;
+		}
+		*d.data = (u8)(d.len - 1);
+		to->len += d.len;
+	}

The above does not perform any bound checking vs the destination buffer, it feels fragile.

/P

author
#12 lore raw

Re: [PATCH net-next v2 03/15] quic: provide common utilities and data structures

Quoted 14 lines · Paolo Abeni

On 8/18/25 4:04 PM, Xin Long wrote:
[...]

net/quic/common.c line 73

Quoted 8 lines
net/quic/common.c new file +8 -0
+void quic_hash_tables_destroy(void)
+{
+     struct quic_hash_table *ht;
+     int table;
+
+     for (table = 0; table < QUIC_HT_MAX_TABLES; table++) {
+             ht = &quic_hash_tables[table];
+             ht->size = QUIC_HT_SIZE;

Why?

Quoted 16 lines

net/quic/common.c line 86

Quoted 13 lines
net/quic/common.c new file +13 -0
+             kfree(ht->hash);
+     }
+}
+
+int quic_hash_tables_init(void)
+{
+     struct quic_hash_head *head;
+     struct quic_hash_table *ht;
+     int table, i;
+
+     for (table = 0; table < QUIC_HT_MAX_TABLES; table++) {
+             ht = &quic_hash_tables[table];
+             ht->size = QUIC_HT_SIZE;

AFAICS the hash table size is always QUIC_HT_SIZE, which feels like too small for connection and possibly quick sockets.

indeed.

Quoted 2 lines

Do yoi need to differentiate the size among the different hash types?

Yes, I will change to use alloc_large_system_hash() for these hashtables, align with TCP:

- source connection IDs hashtable -> tcp_hashinfo.ehash For Data receiving
- QUIC listening sockets hashtable -> tcp_hashinfo.lhash2 For New connections
- UDP tunnel sockets hashtable -> tcp_hashinfo.bhash For binding
- QUIC sockets hashtable -> tcp_hashinfo.bhash
For some rare special cases, like receiving a stateless reset.

Quoted 17 lines
Quoted 2 lines

+ head = kmalloc_array(ht->size, sizeof(*head), GFP_KERNEL);

If so, possibly you should resort to kvmalloc_array here.

net/quic/common.c line 98

Quoted 11 lines
net/quic/common.c new file +11 -0
+             if (!head) {
+                     quic_hash_tables_destroy();
+                     return -ENOMEM;
+             }
+             for (i = 0; i < ht->size; i++) {
+                     INIT_HLIST_HEAD(&head[i].head);
+                     if (table == QUIC_HT_UDP_SOCK) {
+                             mutex_init(&head[i].m_lock);
+                             continue;
+                     }
+                     spin_lock_init(&head[i].s_lock);
Quoted 2 lines

Doh, I missed the union mutex/spinlock. IMHO it would be cleaner to use separate hash types.

Yeah, I will separate them. :D

Quoted 29 lines

[...]

Quoted 27 lines
net/quic/common.c new file +27 -0
+/* Parse a comma-separated string into a 'quic_data' list format.
+ *
+ * Each comma-separated token is turned into a length-prefixed element. The
+ * first byte of each element stores the length (minus one). Elements are
+ * stored in 'to->data', and 'to->len' is updated.
+ */
+void quic_data_from_string(struct quic_data *to, u8 *from, u32 len)
+{
+     struct quic_data d;
+     u8 *p = to->data;
+
+     to->len = 0;
+     while (len) {
+             d.data = p++;
+             d.len  = 1;
+             while (len && *from == ' ') {
+                     from++;
+                     len--;
+             }
+             while (len) {
+                     if (*from == ',') {
+                             from++;
+                             len--;
+                             break;
+                     }
+                     *p++ = *from++;
+                     len--;
Quoted 9 lines

net/quic/common.c line 481

Quoted 5 lines
net/quic/common.c new file +5 -0
+                     d.len++;
+             }
+             *d.data = (u8)(d.len - 1);
+             to->len += d.len;
+     }

The above does not perform any bound checking vs the destination buffer, it feels fragile.

This function needs two checks:

1. len < U8_MAX
2. to->data memory len >= len + 1.

Although the caller like quic_sock_set_alpn() ensures that, I guess they should still be done in this function as a common helper.

Thanks.

author
#13 lore raw

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

In reply to Xin Long

This patch introduces two new abstraction structures to simplify handling of IPv4 and IPv6 differences across the QUIC stack:

- quic_addr_family_ops: for address comparison, flow routing, UDP config, MTU lookup, formatted output, etc.

- quic_proto_family_ops: for socket address helpers and preference.

With these additions, the QUIC core logic can remain agnostic of the address family and socket type, improving modularity and reducing repetitive checks throughout the codebase.

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

686 lines not on this page · raw message

net/quic/family.h new file +41 -0

41 lines not on this page · raw message

net/quic/protocol.c +1 -1
index 522c194d4577..08eb3b81f62f 100644
@@ -47,7 +47,7 @@ static int quic_inet_listen(struct socket *sock, int backlog)
 
 static int quic_inet_getname(struct socket *sock, struct sockaddr *uaddr, int peer)
 {
-	return -EOPNOTSUPP;
+	return quic_get_sk_addr(sock, uaddr, peer);
 }
 
 static __poll_t quic_inet_poll(struct file *file, struct socket *sock, poll_table *wait)
net/quic/socket.c +2 -2
index 9cab01109db7..025fb3ae2941 100644
@@ -121,7 +121,7 @@ static int quic_setsockopt(struct sock *sk, int level, int optname,
 			   sockptr_t optval, unsigned int optlen)
 {
 	if (level != SOL_QUIC)
-		return -EOPNOTSUPP;
+		return quic_common_setsockopt(sk, level, optname, optval, optlen);
 
 	return quic_do_setsockopt(sk, optname, optval, optlen);
 }
@@ -135,7 +135,7 @@ static int quic_getsockopt(struct sock *sk, int level, int optname,
 			   char __user *optval, int __user *optlen)
 {
 	if (level != SOL_QUIC)
-		return -EOPNOTSUPP;
+		return quic_common_getsockopt(sk, level, optname, optval, optlen);
 
 	return quic_do_getsockopt(sk, optname, USER_SOCKPTR(optval), USER_SOCKPTR(optlen));
 }
net/quic/socket.h +1 -0
index 6cbf12bcae75..3f808489f571 100644
@@ -11,6 +11,7 @@
 #include <net/udp_tunnel.h>
 
 #include "common.h"
+#include "family.h"
 
 #include "protocol.h"
 
Signature · 1 line
2.47.1
maintainer
#14 lore raw

Re: [PATCH net-next v2 04/15] quic: provide family ops for address and protocol

Quoted 11 lines · Xin Long

This patch introduces two new abstraction structures to simplify handling of IPv4 and IPv6 differences across the QUIC stack:

- quic_addr_family_ops: for address comparison, flow routing, UDP config, MTU lookup, formatted output, etc.

- quic_proto_family_ops: for socket address helpers and preference.

With these additions, the QUIC core logic can remain agnostic of the address family and socket type, improving modularity and reducing repetitive checks throughout the codebase.

Given that you wrap the ops call in quick_<op>() helper, I'm wondering if such abstraction is necessary/useful? 'if' statements in the quick helper will likely reduce the code size, and will the indirect function call overhead.

[...]

Quoted 13 lines
net/quic/family.c new file +13 -0
+static void quic_v6_set_sk_addr(struct sock *sk, union quic_addr *a, bool src)
+{
+	if (src) {
+		inet_sk(sk)->inet_sport = a->v4.sin_port;
+		if (a->sa.sa_family == AF_INET) {
+			sk->sk_v6_rcv_saddr.s6_addr32[0] = 0;
+			sk->sk_v6_rcv_saddr.s6_addr32[1] = 0;
+			sk->sk_v6_rcv_saddr.s6_addr32[2] = htonl(0x0000ffff);
+			sk->sk_v6_rcv_saddr.s6_addr32[3] = a->v4.sin_addr.s_addr;
+		} else {
+			sk->sk_v6_rcv_saddr = a->v6.sin6_addr;
+		}
+	} else {

net/quic/family.c line 547

Quoted 10 lines
net/quic/family.c new file +10 -0
+		inet_sk(sk)->inet_dport = a->v4.sin_port;
+		if (a->sa.sa_family == AF_INET) {
+			sk->sk_v6_daddr.s6_addr32[0] = 0;
+			sk->sk_v6_daddr.s6_addr32[1] = 0;
+			sk->sk_v6_daddr.s6_addr32[2] = htonl(0x0000ffff);
+			sk->sk_v6_daddr.s6_addr32[3] = a->v4.sin_addr.s_addr;
+		} else {
+			sk->sk_v6_daddr = a->v6.sin6_addr;
+		}
+	}

You could factor the addr assignment in an helper and avoid some code duplication.

/P

author
#15 lore raw

Re: [PATCH net-next v2 04/15] quic: provide family ops for address and protocol

Quoted 10 lines · Paolo Abeni
Quoted 8 lines · Xin Long

This patch introduces two new abstraction structures to simplify handling of IPv4 and IPv6 differences across the QUIC stack:

- quic_addr_family_ops: for address comparison, flow routing, UDP config, MTU lookup, formatted output, etc.

- quic_proto_family_ops: for socket address helpers and preference.

Quoted 8 lines
Quoted 3 lines

With these additions, the QUIC core logic can remain agnostic of the address family and socket type, improving modularity and reducing repetitive checks throughout the codebase.

Given that you wrap the ops call in quick_<op>() helper, I'm wondering if such abstraction is necessary/useful? 'if' statements in the quick helper will likely reduce the code size, and will the indirect function call overhead.

I'm completely fine to change things to be like:

int quic_flow_route(struct sock *sk, union quic_addr *da, union quic_addr *sa, struct flowi *fl)

{
        return da->sa.sa_family == AF_INET ? quic_v4_flow_route(sk,
da, sa, fl) :
                quic_v6_flow_route(sk, da, sa, fl);
}
Quoted 20 lines

[...]

Quoted 18 lines
net/quic/family.c new file +18 -0
+static void quic_v6_set_sk_addr(struct sock *sk, union quic_addr *a, bool src)
+{
+     if (src) {
+             inet_sk(sk)->inet_sport = a->v4.sin_port;
+             if (a->sa.sa_family == AF_INET) {
+                     sk->sk_v6_rcv_saddr.s6_addr32[0] = 0;
+                     sk->sk_v6_rcv_saddr.s6_addr32[1] = 0;
+                     sk->sk_v6_rcv_saddr.s6_addr32[2] = htonl(0x0000ffff);
+                     sk->sk_v6_rcv_saddr.s6_addr32[3] = a->v4.sin_addr.s_addr;
+             } else {
+                     sk->sk_v6_rcv_saddr = a->v6.sin6_addr;
+             }
+     } else {
+             inet_sk(sk)->inet_dport = a->v4.sin_port;
+             if (a->sa.sa_family == AF_INET) {
+                     sk->sk_v6_daddr.s6_addr32[0] = 0;
+                     sk->sk_v6_daddr.s6_addr32[1] = 0;
+                     sk->sk_v6_daddr.s6_addr32[2] = htonl(0x0000ffff);
Quoted 9 lines

net/quic/family.c line 547

Quoted 5 lines
net/quic/family.c new file +5 -0
+                     sk->sk_v6_daddr.s6_addr32[3] = a->v4.sin_addr.s_addr;
+             } else {
+                     sk->sk_v6_daddr = a->v6.sin6_addr;
+             }
+     }

You could factor the addr assignment in an helper and avoid some code duplication.

Right, I will add a helper.

Thanks.

author
#16 lore raw

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

In reply to Xin Long

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

Since kernel_get/setsockopt() is no longer available to kernel consumers, a corresponding internal header, include/linux/quic.h, is added. This provides kernel subsystems with the necessary declarations to handle QUIC socket options directly.

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

---
 include/linux/quic.h      |  19 +++
 include/uapi/linux/quic.h | 236 ++++++++++++++++++++++++++++++++++++++
 net/quic/socket.c         |  38 ++++++
 net/quic/socket.h         |   7 ++
 4 files changed, 300 insertions(+)
 create mode 100644 include/linux/quic.h
 create mode 100644 include/uapi/linux/quic.h
include/linux/quic.h new file +19 -0
new file mode 100644
index 000000000000..d35ff40bb005
@@ -0,0 +1,19 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/* QUIC kernel implementation
+ * (C) Copyright Red Hat Corp. 2023
+ *
+ * This file is part of the QUIC kernel implementation
+ *
+ * Written or modified by:
+ *    Xin Long <[email protected]>
+ */
+
+#ifndef _LINUX_QUIC_H
+#define _LINUX_QUIC_H
+
+#include <uapi/linux/quic.h>
+
+int quic_kernel_setsockopt(struct sock *sk, int optname, void *optval, unsigned int optlen);
+int quic_kernel_getsockopt(struct sock *sk, int optname, void *optval, unsigned int *optlen);
+
+#endif
include/uapi/linux/quic.h new file +236 -0

236 lines not on this page · raw message

net/quic/socket.c +38 -0
index 025fb3ae2941..58711a224bfd 100644
@@ -126,6 +126,25 @@ static int quic_setsockopt(struct sock *sk, int level, int optname,
 	return quic_do_setsockopt(sk, optname, optval, optlen);
 }
 
+/**
+ * quic_kernel_setsockopt - set a QUIC socket option from within the kernel
+ * @sk: socket to configure
+ * @optname: option name (QUIC-level)
+ * @optval: pointer to the option value
+ * @optlen: size of the option value
+ *
+ * Sets a QUIC socket option on a kernel socket without involving user space.
+ *
+ * Return:
+ * - On success, 0 is returned.
+ * - On error, a negative error value is returned.
+ */
+int quic_kernel_setsockopt(struct sock *sk, int optname, void *optval, unsigned int optlen)
+{
+	return quic_do_setsockopt(sk, optname, KERNEL_SOCKPTR(optval), optlen);
+}
+EXPORT_SYMBOL_GPL(quic_kernel_setsockopt);
+
 static int quic_do_getsockopt(struct sock *sk, int optname, sockptr_t optval, sockptr_t optlen)
 {
 	return -EOPNOTSUPP;
@@ -140,6 +159,25 @@ static int quic_getsockopt(struct sock *sk, int level, int optname,
 	return quic_do_getsockopt(sk, optname, USER_SOCKPTR(optval), USER_SOCKPTR(optlen));
 }
 
+/**
+ * quic_kernel_getsockopt - get a QUIC socket option from within the kernel
+ * @sk: socket to query
+ * @optname: option name (QUIC-level)
+ * @optval: pointer to the buffer to receive the option value
+ * @optlen: pointer to the size of the buffer; updated to actual length on return
+ *
+ * Gets a QUIC socket option from a kernel socket, bypassing user space.
+ *
+ * Return:
+ * - On success, 0 is returned.
+ * - On error, a negative error value is returned.
+ */
+int quic_kernel_getsockopt(struct sock *sk, int optname, void *optval, unsigned int *optlen)
+{
+	return quic_do_getsockopt(sk, optname, KERNEL_SOCKPTR(optval), KERNEL_SOCKPTR(optlen));
+}
+EXPORT_SYMBOL_GPL(quic_kernel_getsockopt);
+
 static void quic_release_cb(struct sock *sk)
 {
 }
net/quic/socket.h +7 -0
index 3f808489f571..aeaefc677973 100644
@@ -9,6 +9,7 @@
  */
 
 #include <net/udp_tunnel.h>
+#include <linux/quic.h>
 
 #include "common.h"
 #include "family.h"
@@ -29,6 +30,7 @@ struct quic_sock {
 	struct inet_sock		inet;
 	struct list_head		reqs;
 
+	struct quic_config		config;
 	struct quic_data		ticket;
 	struct quic_data		token;
 	struct quic_data		alpn;
@@ -49,6 +51,11 @@ static inline struct list_head *quic_reqs(const struct sock *sk)
 	return &quic_sk(sk)->reqs;
 }
 
+static inline struct quic_config *quic_config(const struct sock *sk)
+{
+	return &quic_sk(sk)->config;
+}
+
 static inline struct quic_data *quic_token(const struct sock *sk)
 {
 	return &quic_sk(sk)->token;
Signature · 1 line
2.47.1
author
#17 lore raw

PATCH v2 6/15 quic: add stream management

In reply to Xin Long

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

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

- quic_stream_send_get(): Acquire a send-side stream by ID and flags during TX path.

- quic_stream_recv_get(): Acquire a receive-side stream by ID during RX path.

- quic_stream_send_put(): Release a send-side stream when sending is done.

- quic_stream_recv_put(): Release 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_send_exceeds(): Determines whether a STREAMS_BLOCKED_UNI/BIDI frame should be sent to the peer.

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

---
 net/quic/Makefile |   2 +-
 net/quic/socket.c |   5 +
 net/quic/socket.h |   8 +
 net/quic/stream.c | 549 ++++++++++++++++++++++++++++++++++++++++++++++
 net/quic/stream.h | 135 ++++++++++++
 5 files changed, 698 insertions(+), 1 deletion(-)
 create mode 100644 net/quic/stream.c
 create mode 100644 net/quic/stream.h
net/quic/Makefile +1 -1
index 13bf4a4e5442..094e9da5d739 100644
@@ -5,4 +5,4 @@
 
 obj-$(CONFIG_IP_QUIC) += quic.o
 
-quic-y := common.o family.o protocol.o socket.o
+quic-y := common.o family.o protocol.o socket.o stream.o
net/quic/socket.c +5 -0
index 58711a224bfd..0ac51cc0c249 100644
@@ -42,6 +42,9 @@ static int quic_init_sock(struct sock *sk)
 	sk->sk_write_space = quic_write_space;
 	sock_set_flag(sk, SOCK_USE_WRITE_QUEUE);
 
+	if (quic_stream_init(quic_streams(sk)))
+		return -ENOMEM;
+
 	WRITE_ONCE(sk->sk_sndbuf, READ_ONCE(sysctl_quic_wmem[1]));
 	WRITE_ONCE(sk->sk_rcvbuf, READ_ONCE(sysctl_quic_rmem[1]));
 
@@ -55,6 +58,8 @@ static int quic_init_sock(struct sock *sk)
 
 static void quic_destroy_sock(struct sock *sk)
 {
+	quic_stream_free(quic_streams(sk));
+
 	quic_data_free(quic_ticket(sk));
 	quic_data_free(quic_token(sk));
 	quic_data_free(quic_alpn(sk));
net/quic/socket.h +8 -0
index aeaefc677973..3eba18514ae6 100644
@@ -13,6 +13,7 @@
 
 #include "common.h"
 #include "family.h"
+#include "stream.h"
 
 #include "protocol.h"
 
@@ -34,6 +35,8 @@ struct quic_sock {
 	struct quic_data		ticket;
 	struct quic_data		token;
 	struct quic_data		alpn;
+
+	struct quic_stream_table	streams;
 };
 
 struct quic6_sock {
@@ -71,6 +74,11 @@ static inline struct quic_data *quic_alpn(const struct sock *sk)
 	return &quic_sk(sk)->alpn;
 }
 
+static inline struct quic_stream_table *quic_streams(const struct sock *sk)
+{
+	return &quic_sk(sk)->streams;
+}
+
 static inline bool quic_is_establishing(struct sock *sk)
 {
 	return sk->sk_state == QUIC_SS_ESTABLISHING;
net/quic/stream.c new file +549 -0

549 lines not on this page · raw message

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

135 lines not on this page · raw message

Signature · 1 line
2.47.1
maintainer
#18 lore raw

Re: [PATCH net-next v2 06/15] quic: add stream management

Quoted 17 lines · Xin Long
net/quic/stream.c new file +17 -0
+/* Check if a stream ID is valid for sending. */
+static bool quic_stream_id_send(s64 stream_id, bool is_serv)
+{
+	u8 type = (stream_id & QUIC_STREAM_TYPE_MASK);
+
+	if (is_serv) {
+		if (type == QUIC_STREAM_TYPE_CLIENT_UNI)
+			return false;
+	} else if (type == QUIC_STREAM_TYPE_SERVER_UNI) {
+		return false;
+	}
+	return true;
+}
+
+/* Check if a stream ID is valid for receiving. */
+static bool quic_stream_id_recv(s64 stream_id, bool is_serv)
+{

net/quic/stream.c line 44

Quoted 10 lines
net/quic/stream.c new file +10 -0
+	u8 type = (stream_id & QUIC_STREAM_TYPE_MASK);
+
+	if (is_serv) {
+		if (type == QUIC_STREAM_TYPE_SERVER_UNI)
+			return false;
+	} else if (type == QUIC_STREAM_TYPE_CLIENT_UNI) {
+		return false;
+	}
+	return true;
+}

The above two functions could be implemented using a common helper saving some code duplication.

Quoted 5 lines
net/quic/stream.c new file +5 -0
+/* Create and register new streams for sending. */
+static struct quic_stream *quic_stream_send_create(struct quic_stream_table *streams,
+						   s64 max_stream_id, u8 is_serv)
+{
+	struct quic_stream *stream;

net/quic/stream.c line 98

Quoted 10 lines
net/quic/stream.c new file +10 -0
+	s64 stream_id;
+
+	stream_id = streams->send.next_bidi_stream_id;
+	if (quic_stream_id_uni(max_stream_id))
+		stream_id = streams->send.next_uni_stream_id;
+
+	/* rfc9000#section-2.1: A stream ID that is used out of order results in all streams
+	 * of that type with lower-numbered stream IDs also being opened.
+	 */
+	while (stream_id <= max_stream_id) {

Is wrap around thererically possible?
Who provided max_stream_id, the user-space? or a remote pear? what if max_stream_id - stream_id is say 1M ?

[...]

net/quic/stream.c line 211

Quoted 12 lines
net/quic/stream.c new file +12 -0
+/* Check if a receive stream ID is already closed. */
+static bool quic_stream_id_recv_closed(struct quic_stream_table *streams, s64 stream_id)
+{
+	if (quic_stream_id_uni(stream_id)) {
+		if (stream_id < streams->recv.next_uni_stream_id)
+			return true;
+	} else {
+		if (stream_id < streams->recv.next_bidi_stream_id)
+			return true;
+	}
+	return false;
+}

I guess the above answer my previous questions, but I think that memory accounting for stream allocation is still deserverd.

Quoted 62 lines
net/quic/stream.c new file +62 -0
+
+/* Check if a receive stream ID exceeds would exceed local's limits. */
+static bool quic_stream_id_recv_exceeds(struct quic_stream_table *streams, s64 stream_id)
+{
+	if (quic_stream_id_uni(stream_id)) {
+		if (stream_id > streams->recv.max_uni_stream_id)
+			return true;
+	} else {
+		if (stream_id > streams->recv.max_bidi_stream_id)
+			return true;
+	}
+	return false;
+}
+
+/* Check if a send stream ID would exceed peer's limits. */
+bool quic_stream_id_send_exceeds(struct quic_stream_table *streams, s64 stream_id)
+{
+	u64 nstreams;
+
+	if (quic_stream_id_uni(stream_id)) {
+		if (stream_id > streams->send.max_uni_stream_id)
+			return true;
+	} else {
+		if (stream_id > streams->send.max_bidi_stream_id)
+			return true;
+	}
+
+	if (quic_stream_id_uni(stream_id)) {
+		stream_id -= streams->send.next_uni_stream_id;
+		nstreams = quic_stream_id_to_streams(stream_id);
+		if (nstreams + streams->send.streams_uni > streams->send.max_streams_uni)
+			return true;
+	} else {
+		stream_id -= streams->send.next_bidi_stream_id;
+		nstreams = quic_stream_id_to_streams(stream_id);
+		if (nstreams + streams->send.streams_bidi > streams->send.max_streams_bidi)
+			return true;
+	}
+	return false;
+}
+
+/* Get or create a send stream by ID. */
+struct quic_stream *quic_stream_send_get(struct quic_stream_table *streams, s64 stream_id,
+					 u32 flags, bool is_serv)
+{
+	struct quic_stream *stream;
+
+	if (!quic_stream_id_send(stream_id, is_serv))
+		return ERR_PTR(-EINVAL);
+
+	stream = quic_stream_find(streams, stream_id);
+	if (stream) {
+		if ((flags & MSG_STREAM_NEW) &&
+		    stream->send.state != QUIC_STREAM_SEND_STATE_READY)
+			return ERR_PTR(-EINVAL);
+		return stream;
+	}
+
+	if (quic_stream_id_send_closed(streams, stream_id))
+		return ERR_PTR(-ENOSTR);
+
+	if (!(flags & MSG_STREAM_NEW))

net/quic/stream.c line 283

Quoted 10 lines
net/quic/stream.c new file +10 -0
+		return ERR_PTR(-EINVAL);
+
+	if (quic_stream_id_send_exceeds(streams, stream_id))
+		return ERR_PTR(-EAGAIN);
+
+	stream = quic_stream_send_create(streams, stream_id, is_serv);
+	if (!stream)
+		return ERR_PTR(-ENOSTR);
+	streams->send.active_stream_id = stream_id;
+	return stream;

There is no locking at all in lookup/add/remove. Lacking the caller of such functions is hard to say if that is safe. You should add some info about that in the commit message (or lock here ;)

/P

author
#19 lore raw

Re: [PATCH net-next v2 06/15] quic: add stream management

Quoted 24 lines · Paolo Abeni
Quoted 22 lines · Xin Long
net/quic/stream.c new file +22 -0
+/* Check if a stream ID is valid for sending. */
+static bool quic_stream_id_send(s64 stream_id, bool is_serv)
+{
+     u8 type = (stream_id & QUIC_STREAM_TYPE_MASK);
+
+     if (is_serv) {
+             if (type == QUIC_STREAM_TYPE_CLIENT_UNI)
+                     return false;
+     } else if (type == QUIC_STREAM_TYPE_SERVER_UNI) {
+             return false;
+     }
+     return true;
+}
+
+/* Check if a stream ID is valid for receiving. */
+static bool quic_stream_id_recv(s64 stream_id, bool is_serv)
+{
+     u8 type = (stream_id & QUIC_STREAM_TYPE_MASK);
+
+     if (is_serv) {
+             if (type == QUIC_STREAM_TYPE_SERVER_UNI)
+                     return false;
Quoted 8 lines

net/quic/stream.c line 44

Quoted 5 lines
net/quic/stream.c new file +5 -0
+     } else if (type == QUIC_STREAM_TYPE_CLIENT_UNI) {
+             return false;
+     }
+     return true;
+}

The above two functions could be implemented using a common helper saving some code duplication.

Not yet sure if it's worth a helper, I need to think about it.

Quoted 12 lines
Quoted 12 lines
net/quic/stream.c new file +11 -0
+/* Create and register new streams for sending. */
+static struct quic_stream *quic_stream_send_create(struct quic_stream_table *streams,
+                                                s64 max_stream_id, u8 is_serv)
+{
+     struct quic_stream *stream;
+     s64 stream_id;
+
+     stream_id = streams->send.next_bidi_stream_id;
+     if (quic_stream_id_uni(max_stream_id))
+             stream_id = streams->send.next_uni_stream_id;
+
Quoted 8 lines

net/quic/stream.c line 98

Quoted 4 lines
net/quic/stream.c new file +4 -0
+     /* rfc9000#section-2.1: A stream ID that is used out of order results in all streams
+      * of that type with lower-numbered stream IDs also being opened.
+      */
+     while (stream_id <= max_stream_id) {

Is wrap around thererically possible?
Who provided max_stream_id, the user-space? or a remote pear? what if max_stream_id - stream_id is say 1M ?

There are two values limiting this:

1. streams->send.max_uni/bidi_stream_id:
the max_uni/bidi_stream_id that peer informs to be open.

2. streams->send.max_streams_uni/bidi (max value: QUIC_MAX_STREAMS(4096)):
to limit the number of the existing streams.

Quoted 18 lines

[...]

net/quic/stream.c line 211

Quoted 12 lines
net/quic/stream.c new file +12 -0
+/* Check if a receive stream ID is already closed. */
+static bool quic_stream_id_recv_closed(struct quic_stream_table *streams, s64 stream_id)
+{
+     if (quic_stream_id_uni(stream_id)) {
+             if (stream_id < streams->recv.next_uni_stream_id)
+                     return true;
+     } else {
+             if (stream_id < streams->recv.next_bidi_stream_id)
+                     return true;
+     }
+     return false;
+}

I guess the above answer my previous questions, but I think that memory accounting for stream allocation is still deserverd.

I can give it a try. sk_r/wmem_schedule() should be used for this I suppose.

Quoted 68 lines
Quoted 68 lines
net/quic/stream.c new file +68 -0
+
+/* Check if a receive stream ID exceeds would exceed local's limits. */
+static bool quic_stream_id_recv_exceeds(struct quic_stream_table *streams, s64 stream_id)
+{
+     if (quic_stream_id_uni(stream_id)) {
+             if (stream_id > streams->recv.max_uni_stream_id)
+                     return true;
+     } else {
+             if (stream_id > streams->recv.max_bidi_stream_id)
+                     return true;
+     }
+     return false;
+}
+
+/* Check if a send stream ID would exceed peer's limits. */
+bool quic_stream_id_send_exceeds(struct quic_stream_table *streams, s64 stream_id)
+{
+     u64 nstreams;
+
+     if (quic_stream_id_uni(stream_id)) {
+             if (stream_id > streams->send.max_uni_stream_id)
+                     return true;
+     } else {
+             if (stream_id > streams->send.max_bidi_stream_id)
+                     return true;
+     }
+
+     if (quic_stream_id_uni(stream_id)) {
+             stream_id -= streams->send.next_uni_stream_id;
+             nstreams = quic_stream_id_to_streams(stream_id);
+             if (nstreams + streams->send.streams_uni > streams->send.max_streams_uni)
+                     return true;
+     } else {
+             stream_id -= streams->send.next_bidi_stream_id;
+             nstreams = quic_stream_id_to_streams(stream_id);
+             if (nstreams + streams->send.streams_bidi > streams->send.max_streams_bidi)
+                     return true;
+     }
+     return false;
+}
+
+/* Get or create a send stream by ID. */
+struct quic_stream *quic_stream_send_get(struct quic_stream_table *streams, s64 stream_id,
+                                      u32 flags, bool is_serv)
+{
+     struct quic_stream *stream;
+
+     if (!quic_stream_id_send(stream_id, is_serv))
+             return ERR_PTR(-EINVAL);
+
+     stream = quic_stream_find(streams, stream_id);
+     if (stream) {
+             if ((flags & MSG_STREAM_NEW) &&
+                 stream->send.state != QUIC_STREAM_SEND_STATE_READY)
+                     return ERR_PTR(-EINVAL);
+             return stream;
+     }
+
+     if (quic_stream_id_send_closed(streams, stream_id))
+             return ERR_PTR(-ENOSTR);
+
+     if (!(flags & MSG_STREAM_NEW))
+             return ERR_PTR(-EINVAL);
+
+     if (quic_stream_id_send_exceeds(streams, stream_id))
+             return ERR_PTR(-EAGAIN);
+
+     stream = quic_stream_send_create(streams, stream_id, is_serv);
Quoted 9 lines

net/quic/stream.c line 283

Quoted 4 lines
net/quic/stream.c new file +4 -0
+     if (!stream)
+             return ERR_PTR(-ENOSTR);
+     streams->send.active_stream_id = stream_id;
+     return stream;

There is no locking at all in lookup/add/remove. Lacking the caller of such functions is hard to say if that is safe. You should add some info about that in the commit message (or lock here ;)

stream_hashtable is per connection/socket, it's always protected by sock lock, I will add information into the commit message.

Thanks.

author
#20 lore raw

PATCH v2 7/15 quic: add connection id management

In reply to Xin Long

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

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

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

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

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

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

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

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

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

222 lines not on this page · raw message

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

162 lines not on this page · raw message

net/quic/socket.c +6 -0
index 0ac51cc0c249..02b2056078dc 100644
@@ -42,6 +42,9 @@ static int quic_init_sock(struct sock *sk)
 	sk->sk_write_space = quic_write_space;
 	sock_set_flag(sk, SOCK_USE_WRITE_QUEUE);
 
+	quic_conn_id_set_init(quic_source(sk), 1);
+	quic_conn_id_set_init(quic_dest(sk), 0);
+
 	if (quic_stream_init(quic_streams(sk)))
 		return -ENOMEM;
 
@@ -58,6 +61,9 @@ static int quic_init_sock(struct sock *sk)
 
 static void quic_destroy_sock(struct sock *sk)
 {
+	quic_conn_id_set_free(quic_source(sk));
+	quic_conn_id_set_free(quic_dest(sk));
+
 	quic_stream_free(quic_streams(sk));
 
 	quic_data_free(quic_ticket(sk));
net/quic/socket.h +13 -0
index 3eba18514ae6..43f86cabb698 100644
@@ -14,6 +14,7 @@
 #include "common.h"
 #include "family.h"
 #include "stream.h"
+#include "connid.h"
 
 #include "protocol.h"
 
@@ -37,6 +38,8 @@ struct quic_sock {
 	struct quic_data		alpn;
 
 	struct quic_stream_table	streams;
+	struct quic_conn_id_set		source;
+	struct quic_conn_id_set		dest;
 };
 
 struct quic6_sock {
@@ -79,6 +82,16 @@ static inline struct quic_stream_table *quic_streams(const struct sock *sk)
 	return &quic_sk(sk)->streams;
 }
 
+static inline struct quic_conn_id_set *quic_source(const struct sock *sk)
+{
+	return &quic_sk(sk)->source;
+}
+
+static inline struct quic_conn_id_set *quic_dest(const struct sock *sk)
+{
+	return &quic_sk(sk)->dest;
+}
+
 static inline bool quic_is_establishing(struct sock *sk)
 {
 	return sk->sk_state == QUIC_SS_ESTABLISHING;
Signature · 1 line
2.47.1
maintainer
#21 lore raw

Re: [PATCH net-next v2 07/15] quic: add connection id management

Quoted 8 lines · Xin Long

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

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

Quoted 5 lines

- 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's unclear how many connections are expected to be contained in each set. If more than an handful you should consider using RB-tree instead of lists.

[...]

Quoted 14 lines
net/quic/connid.c new file +14 -0
+static void quic_source_conn_id_free(struct quic_source_conn_id *s_conn_id)
+{
+	u8 *data = s_conn_id->common.id.data;
+	struct quic_hash_head *head;
+
+	if (!hlist_unhashed(&s_conn_id->node)) {
+		head = quic_source_conn_id_head(sock_net(s_conn_id->sk), data);
+		spin_lock_bh(&head->s_lock);
+		hlist_del_init(&s_conn_id->node);
+		spin_unlock_bh(&head->s_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);

net/quic/connid.c line 91

Quoted 10 lines
net/quic/connid.c new file +10 -0
+}
+
+static void quic_conn_id_del(struct quic_common_conn_id *common)
+{
+	list_del(&common->list);
+	if (!common->hashed) {
+		kfree(common);
+		return;
+	}
+	quic_source_conn_id_free((struct quic_source_conn_id *)common);

It looks like the above cast is not needed.

Quoted 40 lines
net/quic/connid.c new file +40 -0
+}
+
+/* Add a connection ID with sequence number and associated private data to the connection ID set. */
+int quic_conn_id_add(struct quic_conn_id_set *id_set,
+		     struct quic_conn_id *conn_id, u32 number, void *data)
+{
+	struct quic_source_conn_id *s_conn_id;
+	struct quic_dest_conn_id *d_conn_id;
+	struct quic_common_conn_id *common;
+	struct quic_hash_head *head;
+	struct list_head *list;
+
+	/* Locate insertion point to keep list ordered by number. */
+	list = &id_set->head;
+	list_for_each_entry(common, list, list) {
+		if (number == common->number)
+			return 0; /* Ignore if it is 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(struct quic_dest_conn_id)) {
+		/* For destination connection IDs, copy the stateless reset token if available. */
+		if (data) {
+			d_conn_id = (struct quic_dest_conn_id *)common;
+			memcpy(d_conn_id->token, data, QUIC_CONN_ID_TOKEN_LEN);
+		}
+	} else {
+		/* For source connection IDs, mark as hashed and insert into the global source
+		 * connection ID hashtable.
+		 */

net/quic/connid.c line 141

Quoted 10 lines
net/quic/connid.c new file +10 -0
+		common->hashed = 1;
+		s_conn_id = (struct quic_source_conn_id *)common;
+		s_conn_id->sk = data;
+
+		head = quic_source_conn_id_head(sock_net(s_conn_id->sk), common->id.data);
+		spin_lock_bh(&head->s_lock);
+		hlist_add_head(&s_conn_id->node, &head->head);
+		spin_unlock_bh(&head->s_lock);
+	}
+	list_add_tail(&common->list, list);

It's unclear if/how id_set->list is protected vs concurrent accesses.

/P

author
#22 lore raw

Re: [PATCH net-next v2 07/15] quic: add connection id management

Quoted 13 lines · Paolo Abeni
Quoted 11 lines · Xin Long

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

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

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

Quoted 7 lines
Quoted 2 lines

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

It's unclear how many connections are expected to be contained in each set. If more than an handful you should consider using RB-tree instead of lists.

We limit the max number of issued CIDs to 8 per connection, and the CID per connection traversal is not on the data path, so it's fine to use lists here.

Note that one connection/sk has one source CID set which contains a couple of CIDs used for connection migration, and one dest CID set to saving peer's CIDs.

Quoted 19 lines

[...]

Quoted 18 lines
net/quic/connid.c new file +18 -0
+static void quic_source_conn_id_free(struct quic_source_conn_id *s_conn_id)
+{
+     u8 *data = s_conn_id->common.id.data;
+     struct quic_hash_head *head;
+
+     if (!hlist_unhashed(&s_conn_id->node)) {
+             head = quic_source_conn_id_head(sock_net(s_conn_id->sk), data);
+             spin_lock_bh(&head->s_lock);
+             hlist_del_init(&s_conn_id->node);
+             spin_unlock_bh(&head->s_lock);
+     }
+
+     /* Freeing is deferred via RCU to avoid use-after-free during concurrent lookups. */
+     call_rcu(&s_conn_id->rcu, quic_source_conn_id_free_rcu);
+}
+
+static void quic_conn_id_del(struct quic_common_conn_id *common)
+{
Quoted 8 lines

net/quic/connid.c line 91

Quoted 6 lines
net/quic/connid.c new file +6 -0
+     list_del(&common->list);
+     if (!common->hashed) {
+             kfree(common);
+             return;
+     }
+     quic_source_conn_id_free((struct quic_source_conn_id *)common);

It looks like the above cast is not needed.

there will be a compiling error:

/root/quic/modules/net/quic/connid.c:68:66: note: expected ‘struct quic_source_conn_id *’ but argument is of type ‘struct quic_common_conn_id *’
68 | static void quic_source_conn_id_free(struct
quic_source_conn_id *s_conn_id)

Or you mean change the parameter type of quic_source_conn_id_free() to:

static void quic_source_conn_id_free(struct quic_common_conn_id *common)

Quoted 45 lines
Quoted 45 lines
net/quic/connid.c new file +44 -0
+}
+
+/* Add a connection ID with sequence number and associated private data to the connection ID set. */
+int quic_conn_id_add(struct quic_conn_id_set *id_set,
+                  struct quic_conn_id *conn_id, u32 number, void *data)
+{
+     struct quic_source_conn_id *s_conn_id;
+     struct quic_dest_conn_id *d_conn_id;
+     struct quic_common_conn_id *common;
+     struct quic_hash_head *head;
+     struct list_head *list;
+
+     /* Locate insertion point to keep list ordered by number. */
+     list = &id_set->head;
+     list_for_each_entry(common, list, list) {
+             if (number == common->number)
+                     return 0; /* Ignore if it is 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(struct quic_dest_conn_id)) {
+             /* For destination connection IDs, copy the stateless reset token if available. */
+             if (data) {
+                     d_conn_id = (struct quic_dest_conn_id *)common;
+                     memcpy(d_conn_id->token, data, QUIC_CONN_ID_TOKEN_LEN);
+             }
+     } else {
+             /* For source connection IDs, mark as hashed and insert into the global source
+              * connection ID hashtable.
+              */
+             common->hashed = 1;
+             s_conn_id = (struct quic_source_conn_id *)common;
+             s_conn_id->sk = data;
+
Quoted 9 lines

net/quic/connid.c line 141

Quoted 6 lines
net/quic/connid.c new file +6 -0
+             head = quic_source_conn_id_head(sock_net(s_conn_id->sk), common->id.data);
+             spin_lock_bh(&head->s_lock);
+             hlist_add_head(&s_conn_id->node, &head->head);
+             spin_unlock_bh(&head->s_lock);
+     }
+     list_add_tail(&common->list, list);

It's unclear if/how id_set->list is protected vs concurrent accesses.

id_set is per connection/socket, it's always protected by sock lock. I will leave an annotation in the description of the function for that.

Thanks.

#23 lore raw

Re: [PATCH net-next v2 07/15] quic: add connection id management

In reply to Xin Long

Hi Xin,

Quoted 26 lines · Xin Long

!-------------------------------------------------------------------|
This Message Is From an External Sender
This message came from outside your organization.
|-------------------------------------------------------------------!

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.

Quoted 8 lines

- 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).

---

Thanks Xin for all your work on this!

For QUIC-LB, where the server endpoint may want to choose a specific source CID to enable 'stateless' routing, I don't currently see an API to allow that? It appears source CIDs are created with random values and while userspace can get/set the indexes of the current ones in use, I don't see a way to set specific CID values?

For reference here is a proposal around it -
https://datatracker.ietf.org/doc/draft-ietf-quic-load-balancers/

In the reference above, the source CID is encrypted to help protect traceability if the connection migrates. Thus, if the kernel were to support such a feature, I don't think it wants to enforce a specific encoding scheme, but perhaps it might want to be a privileged operation, perhaps requiring CAP_NET_ADMIN to set specific source CIDs.

Thanks,

-Jason

author
#24 lore raw

Re: [PATCH net-next v2 07/15] quic: add connection id management

Quoted 47 lines · Jason Baron

Hi Xin,

Quoted 26 lines · Xin Long

!-------------------------------------------------------------------|
This Message Is From an External Sender
This message came from outside your organization.
|-------------------------------------------------------------------!

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.

Quoted 8 lines

- 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).

---

Thanks Xin for all your work on this!

For QUIC-LB, where the server endpoint may want to choose a specific source CID to enable 'stateless' routing, I don't currently see an API to allow that? It appears source CIDs are created with random values and while userspace can get/set the indexes of the current ones in use, I don't see a way to set specific CID values?

Quoted 9 lines

For reference here is a proposal around it -
https://datatracker.ietf.org/doc/draft-ietf-quic-load-balancers/

In the reference above, the source CID is encrypted to help protect traceability if the connection migrates. Thus, if the kernel were to support such a feature, I don't think it wants to enforce a specific encoding scheme, but perhaps it might want to be a privileged operation, perhaps requiring CAP_NET_ADMIN to set specific source CIDs.

Hi, Jason,

We currently support only the finalized QUIC RFCs. Drafts like 'Load Balancers' and 'Multipath' aren’t covered yet. I think we will plan the APIs after they are standardized.

Thanks for pointing out this RFC.

author
#25 lore raw

PATCH v2 8/15 quic: add path management

In reply to Xin Long

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

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

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

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

- quic_path_free(): 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.

---
 net/quic/Makefile |   2 +-
 net/quic/path.c   | 512 ++++++++++++++++++++++++++++++++++++++++++++++
 net/quic/path.h   | 168 +++++++++++++++
 net/quic/socket.c |   3 +
 net/quic/socket.h |  21 +-
 5 files changed, 703 insertions(+), 3 deletions(-)
 create mode 100644 net/quic/path.c
 create mode 100644 net/quic/path.h
net/quic/Makefile +1 -1
index eee7501588d3..1565fb5cef9d 100644
@@ -5,4 +5,4 @@
 
 obj-$(CONFIG_IP_QUIC) += quic.o
 
-quic-y := common.o family.o protocol.o socket.o stream.o connid.o
+quic-y := common.o family.o protocol.o socket.o stream.o connid.o path.o
net/quic/path.c new file +512 -0

512 lines not on this page · raw message

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

168 lines not on this page · raw message

net/quic/socket.c +3 -0
index 02b2056078dc..c549e76623e3 100644
@@ -61,6 +61,9 @@ static int quic_init_sock(struct sock *sk)
 
 static void quic_destroy_sock(struct sock *sk)
 {
+	quic_path_free(sk, quic_paths(sk), 0);
+	quic_path_free(sk, quic_paths(sk), 1);
+
 	quic_conn_id_set_free(quic_source(sk));
 	quic_conn_id_set_free(quic_dest(sk));
 
net/quic/socket.h +19 -2
index 43f86cabb698..3cff2a1d478a 100644
@@ -15,6 +15,7 @@
 #include "family.h"
 #include "stream.h"
 #include "connid.h"
+#include "path.h"
 
 #include "protocol.h"
 
@@ -40,6 +41,7 @@ struct quic_sock {
 	struct quic_stream_table	streams;
 	struct quic_conn_id_set		source;
 	struct quic_conn_id_set		dest;
+	struct quic_path_group		paths;
 };
 
 struct quic6_sock {
@@ -92,6 +94,16 @@ static inline struct quic_conn_id_set *quic_dest(const struct sock *sk)
 	return &quic_sk(sk)->dest;
 }
 
+static inline struct quic_path_group *quic_paths(const struct sock *sk)
+{
+	return &quic_sk(sk)->paths;
+}
+
+static inline bool quic_is_serv(const struct sock *sk)
+{
+	return quic_paths(sk)->serv;
+}
+
 static inline bool quic_is_establishing(struct sock *sk)
 {
 	return sk->sk_state == QUIC_SS_ESTABLISHING;
@@ -115,14 +127,19 @@ static inline bool quic_is_closed(struct sock *sk)
 static inline void quic_set_state(struct sock *sk, int state)
 {
 	struct net *net = sock_net(sk);
+	int mib;
 
 	if (sk->sk_state == state)
 		return;
 
-	if (state == QUIC_SS_ESTABLISHED)
+	if (state == QUIC_SS_ESTABLISHED) {
+		mib = quic_is_serv(sk) ? QUIC_MIB_CONN_PASSIVEESTABS
+				       : QUIC_MIB_CONN_ACTIVEESTABS;
+		QUIC_INC_STATS(net, mib);
 		QUIC_INC_STATS(net, QUIC_MIB_CONN_CURRENTESTABS);
-	else if (quic_is_established(sk))
+	} else if (quic_is_established(sk)) {
 		QUIC_DEC_STATS(net, QUIC_MIB_CONN_CURRENTESTABS);
+	}
 
 	inet_sk_set_state(sk, state);
 	sk->sk_state_change(sk);
Signature · 1 line
2.47.1
maintainer
#26 lore raw

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

Quoted 15 lines · Xin Long
net/quic/path.c new file +15 -0
+/* Lookup a quic_udp_sock in the global hash table. If not found, creates and returns a new one
+ * associated with the given kernel socket.
+ */
+static struct quic_udp_sock *quic_udp_sock_lookup(struct sock *sk, union quic_addr *a, u16 port)
+{
+	struct net *net = sock_net(sk);
+	struct quic_hash_head *head;
+	struct quic_udp_sock *us;
+
+	head = quic_udp_sock_head(net, port);
+	hlist_for_each_entry(us, &head->head, node) {
+		if (net != sock_net(us->sk))
+			continue;
+		if (a) {
+			if (quic_cmp_sk_addr(us->sk, &us->addr, a) &&

net/quic/path.c line 133

Quoted 10 lines
net/quic/path.c new file +10 -0
+			    (!us->bind_ifindex || !sk->sk_bound_dev_if ||
+			     us->bind_ifindex == sk->sk_bound_dev_if))
+				return us;
+			continue;
+		}
+		if (ntohs(us->addr.v4.sin_port) == port)
+			return us;
+	}
+	return NULL;
+}

The function description does not match the actual function implementation.

Quoted 21 lines
net/quic/path.c new file +21 -0
+
+/* Binds a QUIC path to a local port and sets up a UDP socket. */
+int quic_path_bind(struct sock *sk, struct quic_path_group *paths, u8 path)
+{
+	union quic_addr *a = quic_path_saddr(paths, path);
+	int rover, low, high, remaining;
+	struct net *net = sock_net(sk);
+	struct quic_hash_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->m_lock);
+		us = quic_udp_sock_lookup(sk, a, port);
+		if (!quic_udp_sock_get(us)) {
+			us = quic_udp_sock_create(sk, a);
+			if (!us) {
+				mutex_unlock(&head->m_lock);
+				return -EINVAL;

net/quic/path.c line 164

Quoted 10 lines
net/quic/path.c new file +10 -0
+			}
+		}
+		mutex_unlock(&head->m_lock);
+
+		quic_udp_sock_put(paths->path[path].udp_sk);
+		paths->path[path].udp_sk = us;
+		return 0;
+	}
+
+	inet_get_local_port_range(net, &low, &high);

you could/should use inet_sk_get_local_port_range().

/P

author
#27 lore raw

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

Quoted 21 lines · Paolo Abeni
Quoted 19 lines · Xin Long
net/quic/path.c new file +19 -0
+/* Lookup a quic_udp_sock in the global hash table. If not found, creates and returns a new one
+ * associated with the given kernel socket.
+ */
+static struct quic_udp_sock *quic_udp_sock_lookup(struct sock *sk, union quic_addr *a, u16 port)
+{
+     struct net *net = sock_net(sk);
+     struct quic_hash_head *head;
+     struct quic_udp_sock *us;
+
+     head = quic_udp_sock_head(net, port);
+     hlist_for_each_entry(us, &head->head, node) {
+             if (net != sock_net(us->sk))
+                     continue;
+             if (a) {
+                     if (quic_cmp_sk_addr(us->sk, &us->addr, a) &&
+                         (!us->bind_ifindex || !sk->sk_bound_dev_if ||
+                          us->bind_ifindex == sk->sk_bound_dev_if))
+                             return us;
+                     continue;
Quoted 8 lines

net/quic/path.c line 133

Quoted 6 lines
net/quic/path.c new file +6 -0
+             }
+             if (ntohs(us->addr.v4.sin_port) == port)
+                     return us;
+     }
+     return NULL;
+}

The function description does not match the actual function implementation.

Right, I forgot to update the description after moving the creation out.

Quoted 26 lines
Quoted 26 lines
net/quic/path.c new file +25 -0
+
+/* Binds a QUIC path to a local port and sets up a UDP socket. */
+int quic_path_bind(struct sock *sk, struct quic_path_group *paths, u8 path)
+{
+     union quic_addr *a = quic_path_saddr(paths, path);
+     int rover, low, high, remaining;
+     struct net *net = sock_net(sk);
+     struct quic_hash_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->m_lock);
+             us = quic_udp_sock_lookup(sk, a, port);
+             if (!quic_udp_sock_get(us)) {
+                     us = quic_udp_sock_create(sk, a);
+                     if (!us) {
+                             mutex_unlock(&head->m_lock);
+                             return -EINVAL;
+                     }
+             }
+             mutex_unlock(&head->m_lock);
+
Quoted 9 lines

net/quic/path.c line 164

Quoted 6 lines
net/quic/path.c new file +6 -0
+             quic_udp_sock_put(paths->path[path].udp_sk);
+             paths->path[path].udp_sk = us;
+             return 0;
+     }
+
+     inet_get_local_port_range(net, &low, &high);

you could/should use inet_sk_get_local_port_range().

True, will update.

Thanks.

author
#28 lore raw

PATCH v2 9/15 quic: add congestion control

In reply to Xin Long

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

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

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.

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

700 lines not on this page · raw message

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

120 lines not on this page · raw message

net/quic/socket.c +1 -0
index c549e76623e3..8ee4e4cd4ee3 100644
@@ -44,6 +44,7 @@ static int quic_init_sock(struct sock *sk)
 
 	quic_conn_id_set_init(quic_source(sk), 1);
 	quic_conn_id_set_init(quic_dest(sk), 0);
+	quic_cong_init(quic_cong(sk));
 
 	if (quic_stream_init(quic_streams(sk)))
 		return -ENOMEM;
net/quic/socket.h +7 -0
index 3cff2a1d478a..019f8752fc87 100644
@@ -16,6 +16,7 @@
 #include "stream.h"
 #include "connid.h"
 #include "path.h"
+#include "cong.h"
 
 #include "protocol.h"
 
@@ -42,6 +43,7 @@ struct quic_sock {
 	struct quic_conn_id_set		source;
 	struct quic_conn_id_set		dest;
 	struct quic_path_group		paths;
+	struct quic_cong		cong;
 };
 
 struct quic6_sock {
@@ -104,6 +106,11 @@ static inline bool quic_is_serv(const struct sock *sk)
 	return quic_paths(sk)->serv;
 }
 
+static inline struct quic_cong *quic_cong(const struct sock *sk)
+{
+	return &quic_sk(sk)->cong;
+}
+
 static inline bool quic_is_establishing(struct sock *sk)
 {
 	return sk->sk_state == QUIC_SS_ESTABLISHING;
Signature · 1 line
2.47.1
author
#29 lore raw

PATCH v2 10/15 quic: add packet number space

In reply to Xin Long

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

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

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

- quic_pnspace_check(): Validates a received packet number.

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

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

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

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

224 lines not on this page · raw message

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

150 lines not on this page · raw message

net/quic/socket.c +12 -0
index 8ee4e4cd4ee3..05569b58f10b 100644
@@ -38,6 +38,8 @@ static void quic_write_space(struct sock *sk)
 
 static int quic_init_sock(struct sock *sk)
 {
+	u8 i;
+
 	sk->sk_destruct = inet_sock_destruct;
 	sk->sk_write_space = quic_write_space;
 	sock_set_flag(sk, SOCK_USE_WRITE_QUEUE);
@@ -49,6 +51,11 @@ static int quic_init_sock(struct sock *sk)
 	if (quic_stream_init(quic_streams(sk)))
 		return -ENOMEM;
 
+	for (i = 0; i < QUIC_PNSPACE_MAX; i++) {
+		if (quic_pnspace_init(quic_pnspace(sk, i)))
+			return -ENOMEM;
+	}
+
 	WRITE_ONCE(sk->sk_sndbuf, READ_ONCE(sysctl_quic_wmem[1]));
 	WRITE_ONCE(sk->sk_rcvbuf, READ_ONCE(sysctl_quic_rmem[1]));
 
@@ -62,6 +69,11 @@ static int quic_init_sock(struct sock *sk)
 
 static void quic_destroy_sock(struct sock *sk)
 {
+	u8 i;
+
+	for (i = 0; i < QUIC_PNSPACE_MAX; i++)
+		quic_pnspace_free(quic_pnspace(sk, i));
+
 	quic_path_free(sk, quic_paths(sk), 0);
 	quic_path_free(sk, quic_paths(sk), 1);
 
net/quic/socket.h +7 -0
index 019f8752fc87..7e87de319c40 100644
@@ -11,6 +11,7 @@
 #include <net/udp_tunnel.h>
 #include <linux/quic.h>
 
+#include "pnspace.h"
 #include "common.h"
 #include "family.h"
 #include "stream.h"
@@ -44,6 +45,7 @@ struct quic_sock {
 	struct quic_conn_id_set		dest;
 	struct quic_path_group		paths;
 	struct quic_cong		cong;
+	struct quic_pnspace		space[QUIC_PNSPACE_MAX];
 };
 
 struct quic6_sock {
@@ -111,6 +113,11 @@ static inline struct quic_cong *quic_cong(const struct sock *sk)
 	return &quic_sk(sk)->cong;
 }
 
+static inline struct quic_pnspace *quic_pnspace(const struct sock *sk, u8 level)
+{
+	return &quic_sk(sk)->space[level % QUIC_CRYPTO_EARLY];
+}
+
 static inline bool quic_is_establishing(struct sock *sk)
 {
 	return sk->sk_state == QUIC_SS_ESTABLISHING;
Signature · 1 line
2.47.1
author
#30 lore raw

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

In reply to Xin Long

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

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

- quic_crypto_initial_keys_install(): Derive and install initial keys.

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

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

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

- quic_crypto_generate_stateless_reset_token(): Generate token for stateless reset.

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

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

542 lines not on this page · raw message

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

73 lines not on this page · raw message

net/quic/protocol.c +12 -0
index 08eb3b81f62f..fb98ef10f852 100644
@@ -258,9 +258,18 @@ static int __net_init quic_net_init(struct net *net)
 	if (!qn->stat)
 		return -ENOMEM;
 
+	err = quic_crypto_set_cipher(&qn->crypto, TLS_CIPHER_AES_GCM_128, CRYPTO_ALG_ASYNC);
+	if (err) {
+		free_percpu(qn->stat);
+		qn->stat = NULL;
+		return err;
+	}
+	spin_lock_init(&qn->lock);
+
 #ifdef CONFIG_PROC_FS
 	err = quic_net_proc_init(net);
 	if (err) {
+		quic_crypto_free(&qn->crypto);
 		free_percpu(qn->stat);
 		qn->stat = NULL;
 	}
@@ -275,6 +284,7 @@ static void __net_exit quic_net_exit(struct net *net)
 #ifdef CONFIG_PROC_FS
 	quic_net_proc_exit(net);
 #endif
+	quic_crypto_free(&qn->crypto);
 	free_percpu(qn->stat);
 	qn->stat = NULL;
 }
@@ -323,6 +333,8 @@ static __init int quic_init(void)
 	sysctl_quic_wmem[1] = 16 * 1024;
 	sysctl_quic_wmem[2] = max(64 * 1024, max_share);
 
+	quic_crypto_init();
+
 	err = percpu_counter_init(&quic_sockets_allocated, 0, GFP_KERNEL);
 	if (err)
 		goto err_percpu_counter;
net/quic/protocol.h +2 -0
index 6e6c5a6fc3f8..1df926ef0a75 100644
@@ -47,6 +47,8 @@ struct quic_net {
 #ifdef CONFIG_PROC_FS
 	struct proc_dir_entry *proc_net;	/* procfs entry for dumping QUIC socket stats */
 #endif
+	struct quic_crypto crypto;	/* Context for decrypting Initial packets for ALPN */
+	spinlock_t lock;	/* Lock protecting crypto context for Initial packet decryption */
 };
 
 struct quic_net *quic_net(struct net *net);
net/quic/socket.c +2 -0
index 05569b58f10b..2425494a3df3 100644
@@ -73,6 +73,8 @@ static void quic_destroy_sock(struct sock *sk)
 
 	for (i = 0; i < QUIC_PNSPACE_MAX; i++)
 		quic_pnspace_free(quic_pnspace(sk, i));
+	for (i = 0; i < QUIC_CRYPTO_MAX; i++)
+		quic_crypto_free(quic_crypto(sk, i));
 
 	quic_path_free(sk, quic_paths(sk), 0);
 	quic_path_free(sk, quic_paths(sk), 1);
net/quic/socket.h +7 -0
index 7e87de319c40..c8df98351c6b 100644
@@ -16,6 +16,7 @@
 #include "family.h"
 #include "stream.h"
 #include "connid.h"
+#include "crypto.h"
 #include "path.h"
 #include "cong.h"
 
@@ -46,6 +47,7 @@ struct quic_sock {
 	struct quic_path_group		paths;
 	struct quic_cong		cong;
 	struct quic_pnspace		space[QUIC_PNSPACE_MAX];
+	struct quic_crypto		crypto[QUIC_CRYPTO_MAX];
 };
 
 struct quic6_sock {
@@ -118,6 +120,11 @@ static inline struct quic_pnspace *quic_pnspace(const struct sock *sk, u8 level)
 	return &quic_sk(sk)->space[level % QUIC_CRYPTO_EARLY];
 }
 
+static inline struct quic_crypto *quic_crypto(const struct sock *sk, u8 level)
+{
+	return &quic_sk(sk)->crypto[level];
+}
+
 static inline bool quic_is_establishing(struct sock *sk)
 {
 	return sk->sk_state == QUIC_SS_ESTABLISHING;
Signature · 1 line
2.47.1
author
#31 lore raw

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

In reply to Xin Long

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

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

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

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

- quic_crypto_get_retry_tag(): Compute tag for Retry packets.

- quic_crypto_generate_token(): Generate retry token.

- quic_crypto_verify_token(): Verify retry token.

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

---
 net/quic/crypto.c | 658 ++++++++++++++++++++++++++++++++++++++++++++++
 net/quic/crypto.h |  10 +
 2 files changed, 668 insertions(+)
net/quic/crypto.c +658 -0
index 860e3dfd4a28..bb8fc6acce08 100644
@@ -201,6 +201,343 @@ static int quic_crypto_rx_keys_derive_and_install(struct quic_crypto *crypto)
 	return 0;
 }
 
+static void *quic_crypto_skcipher_mem_alloc(struct crypto_skcipher *tfm, u32 mask_size,
+					    u8 **iv, struct skcipher_request **req)
+{
+	unsigned int iv_size, req_size;
+	unsigned int len;
+	u8 *mem;
+
+	iv_size = crypto_skcipher_ivsize(tfm);
+	req_size = sizeof(**req) + crypto_skcipher_reqsize(tfm);
+
+	len = mask_size;
+	len += iv_size;
+	len += crypto_skcipher_alignmask(tfm) & ~(crypto_tfm_ctx_alignment() - 1);
+	len = ALIGN(len, crypto_tfm_ctx_alignment());
+	len += req_size;
+
+	mem = kzalloc(len, GFP_ATOMIC);
+	if (!mem)
+		return NULL;
+
+	*iv = (u8 *)PTR_ALIGN(mem + mask_size, crypto_skcipher_alignmask(tfm) + 1);
+	*req = (struct skcipher_request *)PTR_ALIGN(*iv + iv_size,
+			crypto_tfm_ctx_alignment());
+
+	return (void *)mem;
+}
+
+#define QUIC_SAMPLE_LEN		16
+#define QUIC_MAX_PN_LEN		4
+
+#define QUIC_HEADER_FORM_BIT	0x80
+#define QUIC_LONG_HEADER_MASK	0x0f
+#define QUIC_SHORT_HEADER_MASK	0x1f
+
+/* Header Protection. */
+static int quic_crypto_header_encrypt(struct crypto_skcipher *tfm, struct sk_buff *skb, bool chacha)
+{
+	struct quic_skb_cb *cb = QUIC_SKB_CB(skb);
+	struct skcipher_request *req;
+	struct scatterlist sg;
+	u8 *mask, *iv, *p;
+	int err, i;
+
+	mask = quic_crypto_skcipher_mem_alloc(tfm, QUIC_SAMPLE_LEN, &iv, &req);
+	if (!mask)
+		return -ENOMEM;
+
+	/* rfc9001#section-5.4.2: Header Protection Sample:
+	 *
+	 *   # pn_offset is the start of the Packet Number field.
+	 *   sample_offset = pn_offset + 4
+	 *
+	 *   sample = packet[sample_offset..sample_offset+sample_length]
+	 *
+	 * rfc9001#section-5.4.3: AES-Based Header Protection:
+	 *
+	 *   header_protection(hp_key, sample):
+	 *     mask = AES-ECB(hp_key, sample)
+	 *
+	 * rfc9001#section-5.4.4: ChaCha20-Based Header Protection:
+	 *
+	 *   header_protection(hp_key, sample):
+	 *     counter = sample[0..3]
+	 *     nonce = sample[4..15]
+	 *     mask = ChaCha20(hp_key, counter, nonce, {0,0,0,0,0})
+	 */
+	memcpy((chacha ? iv : mask), skb->data + cb->number_offset + QUIC_MAX_PN_LEN,
+	       QUIC_SAMPLE_LEN);
+	sg_init_one(&sg, mask, QUIC_SAMPLE_LEN);
+	skcipher_request_set_tfm(req, tfm);
+	skcipher_request_set_crypt(req, &sg, &sg, QUIC_SAMPLE_LEN, iv);
+	err = crypto_skcipher_encrypt(req);
+	if (err)
+		goto err;
+
+	/* rfc9001#section-5.4.1:
+	 *
+	 * mask = header_protection(hp_key, sample)
+	 *
+	 * pn_length = (packet[0] & 0x03) + 1
+	 * if (packet[0] & 0x80) == 0x80:
+	 *    # Long header: 4 bits masked
+	 *    packet[0] ^= mask[0] & 0x0f
+	 * else:
+	 *    # Short header: 5 bits masked
+	 *    packet[0] ^= mask[0] & 0x1f
+	 *
+	 * # pn_offset is the start of the Packet Number field.
+	 * packet[pn_offset:pn_offset+pn_length] ^= mask[1:1+pn_length]
+	 */
+	p = skb->data;
+	*p = (u8)(*p ^ (mask[0] & (((*p & QUIC_HEADER_FORM_BIT) == QUIC_HEADER_FORM_BIT) ?
+				   QUIC_LONG_HEADER_MASK : QUIC_SHORT_HEADER_MASK)));
+	p = skb->data + cb->number_offset;
+	for (i = 1; i <= cb->number_len; i++)
+		*p++ ^= mask[i];
+err:
+	kfree(mask);
+	return err;
+}
+
+/* Extracts and reconstructs the packet number from an incoming QUIC packet. */
+static void quic_crypto_get_header(struct sk_buff *skb)
+{
+	struct quic_skb_cb *cb = QUIC_SKB_CB(skb);
+	struct quichdr *hdr = quic_hdr(skb);
+	u32 len = QUIC_MAX_PN_LEN;
+	u8 *p = (u8 *)hdr;
+
+	/* rfc9000#section-17.1:
+	 *
+	 * Once header protection is removed, the packet number is decoded by finding the packet
+	 * number value that is closest to the next expected packet. The next expected packet is
+	 * the highest received packet number plus one.
+	 */
+	p += cb->number_offset;
+	cb->key_phase = hdr->key;
+	cb->number_len = hdr->pnl + 1;
+	quic_get_int(&p, &len, &cb->number, cb->number_len);
+	cb->number = quic_get_num(cb->number_max, cb->number, cb->number_len);
+
+	if (cb->number > cb->number_max)
+		cb->number_max = cb->number;
+}
+
+#define QUIC_PN_LEN_BITS_MASK	0x03
+
+static int quic_crypto_header_decrypt(struct crypto_skcipher *tfm, struct sk_buff *skb, bool chacha)
+{
+	struct quic_skb_cb *cb = QUIC_SKB_CB(skb);
+	struct quichdr *hdr = quic_hdr(skb);
+	int err, i, len = cb->length;
+	struct skcipher_request *req;
+	struct scatterlist sg;
+	u8 *mask, *iv, *p;
+
+	mask = quic_crypto_skcipher_mem_alloc(tfm, QUIC_SAMPLE_LEN, &iv, &req);
+	if (!mask)
+		return -ENOMEM;
+
+	if (len < QUIC_MAX_PN_LEN + QUIC_SAMPLE_LEN) {
+		err = -EINVAL;
+		goto err;
+	}
+
+	/* Similar logic to quic_crypto_header_encrypt(). */
+	p = (u8 *)hdr + cb->number_offset;
+	memcpy((chacha ? iv : mask), p + QUIC_MAX_PN_LEN, QUIC_SAMPLE_LEN);
+	sg_init_one(&sg, mask, QUIC_SAMPLE_LEN);
+	skcipher_request_set_tfm(req, tfm);
+	skcipher_request_set_crypt(req, &sg, &sg, QUIC_SAMPLE_LEN, iv);
+	err = crypto_skcipher_encrypt(req);
+	if (err)
+		goto err;
+
+	p = (u8 *)hdr;
+	*p = (u8)(*p ^ (mask[0] & (((*p & QUIC_HEADER_FORM_BIT) == QUIC_HEADER_FORM_BIT) ?
+				   QUIC_LONG_HEADER_MASK : QUIC_SHORT_HEADER_MASK)));
+	cb->number_len = (*p & QUIC_PN_LEN_BITS_MASK) + 1;
+	p += cb->number_offset;
+	for (i = 0; i < cb->number_len; ++i)
+		*(p + i) = *((u8 *)hdr + cb->number_offset + i) ^ mask[i + 1];
+	quic_crypto_get_header(skb);
+
+err:
+	kfree(mask);
+	return err;
+}
+
+static void *quic_crypto_aead_mem_alloc(struct crypto_aead *tfm, u32 ctx_size,
+					u8 **iv, struct aead_request **req,
+					struct scatterlist **sg, u32 nsg)
+{
+	unsigned int iv_size, req_size;
+	unsigned int len;
+	u8 *mem;
+
+	iv_size = crypto_aead_ivsize(tfm);
+	req_size = sizeof(**req) + crypto_aead_reqsize(tfm);
+
+	len = ctx_size;
+	len += iv_size;
+	len += crypto_aead_alignmask(tfm) & ~(crypto_tfm_ctx_alignment() - 1);
+	len = ALIGN(len, crypto_tfm_ctx_alignment());
+	len += req_size;
+	len = ALIGN(len, __alignof__(struct scatterlist));
+	len += nsg * sizeof(**sg);
+
+	mem = kzalloc(len, GFP_ATOMIC);
+	if (!mem)
+		return NULL;
+
+	*iv = (u8 *)PTR_ALIGN(mem + ctx_size, crypto_aead_alignmask(tfm) + 1);
+	*req = (struct aead_request *)PTR_ALIGN(*iv + iv_size,
+			crypto_tfm_ctx_alignment());
+	*sg = (struct scatterlist *)PTR_ALIGN((u8 *)*req + req_size,
+			__alignof__(struct scatterlist));
+
+	return (void *)mem;
+}
+
+static void quic_crypto_destruct_skb(struct sk_buff *skb)
+{
+	kfree(skb_shinfo(skb)->destructor_arg);
+	sock_efree(skb);
+}
+
+static void quic_crypto_done(void *data, int err)
+{
+	struct sk_buff *skb = data;
+
+	QUIC_SKB_CB(skb)->crypto_done(skb, err);
+}
+
+/* AEAD Usage. */
+static int quic_crypto_payload_encrypt(struct crypto_aead *tfm, struct sk_buff *skb,
+				       u8 *tx_iv, bool ccm)
+{
+	struct quic_skb_cb *cb = QUIC_SKB_CB(skb);
+	struct quichdr *hdr = quic_hdr(skb);
+	u8 *iv, i, nonce[QUIC_IV_LEN];
+	struct aead_request *req;
+	struct sk_buff *trailer;
+	struct scatterlist *sg;
+	u32 nsg, hlen, len;
+	void *ctx;
+	__be64 n;
+	int err;
+
+	len = skb->len;
+	err = skb_cow_data(skb, QUIC_TAG_LEN, &trailer);
+	if (err < 0)
+		return err;
+	nsg = (u32)err;
+	pskb_put(skb, trailer, QUIC_TAG_LEN);
+	hdr->key = cb->key_phase;
+
+	ctx = quic_crypto_aead_mem_alloc(tfm, 0, &iv, &req, &sg, nsg);
+	if (!ctx)
+		return -ENOMEM;
+
+	sg_init_table(sg, nsg);
+	err = skb_to_sgvec(skb, sg, 0, (int)skb->len);
+	if (err < 0)
+		goto err;
+
+	/* rfc9001#section-5.3:
+	 *
+	 * The associated data, A, for the AEAD is the contents of the QUIC header,
+	 * starting from the first byte of either the short or long header, up to and
+	 * including the unprotected packet number.
+	 *
+	 * The nonce, N, is formed by combining the packet protection IV with the packet
+	 * number.  The 62 bits of the reconstructed QUIC packet number in network byte
+	 * order are left-padded with zeros to the size of the IV. The exclusive OR of the
+	 * padded packet number and the IV forms the AEAD nonce.
+	 */
+	hlen = cb->number_offset + cb->number_len;
+	memcpy(nonce, tx_iv, QUIC_IV_LEN);
+	n = cpu_to_be64(cb->number);
+	for (i = 0; i < sizeof(n); i++)
+		nonce[QUIC_IV_LEN - sizeof(n) + i] ^= ((u8 *)&n)[i];
+
+	/* For CCM based ciphers, first byte of IV is a constant. */
+	iv[0] = TLS_AES_CCM_IV_B0_BYTE;
+	memcpy(&iv[ccm], nonce, QUIC_IV_LEN);
+	aead_request_set_tfm(req, tfm);
+	aead_request_set_ad(req, hlen);
+	aead_request_set_crypt(req, sg, sg, len - hlen, iv);
+	aead_request_set_callback(req, CRYPTO_TFM_REQ_MAY_BACKLOG, (void *)quic_crypto_done, skb);
+
+	err = crypto_aead_encrypt(req);
+	if (err == -EINPROGRESS) {
+		/* Will complete asynchronously; set destructor to free context. */
+		skb->destructor = quic_crypto_destruct_skb;
+		skb_shinfo(skb)->destructor_arg = ctx;
+		return err;
+	}
+
+err:
+	kfree(ctx);
+	return err;
+}
+
+static int quic_crypto_payload_decrypt(struct crypto_aead *tfm, struct sk_buff *skb,
+				       u8 *rx_iv, bool ccm)
+{
+	struct quic_skb_cb *cb = QUIC_SKB_CB(skb);
+	u8 *iv, i, nonce[QUIC_IV_LEN];
+	struct aead_request *req;
+	struct sk_buff *trailer;
+	int nsg, hlen, len, err;
+	struct scatterlist *sg;
+	void *ctx;
+	__be64 n;
+
+	len = cb->length + cb->number_offset;
+	hlen = cb->number_offset + cb->number_len;
+	if (len - hlen < QUIC_TAG_LEN)
+		return -EINVAL;
+	nsg = skb_cow_data(skb, 0, &trailer);
+	if (nsg < 0)
+		return nsg;
+	ctx = quic_crypto_aead_mem_alloc(tfm, 0, &iv, &req, &sg, nsg);
+	if (!ctx)
+		return -ENOMEM;
+
+	sg_init_table(sg, nsg);
+	err = skb_to_sgvec(skb, sg, 0, len);
+	if (err < 0)
+		goto err;
+	skb_dst_force(skb);
+
+	/* 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, (void *)quic_crypto_done, skb);
+
+	err = crypto_aead_decrypt(req);
+	if (err == -EINPROGRESS) {
+		skb->destructor = quic_crypto_destruct_skb;
+		skb_shinfo(skb)->destructor_arg = ctx;
+		return err;
+	}
+err:
+	kfree(ctx);
+	return err;
+}
+
 #define QUIC_CIPHER_MIN TLS_CIPHER_AES_GCM_128
 #define QUIC_CIPHER_MAX TLS_CIPHER_CHACHA20_POLY1305
 
@@ -225,6 +562,132 @@ static struct quic_cipher ciphers[QUIC_CIPHER_MAX + 1 - QUIC_CIPHER_MIN] = {
 		    "rfc7539(chacha20,poly1305)", "chacha20", "hmac(sha256)"),
 };
 
+static bool quic_crypto_is_cipher_ccm(struct quic_crypto *crypto)
+{
+	return crypto->cipher_type == TLS_CIPHER_AES_CCM_128;
+}
+
+static bool quic_crypto_is_cipher_chacha(struct quic_crypto *crypto)
+{
+	return crypto->cipher_type == TLS_CIPHER_CHACHA20_POLY1305;
+}
+
+/* Encrypts a QUIC packet before transmission.  This function performs AEAD encryption of
+ * the packet payload and applies header protection. It handles key phase tracking and key
+ * update timing..
+ *
+ * Return: 0 on success, or a negative error code.
+ */
+int quic_crypto_encrypt(struct quic_crypto *crypto, struct sk_buff *skb)
+{
+	u8 *iv, cha, ccm, phase = crypto->key_phase;
+	struct quic_skb_cb *cb = QUIC_SKB_CB(skb);
+	int err;
+
+	cb->key_phase = phase;
+	iv = crypto->tx_iv[phase];
+	/* Packet payload is already encrypted (e.g., resumed from async), proceed to header
+	 * protection only.
+	 */
+	if (cb->resume)
+		goto out;
+
+	/* If a key update is pending and this is the first packet using the new key, save the
+	 * current time. Later used to clear old keys after some time has passed (see
+	 * quic_crypto_decrypt()).
+	 */
+	if (crypto->key_pending && !crypto->key_update_send_time)
+		crypto->key_update_send_time = jiffies_to_usecs(jiffies);
+
+	ccm = quic_crypto_is_cipher_ccm(crypto);
+	err = quic_crypto_payload_encrypt(crypto->tx_tfm[phase], skb, iv, ccm);
+	if (err)
+		return err;
+out:
+	cha = quic_crypto_is_cipher_chacha(crypto);
+	return quic_crypto_header_encrypt(crypto->tx_hp_tfm, skb, cha);
+}
+
+/* Decrypts a QUIC packet after reception.  This function removes header protection,
+ * decrypts the payload, and processes any key updates if the key phase bit changes.
+ *
+ * Return: 0 on success, or a negative error code.
+ */
+int quic_crypto_decrypt(struct quic_crypto *crypto, struct sk_buff *skb)
+{
+	struct quic_skb_cb *cb = QUIC_SKB_CB(skb);
+	u8 *iv, cha, ccm, phase;
+	int err = 0;
+	u32 time;
+
+	/* Payload was decrypted asynchronously.  Proceed with parsing packet number and key
+	 * phase.
+	 */
+	if (cb->resume) {
+		quic_crypto_get_header(skb);
+		goto out;
+	}
+
+	cha = quic_crypto_is_cipher_chacha(crypto);
+	err = quic_crypto_header_decrypt(crypto->rx_hp_tfm, skb, cha);
+	if (err) {
+		pr_debug("%s: hd decrypt err %d\n", __func__, err);
+		return err;
+	}
+
+	/* rfc9001#section-6:
+	 *
+	 * The Key Phase bit allows a recipient to detect a change in keying material without
+	 * needing to receive the first packet that triggered the change. An endpoint that
+	 * notices a changed Key Phase bit updates keys and decrypts the packet that contains
+	 * the changed value.
+	 */
+	if (cb->key_phase != crypto->key_phase && !crypto->key_pending) {
+		if (!crypto->send_ready) /* Not ready for key update. */
+			return -EINVAL;
+		err = quic_crypto_key_update(crypto); /* Perform a key update. */
+		if (err) {
+			cb->errcode = QUIC_TRANSPORT_ERROR_KEY_UPDATE;
+			return err;
+		}
+		cb->key_update = 1; /* Mark packet as triggering key update. */
+	}
+
+	phase = cb->key_phase;
+	iv = crypto->rx_iv[phase];
+	ccm = quic_crypto_is_cipher_ccm(crypto);
+	err = quic_crypto_payload_decrypt(crypto->rx_tfm[phase], skb, iv, ccm);
+	if (err) {
+		if (err == -EINPROGRESS)
+			return err;
+		/* When using the old keys can not decrypt the packets, the peer might
+		 * start another key_update. Thus, clear the last key_pending so that
+		 * next packets will trigger the new key-update.
+		 */
+		if (crypto->key_pending && cb->key_phase != crypto->key_phase) {
+			crypto->key_pending = 0;
+			crypto->key_update_time = 0;
+		}
+		return err;
+	}
+
+out:
+	/* rfc9001#section-6.1:
+	 *
+	 * An endpoint MUST retain old keys until it has successfully unprotected a
+	 * packet sent using the new keys. An endpoint SHOULD retain old keys for
+	 * some time after unprotecting a packet sent using the new keys.
+	 */
+	if (crypto->key_pending && cb->key_phase == crypto->key_phase) {
+		time = crypto->key_update_send_time;
+		if (time && jiffies_to_usecs(jiffies) - time >= crypto->key_update_time) {
+			crypto->key_pending = 0;
+			crypto->key_update_time = 0;
+		}
+	}
+	return err;
+}
+
 int quic_crypto_set_cipher(struct quic_crypto *crypto, u32 type, u8 flag)
 {
 	struct quic_cipher *cipher;
@@ -501,6 +964,201 @@ int quic_crypto_initial_keys_install(struct quic_crypto *crypto, struct quic_con
 	return quic_crypto_set_secret(crypto, &srt, version, 0);
 }
 
+#define QUIC_RETRY_KEY_V1 "\xbe\x0c\x69\x0b\x9f\x66\x57\x5a\x1d\x76\x6b\x54\xe3\x68\xc8\x4e"
+#define QUIC_RETRY_KEY_V2 "\x8f\xb4\xb0\x1b\x56\xac\x48\xe2\x60\xfb\xcb\xce\xad\x7c\xcc\x92"
+
+#define QUIC_RETRY_NONCE_V1 "\x46\x15\x99\xd3\x5d\x63\x2b\xf2\x23\x98\x25\xbb"
+#define QUIC_RETRY_NONCE_V2 "\xd8\x69\x69\xbc\x2d\x7c\x6d\x99\x90\xef\xb0\x4a"
+
+/* Retry Packet Integrity. */
+int quic_crypto_get_retry_tag(struct quic_crypto *crypto, struct sk_buff *skb,
+			      struct quic_conn_id *odcid, u32 version, u8 *tag)
+{
+	struct crypto_aead *tfm = crypto->tag_tfm;
+	u8 *pseudo_retry, *p, *iv, *key;
+	struct aead_request *req;
+	struct scatterlist *sg;
+	u32 plen;
+	int err;
+
+	/* rfc9001#section-5.8:
+	 *
+	 * The Retry Integrity Tag is a 128-bit field that is computed as the output of
+	 * AEAD_AES_128_GCM used with the following inputs:
+	 *
+	 * - The secret key, K, is 128 bits equal to 0xbe0c690b9f66575a1d766b54e368c84e.
+	 * - The nonce, N, is 96 bits equal to 0x461599d35d632bf2239825bb.
+	 * - The plaintext, P, is empty.
+	 * - The associated data, A, is the contents of the Retry Pseudo-Packet,
+	 *
+	 * The Retry Pseudo-Packet is not sent over the wire. It is computed by taking the
+	 * transmitted Retry packet, removing the Retry Integrity Tag, and prepending the
+	 * two following fields: ODCID Length + Original Destination Connection ID (ODCID).
+	 */
+	err = crypto_aead_setauthsize(tfm, QUIC_TAG_LEN);
+	if (err)
+		return err;
+	key = QUIC_RETRY_KEY_V1;
+	if (version == QUIC_VERSION_V2)
+		key = QUIC_RETRY_KEY_V2;
+	err = crypto_aead_setkey(tfm, key, TLS_CIPHER_AES_GCM_128_KEY_SIZE);
+	if (err)
+		return err;
+
+	plen = 1 + odcid->len + skb->len - QUIC_TAG_LEN;
+	pseudo_retry = quic_crypto_aead_mem_alloc(tfm, plen + QUIC_TAG_LEN, &iv, &req, &sg, 1);
+	if (!pseudo_retry)
+		return -ENOMEM;
+
+	p = pseudo_retry;
+	p = quic_put_int(p, odcid->len, 1);
+	p = quic_put_data(p, odcid->data, odcid->len);
+	p = quic_put_data(p, skb->data, skb->len - QUIC_TAG_LEN);
+	sg_init_one(sg, pseudo_retry, plen + QUIC_TAG_LEN);
+
+	memcpy(iv, QUIC_RETRY_NONCE_V1, QUIC_IV_LEN);
+	if (version == QUIC_VERSION_V2)
+		memcpy(iv, QUIC_RETRY_NONCE_V2, QUIC_IV_LEN);
+	aead_request_set_tfm(req, tfm);
+	aead_request_set_ad(req, plen);
+	aead_request_set_crypt(req, sg, sg, 0, iv);
+	err = crypto_aead_encrypt(req);
+	if (!err)
+		memcpy(tag, p, QUIC_TAG_LEN);
+	kfree(pseudo_retry);
+	return err;
+}
+
+/* Generate a token for Retry or address validation.
+ *
+ * Builds a token with the format: [client address][timestamp][original DCID][auth tag]
+ *
+ * Encrypts the token (excluding the first flag byte) using AES-GCM with a key and IV
+ * derived via HKDF. The original DCID is stored to be recovered later from a Client
+ * Initial packet.  Ensures the token is bound to the client address and time, preventing
+ * reuse or tampering.
+ *
+ * Returns 0 on success or a negative error code on failure.
+ */
+int quic_crypto_generate_token(struct quic_crypto *crypto, void *addr, u32 addrlen,
+			       struct quic_conn_id *conn_id, u8 *token, u32 *tlen)
+{
+	u8 key[TLS_CIPHER_AES_GCM_128_KEY_SIZE], iv[QUIC_IV_LEN], *retry_token, *tx_iv, *p;
+	struct crypto_aead *tfm = crypto->tag_tfm;
+	u32 ts = jiffies_to_usecs(jiffies), len;
+	struct quic_data srt = {}, k, i;
+	struct aead_request *req;
+	struct scatterlist *sg;
+	int err;
+
+	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)
+		return err;
+	err = crypto_aead_setauthsize(tfm, QUIC_TAG_LEN);
+	if (err)
+		return err;
+	err = crypto_aead_setkey(tfm, key, TLS_CIPHER_AES_GCM_128_KEY_SIZE);
+	if (err)
+		return err;
+	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)
+		return -ENOMEM;
+
+	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) {
+		memcpy(token, retry_token, len);
+		*tlen = len + 1;
+	}
+	kfree(retry_token);
+	return err;
+}
+
+/* Validate a Retry or address validation token.
+ *
+ * Decrypts the token using derived key and IV. Checks that the decrypted address matches
+ * the provided address, validates the embedded timestamp against current time with a
+ * version-specific timeout. If applicable, it extracts and returns the original
+ * destination connection ID (ODCID) for Retry packets.
+ *
+ * Returns 0 if the token is valid, -EINVAL if invalid, or another negative error code.
+ */
+int quic_crypto_verify_token(struct quic_crypto *crypto, void *addr, u32 addrlen,
+			     struct quic_conn_id *conn_id, u8 *token, u32 len)
+{
+	u32 ts = jiffies_to_usecs(jiffies), timeout = QUIC_TOKEN_TIMEOUT_RETRY;
+	u8 key[TLS_CIPHER_AES_GCM_128_KEY_SIZE], iv[QUIC_IV_LEN];
+	u8 *retry_token, *rx_iv, *p, flag = *token;
+	struct crypto_aead *tfm = crypto->tag_tfm;
+	struct quic_data srt = {}, k, i;
+	struct aead_request *req;
+	struct scatterlist *sg;
+	int err;
+	u64 t;
+
+	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)
+		return err;
+	err = crypto_aead_setauthsize(tfm, QUIC_TAG_LEN);
+	if (err)
+		return err;
+	err = crypto_aead_setkey(tfm, key, TLS_CIPHER_AES_GCM_128_KEY_SIZE);
+	if (err)
+		return err;
+	len--;
+	token++;
+	retry_token = quic_crypto_aead_mem_alloc(tfm, len, &rx_iv, &req, &sg, 1);
+	if (!retry_token)
+		return -ENOMEM;
+
+	memcpy(retry_token, token, len);
+	sg_init_one(sg, retry_token, len);
+	aead_request_set_tfm(req, tfm);
+	aead_request_set_ad(req, addrlen);
+	aead_request_set_crypt(req, sg, sg, len - addrlen, iv);
+	err = crypto_aead_decrypt(req);
+	if (err)
+		goto out;
+
+	err = -EINVAL;
+	p = retry_token;
+	if (memcmp(p, addr, addrlen))
+		goto out;
+	p += addrlen;
+	len -= addrlen;
+	if (flag == QUIC_TOKEN_FLAG_REGULAR)
+		timeout = QUIC_TOKEN_TIMEOUT_REGULAR;
+	if (!quic_get_int(&p, &len, &t, sizeof(ts)) || t + timeout < ts)
+		goto out;
+	len -= QUIC_TAG_LEN;
+	if (len > QUIC_CONN_ID_MAX_LEN)
+		goto out;
+
+	if (flag == QUIC_TOKEN_FLAG_RETRY)
+		quic_conn_id_update(conn_id, p, len);
+	err = 0;
+out:
+	kfree(retry_token);
+	return err;
+}
+
 /* Generate a derived key using HKDF-Extract and HKDF-Expand with a given label. */
 static int quic_crypto_generate_key(struct quic_crypto *crypto, void *data, u32 len,
 				    char *label, u8 *token, u32 key_len)
net/quic/crypto.h +10 -0
index 2bc960a8489e..91ccd0ec0590 100644
@@ -62,6 +62,9 @@ int quic_crypto_get_secret(struct quic_crypto *crypto, struct quic_crypto_secret
 int quic_crypto_set_cipher(struct quic_crypto *crypto, u32 type, u8 flag);
 int quic_crypto_key_update(struct quic_crypto *crypto);
 
+int quic_crypto_encrypt(struct quic_crypto *crypto, struct sk_buff *skb);
+int quic_crypto_decrypt(struct quic_crypto *crypto, struct sk_buff *skb);
+
 int quic_crypto_initial_keys_install(struct quic_crypto *crypto, struct quic_conn_id *conn_id,
 				     u32 version, bool is_serv);
 int quic_crypto_generate_session_ticket_key(struct quic_crypto *crypto, void *data,
@@ -69,5 +72,12 @@ int quic_crypto_generate_session_ticket_key(struct quic_crypto *crypto, void *da
 int quic_crypto_generate_stateless_reset_token(struct quic_crypto *crypto, void *data,
 					       u32 len, u8 *key, u32 key_len);
 
+int quic_crypto_generate_token(struct quic_crypto *crypto, void *addr, u32 addrlen,
+			       struct quic_conn_id *conn_id, u8 *token, u32 *tlen);
+int quic_crypto_get_retry_tag(struct quic_crypto *crypto, struct sk_buff *skb,
+			      struct quic_conn_id *odcid, u32 version, u8 *tag);
+int quic_crypto_verify_token(struct quic_crypto *crypto, void *addr, u32 addrlen,
+			     struct quic_conn_id *conn_id, u8 *token, u32 len);
+
 void quic_crypto_free(struct quic_crypto *crypto);
 void quic_crypto_init(void);
Signature · 1 line
2.47.1
author
#32 lore raw

PATCH v2 13/15 quic: add timer management

In reply to Xin Long

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

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

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

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

- quic_timer_stop(): Stop a timer with type

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

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

---
 net/quic/Makefile |   2 +-
 net/quic/socket.c |  33 ++++++++
 net/quic/socket.h |  33 ++++++++
 net/quic/timer.c  | 196 ++++++++++++++++++++++++++++++++++++++++++++++
 net/quic/timer.h  |  47 +++++++++++
 5 files changed, 310 insertions(+), 1 deletion(-)
 create mode 100644 net/quic/timer.c
 create mode 100644 net/quic/timer.h
net/quic/Makefile +1 -1
index 58bb18f7926d..2ccf01ad9e22 100644
@@ -6,4 +6,4 @@
 obj-$(CONFIG_IP_QUIC) += quic.o
 
 quic-y := common.o family.o protocol.o socket.o stream.o connid.o path.o \
-	  cong.o pnspace.o crypto.o
+	  cong.o pnspace.o crypto.o timer.o
net/quic/socket.c +33 -0
index 2425494a3df3..cbcfec3a02b2 100644
@@ -48,6 +48,8 @@ static int quic_init_sock(struct sock *sk)
 	quic_conn_id_set_init(quic_dest(sk), 0);
 	quic_cong_init(quic_cong(sk));
 
+	quic_timer_init(sk);
+
 	if (quic_stream_init(quic_streams(sk)))
 		return -ENOMEM;
 
@@ -71,6 +73,8 @@ static void quic_destroy_sock(struct sock *sk)
 {
 	u8 i;
 
+	quic_timer_free(sk);
+
 	for (i = 0; i < QUIC_PNSPACE_MAX; i++)
 		quic_pnspace_free(quic_pnspace(sk, i));
 	for (i = 0; i < QUIC_CRYPTO_MAX; i++)
@@ -209,6 +213,35 @@ EXPORT_SYMBOL_GPL(quic_kernel_getsockopt);
 
 static void quic_release_cb(struct sock *sk)
 {
+	/* Similar to tcp_release_cb(). */
+	unsigned long nflags, flags = smp_load_acquire(&sk->sk_tsq_flags);
+
+	do {
+		if (!(flags & QUIC_DEFERRED_ALL))
+			return;
+		nflags = flags & ~QUIC_DEFERRED_ALL;
+	} while (!try_cmpxchg(&sk->sk_tsq_flags, &flags, nflags));
+
+	if (flags & QUIC_F_LOSS_DEFERRED) {
+		quic_timer_loss_handler(sk);
+		__sock_put(sk);
+	}
+	if (flags & QUIC_F_SACK_DEFERRED) {
+		quic_timer_sack_handler(sk);
+		__sock_put(sk);
+	}
+	if (flags & QUIC_F_PATH_DEFERRED) {
+		quic_timer_path_handler(sk);
+		__sock_put(sk);
+	}
+	if (flags & QUIC_F_PMTU_DEFERRED) {
+		quic_timer_pmtu_handler(sk);
+		__sock_put(sk);
+	}
+	if (flags & QUIC_F_TSQ_DEFERRED) {
+		quic_timer_pace_handler(sk);
+		__sock_put(sk);
+	}
 }
 
 static int quic_disconnect(struct sock *sk, int flags)
net/quic/socket.h +33 -0
index c8df98351c6b..b3cf31e005ce 100644
@@ -21,6 +21,7 @@
 #include "cong.h"
 
 #include "protocol.h"
+#include "timer.h"
 
 extern struct proto quic_prot;
 extern struct proto quicv6_prot;
@@ -32,6 +33,31 @@ enum quic_state {
 	QUIC_SS_ESTABLISHED	= TCP_ESTABLISHED,
 };
 
+enum quic_tsq_enum {
+	QUIC_MTU_REDUCED_DEFERRED,
+	QUIC_LOSS_DEFERRED,
+	QUIC_SACK_DEFERRED,
+	QUIC_PATH_DEFERRED,
+	QUIC_PMTU_DEFERRED,
+	QUIC_TSQ_DEFERRED,
+};
+
+enum quic_tsq_flags {
+	QUIC_F_MTU_REDUCED_DEFERRED	= BIT(QUIC_MTU_REDUCED_DEFERRED),
+	QUIC_F_LOSS_DEFERRED		= BIT(QUIC_LOSS_DEFERRED),
+	QUIC_F_SACK_DEFERRED		= BIT(QUIC_SACK_DEFERRED),
+	QUIC_F_PATH_DEFERRED		= BIT(QUIC_PATH_DEFERRED),
+	QUIC_F_PMTU_DEFERRED		= BIT(QUIC_PMTU_DEFERRED),
+	QUIC_F_TSQ_DEFERRED		= BIT(QUIC_TSQ_DEFERRED),
+};
+
+#define QUIC_DEFERRED_ALL (QUIC_F_MTU_REDUCED_DEFERRED |	\
+			   QUIC_F_LOSS_DEFERRED |		\
+			   QUIC_F_SACK_DEFERRED |		\
+			   QUIC_F_PATH_DEFERRED |		\
+			   QUIC_F_PMTU_DEFERRED |		\
+			   QUIC_F_TSQ_DEFERRED)
+
 struct quic_sock {
 	struct inet_sock		inet;
 	struct list_head		reqs;
@@ -48,6 +74,8 @@ struct quic_sock {
 	struct quic_cong		cong;
 	struct quic_pnspace		space[QUIC_PNSPACE_MAX];
 	struct quic_crypto		crypto[QUIC_CRYPTO_MAX];
+
+	struct quic_timer		timers[QUIC_TIMER_MAX];
 };
 
 struct quic6_sock {
@@ -125,6 +153,11 @@ static inline struct quic_crypto *quic_crypto(const struct sock *sk, u8 level)
 	return &quic_sk(sk)->crypto[level];
 }
 
+static inline void *quic_timer(const struct sock *sk, u8 type)
+{
+	return (void *)&quic_sk(sk)->timers[type];
+}
+
 static inline bool quic_is_establishing(struct sock *sk)
 {
 	return sk->sk_state == QUIC_SS_ESTABLISHING;
net/quic/timer.c new file +196 -0

196 lines not on this page · raw message

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

47 lines not on this page · raw message

Signature · 1 line
2.47.1
author
#33 lore raw

PATCH v2 14/15 quic: add frame encoder and decoder base

In reply to Xin Long

This patch introduces 'quic_frame' to represent QUIC frames and 'quic_frame_ops' to define the associated operations for encoding, processing, and acknowledgment.

This abstraction sets the foundation for flexible and modular frame handling. While core operations are defined, actual implementation will follow in subsequent patches once packet handling and inqueue/outqueue infrastructure are in place.

The patch introduces hooks for invoking frame-specific logic:

- quic_frame_create(): Invoke the .create operation of the frame.

- quic_frame_process(): Invoke the .process operation of the frame.

- quic_frame_ack(): Invoke the .ack operation of the frame.

To manage frame lifecycles, reference counting is used, supported by

- quic_frame_get(): Increment the reference count of a frame.

- quic_frame_put(): Decrement the reference count of a frame.

- quic_frame_alloc(): Allocate a frame and set its data.

Frames are allocated through quic_frame_alloc(), and a dedicated kmem_cache (quic_frame_cachep) is added to optimize memory usage.

For STREAM frames, additional data can be appended using

- quic_frame_stream_append(): Append more data to a STREAM frame.

---
 net/quic/Makefile   |   2 +-
 net/quic/frame.c    | 558 ++++++++++++++++++++++++++++++++++++++++++++
 net/quic/frame.h    | 195 ++++++++++++++++
 net/quic/protocol.c |   9 +
 net/quic/protocol.h |   1 +
 net/quic/socket.h   |   2 +
 6 files changed, 766 insertions(+), 1 deletion(-)
 create mode 100644 net/quic/frame.c
 create mode 100644 net/quic/frame.h
net/quic/Makefile +1 -1
index 2ccf01ad9e22..645ee470c95e 100644
@@ -6,4 +6,4 @@
 obj-$(CONFIG_IP_QUIC) += quic.o
 
 quic-y := common.o family.o protocol.o socket.o stream.o connid.o path.o \
-	  cong.o pnspace.o crypto.o timer.o
+	  cong.o pnspace.o crypto.o timer.o frame.o
net/quic/frame.c new file +558 -0

558 lines not on this page · raw message

net/quic/frame.h new file +195 -0

195 lines not on this page · raw message

net/quic/protocol.c +9 -0
index fb98ef10f852..4725e3aa7785 100644
@@ -21,6 +21,7 @@
 
 static unsigned int quic_net_id __read_mostly;
 
+struct kmem_cache *quic_frame_cachep __read_mostly;
 struct percpu_counter quic_sockets_allocated;
 
 long sysctl_quic_mem[3];
@@ -335,6 +336,11 @@ static __init int quic_init(void)
 
 	quic_crypto_init();
 
+	quic_frame_cachep = kmem_cache_create("quic_frame", sizeof(struct quic_frame),
+					      0, SLAB_HWCACHE_ALIGN, NULL);
+	if (!quic_frame_cachep)
+		goto err;
+
 	err = percpu_counter_init(&quic_sockets_allocated, 0, GFP_KERNEL);
 	if (err)
 		goto err_percpu_counter;
@@ -363,6 +369,8 @@ static __init int quic_init(void)
 err_hash:
 	percpu_counter_destroy(&quic_sockets_allocated);
 err_percpu_counter:
+	kmem_cache_destroy(quic_frame_cachep);
+err:
 	return err;
 }
 
@@ -375,6 +383,7 @@ static __exit void quic_exit(void)
 	unregister_pernet_subsys(&quic_net_ops);
 	quic_hash_tables_destroy();
 	percpu_counter_destroy(&quic_sockets_allocated);
+	kmem_cache_destroy(quic_frame_cachep);
 	pr_info("quic: exit\n");
 }
 
net/quic/protocol.h +1 -0
index 1df926ef0a75..92ad261199c1 100644
@@ -8,6 +8,7 @@
  *    Xin Long <[email protected]>
  */
 
+extern struct kmem_cache *quic_frame_cachep __read_mostly;
 extern struct percpu_counter quic_sockets_allocated;
 
 extern long sysctl_quic_mem[3];
net/quic/socket.h +2 -0
index b3cf31e005ce..75cb90177a01 100644
@@ -20,6 +20,8 @@
 #include "path.h"
 #include "cong.h"
 
+#include "frame.h"
+
 #include "protocol.h"
 #include "timer.h"
 
Signature · 1 line
2.47.1
author
#34 lore raw

PATCH v2 15/15 quic: add packet builder and parser base

In reply to Xin Long

This patch introduces 'quic_packet' to handle packing and unpacking of QUIC packets on both the transmit (TX) and receive (RX) paths.

On the 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_tail(): Append a frame to the packet for transmission.

- quic_packet_create(): Create 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.

On the RX side, the patch 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().

---
 net/quic/Makefile   |   2 +-
 net/quic/packet.c   | 892 ++++++++++++++++++++++++++++++++++++++++++++
 net/quic/packet.h   | 129 +++++++
 net/quic/protocol.c |   7 +
 net/quic/socket.c   | 114 ++++++
 net/quic/socket.h   |  12 +
 6 files changed, 1155 insertions(+), 1 deletion(-)
 create mode 100644 net/quic/packet.c
 create mode 100644 net/quic/packet.h
net/quic/Makefile +1 -1
index 645ee470c95e..4a43052eb441 100644
@@ -6,4 +6,4 @@
 obj-$(CONFIG_IP_QUIC) += quic.o
 
 quic-y := common.o family.o protocol.o socket.o stream.o connid.o path.o \
-	  cong.o pnspace.o crypto.o timer.o frame.o
+	  cong.o pnspace.o crypto.o timer.o frame.o packet.o
net/quic/packet.c new file +892 -0

892 lines not on this page · raw message

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

129 lines not on this page · raw message

net/quic/protocol.c +7 -0
index 4725e3aa7785..65f2036fb5f3 100644
@@ -352,6 +352,10 @@ static __init int quic_init(void)
 	if (err)
 		goto err_def_ops;
 
+	err = quic_path_init(quic_packet_rcv);
+	if (err)
+		goto err_path;
+
 	err = quic_protosw_init();
 	if (err)
 		goto err_protosw;
@@ -363,6 +367,8 @@ static __init int quic_init(void)
 	return 0;
 
 err_protosw:
+	quic_path_destroy();
+err_path:
 	unregister_pernet_subsys(&quic_net_ops);
 err_def_ops:
 	quic_hash_tables_destroy();
@@ -380,6 +386,7 @@ static __exit void quic_exit(void)
 	quic_sysctl_unregister();
 #endif
 	quic_protosw_exit();
+	quic_path_destroy();
 	unregister_pernet_subsys(&quic_net_ops);
 	quic_hash_tables_destroy();
 	percpu_counter_destroy(&quic_sockets_allocated);
net/quic/socket.c +114 -0

144 lines not on this page · raw message

net/quic/socket.h +12 -0
index 75cb90177a01..99cc460158a0 100644
@@ -20,6 +20,7 @@
 #include "path.h"
 #include "cong.h"
 
+#include "packet.h"
 #include "frame.h"
 
 #include "protocol.h"
@@ -77,6 +78,7 @@ struct quic_sock {
 	struct quic_pnspace		space[QUIC_PNSPACE_MAX];
 	struct quic_crypto		crypto[QUIC_CRYPTO_MAX];
 
+	struct quic_packet		packet;
 	struct quic_timer		timers[QUIC_TIMER_MAX];
 };
 
@@ -155,6 +157,11 @@ static inline struct quic_crypto *quic_crypto(const struct sock *sk, u8 level)
 	return &quic_sk(sk)->crypto[level];
 }
 
+static inline struct quic_packet *quic_packet(const struct sock *sk)
+{
+	return &quic_sk(sk)->packet;
+}
+
 static inline void *quic_timer(const struct sock *sk, u8 type)
 {
 	return (void *)&quic_sk(sk)->timers[type];
@@ -200,3 +207,8 @@ static inline void quic_set_state(struct sock *sk, int state)
 	inet_sk_set_state(sk, state);
 	sk->sk_state_change(sk);
 }
+
+struct sock *quic_listen_sock_lookup(struct sk_buff *skb, union quic_addr *sa, union quic_addr *da,
+				     struct quic_data *alpns);
+struct sock *quic_sock_lookup(struct sk_buff *skb, union quic_addr *sa, union quic_addr *da,
+			      struct quic_conn_id *dcid);
Signature · 1 line
2.47.1
#35 lore raw
In reply to Xin Long

(Note: This is an interface more than implementation question --- apologies in advanced if this is not the right place to ask. I originally sent this message to [0] about the IETF internet draft [1], but then I realized that is just an alias for the draft authors, and not a public mailing list, so I figured this would be better in order to have something in the public record.)

---

I was surprised to see that (if I understand correctly) in the current design, all communication over one connection must happen with the same socket, and instead stream ids are the sole mechanism to distinguish between different streams (e.g. for sending and receiving).

This does work, but it is bad for application programming which wants to take advantage of separate streams while being transport-agnostic. For example, it would be very nice to run an arbitrary program with stdout and stderr hooked up to separate QUIC streams. This can be elegantly accomplished if there is an option to create a fresh socket / file descriptor which is just associated with a single stream. Then "regular" send/rescv, or even read/write, can be used with multiple streams.

I see that the SCTP socket interface has sctp_peeloff [2] for this purpose. Could something similar be included in this specification?

John

[0]: [email protected]
[1]: https://datatracker.ietf.org/doc/draft-lxin-quic-socket-apis/
[2]: https://man7.org/linux/man-pages/man3/sctp_peeloff.3.html

author
#36 lore raw
Quoted 10 lines · John Ericson

(Note: This is an interface more than implementation question --- apologies in advanced if this is not the right place to ask. I originally sent this message to [0] about the IETF internet draft [1], but then I realized that is just an alias for the draft authors, and not a public mailing list, so I figured this would be better in order to have something in the public record.)

---

I was surprised to see that (if I understand correctly) in the current design, all communication over one connection must happen with the same socket, and instead stream ids are the sole mechanism to distinguish between different streams (e.g. for sending and receiving).

This does work, but it is bad for application programming which wants to take advantage of separate streams while being transport-agnostic. For example, it would be very nice to run an arbitrary program with stdout and stderr hooked up to separate QUIC streams. This can be elegantly accomplished if there is an option to create a fresh socket / file descriptor which is just associated with a single stream. Then "regular" send/rescv, or even read/write, can be used with multiple streams.

I see that the SCTP socket interface has sctp_peeloff [2] for this purpose. Could something similar be included in this specification?

Hi, John,

That is a bit different. In SCTP, sctp_peeloff() detaches an association/connection from a one-to-many socket and returns it as a new socket. It does not peel off a stream. Stream send/receive operations in SCTP are actually quite similar to how QUIC handles streams in the proposed QUIC socket API.

For QUIC, supporting 'stream peeloff' might mean creating a new socket type that carries a stream ID and maps its sendmsg/recvmsg to the 'parent' QUIC socket. But there are details to sort out, like whether the 'parent-child relationship' should be maintained. We also need to consider whether this is worth implementing in the kernel, or if a similar API could be provided in libquic.

I’ll be requesting a mailing list for QUIC development and new interfaces, and this would be a good topic to continue there.

Thanks for your comment.

author
#37 lore raw
Quoted 27 lines · Xin Long
Quoted 10 lines · John Ericson

(Note: This is an interface more than implementation question --- apologies in advanced if this is not the right place to ask. I originally sent this message to [0] about the IETF internet draft [1], but then I realized that is just an alias for the draft authors, and not a public mailing list, so I figured this would be better in order to have something in the public record.)

---

I was surprised to see that (if I understand correctly) in the current design, all communication over one connection must happen with the same socket, and instead stream ids are the sole mechanism to distinguish between different streams (e.g. for sending and receiving).

This does work, but it is bad for application programming which wants to take advantage of separate streams while being transport-agnostic. For example, it would be very nice to run an arbitrary program with stdout and stderr hooked up to separate QUIC streams. This can be elegantly accomplished if there is an option to create a fresh socket / file descriptor which is just associated with a single stream. Then "regular" send/rescv, or even read/write, can be used with multiple streams.

I see that the SCTP socket interface has sctp_peeloff [2] for this purpose. Could something similar be included in this specification?

Hi, John,

That is a bit different. In SCTP, sctp_peeloff() detaches an association/connection from a one-to-many socket and returns it as a new socket. It does not peel off a stream. Stream send/receive operations in SCTP are actually quite similar to how QUIC handles streams in the proposed QUIC socket API.

For QUIC, supporting 'stream peeloff' might mean creating a new socket type that carries a stream ID and maps its sendmsg/recvmsg to the 'parent' QUIC socket. But there are details to sort out, like whether the 'parent-child relationship' should be maintained. We also need to consider whether this is worth implementing in the kernel, or if a similar API could be provided in libquic.

Quoted 3 lines

I’ll be requesting a mailing list for QUIC development and new interfaces, and this would be a good topic to continue there.

Hi, John,

Feel free to create a thread on [email protected] for this.

Thanks.

#38 lore raw
Quoted 5 lines · Xin Long

Hi, John,

Feel free to create a thread on [email protected] for this.

Thanks.

Kicking of the new linux QUIC dev mailing list with this, as requested.

(The last email in netdev is
https://lore.kernel.org/netdev/CADvbK_e9sNbvHSCNuvetOCFY5OQPG99tmZLW=odcRzcN9xK8rQ@mail.gmail.com/,
for reference.)

Quoted 32 lines · Xin Long
Quoted 30 lines · John Ericson
Quoted 28 lines

(Note: This is an interface more than implementation question --- apologies in advanced if this is not the right place to ask. I originally sent this message to [0] about the IETF internet draft [1], but then I realized that is just an alias for the draft authors, and not a public mailing list, so I figured this would be better in order to have something in the public record.)

---

I was surprised to see that (if I understand correctly) in the current design, all communication over one connection must happen with the same socket, and instead stream ids are the sole mechanism to distinguish between different streams (e.g. for sending and receiving).

This does work, but it is bad for application programming which wants to take advantage of separate streams while being transport-agnostic. For example, it would be very nice to run an arbitrary program with stdout and stderr hooked up to separate QUIC streams. This can be elegantly accomplished if there is an option to create a fresh socket / file descriptor which is just associated with a single stream. Then "regular" send/rescv, or even read/write, can be used with multiple streams.

I see that the SCTP socket interface has sctp_peeloff [2] for this purpose. Could something similar be included in this specification?

Quoted 7 lines
Quoted 7 lines

Hi, John,

That is a bit different. In SCTP, sctp_peeloff() detaches an association/connection from a one-to-many socket and returns it as a new socket. It does not peel off a stream. Stream send/receive operations in SCTP are actually quite similar to how QUIC handles streams in the proposed QUIC socket API.

OK fair enough. sctp_peeloff() was the closest prior art I could find, but I don't know much about SCTP. Rest assured, I did have the QUIC semantics in mind. E.g. closing one of these QUIC per-stream peeled off sockets should close just the stream in question, not the entire connection.

Quoted 3 lines
Quoted 3 lines

For QUIC, supporting 'stream peeloff' might mean creating a new socket type that carries a stream ID and maps its sendmsg/recvmsg to the 'parent' QUIC socket.

Yes, exactly.

Quoted 2 lines
Quoted 2 lines

But there are details to sort out, like whether the 'parent-child relationship' should be maintained.

What do you mean by this? I assume the answer is that it should be maintained? e.g. if the connection is closed, then any child per-stream sockets are also invalidated and must be closed.

Quoted 2 lines
Quoted 2 lines

We also need to consider whether this is worth implementing in the kernel, or if a similar API could be provided in libquic.

So this is sort of the crux of my argument. If it is in userland, then any application that wants to act per-stream needs to know about QUIC. But if it is in kernel, just a a tiny bit of QUIC-aware glue code is to plug together QUIC-agnostic software, by passing stream sockets to that software. (You could do it by passing pipes and a little userland man-in-the-middle using *quic_sendmsg and quic_recvmsg*, of course, but those extra context switches and copies are rather lousy.)

For what it's worth, I would go further in fact and say that this "stream peeloff" system call should not just be supported by QUIC, too. It is very nice today how many code can be agnostic to TCP vs unix domain sockets, for example. I would ideally want the same thing to be true with QUIC too, via an "extended unix domain socket" that would replicate the QUIC state machine(s) just as regular unix domain sockets replicate the TCP state machine.

I bring up such an "extended unix domain socket" not to indulge in scope creep, but just to point out that a good litmus test for a new socket interface is that multiple domains could meaningfully support it, and that litmus test is met in this case.

Cheers,

John

#39 lore raw

Separate sockets for separate connections

Aye, I did everything I meant to do email-wise but change the subject line to something more appropriate. Let me just reply to myself right away doing that before there are more messages.

John

Quoted 99 lines · John Ericson
Quoted 5 lines · Xin Long

Hi, John,

Feel free to create a thread on [email protected] for this.

Thanks.

Kicking of the new linux QUIC dev mailing list with this, as requested.

(The last email in netdev is
https://lore.kernel.org/netdev/CADvbK_e9sNbvHSCNuvetOCFY5OQPG99tmZLW=odcRzcN9xK8rQ@mail.gmail.com/,
for reference.)

Quoted 32 lines · Xin Long
Quoted 30 lines · John Ericson
Quoted 28 lines

(Note: This is an interface more than implementation question --- apologies in advanced if this is not the right place to ask. I originally sent this message to [0] about the IETF internet draft [1], but then I realized that is just an alias for the draft authors, and not a public mailing list, so I figured this would be better in order to have something in the public record.)

---

I was surprised to see that (if I understand correctly) in the current design, all communication over one connection must happen with the same socket, and instead stream ids are the sole mechanism to distinguish between different streams (e.g. for sending and receiving).

This does work, but it is bad for application programming which wants to take advantage of separate streams while being transport-agnostic. For example, it would be very nice to run an arbitrary program with stdout and stderr hooked up to separate QUIC streams. This can be elegantly accomplished if there is an option to create a fresh socket / file descriptor which is just associated with a single stream. Then "regular" send/rescv, or even read/write, can be used with multiple streams.

I see that the SCTP socket interface has sctp_peeloff [2] for this purpose. Could something similar be included in this specification?

Quoted 7 lines
Quoted 7 lines

Hi, John,

That is a bit different. In SCTP, sctp_peeloff() detaches an association/connection from a one-to-many socket and returns it as a new socket. It does not peel off a stream. Stream send/receive operations in SCTP are actually quite similar to how QUIC handles streams in the proposed QUIC socket API.

OK fair enough. sctp_peeloff() was the closest prior art I could find, but I don't know much about SCTP. Rest assured, I did have the QUIC semantics in mind. E.g. closing one of these QUIC per-stream peeled off sockets should close just the stream in question, not the entire connection.

Quoted 3 lines
Quoted 3 lines

For QUIC, supporting 'stream peeloff' might mean creating a new socket type that carries a stream ID and maps its sendmsg/recvmsg to the 'parent' QUIC socket.

Yes, exactly.

Quoted 2 lines
Quoted 2 lines

But there are details to sort out, like whether the 'parent-child relationship' should be maintained.

What do you mean by this? I assume the answer is that it should be maintained? e.g. if the connection is closed, then any child per-stream sockets are also invalidated and must be closed.

Quoted 2 lines
Quoted 2 lines

We also need to consider whether this is worth implementing in the kernel, or if a similar API could be provided in libquic.

So this is sort of the crux of my argument. If it is in userland, then any application that wants to act per-stream needs to know about QUIC. But if it is in kernel, just a a tiny bit of QUIC-aware glue code is to plug together QUIC-agnostic software, by passing stream sockets to that software. (You could do it by passing pipes and a little userland man-in-the-middle using *quic_sendmsg and quic_recvmsg*, of course, but those extra context switches and copies are rather lousy.)

For what it's worth, I would go further in fact and say that this "stream peeloff" system call should not just be supported by QUIC, too. It is very nice today how many code can be agnostic to TCP vs unix domain sockets, for example. I would ideally want the same thing to be true with QUIC too, via an "extended unix domain socket" that would replicate the QUIC state machine(s) just as regular unix domain sockets replicate the TCP state machine.

I bring up such an "extended unix domain socket" not to indulge in scope creep, but just to point out that a good litmus test for a new socket interface is that multiple domains could meaningfully support it, and that litmus test is met in this case.

Cheers,

John

A message that is not in the archive (sent off-list or not yet received): [email protected]

author
#41 lore raw

Re: Separate sockets for separate connections

Quoted 5 lines · John Ericson

Aye, I did everything I meant to do email-wise but change the subject line to something more appropriate. Let me just reply to myself right away doing that before there are more messages.

Thanks for opening this thread with a new subject!

Quoted 56 lines · John Ericson
Quoted 5 lines · Xin Long

Hi, John,

Feel free to create a thread on [email protected] for this.

Thanks.

Kicking of the new linux QUIC dev mailing list with this, as requested.

(The last email in netdev is
https://lore.kernel.org/netdev/CADvbK_e9sNbvHSCNuvetOCFY5OQPG99tmZLW=odcRzcN9xK8rQ@mail.gmail.com/,
for reference.)

Quoted 32 lines · Xin Long
Quoted 30 lines · John Ericson
Quoted 28 lines

(Note: This is an interface more than implementation question --- apologies in advanced if this is not the right place to ask. I originally sent this message to [0] about the IETF internet draft [1], but then I realized that is just an alias for the draft authors, and not a public mailing list, so I figured this would be better in order to have something in the public record.)

---

I was surprised to see that (if I understand correctly) in the current design, all communication over one connection must happen with the same socket, and instead stream ids are the sole mechanism to distinguish between different streams (e.g. for sending and receiving).

This does work, but it is bad for application programming which wants to take advantage of separate streams while being transport-agnostic. For example, it would be very nice to run an arbitrary program with stdout and stderr hooked up to separate QUIC streams. This can be elegantly accomplished if there is an option to create a fresh socket / file descriptor which is just associated with a single stream. Then "regular" send/rescv, or even read/write, can be used with multiple streams.

I see that the SCTP socket interface has sctp_peeloff [2] for this purpose. Could something similar be included in this specification?

Quoted 7 lines
Quoted 7 lines

Hi, John,

That is a bit different. In SCTP, sctp_peeloff() detaches an association/connection from a one-to-many socket and returns it as a new socket. It does not peel off a stream. Stream send/receive operations in SCTP are actually quite similar to how QUIC handles streams in the proposed QUIC socket API.

Quoted 6 lines

OK fair enough. sctp_peeloff() was the closest prior art I could find, but I don't know much about SCTP. Rest assured, I did have the QUIC semantics in mind. E.g. closing one of these QUIC per-stream peeled off sockets should close just the stream in question, not the entire connection.

I wrote some code to explore this:
https://github.com/lxin/quic/pull/53

- A stream can be peeled off from a parent/connection socket using getsockopt(QUIC_SOCKOPT_STREAM_PEELOFF) with a stream_id, similar to SCTP's connection peeloff.

- For stream sockets, in addition to send(), recv(), and close(), support for poll() and shutdown() is also implemented. Note that close() and shutdown() send a FIN on the sending side, issue a STOP_SENDING on the receiving side, or both for bidirectional streams, as applicable.

There's also a sample test:
https://github.com/lxin/quic/blob/stream-peeloff/tests/peeloff_test.c

- Sender: Opens a stream with getsockopt(QUIC_SOCKOPT_STREAM_OPEN), peels it off with getsockopt(QUIC_SOCKOPT_STREAM_PEELOFF), and sends data via the new file descriptor.

- Receiver: Detects stream creation through a QUIC_EVENT_STREAM_UPDATE event, peels off the stream with getsockopt(QUIC_SOCKOPT_STREAM_PEELOFF), and receives data via the new file descriptor.

Quoted 4 lines
Quoted 3 lines
Quoted 3 lines

For QUIC, supporting 'stream peeloff' might mean creating a new socket type that carries a stream ID and maps its sendmsg/recvmsg to the 'parent' QUIC socket.

Quoted 9 lines

Yes, exactly.

Quoted 2 lines
Quoted 2 lines

But there are details to sort out, like whether the 'parent-child relationship' should be maintained.

What do you mean by this? I assume the answer is that it should be maintained? e.g. if the connection is closed, then any child per-stream sockets are also invalidated and must be closed.

I aimed to make a peeled-off stream socket fully independent to keep the design simple. However, since the connection socket may close at any time, the stream socket must hold a reference to it. To keep the relationship strictly one-way, the connection socket remains unaware of any peeled-off stream sockets.

Quoted 11 lines
Quoted 2 lines
Quoted 2 lines

We also need to consider whether this is worth implementing in the kernel, or if a similar API could be provided in libquic.

So this is sort of the crux of my argument. If it is in userland, then any application that wants to act per-stream needs to know about QUIC. But if it is in kernel, just a a tiny bit of QUIC-aware glue code is to plug together QUIC-agnostic software, by passing stream sockets to that software. (You could do it by passing pipes and a little userland man-in-the-middle using *quic_sendmsg and quic_recvmsg*, of course, but those extra context switches and copies are rather lousy.)

Right, providing it in libquic won't work for kernel consumers.

Quoted 8 lines

For what it's worth, I would go further in fact and say that this "stream peeloff" system call should not just be supported by QUIC, too. It is very nice today how many code can be agnostic to TCP vs unix domain sockets, for example. I would ideally want the same thing to be true with QUIC too, via an "extended unix domain socket" that would replicate the QUIC state machine(s) just as regular unix domain sockets replicate the TCP state machine.

Quoted 5 lines

I bring up such an "extended unix domain socket" not to indulge in scope creep, but just to point out that a good litmus test for a new socket interface is that multiple domains could meaningfully support it, and that litmus test is met in this case.

That’s a good point. Stream peel-off is an idea I find quite interesting. I originally based the QUIC stream API on SCTP’s model, but it now seems that most real-world use cases are closer to applications that traditionally relied on TCP rather than SCTP.

Please check out the stream socket interfaces in the PR above and comment on anything that you think could be improved.

Thanks.

#42 lore raw

Re: Separate sockets for separate connections

(Sorry for not responding to this sooner, I was attending NixCon 2025)

Quoted 16 lines · Xin Long

I wrote some code to explore this:
https://github.com/lxin/quic/pull/53

- A stream can be peeled off from a parent/connection socket using getsockopt(QUIC_SOCKOPT_STREAM_PEELOFF) with a stream_id, similar to SCTP's connection peeloff.

- For stream sockets, in addition to send(), recv(), and close(), support for poll() and shutdown() is also implemented. Note that close() and shutdown() send a FIN on the sending side, issue a STOP_SENDING on the receiving side, or both for bidirectional streams, as applicable.

There's also a sample test:
https://github.com/lxin/quic/blob/stream-peeloff/tests/peeloff_test.c

Quoted 7 lines

- Sender: Opens a stream with getsockopt(QUIC_SOCKOPT_STREAM_OPEN), peels it off with getsockopt(QUIC_SOCKOPT_STREAM_PEELOFF), and sends data via the new file descriptor.

- Receiver: Detects stream creation through a QUIC_EVENT_STREAM_UPDATE event, peels off the stream with getsockopt(QUIC_SOCKOPT_STREAM_PEELOFF), and receives data via the new file descriptor.

Fantastic! Thank you for investigating this; I really appreciate it.

Quoted 2 lines

Please check out the stream socket interfaces in the PR above and comment on anything that you think could be improved.

Gladly! I looked at the linked PR and left some comment now, but they are only "mild questions". The basic design here looks like exactly like what I was hoping for. Hooray!

Quoted 5 lines

I aimed to make a peeled-off stream socket fully independent to keep the design simple. However, since the connection socket may close at any time, the stream socket must hold a reference to it. To keep the relationship strictly one-way, the connection socket remains unaware of any peeled-off stream sockets.

That makes sense to me. Maybe someday someone will want "please close connection when the last stream socket is closed", but that can come later. (If I understand what sock_hold is doing with ref counts correctly, I'd hope that would be possible without any bidirectional references.)

Quoted 1 line

Right, providing it in libquic won't work for kernel consumers.

Just to be clear, I was thinking of one user-land process sending the stream socket to another. But yes, kernel consumer would face the exact same issues as other userspace processes without this.

Quoted 8 lines
Quoted 8 lines

For what it's worth, I would go further in fact and say that this "stream peeloff" system call should not just be supported by QUIC, too. It is very nice today how many code can be agnostic to TCP vs unix domain sockets, for example. I would ideally want the same thing to be true with QUIC too, via an "extended unix domain socket" that would replicate the QUIC state machine(s) just as regular unix domain sockets replicate the TCP state machine.

Quoted 6 lines
Quoted 5 lines

I bring up such an "extended unix domain socket" not to indulge in scope creep, but just to point out that a good litmus test for a new socket interface is that multiple domains could meaningfully support it, and that litmus test is met in this case.

That’s a good point.

Glad you like it! I had been thinking abstractly at that point. But putting on my hat as a Nix developer, yes

Quoted 3 lines

I originally based the QUIC stream API on SCTP’s model, but it now seems that most real-world use cases are closer to applications that traditionally relied on TCP rather than SCTP.

I don't really know enough about who is using SCTP to know for sure, but yes I think that it is right :).

Cheers,

John