xref: /openssl/ssl/quic/quic_fifd.c (revision b6461792)
1 /*
2  * Copyright 2022-2024 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 
ossl_quic_fifd_init(QUIC_FIFD * fifd,QUIC_CFQ * cfq,OSSL_ACKM * ackm,QUIC_TXPIM * txpim,QUIC_SSTREAM * (* get_sstream_by_id)(uint64_t stream_id,uint32_t pn_space,void * arg),void * get_sstream_by_id_arg,void (* regen_frame)(uint64_t frame_type,uint64_t stream_id,QUIC_TXPIM_PKT * pkt,void * arg),void * regen_frame_arg,void (* confirm_frame)(uint64_t frame_type,uint64_t stream_id,QUIC_TXPIM_PKT * pkt,void * arg),void * confirm_frame_arg,void (* sstream_updated)(uint64_t stream_id,void * arg),void * sstream_updated_arg,QLOG * (* get_qlog_cb)(void * arg),void * get_qlog_cb_arg)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 {
42     if (cfq == NULL || ackm == NULL || txpim == NULL
43         || get_sstream_by_id == NULL || regen_frame == NULL)
44         return 0;
45 
46     fifd->cfq                   = cfq;
47     fifd->ackm                  = ackm;
48     fifd->txpim                 = txpim;
49     fifd->get_sstream_by_id     = get_sstream_by_id;
50     fifd->get_sstream_by_id_arg = get_sstream_by_id_arg;
51     fifd->regen_frame           = regen_frame;
52     fifd->regen_frame_arg       = regen_frame_arg;
53     fifd->confirm_frame         = confirm_frame;
54     fifd->confirm_frame_arg     = confirm_frame_arg;
55     fifd->sstream_updated       = sstream_updated;
56     fifd->sstream_updated_arg   = sstream_updated_arg;
57     fifd->get_qlog_cb           = get_qlog_cb;
58     fifd->get_qlog_cb_arg       = get_qlog_cb_arg;
59     return 1;
60 }
61 
ossl_quic_fifd_cleanup(QUIC_FIFD * fifd)62 void ossl_quic_fifd_cleanup(QUIC_FIFD *fifd)
63 {
64     /* No-op. */
65 }
66 
on_acked(void * arg)67 static void on_acked(void *arg)
68 {
69     QUIC_TXPIM_PKT *pkt = arg;
70     QUIC_FIFD *fifd = pkt->fifd;
71     const QUIC_TXPIM_CHUNK *chunks = ossl_quic_txpim_pkt_get_chunks(pkt);
72     size_t i, num_chunks = ossl_quic_txpim_pkt_get_num_chunks(pkt);
73     QUIC_SSTREAM *sstream;
74     QUIC_CFQ_ITEM *cfq_item, *cfq_item_next;
75 
76     /* STREAM and CRYPTO stream chunks, FINs and stream FC frames */
77     for (i = 0; i < num_chunks; ++i) {
78         sstream = fifd->get_sstream_by_id(chunks[i].stream_id,
79                                           pkt->ackm_pkt.pkt_space,
80                                           fifd->get_sstream_by_id_arg);
81         if (sstream == NULL)
82             continue;
83 
84         if (chunks[i].end >= chunks[i].start)
85             /* coverity[check_return]: Best effort - we cannot fail here. */
86             ossl_quic_sstream_mark_acked(sstream,
87                                          chunks[i].start, chunks[i].end);
88 
89         if (chunks[i].has_fin && chunks[i].stream_id != UINT64_MAX)
90             ossl_quic_sstream_mark_acked_fin(sstream);
91 
92         if (chunks[i].has_stop_sending && chunks[i].stream_id != UINT64_MAX)
93             fifd->confirm_frame(OSSL_QUIC_FRAME_TYPE_STOP_SENDING,
94                                 chunks[i].stream_id, pkt,
95                                 fifd->confirm_frame_arg);
96 
97         if (chunks[i].has_reset_stream && chunks[i].stream_id != UINT64_MAX)
98             fifd->confirm_frame(OSSL_QUIC_FRAME_TYPE_RESET_STREAM,
99                                 chunks[i].stream_id, pkt,
100                                 fifd->confirm_frame_arg);
101 
102         if (ossl_quic_sstream_is_totally_acked(sstream))
103             fifd->sstream_updated(chunks[i].stream_id, fifd->sstream_updated_arg);
104     }
105 
106     /* GCR */
107     for (cfq_item = pkt->retx_head; cfq_item != NULL; cfq_item = cfq_item_next) {
108         cfq_item_next = cfq_item->pkt_next;
109         ossl_quic_cfq_release(fifd->cfq, cfq_item);
110     }
111 
112     ossl_quic_txpim_pkt_release(fifd->txpim, pkt);
113 }
114 
fifd_get_qlog(QUIC_FIFD * fifd)115 static QLOG *fifd_get_qlog(QUIC_FIFD *fifd)
116 {
117     if (fifd->get_qlog_cb == NULL)
118         return NULL;
119 
120     return fifd->get_qlog_cb(fifd->get_qlog_cb_arg);
121 }
122 
on_lost(void * arg)123 static void on_lost(void *arg)
124 {
125     QUIC_TXPIM_PKT *pkt = arg;
126     QUIC_FIFD *fifd = pkt->fifd;
127     const QUIC_TXPIM_CHUNK *chunks = ossl_quic_txpim_pkt_get_chunks(pkt);
128     size_t i, num_chunks = ossl_quic_txpim_pkt_get_num_chunks(pkt);
129     QUIC_SSTREAM *sstream;
130     QUIC_CFQ_ITEM *cfq_item, *cfq_item_next;
131     int sstream_updated;
132 
133     ossl_qlog_event_recovery_packet_lost(fifd_get_qlog(fifd), pkt);
134 
135     /* STREAM and CRYPTO stream chunks, FIN and stream FC frames */
136     for (i = 0; i < num_chunks; ++i) {
137         sstream = fifd->get_sstream_by_id(chunks[i].stream_id,
138                                           pkt->ackm_pkt.pkt_space,
139                                           fifd->get_sstream_by_id_arg);
140         if (sstream == NULL)
141             continue;
142 
143         sstream_updated = 0;
144 
145         if (chunks[i].end >= chunks[i].start) {
146             /*
147              * Note: If the stream is being reset, we do not need to retransmit
148              * old data as this is pointless. In this case this will be handled
149              * by (sstream == NULL) above as the QSM will free the QUIC_SSTREAM
150              * and our call to get_sstream_by_id above will return NULL.
151              */
152             ossl_quic_sstream_mark_lost(sstream,
153                                         chunks[i].start, chunks[i].end);
154             sstream_updated = 1;
155         }
156 
157         if (chunks[i].has_fin && chunks[i].stream_id != UINT64_MAX) {
158             ossl_quic_sstream_mark_lost_fin(sstream);
159             sstream_updated = 1;
160         }
161 
162         if (chunks[i].has_stop_sending && chunks[i].stream_id != UINT64_MAX)
163             fifd->regen_frame(OSSL_QUIC_FRAME_TYPE_STOP_SENDING,
164                               chunks[i].stream_id, pkt,
165                               fifd->regen_frame_arg);
166 
167         if (chunks[i].has_reset_stream && chunks[i].stream_id != UINT64_MAX)
168             fifd->regen_frame(OSSL_QUIC_FRAME_TYPE_RESET_STREAM,
169                               chunks[i].stream_id, pkt,
170                               fifd->regen_frame_arg);
171 
172         /*
173          * Inform caller that stream needs an FC frame.
174          *
175          * Note: We could track whether an FC frame was sent originally for the
176          * stream to determine if it really needs to be regenerated or not.
177          * However, if loss has occurred, it's probably better to ensure the
178          * peer has up-to-date flow control data for the stream. Given that
179          * these frames are extremely small, we may as well always send it when
180          * handling loss.
181          */
182         fifd->regen_frame(OSSL_QUIC_FRAME_TYPE_MAX_STREAM_DATA,
183                           chunks[i].stream_id,
184                           pkt,
185                           fifd->regen_frame_arg);
186 
187         if (sstream_updated && chunks[i].stream_id != UINT64_MAX)
188             fifd->sstream_updated(chunks[i].stream_id,
189                                   fifd->sstream_updated_arg);
190     }
191 
192     /* GCR */
193     for (cfq_item = pkt->retx_head; cfq_item != NULL; cfq_item = cfq_item_next) {
194         cfq_item_next = cfq_item->pkt_next;
195         ossl_quic_cfq_mark_lost(fifd->cfq, cfq_item, UINT32_MAX);
196     }
197 
198     /* Regenerate flag frames */
199     if (pkt->had_handshake_done_frame)
200         fifd->regen_frame(OSSL_QUIC_FRAME_TYPE_HANDSHAKE_DONE,
201                           UINT64_MAX, pkt,
202                           fifd->regen_frame_arg);
203 
204     if (pkt->had_max_data_frame)
205         fifd->regen_frame(OSSL_QUIC_FRAME_TYPE_MAX_DATA,
206                           UINT64_MAX, pkt,
207                           fifd->regen_frame_arg);
208 
209     if (pkt->had_max_streams_bidi_frame)
210         fifd->regen_frame(OSSL_QUIC_FRAME_TYPE_MAX_STREAMS_BIDI,
211                           UINT64_MAX, pkt,
212                           fifd->regen_frame_arg);
213 
214     if (pkt->had_max_streams_uni_frame)
215         fifd->regen_frame(OSSL_QUIC_FRAME_TYPE_MAX_STREAMS_UNI,
216                           UINT64_MAX, pkt,
217                           fifd->regen_frame_arg);
218 
219     if (pkt->had_ack_frame)
220         /*
221          * We always use the ACK_WITH_ECN frame type to represent the ACK frame
222          * type in our callback; we assume it is the caller's job to decide
223          * whether it wants to send ECN data or not.
224          */
225         fifd->regen_frame(OSSL_QUIC_FRAME_TYPE_ACK_WITH_ECN,
226                           UINT64_MAX, pkt,
227                           fifd->regen_frame_arg);
228 
229     ossl_quic_txpim_pkt_release(fifd->txpim, pkt);
230 }
231 
on_discarded(void * arg)232 static void on_discarded(void *arg)
233 {
234     QUIC_TXPIM_PKT *pkt = arg;
235     QUIC_FIFD *fifd = pkt->fifd;
236     QUIC_CFQ_ITEM *cfq_item, *cfq_item_next;
237 
238     /*
239      * Don't need to do anything to SSTREAMs for STREAM and CRYPTO streams, as
240      * we assume caller will clean them up.
241      */
242 
243     /* GCR */
244     for (cfq_item = pkt->retx_head; cfq_item != NULL; cfq_item = cfq_item_next) {
245         cfq_item_next = cfq_item->pkt_next;
246         ossl_quic_cfq_release(fifd->cfq, cfq_item);
247     }
248 
249     ossl_quic_txpim_pkt_release(fifd->txpim, pkt);
250 }
251 
ossl_quic_fifd_pkt_commit(QUIC_FIFD * fifd,QUIC_TXPIM_PKT * pkt)252 int ossl_quic_fifd_pkt_commit(QUIC_FIFD *fifd, QUIC_TXPIM_PKT *pkt)
253 {
254     QUIC_CFQ_ITEM *cfq_item;
255     const QUIC_TXPIM_CHUNK *chunks;
256     size_t i, num_chunks;
257     QUIC_SSTREAM *sstream;
258 
259     pkt->fifd                   = fifd;
260 
261     pkt->ackm_pkt.on_lost       = on_lost;
262     pkt->ackm_pkt.on_acked      = on_acked;
263     pkt->ackm_pkt.on_discarded  = on_discarded;
264     pkt->ackm_pkt.cb_arg        = pkt;
265 
266     ossl_list_tx_history_init_elem(&pkt->ackm_pkt);
267     pkt->ackm_pkt.anext = pkt->ackm_pkt.lnext = NULL;
268 
269     /*
270      * Mark the CFQ items which have been added to this packet as having been
271      * transmitted.
272      */
273     for (cfq_item = pkt->retx_head;
274          cfq_item != NULL;
275          cfq_item = cfq_item->pkt_next)
276         ossl_quic_cfq_mark_tx(fifd->cfq, cfq_item);
277 
278     /*
279      * Mark the send stream chunks which have been added to the packet as having
280      * been transmitted.
281      */
282     chunks = ossl_quic_txpim_pkt_get_chunks(pkt);
283     num_chunks = ossl_quic_txpim_pkt_get_num_chunks(pkt);
284     for (i = 0; i < num_chunks; ++i) {
285         sstream = fifd->get_sstream_by_id(chunks[i].stream_id,
286                                           pkt->ackm_pkt.pkt_space,
287                                           fifd->get_sstream_by_id_arg);
288         if (sstream == NULL)
289             continue;
290 
291         if (chunks[i].end >= chunks[i].start
292             && !ossl_quic_sstream_mark_transmitted(sstream,
293                                                    chunks[i].start,
294                                                    chunks[i].end))
295             return 0;
296 
297         if (chunks[i].has_fin
298             && !ossl_quic_sstream_mark_transmitted_fin(sstream,
299                                                        chunks[i].end + 1))
300                 return 0;
301     }
302 
303     /* Inform the ACKM. */
304     return ossl_ackm_on_tx_packet(fifd->ackm, &pkt->ackm_pkt);
305 }
306 
ossl_quic_fifd_set_qlog_cb(QUIC_FIFD * fifd,QLOG * (* get_qlog_cb)(void * arg),void * get_qlog_cb_arg)307 void ossl_quic_fifd_set_qlog_cb(QUIC_FIFD *fifd, QLOG *(*get_qlog_cb)(void *arg),
308                                 void *get_qlog_cb_arg)
309 {
310     fifd->get_qlog_cb       = get_qlog_cb;
311     fifd->get_qlog_cb_arg   = get_qlog_cb_arg;
312 }
313