Home | History | Annotate | Line # | Download | only in quic
      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 {
     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 
     62 void ossl_quic_fifd_cleanup(QUIC_FIFD *fifd)
     63 {
     64     /* No-op. */
     65 }
     66 
     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 
    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 
    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 
    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 
    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 
    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 
    314 static void txpim_pkt_remove_cfq_item(QUIC_TXPIM_PKT *pkt, QUIC_CFQ_ITEM *cfq_item)
    315 {
    316     QUIC_CFQ_ITEM *prev = cfq_item->pkt_prev;
    317 
    318     if (prev != NULL) {
    319         prev->pkt_next = cfq_item->pkt_next;
    320     } else {
    321         pkt->retx_head = cfq_item->pkt_next;
    322     }
    323 
    324     if (cfq_item->pkt_next != NULL)
    325         cfq_item->pkt_next->pkt_prev = prev;
    326 
    327     cfq_item->pkt_prev = NULL;
    328     cfq_item->pkt_next = NULL;
    329 }
    330 
    331 void ossl_quic_fifd_pkt_discard_unreliable(QUIC_FIFD *fifd, QUIC_TXPIM_PKT *pkt)
    332 {
    333     QUIC_CFQ_ITEM *cfq_item, *cfq_next;
    334 
    335     /*
    336      * The packet has been written to network. We can discard frames we don't
    337      * retransmit when loss is detected.
    338      */
    339     cfq_item = pkt->retx_head;
    340     while (cfq_item != NULL) {
    341         /*
    342          * Discarded items are moved to free list. If item
    343          * got moved to free list we must also remove it from
    344          * cfq list kept in pkt, so ACKM does not find it when
    345          * receives an ACK for pkt.
    346          */
    347         if (ossl_quic_cfq_discard_unreliable(fifd->cfq, cfq_item)) {
    348             cfq_next = cfq_item->pkt_next;
    349             txpim_pkt_remove_cfq_item(pkt, cfq_item);
    350             cfq_item = cfq_next;
    351         } else {
    352             cfq_item = cfq_item->pkt_next;
    353         }
    354     }
    355 }
    356