/src/openssl41/ssl/quic/quic_fifd.c
Line | Count | Source |
1 | | /* |
2 | | * Copyright 2022-2026 The OpenSSL Project Authors. All Rights Reserved. |
3 | | * |
4 | | * Licensed under the Apache License 2.0 (the "License"). You may not use |
5 | | * this file except in compliance with the License. You can obtain a copy |
6 | | * in the file LICENSE in the source distribution or at |
7 | | * https://www.openssl.org/source/license.html |
8 | | */ |
9 | | |
10 | | #include "internal/quic_fifd.h" |
11 | | #include "internal/quic_wire.h" |
12 | | #include "internal/qlog_event_helpers.h" |
13 | | |
14 | | DEFINE_LIST_OF(tx_history, OSSL_ACKM_TX_PKT); |
15 | | |
16 | | int ossl_quic_fifd_init(QUIC_FIFD *fifd, |
17 | | QUIC_CFQ *cfq, |
18 | | OSSL_ACKM *ackm, |
19 | | QUIC_TXPIM *txpim, |
20 | | /* stream_id is UINT64_MAX for the crypto stream */ |
21 | | QUIC_SSTREAM *(*get_sstream_by_id)(uint64_t stream_id, |
22 | | uint32_t pn_space, |
23 | | void *arg), |
24 | | void *get_sstream_by_id_arg, |
25 | | /* stream_id is UINT64_MAX if not applicable */ |
26 | | void (*regen_frame)(uint64_t frame_type, |
27 | | uint64_t stream_id, |
28 | | QUIC_TXPIM_PKT *pkt, |
29 | | void *arg), |
30 | | void *regen_frame_arg, |
31 | | void (*confirm_frame)(uint64_t frame_type, |
32 | | uint64_t stream_id, |
33 | | QUIC_TXPIM_PKT *pkt, |
34 | | void *arg), |
35 | | void *confirm_frame_arg, |
36 | | void (*sstream_updated)(uint64_t stream_id, |
37 | | void *arg), |
38 | | void *sstream_updated_arg, |
39 | | QLOG *(*get_qlog_cb)(void *arg), |
40 | | void *get_qlog_cb_arg) |
41 | 41.7k | { |
42 | 41.7k | if (cfq == NULL || ackm == NULL || txpim == NULL |
43 | 41.7k | || get_sstream_by_id == NULL || regen_frame == NULL) |
44 | 0 | return 0; |
45 | | |
46 | 41.7k | fifd->cfq = cfq; |
47 | 41.7k | fifd->ackm = ackm; |
48 | 41.7k | fifd->txpim = txpim; |
49 | 41.7k | fifd->get_sstream_by_id = get_sstream_by_id; |
50 | 41.7k | fifd->get_sstream_by_id_arg = get_sstream_by_id_arg; |
51 | 41.7k | fifd->regen_frame = regen_frame; |
52 | 41.7k | fifd->regen_frame_arg = regen_frame_arg; |
53 | 41.7k | fifd->confirm_frame = confirm_frame; |
54 | 41.7k | fifd->confirm_frame_arg = confirm_frame_arg; |
55 | 41.7k | fifd->sstream_updated = sstream_updated; |
56 | 41.7k | fifd->sstream_updated_arg = sstream_updated_arg; |
57 | 41.7k | fifd->get_qlog_cb = get_qlog_cb; |
58 | 41.7k | fifd->get_qlog_cb_arg = get_qlog_cb_arg; |
59 | 41.7k | return 1; |
60 | 41.7k | } |
61 | | |
62 | | void ossl_quic_fifd_cleanup(QUIC_FIFD *fifd) |
63 | 41.7k | { |
64 | | /* No-op. */ |
65 | 41.7k | } |
66 | | |
67 | | static void on_acked(void *arg) |
68 | 31.2k | { |
69 | 31.2k | QUIC_TXPIM_PKT *pkt = arg; |
70 | 31.2k | QUIC_FIFD *fifd = pkt->fifd; |
71 | 31.2k | const QUIC_TXPIM_CHUNK *chunks = ossl_quic_txpim_pkt_get_chunks(pkt); |
72 | 31.2k | size_t i, num_chunks = ossl_quic_txpim_pkt_get_num_chunks(pkt); |
73 | 31.2k | QUIC_SSTREAM *sstream; |
74 | 31.2k | QUIC_CFQ_ITEM *cfq_item, *cfq_item_next; |
75 | | |
76 | | /* STREAM and CRYPTO stream chunks, FINs and stream FC frames */ |
77 | 48.2k | for (i = 0; i < num_chunks; ++i) { |
78 | 16.9k | sstream = fifd->get_sstream_by_id(chunks[i].stream_id, |
79 | 16.9k | pkt->ackm_pkt.pkt_space, |
80 | 16.9k | fifd->get_sstream_by_id_arg); |
81 | | |
82 | 16.9k | if (sstream != NULL) { |
83 | 16.4k | if (chunks[i].end >= chunks[i].start) |
84 | | /* coverity[check_return]: Best effort - we cannot fail here. */ |
85 | 16.4k | ossl_quic_sstream_mark_acked(sstream, |
86 | 16.4k | chunks[i].start, chunks[i].end); |
87 | | |
88 | 16.4k | if (chunks[i].has_fin && chunks[i].stream_id != UINT64_MAX) |
89 | 0 | ossl_quic_sstream_mark_acked_fin(sstream); |
90 | 16.4k | } |
91 | | |
92 | | /* |
93 | | * Resetting the send part frees the send stream, so these must be |
94 | | * confirmed even when it is already gone. |
95 | | */ |
96 | 16.9k | if (chunks[i].has_stop_sending && chunks[i].stream_id != UINT64_MAX) |
97 | 0 | fifd->confirm_frame(OSSL_QUIC_FRAME_TYPE_STOP_SENDING, |
98 | 0 | chunks[i].stream_id, pkt, |
99 | 0 | fifd->confirm_frame_arg); |
100 | | |
101 | 16.9k | if (chunks[i].has_reset_stream && chunks[i].stream_id != UINT64_MAX) |
102 | 479 | fifd->confirm_frame(OSSL_QUIC_FRAME_TYPE_RESET_STREAM, |
103 | 479 | chunks[i].stream_id, pkt, |
104 | 479 | fifd->confirm_frame_arg); |
105 | | |
106 | 16.9k | if (sstream != NULL && ossl_quic_sstream_is_totally_acked(sstream)) |
107 | 1.77k | fifd->sstream_updated(chunks[i].stream_id, fifd->sstream_updated_arg); |
108 | 16.9k | } |
109 | | |
110 | | /* GCR */ |
111 | 31.7k | for (cfq_item = pkt->retx_head; cfq_item != NULL; cfq_item = cfq_item_next) { |
112 | 460 | cfq_item_next = cfq_item->pkt_next; |
113 | 460 | ossl_quic_cfq_release(fifd->cfq, cfq_item); |
114 | 460 | } |
115 | | |
116 | 31.2k | ossl_quic_txpim_pkt_release(fifd->txpim, pkt); |
117 | 31.2k | } |
118 | | |
119 | | static QLOG *fifd_get_qlog(QUIC_FIFD *fifd) |
120 | 23.7k | { |
121 | 23.7k | if (fifd->get_qlog_cb == NULL) |
122 | 0 | return NULL; |
123 | | |
124 | 23.7k | return fifd->get_qlog_cb(fifd->get_qlog_cb_arg); |
125 | 23.7k | } |
126 | | |
127 | | static void on_lost(void *arg) |
128 | 23.7k | { |
129 | 23.7k | QUIC_TXPIM_PKT *pkt = arg; |
130 | 23.7k | QUIC_FIFD *fifd = pkt->fifd; |
131 | 23.7k | const QUIC_TXPIM_CHUNK *chunks = ossl_quic_txpim_pkt_get_chunks(pkt); |
132 | 23.7k | size_t i, num_chunks = ossl_quic_txpim_pkt_get_num_chunks(pkt); |
133 | 23.7k | QUIC_SSTREAM *sstream; |
134 | 23.7k | QUIC_CFQ_ITEM *cfq_item, *cfq_item_next; |
135 | 23.7k | int sstream_updated; |
136 | | |
137 | 23.7k | ossl_qlog_event_recovery_packet_lost(fifd_get_qlog(fifd), pkt); |
138 | | |
139 | | /* STREAM and CRYPTO stream chunks, FIN and stream FC frames */ |
140 | 30.2k | for (i = 0; i < num_chunks; ++i) { |
141 | 6.45k | sstream = fifd->get_sstream_by_id(chunks[i].stream_id, |
142 | 6.45k | pkt->ackm_pkt.pkt_space, |
143 | 6.45k | fifd->get_sstream_by_id_arg); |
144 | 6.45k | if (sstream == NULL) |
145 | 182 | continue; |
146 | | |
147 | 6.26k | sstream_updated = 0; |
148 | | |
149 | 6.26k | if (chunks[i].end >= chunks[i].start) { |
150 | | /* |
151 | | * Note: If the stream is being reset, we do not need to retransmit |
152 | | * old data as this is pointless. In this case this will be handled |
153 | | * by (sstream == NULL) above as the QSM will free the QUIC_SSTREAM |
154 | | * and our call to get_sstream_by_id above will return NULL. |
155 | | */ |
156 | 6.26k | ossl_quic_sstream_mark_lost(sstream, |
157 | 6.26k | chunks[i].start, chunks[i].end); |
158 | 6.26k | sstream_updated = 1; |
159 | 6.26k | } |
160 | | |
161 | 6.26k | if (chunks[i].has_fin && chunks[i].stream_id != UINT64_MAX) { |
162 | 0 | ossl_quic_sstream_mark_lost_fin(sstream); |
163 | 0 | sstream_updated = 1; |
164 | 0 | } |
165 | | |
166 | 6.26k | if (chunks[i].has_stop_sending && chunks[i].stream_id != UINT64_MAX) |
167 | 0 | fifd->regen_frame(OSSL_QUIC_FRAME_TYPE_STOP_SENDING, |
168 | 0 | chunks[i].stream_id, pkt, |
169 | 0 | fifd->regen_frame_arg); |
170 | | |
171 | 6.26k | if (chunks[i].has_reset_stream && chunks[i].stream_id != UINT64_MAX) |
172 | 0 | fifd->regen_frame(OSSL_QUIC_FRAME_TYPE_RESET_STREAM, |
173 | 0 | chunks[i].stream_id, pkt, |
174 | 0 | fifd->regen_frame_arg); |
175 | | |
176 | | /* |
177 | | * Inform caller that stream needs an FC frame. |
178 | | * |
179 | | * Note: We could track whether an FC frame was sent originally for the |
180 | | * stream to determine if it really needs to be regenerated or not. |
181 | | * However, if loss has occurred, it's probably better to ensure the |
182 | | * peer has up-to-date flow control data for the stream. Given that |
183 | | * these frames are extremely small, we may as well always send it when |
184 | | * handling loss. |
185 | | */ |
186 | 6.26k | fifd->regen_frame(OSSL_QUIC_FRAME_TYPE_MAX_STREAM_DATA, |
187 | 6.26k | chunks[i].stream_id, |
188 | 6.26k | pkt, |
189 | 6.26k | fifd->regen_frame_arg); |
190 | | |
191 | 6.26k | if (sstream_updated && chunks[i].stream_id != UINT64_MAX) |
192 | 194 | fifd->sstream_updated(chunks[i].stream_id, |
193 | 194 | fifd->sstream_updated_arg); |
194 | 6.26k | } |
195 | | |
196 | | /* GCR */ |
197 | 23.8k | for (cfq_item = pkt->retx_head; cfq_item != NULL; cfq_item = cfq_item_next) { |
198 | 63 | cfq_item_next = cfq_item->pkt_next; |
199 | 63 | ossl_quic_cfq_mark_lost(fifd->cfq, cfq_item, UINT32_MAX); |
200 | 63 | } |
201 | | |
202 | | /* Regenerate flag frames */ |
203 | 23.7k | if (pkt->had_handshake_done_frame) |
204 | 0 | fifd->regen_frame(OSSL_QUIC_FRAME_TYPE_HANDSHAKE_DONE, |
205 | 0 | UINT64_MAX, pkt, |
206 | 0 | fifd->regen_frame_arg); |
207 | | |
208 | 23.7k | if (pkt->had_max_data_frame) |
209 | 0 | fifd->regen_frame(OSSL_QUIC_FRAME_TYPE_MAX_DATA, |
210 | 0 | UINT64_MAX, pkt, |
211 | 0 | fifd->regen_frame_arg); |
212 | | |
213 | 23.7k | if (pkt->had_max_streams_bidi_frame) |
214 | 0 | fifd->regen_frame(OSSL_QUIC_FRAME_TYPE_MAX_STREAMS_BIDI, |
215 | 0 | UINT64_MAX, pkt, |
216 | 0 | fifd->regen_frame_arg); |
217 | | |
218 | 23.7k | if (pkt->had_max_streams_uni_frame) |
219 | 0 | fifd->regen_frame(OSSL_QUIC_FRAME_TYPE_MAX_STREAMS_UNI, |
220 | 0 | UINT64_MAX, pkt, |
221 | 0 | fifd->regen_frame_arg); |
222 | | |
223 | 23.7k | if (pkt->had_ack_frame) |
224 | | /* |
225 | | * We always use the ACK_WITH_ECN frame type to represent the ACK frame |
226 | | * type in our callback; we assume it is the caller's job to decide |
227 | | * whether it wants to send ECN data or not. |
228 | | */ |
229 | 2.67k | fifd->regen_frame(OSSL_QUIC_FRAME_TYPE_ACK_WITH_ECN, |
230 | 2.67k | UINT64_MAX, pkt, |
231 | 2.67k | fifd->regen_frame_arg); |
232 | | |
233 | 23.7k | ossl_quic_txpim_pkt_release(fifd->txpim, pkt); |
234 | 23.7k | } |
235 | | |
236 | | static void on_discarded(void *arg) |
237 | 733k | { |
238 | 733k | QUIC_TXPIM_PKT *pkt = arg; |
239 | 733k | QUIC_FIFD *fifd = pkt->fifd; |
240 | 733k | QUIC_CFQ_ITEM *cfq_item, *cfq_item_next; |
241 | | |
242 | | /* |
243 | | * Don't need to do anything to SSTREAMs for STREAM and CRYPTO streams, as |
244 | | * we assume caller will clean them up. |
245 | | */ |
246 | | |
247 | | /* GCR */ |
248 | 735k | for (cfq_item = pkt->retx_head; cfq_item != NULL; cfq_item = cfq_item_next) { |
249 | 2.40k | cfq_item_next = cfq_item->pkt_next; |
250 | 2.40k | ossl_quic_cfq_release(fifd->cfq, cfq_item); |
251 | 2.40k | } |
252 | | |
253 | 733k | ossl_quic_txpim_pkt_release(fifd->txpim, pkt); |
254 | 733k | } |
255 | | |
256 | | int ossl_quic_fifd_pkt_commit(QUIC_FIFD *fifd, QUIC_TXPIM_PKT *pkt) |
257 | 811k | { |
258 | 811k | QUIC_CFQ_ITEM *cfq_item; |
259 | 811k | const QUIC_TXPIM_CHUNK *chunks; |
260 | 811k | size_t i, num_chunks; |
261 | 811k | QUIC_SSTREAM *sstream; |
262 | | |
263 | 811k | pkt->fifd = fifd; |
264 | | |
265 | 811k | pkt->ackm_pkt.on_lost = on_lost; |
266 | 811k | pkt->ackm_pkt.on_acked = on_acked; |
267 | 811k | pkt->ackm_pkt.on_discarded = on_discarded; |
268 | 811k | pkt->ackm_pkt.cb_arg = pkt; |
269 | | |
270 | 811k | ossl_list_tx_history_init_elem(&pkt->ackm_pkt); |
271 | 811k | pkt->ackm_pkt.anext = pkt->ackm_pkt.lnext = NULL; |
272 | | |
273 | | /* |
274 | | * Mark the CFQ items which have been added to this packet as having been |
275 | | * transmitted. |
276 | | */ |
277 | 811k | for (cfq_item = pkt->retx_head; |
278 | 820k | cfq_item != NULL; |
279 | 811k | cfq_item = cfq_item->pkt_next) |
280 | 9.45k | ossl_quic_cfq_mark_tx(fifd->cfq, cfq_item); |
281 | | |
282 | | /* |
283 | | * Mark the send stream chunks which have been added to the packet as having |
284 | | * been transmitted. |
285 | | */ |
286 | 811k | chunks = ossl_quic_txpim_pkt_get_chunks(pkt); |
287 | 811k | num_chunks = ossl_quic_txpim_pkt_get_num_chunks(pkt); |
288 | 907k | for (i = 0; i < num_chunks; ++i) { |
289 | 95.6k | sstream = fifd->get_sstream_by_id(chunks[i].stream_id, |
290 | 95.6k | pkt->ackm_pkt.pkt_space, |
291 | 95.6k | fifd->get_sstream_by_id_arg); |
292 | 95.6k | if (sstream == NULL) |
293 | 3.69k | continue; |
294 | | |
295 | 91.9k | if (chunks[i].end >= chunks[i].start |
296 | 91.9k | && !ossl_quic_sstream_mark_transmitted(sstream, |
297 | 91.9k | chunks[i].start, |
298 | 91.9k | chunks[i].end)) |
299 | 0 | return 0; |
300 | | |
301 | 91.9k | if (chunks[i].has_fin |
302 | 0 | && !ossl_quic_sstream_mark_transmitted_fin(sstream, |
303 | 0 | chunks[i].end + 1)) |
304 | 0 | return 0; |
305 | 91.9k | } |
306 | | |
307 | | /* Inform the ACKM. */ |
308 | 811k | return ossl_ackm_on_tx_packet(fifd->ackm, &pkt->ackm_pkt); |
309 | 811k | } |
310 | | |
311 | | void ossl_quic_fifd_set_qlog_cb(QUIC_FIFD *fifd, QLOG *(*get_qlog_cb)(void *arg), |
312 | | void *get_qlog_cb_arg) |
313 | 0 | { |
314 | 0 | fifd->get_qlog_cb = get_qlog_cb; |
315 | 0 | fifd->get_qlog_cb_arg = get_qlog_cb_arg; |
316 | 0 | } |
317 | | |
318 | | static void txpim_pkt_remove_cfq_item(QUIC_TXPIM_PKT *pkt, QUIC_CFQ_ITEM *cfq_item) |
319 | 6.30k | { |
320 | 6.30k | QUIC_CFQ_ITEM *prev = cfq_item->pkt_prev; |
321 | | |
322 | 6.30k | if (prev != NULL) { |
323 | 0 | prev->pkt_next = cfq_item->pkt_next; |
324 | 6.30k | } else { |
325 | 6.30k | pkt->retx_head = cfq_item->pkt_next; |
326 | 6.30k | } |
327 | | |
328 | 6.30k | if (cfq_item->pkt_next != NULL) |
329 | 3.62k | cfq_item->pkt_next->pkt_prev = prev; |
330 | | |
331 | 6.30k | cfq_item->pkt_prev = NULL; |
332 | 6.30k | cfq_item->pkt_next = NULL; |
333 | 6.30k | } |
334 | | |
335 | | void ossl_quic_fifd_pkt_discard_unreliable(QUIC_FIFD *fifd, QUIC_TXPIM_PKT *pkt) |
336 | 860k | { |
337 | 860k | QUIC_CFQ_ITEM *cfq_item, *cfq_next; |
338 | | |
339 | | /* |
340 | | * The packet has been written to network. We can discard frames we don't |
341 | | * retransmit when loss is detected. |
342 | | */ |
343 | 860k | cfq_item = pkt->retx_head; |
344 | 870k | while (cfq_item != NULL) { |
345 | | /* |
346 | | * Discarded items are moved to free list. If item |
347 | | * got moved to free list we must also remove it from |
348 | | * cfq list kept in pkt, so ACKM does not find it when |
349 | | * receives an ACK for pkt. |
350 | | */ |
351 | 9.45k | if (ossl_quic_cfq_discard_unreliable(fifd->cfq, cfq_item)) { |
352 | 6.30k | cfq_next = cfq_item->pkt_next; |
353 | 6.30k | txpim_pkt_remove_cfq_item(pkt, cfq_item); |
354 | 6.30k | cfq_item = cfq_next; |
355 | 6.30k | } else { |
356 | 3.15k | cfq_item = cfq_item->pkt_next; |
357 | 3.15k | } |
358 | 9.45k | } |
359 | 860k | } |