net: introduce QUIC infrastructure and core subcomponents
Subsystems (6)
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:
Test Coverage
=============
The Coverage (gcov) of Functional and Interop Tests:
- 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:
- 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.2The 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
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
@@ -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
@@ -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_MPTCPIPPROTO_MAX
Signature · 1 line
2.47.1
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@@ -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 1include/uapi/linux/in.h +0
-0
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_MPTCPIPPROTO_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
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@@ -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 1include/uapi/linux/in.h +0
-0@@ -85,6 +85,8 @@ enum { #define IPPROTO_RAW IPPROTO_RAWQuoted 8 lines
IPPROTO_SMC= 256, /* Shared Memory Communications */ #define IPPROTO_SMCIPPROTO_SMC
+IPPROTO_QUIC= 261, /* A UDP-Based Multiplexed and Secure Transport */
+#define IPPROTO_QUICIPPROTO_QUIC
IPPROTO_MPTCP= 262, /* Multipath TCP connection */ #define IPPROTO_MPTCPIPPROTO_MPTCP
IPPROTO_MAXCan 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 forIPPROTO_MAXininet_create,inet6_create,inet_diag_register
and the value ofIPPROTO_MAXis 263 starting with commitd25a92ccae6bed02327b63d138e12e7806830f78in 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.
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@@ -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 1include/uapi/linux/in.h +1
-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 SecureQuoted 5 lines
Transport */
+#defineIPPROTO_QUICIPPROTO_QUIC
IPPROTO_MPTCP= 262, /* Multipath TCP connection */
#defineIPPROTO_MPTCPIPPROTO_MPTCP
IPPROTO_MAXCan 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 forIPPROTO_MAXininet_create,inet6_create,inet_diag_register
and the value ofIPPROTO_MAXis 263 starting with commitd25a92ccae6bed02327b63d138e12e7806830f78in 6.11.
Quoted 7 lines
I would also recommend not changing
IPPROTO_MAXhere. WhenIPPROTO_MAXgot 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_QUICis 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 thanU8_MAX. At the end, it is good for new protocols, not to increaseIPPROTO_MAXeach time :)
Yes, this approach saves a lot of trouble for new protocols!
Thanks.
Namjae Jeon
maintainerRe: [PATCH net-next v2 01/15] net: define IPPROTO_QUIC and SOL_QUIC constants
In reply to Stefan MetzmacherQuoted 31 lines · Stefan Metzmacher
Hi,
Quoted 17 lines
include/linux/socket.h +1
-0@@ -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 1include/uapi/linux/in.h +0
-0include/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_MPTCPIPPROTO_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 forIPPROTO_MAXininet_create,inet6_create,inet_diag_registerand the value ofIPPROTO_MAXis 263 starting with commitd25a92ccae6bed02327b63d138e12e7806830f78in 6.11.Thanks!
metze
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@@ -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 1include/uapi/linux/in.h +0
-0include/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_MPTCPIPPROTO_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
PATCH v2 2/15 net: build socket infrastructure for QUIC protocol
In reply to Xin LongThis 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
@@ -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
@@ -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
@@ -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
@@ -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
Paolo Abeni
maintainerRe: [PATCH net-next v2 02/15] net: build socket infrastructure for QUIC protocol
Quoted 18 lines · Xin Long
net/Makefile +1
-0@@ -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
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
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@@ -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@@ -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 IPV6What 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!
PATCH v2 3/15 quic: provide common utilities and data structures
In reply to Xin LongThis 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
@@ -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
@@ -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
@@ -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
@@ -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
Paolo Abeni
maintainerRe: [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
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_arrayhere.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.
PATCH v2 4/15 quic: provide family ops for address and protocol
In reply to Xin LongThis 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
@@ -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
@@ -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
@@ -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
@@ -11,6 +11,7 @@#include <net/udp_tunnel.h> #include "common.h" +#include "family.h" #include "protocol.h"
Signature · 1 line
2.47.1
Paolo Abeni
maintainerRe: [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
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.
PATCH v2 5/15 quic: provide quic.h header files for kernel and userspace
In reply to Xin LongThis 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
@@ -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
@@ -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
@@ -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
PATCH v2 6/15 quic: add stream management
In reply to Xin LongThis 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
@@ -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
@@ -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
@@ -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
Paolo Abeni
maintainerRe: [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
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 providedmax_stream_id, the user-space? or a remote pear? what ifmax_stream_id-stream_idis 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.
PATCH v2 7/15 quic: add connection id management
In reply to Xin LongThis 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
@@ -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
@@ -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
@@ -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
Paolo Abeni
maintainerRe: [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 'structquic_source_conn_id' and 'structquic_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 toconn_idhash table if it is a sourceconn_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
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 'structquic_source_conn_id' and 'structquic_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 toconn_idhash table if it is a sourceconn_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(structquic_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->listis 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.
Re: [PATCH net-next v2 07/15] quic: add connection id management
In reply to Xin LongHi 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 'structquic_source_conn_id' and 'structquic_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 toconn_idhash table if it is a sourceconn_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).Signed-off-by Xin Long <[email protected]>---
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
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 'structquic_source_conn_id' and 'structquic_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 toconn_idhash table if it is a sourceconn_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).Signed-off-by Xin Long <[email protected]>---
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_ADMINto 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.
PATCH v2 8/15 quic: add path management
In reply to Xin LongThis 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
@@ -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
@@ -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
@@ -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
Paolo Abeni
maintainerRe: [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
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.
PATCH v2 9/15 quic: add congestion control
In reply to Xin LongThis 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
@@ -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
@@ -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
@@ -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
PATCH v2 10/15 quic: add packet number space
In reply to Xin LongThis 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
@@ -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
@@ -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
@@ -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
PATCH v2 11/15 quic: add crypto key derivation and installation
In reply to Xin LongThis 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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
PATCH v2 12/15 quic: add crypto packet encryption and decryption
In reply to Xin LongThis 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
@@ -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_conreturn 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
@@ -62,6 +62,9 @@ int quic_crypto_get_secret(struct quic_crypto *crypto, struct quic_crypto_secretint 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 *daint 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
PATCH v2 13/15 quic: add timer management
In reply to Xin LongThis 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
@@ -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
@@ -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
@@ -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
PATCH v2 14/15 quic: add frame encoder and decoder base
In reply to Xin LongThis 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
@@ -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
@@ -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
@@ -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
@@ -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
PATCH v2 15/15 quic: add packet builder and parser base
In reply to Xin LongThis 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
@@ -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
@@ -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
@@ -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
(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
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.
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.
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
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_sendmsgand 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]
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_sendmsgand 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.
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 astream_id, similar to SCTP's connection peeloff.- For stream sockets, in addition to
send(),recv(), andclose(), support forpoll()andshutdown()is also implemented. Note thatclose()andshutdown()send a FIN on the sending side, issue aSTOP_SENDINGon 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 withgetsockopt(QUIC_SOCKOPT_STREAM_PEELOFF), and sends data via the new file descriptor.- Receiver: Detects stream creation through a
QUIC_EVENT_STREAM_UPDATEevent, peels off the stream withgetsockopt(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