Re: [PATCH net-next v3 06/15] quic: add stream management
flat view
From: Paolo Abeni <pabeni@redhat.com>
Date: 2025-09-23 13:39:12
Also in:
linux-cifs
On 9/19/25 12:34 AM, Xin Long wrote:
quoted hunk ↗ jump to hunk
+/* 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; + + /* rfc9000#section-2.1: A stream ID that is used out of order results in all streams + * of that type with lower-numbered stream IDs also being opened. + */ + while (stream_id <= max_stream_id) { + stream = kzalloc(sizeof(*stream), GFP_KERNEL); + if (!stream) + return NULL;
How many streams and connections ID do you foresee per socket? Could such number grow significantly (possibly under misuse/attack)? If so you you likely use GFP_KERNEL_ACCOUNT here (and in conn_id allocation). /P
quoted hunk ↗ jump to hunk
+ + stream->id = stream_id; + if (quic_stream_id_uni(stream_id)) { + stream->send.max_bytes = streams->send.max_stream_data_uni; + + if (streams->send.next_uni_stream_id < stream_id + QUIC_STREAM_ID_STEP) + streams->send.next_uni_stream_id = stream_id + QUIC_STREAM_ID_STEP; + streams->send.streams_uni++; + + quic_stream_add(streams, stream); + stream_id += QUIC_STREAM_ID_STEP; + continue; + } + + if (streams->send.next_bidi_stream_id < stream_id + QUIC_STREAM_ID_STEP) + streams->send.next_bidi_stream_id = stream_id + QUIC_STREAM_ID_STEP; + streams->send.streams_bidi++; + + if (quic_stream_id_local(stream_id, is_serv)) { + stream->send.max_bytes = streams->send.max_stream_data_bidi_remote; + stream->recv.max_bytes = streams->recv.max_stream_data_bidi_local; + } else { + stream->send.max_bytes = streams->send.max_stream_data_bidi_local; + stream->recv.max_bytes = streams->recv.max_stream_data_bidi_remote; + } + stream->recv.window = stream->recv.max_bytes; + + quic_stream_add(streams, stream); + stream_id += QUIC_STREAM_ID_STEP; + } + return stream; +} + +/* Create and register new streams for receiving. */ +static struct quic_stream *quic_stream_recv_create(struct quic_stream_table *streams, + s64 max_stream_id, u8 is_serv) +{ + struct quic_stream *stream; + s64 stream_id; + + stream_id = streams->recv.next_bidi_stream_id; + if (quic_stream_id_uni(max_stream_id)) + stream_id = streams->recv.next_uni_stream_id; + + /* rfc9000#section-2.1: A stream ID that is used out of order results in all streams + * of that type with lower-numbered stream IDs also being opened. + */ + while (stream_id <= max_stream_id) { + stream = kzalloc(sizeof(*stream), GFP_ATOMIC); + if (!stream) + return NULL; + + stream->id = stream_id; + if (quic_stream_id_uni(stream_id)) { + stream->recv.window = streams->recv.max_stream_data_uni; + stream->recv.max_bytes = stream->recv.window; + + if (streams->recv.next_uni_stream_id < stream_id + QUIC_STREAM_ID_STEP) + streams->recv.next_uni_stream_id = stream_id + QUIC_STREAM_ID_STEP; + streams->recv.streams_uni++; + + quic_stream_add(streams, stream); + stream_id += QUIC_STREAM_ID_STEP; + continue; + } + + if (streams->recv.next_bidi_stream_id < stream_id + QUIC_STREAM_ID_STEP) + streams->recv.next_bidi_stream_id = stream_id + QUIC_STREAM_ID_STEP; + streams->recv.streams_bidi++; + + if (quic_stream_id_local(stream_id, is_serv)) { + stream->send.max_bytes = streams->send.max_stream_data_bidi_remote; + stream->recv.max_bytes = streams->recv.max_stream_data_bidi_local; + } else { + stream->send.max_bytes = streams->send.max_stream_data_bidi_local; + stream->recv.max_bytes = streams->recv.max_stream_data_bidi_remote; + } + stream->recv.window = stream->recv.max_bytes; + + quic_stream_add(streams, stream); + stream_id += QUIC_STREAM_ID_STEP; + } + return stream; +} + +/* Check if a send or receive stream ID is already closed. */ +static bool quic_stream_id_closed(struct quic_stream_table *streams, s64 stream_id, bool send) +{ + if (quic_stream_id_uni(stream_id)) { + if (send) + return stream_id < streams->send.next_uni_stream_id; + return stream_id < streams->recv.next_uni_stream_id; + } + if (send) + return stream_id < streams->send.next_bidi_stream_id; + return stream_id < streams->recv.next_bidi_stream_id; +} + +/* Check if a stream ID would exceed local (recv) or peer (send) limits. */ +bool quic_stream_id_exceeds(struct quic_stream_table *streams, s64 stream_id, bool send) +{ + u64 nstreams; + + if (!send) { + if (quic_stream_id_uni(stream_id)) + return stream_id > streams->recv.max_uni_stream_id; + return stream_id > streams->recv.max_bidi_stream_id; + } + + if (quic_stream_id_uni(stream_id)) { + if (stream_id > streams->send.max_uni_stream_id) + return true; + stream_id -= streams->send.next_uni_stream_id; + nstreams = quic_stream_id_to_streams(stream_id); + return nstreams + streams->send.streams_uni > streams->send.max_streams_uni; + } + + if (stream_id > streams->send.max_bidi_stream_id) + return true; + stream_id -= streams->send.next_bidi_stream_id; + nstreams = quic_stream_id_to_streams(stream_id); + return nstreams + streams->send.streams_bidi > streams->send.max_streams_bidi; +} + +/* Get or create a send 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_valid(stream_id, is_serv, true)) + 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_closed(streams, stream_id, true)) + return ERR_PTR(-ENOSTR); + + if (!(flags & MSG_STREAM_NEW)) + return ERR_PTR(-EINVAL); + + if (quic_stream_id_exceeds(streams, stream_id, true)) + 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; +} + +/* Get or create a receive stream by ID. */ +struct quic_stream *quic_stream_recv_get(struct quic_stream_table *streams, s64 stream_id, + bool is_serv) +{ + struct quic_stream *stream; + + if (!quic_stream_id_valid(stream_id, is_serv, false)) + return ERR_PTR(-EINVAL); + + stream = quic_stream_find(streams, stream_id); + if (stream) + return stream; + + if (quic_stream_id_local(stream_id, is_serv)) { + if (quic_stream_id_closed(streams, stream_id, true)) + return ERR_PTR(-ENOSTR); + return ERR_PTR(-EINVAL); + } + + if (quic_stream_id_closed(streams, stream_id, false)) + return ERR_PTR(-ENOSTR); + + if (quic_stream_id_exceeds(streams, stream_id, false)) + return ERR_PTR(-EAGAIN); + + stream = quic_stream_recv_create(streams, stream_id, is_serv); + if (!stream) + return ERR_PTR(-ENOSTR); + if (quic_stream_id_valid(stream_id, is_serv, true)) + streams->send.active_stream_id = stream_id; + return stream; +} + +/* Release or clean up a send stream. This function updates stream counters and state when + * a send stream has either successfully sent all data or has been reset. + */ +void quic_stream_send_put(struct quic_stream_table *streams, struct quic_stream *stream, + bool is_serv) +{ + if (quic_stream_id_uni(stream->id)) { + /* For unidirectional streams, decrement uni count and delete immediately. */ + streams->send.streams_uni--; + quic_stream_delete(stream); + return; + } + + /* For bidi streams, only proceed if receive side is in a final state. */ + if (stream->recv.state != QUIC_STREAM_RECV_STATE_RECVD && + stream->recv.state != QUIC_STREAM_RECV_STATE_READ && + stream->recv.state != QUIC_STREAM_RECV_STATE_RESET_RECVD) + return; + + if (quic_stream_id_local(stream->id, is_serv)) { + /* Local-initiated stream: mark send done and decrement send.bidi count. */ + if (!stream->send.done) { + stream->send.done = 1; + streams->send.streams_bidi--; + } + goto out; + } + /* Remote-initiated stream: mark recv done and decrement recv bidi count. */ + if (!stream->recv.done) { + stream->recv.done = 1; + streams->recv.streams_bidi--; + streams->recv.bidi_pending = 1; + } +out: + /* Delete stream if fully read or reset. */ + if (stream->recv.state != QUIC_STREAM_RECV_STATE_RECVD) + quic_stream_delete(stream); +} + +/* Release or clean up a receive stream. This function updates stream counters and state when + * the receive side has either consumed all data or has been reset. + */ +void quic_stream_recv_put(struct quic_stream_table *streams, struct quic_stream *stream, + bool is_serv) +{ + if (quic_stream_id_uni(stream->id)) { + /* For uni streams, decrement uni count and mark done. */ + if (!stream->recv.done) { + stream->recv.done = 1; + streams->recv.streams_uni--; + streams->recv.uni_pending = 1; + } + goto out; + } + + /* For bidi streams, only proceed if send side is in a final state. */ + if (stream->send.state != QUIC_STREAM_SEND_STATE_RECVD && + stream->send.state != QUIC_STREAM_SEND_STATE_RESET_RECVD) + return; + + if (quic_stream_id_local(stream->id, is_serv)) { + /* Local-initiated stream: mark send done and decrement send.bidi count. */ + if (!stream->send.done) { + stream->send.done = 1; + streams->send.streams_bidi--; + } + goto out; + } + /* Remote-initiated stream: mark recv done and decrement recv bidi count. */ + if (!stream->recv.done) { + stream->recv.done = 1; + streams->recv.streams_bidi--; + streams->recv.bidi_pending = 1; + } +out: + /* Delete stream if fully read or reset. */ + if (stream->recv.state != QUIC_STREAM_RECV_STATE_RECVD) + quic_stream_delete(stream); +} + +/* Updates the maximum allowed incoming stream IDs if any streams were recently closed. + * Recalculates the max_uni and max_bidi stream ID limits based on the number of open + * streams and whether any were marked for deletion. + * + * Returns true if either max_uni or max_bidi was updated, indicating that a + * MAX_STREAMS_UNI or MAX_STREAMS_BIDI frame should be sent to the peer. + */ +bool quic_stream_max_streams_update(struct quic_stream_table *streams, s64 *max_uni, s64 *max_bidi) +{ + if (streams->recv.uni_pending) { + streams->recv.max_uni_stream_id = + streams->recv.next_uni_stream_id - QUIC_STREAM_ID_STEP + + ((streams->recv.max_streams_uni - streams->recv.streams_uni) << + QUIC_STREAM_TYPE_BITS); + *max_uni = quic_stream_id_to_streams(streams->recv.max_uni_stream_id); + streams->recv.uni_pending = 0; + } + if (streams->recv.bidi_pending) { + streams->recv.max_bidi_stream_id = + streams->recv.next_bidi_stream_id - QUIC_STREAM_ID_STEP + + ((streams->recv.max_streams_bidi - streams->recv.streams_bidi) << + QUIC_STREAM_TYPE_BITS); + *max_bidi = quic_stream_id_to_streams(streams->recv.max_bidi_stream_id); + streams->recv.bidi_pending = 0; + } + + return *max_uni || *max_bidi; +} + +#define QUIC_STREAM_HT_SIZE 64 + +int quic_stream_init(struct quic_stream_table *streams) +{ + struct quic_shash_table *ht = &streams->ht; + int i, size = QUIC_STREAM_HT_SIZE; + struct quic_shash_head *head; + + head = kmalloc_array(size, sizeof(*head), GFP_KERNEL); + if (!head) + return -ENOMEM; + for (i = 0; i < size; i++) + INIT_HLIST_HEAD(&head[i].head); + ht->size = size; + ht->hash = head; + return 0; +} + +void quic_stream_free(struct quic_stream_table *streams) +{ + struct quic_shash_table *ht = &streams->ht; + struct quic_shash_head *head; + struct quic_stream *stream; + struct hlist_node *tmp; + int i; + + for (i = 0; i < ht->size; i++) { + head = &ht->hash[i]; + hlist_for_each_entry_safe(stream, tmp, &head->head, node) + quic_stream_delete(stream); + } + kfree(ht->hash); +} + +/* Populate transport parameters from stream hash table. */ +void quic_stream_get_param(struct quic_stream_table *streams, struct quic_transport_param *p, + bool is_serv) +{ + if (p->remote) { + p->max_stream_data_bidi_remote = streams->send.max_stream_data_bidi_remote; + p->max_stream_data_bidi_local = streams->send.max_stream_data_bidi_local; + p->max_stream_data_uni = streams->send.max_stream_data_uni; + p->max_streams_bidi = streams->send.max_streams_bidi; + p->max_streams_uni = streams->send.max_streams_uni; + return; + } + + p->max_stream_data_bidi_remote = streams->recv.max_stream_data_bidi_remote; + p->max_stream_data_bidi_local = streams->recv.max_stream_data_bidi_local; + p->max_stream_data_uni = streams->recv.max_stream_data_uni; + p->max_streams_bidi = streams->recv.max_streams_bidi; + p->max_streams_uni = streams->recv.max_streams_uni; +} + +/* Configure stream hashtable from transport parameters. */ +void quic_stream_set_param(struct quic_stream_table *streams, struct quic_transport_param *p, + bool is_serv) +{ + u8 type; + + if (p->remote) { + streams->send.max_stream_data_bidi_local = p->max_stream_data_bidi_local; + streams->send.max_stream_data_bidi_remote = p->max_stream_data_bidi_remote; + streams->send.max_stream_data_uni = p->max_stream_data_uni; + streams->send.max_streams_bidi = p->max_streams_bidi; + streams->send.max_streams_uni = p->max_streams_uni; + streams->send.active_stream_id = -1; + + if (is_serv) { + type = QUIC_STREAM_TYPE_SERVER_BIDI; + streams->send.max_bidi_stream_id = + quic_stream_streams_to_id(p->max_streams_bidi, type); + streams->send.next_bidi_stream_id = type; + + type = QUIC_STREAM_TYPE_SERVER_UNI; + streams->send.max_uni_stream_id = + quic_stream_streams_to_id(p->max_streams_uni, type); + streams->send.next_uni_stream_id = type; + return; + } + + type = QUIC_STREAM_TYPE_CLIENT_BIDI; + streams->send.max_bidi_stream_id = + quic_stream_streams_to_id(p->max_streams_bidi, type); + streams->send.next_bidi_stream_id = type; + + type = QUIC_STREAM_TYPE_CLIENT_UNI; + streams->send.max_uni_stream_id = + quic_stream_streams_to_id(p->max_streams_uni, type); + streams->send.next_uni_stream_id = type; + return; + } + + streams->recv.max_stream_data_bidi_local = p->max_stream_data_bidi_local; + streams->recv.max_stream_data_bidi_remote = p->max_stream_data_bidi_remote; + streams->recv.max_stream_data_uni = p->max_stream_data_uni; + streams->recv.max_streams_bidi = p->max_streams_bidi; + streams->recv.max_streams_uni = p->max_streams_uni; + + if (is_serv) { + type = QUIC_STREAM_TYPE_CLIENT_BIDI; + streams->recv.max_bidi_stream_id = + quic_stream_streams_to_id(p->max_streams_bidi, type); + streams->recv.next_bidi_stream_id = type; + + type = QUIC_STREAM_TYPE_CLIENT_UNI; + streams->recv.max_uni_stream_id = + quic_stream_streams_to_id(p->max_streams_uni, type); + streams->recv.next_uni_stream_id = type; + return; + } + + type = QUIC_STREAM_TYPE_SERVER_BIDI; + streams->recv.max_bidi_stream_id = + quic_stream_streams_to_id(p->max_streams_bidi, type); + streams->recv.next_bidi_stream_id = type; + + type = QUIC_STREAM_TYPE_SERVER_UNI; + streams->recv.max_uni_stream_id = + quic_stream_streams_to_id(p->max_streams_uni, type); + streams->recv.next_uni_stream_id = type; +}diff --git a/net/quic/stream.h b/net/quic/stream.h new file mode 100644 index 000000000000..c53d9358605c --- /dev/null +++ b/net/quic/stream.h@@ -0,0 +1,136 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* QUIC kernel implementation + * (C) Copyright Red Hat Corp. 2023 + * + * This file is part of the QUIC kernel implementation + * + * Written or modified by: + * Xin Long <lucien.xin@gmail.com> + */ + +#define QUIC_DEF_STREAMS 100 +#define QUIC_MAX_STREAMS 4096ULL + +/* + * rfc9000#section-2.1: + * + * The least significant bit (0x01) of the stream ID identifies the initiator of the stream. + * Client-initiated streams have even-numbered stream IDs (with the bit set to 0), and + * server-initiated streams have odd-numbered stream IDs (with the bit set to 1). + * + * The second least significant bit (0x02) of the stream ID distinguishes between bidirectional + * streams (with the bit set to 0) and unidirectional streams (with the bit set to 1). + */ +#define QUIC_STREAM_TYPE_BITS 2 +#define QUIC_STREAM_ID_STEP BIT(QUIC_STREAM_TYPE_BITS) + +#define QUIC_STREAM_TYPE_CLIENT_BIDI 0x00 +#define QUIC_STREAM_TYPE_SERVER_BIDI 0x01 +#define QUIC_STREAM_TYPE_CLIENT_UNI 0x02 +#define QUIC_STREAM_TYPE_SERVER_UNI 0x03 + +struct quic_stream { + struct hlist_node node; + s64 id; /* Stream ID as defined in RFC 9000 Section 2.1 */ + struct { + /* Sending-side stream level flow control */ + u64 last_max_bytes; /* Maximum send offset advertised by peer at last update */ + u64 max_bytes; /* Current maximum offset we are allowed to send to */ + u64 bytes; /* Bytes already sent to peer */ + + u32 errcode; /* Application error code to send in RESET_STREAM */ + u32 frags; /* Number of sent STREAM frames not yet acknowledged */ + u8 state; /* Send stream state, per rfc9000#section-3.1 */ + + u8 data_blocked:1; /* True if flow control blocks sending more data */ + u8 done:1; /* True if application indicated end of stream (FIN sent) */ + } send; + struct { + /* Receiving-side stream level flow control */ + u64 max_bytes; /* Maximum offset peer is allowed to send to */ + u64 window; /* Remaining receive window before advertise a new limit */ + u64 bytes; /* Bytes consumed by application from the stream */ + + u64 highest; /* Highest received offset */ + u64 offset; /* Offset up to which data is in buffer or consumed */ + u64 finalsz; /* Final size of the stream if FIN received */ + + u32 frags; /* Number of received STREAM frames pending reassembly */ + u8 state; /* Receive stream state, per rfc9000#section-3.2 */ + + u8 stop_sent:1; /* True if STOP_SENDING has been sent */ + u8 done:1; /* True if FIN received and final size validated */ + } recv; +}; + +struct quic_stream_table { + struct quic_shash_table ht; /* Hash table storing all active streams */ + + struct { + /* Parameters received from peer, defined in rfc9000#section-18.2 */ + u64 max_stream_data_bidi_remote; /* initial_max_stream_data_bidi_remote */ + u64 max_stream_data_bidi_local; /* initial_max_stream_data_bidi_local */ + u64 max_stream_data_uni; /* initial_max_stream_data_uni */ + u64 max_streams_bidi; /* initial_max_streams_bidi */ + u64 max_streams_uni; /* initial_max_streams_uni */ + + s64 next_bidi_stream_id; /* Next bidi stream ID to be opened */ + s64 next_uni_stream_id; /* Next uni stream ID to be opened */ + s64 max_bidi_stream_id; /* Highest allowed bidi stream ID */ + s64 max_uni_stream_id; /* Highest allowed uni stream ID */ + s64 active_stream_id; /* Most recently opened stream ID */ + + u8 bidi_blocked:1; /* True if STREAMS_BLOCKED_BIDI was sent and not ACKed */ + u8 uni_blocked:1; /* True if STREAMS_BLOCKED_UNI was sent and not ACKed */ + u16 streams_bidi; /* Number of currently active bidi streams */ + u16 streams_uni; /* Number of currently active uni streams */ + } send; + struct { + /* Our advertised limits to the peer, per rfc9000#section-18.2 */ + u64 max_stream_data_bidi_remote; /* initial_max_stream_data_bidi_remote */ + u64 max_stream_data_bidi_local; /* initial_max_stream_data_bidi_local */ + u64 max_stream_data_uni; /* initial_max_stream_data_uni */ + u64 max_streams_bidi; /* initial_max_streams_bidi */ + u64 max_streams_uni; /* initial_max_streams_uni */ + + s64 next_bidi_stream_id; /* Next expected bidi stream ID from peer */ + s64 next_uni_stream_id; /* Next expected uni stream ID from peer */ + s64 max_bidi_stream_id; /* Current allowed bidi stream ID range */ + s64 max_uni_stream_id; /* Current allowed uni stream ID range */ + + u8 bidi_pending:1; /* True if MAX_STREAMS_BIDI needs to be sent */ + u8 uni_pending:1; /* True if MAX_STREAMS_UNI needs to be sent */ + u16 streams_bidi; /* Number of currently open bidi streams */ + u16 streams_uni; /* Number of currently open uni streams */ + } recv; +}; + +static inline u64 quic_stream_id_to_streams(s64 stream_id) +{ + return (u64)(stream_id >> QUIC_STREAM_TYPE_BITS) + 1; +} + +static inline s64 quic_stream_streams_to_id(u64 streams, u8 type) +{ + return (s64)((streams - 1) << QUIC_STREAM_TYPE_BITS) | type; +} + +struct quic_stream *quic_stream_send_get(struct quic_stream_table *streams, s64 stream_id, + u32 flags, bool is_serv); +struct quic_stream *quic_stream_recv_get(struct quic_stream_table *streams, s64 stream_id, + bool is_serv); +void quic_stream_send_put(struct quic_stream_table *streams, struct quic_stream *stream, + bool is_serv); +void quic_stream_recv_put(struct quic_stream_table *streams, struct quic_stream *stream, + bool is_serv); + +bool quic_stream_max_streams_update(struct quic_stream_table *streams, s64 *max_uni, s64 *max_bidi); +bool quic_stream_id_exceeds(struct quic_stream_table *streams, s64 stream_id, bool send); +struct quic_stream *quic_stream_find(struct quic_stream_table *streams, s64 stream_id); + +void quic_stream_get_param(struct quic_stream_table *streams, struct quic_transport_param *p, + bool is_serv); +void quic_stream_set_param(struct quic_stream_table *streams, struct quic_transport_param *p, + bool is_serv); +void quic_stream_free(struct quic_stream_table *streams); +int quic_stream_init(struct quic_stream_table *streams);