From: Hugo Landau Date: Thu, 15 Sep 2022 11:48:50 +0000 (+0100) Subject: QUIC FIFD X-Git-Tag: openssl-3.2.0-alpha1~1779 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=0ede517cfa73fd3566d2ecd32215b4b12dd1d3b5;p=thirdparty%2Fopenssl.git QUIC FIFD Reviewed-by: Tomas Mraz Reviewed-by: Matt Caswell (Merged from https://github.com/openssl/openssl/pull/19206) --- diff --git a/doc/designs/quic-design/images/quic-overview.odg b/doc/designs/quic-design/images/quic-overview.odg index a844a0deb71..c8a055760bb 100644 Binary files a/doc/designs/quic-design/images/quic-overview.odg and b/doc/designs/quic-design/images/quic-overview.odg differ diff --git a/doc/designs/quic-design/images/quic-overview.svg b/doc/designs/quic-design/images/quic-overview.svg index fbc81cc764c..4bb82282a69 100644 --- a/doc/designs/quic-design/images/quic-overview.svg +++ b/doc/designs/quic-design/images/quic-overview.svg @@ -11,57 +11,58 @@ - + - + - - + + - - - - - + + + + + - + - + - + - - + + - - - - - - - - + + + + + + + + - - - - - - + + + + + + - - - + + + + - + @@ -126,7 +127,7 @@ - Kernel + Kernel @@ -134,7 +135,7 @@ - SSL API + SSL API @@ -142,7 +143,7 @@ - StreamSend Buffers + StreamSend Buffers @@ -150,15 +151,15 @@ - StreamRead Buffers + StreamRead Buffers - - - - ConnectionState Machine + + + + ConnectionState Machine @@ -166,7 +167,7 @@ - TLS HandshakeRecord Layer + TLS HandshakeRecord Layer @@ -174,7 +175,7 @@ - TX Packetizer + TX Packetizer @@ -182,7 +183,7 @@ - RX Depacketizer + RX Depacketizer @@ -190,7 +191,7 @@ - QUIC WriteRecord Layer + QUIC WriteRecord Layer @@ -198,7 +199,7 @@ - QUIC ReadRecord Layer + QUIC ReadRecord Layer @@ -206,7 +207,7 @@ - ConnectionID Cache + ConnectionID Cache @@ -214,7 +215,7 @@ - DatagramBIO + DatagramBIO @@ -222,7 +223,7 @@ - DatagramBIO + DatagramBIO @@ -230,7 +231,7 @@ - DatagramBIO + DatagramBIO @@ -238,7 +239,7 @@ - UDP + UDP @@ -246,7 +247,7 @@ - Hardware Interfaces + Hardware Interfaces @@ -254,7 +255,7 @@ - UDP + UDP @@ -262,7 +263,7 @@ - UDP + UDP @@ -294,7 +295,7 @@ - DatagramBIO + DatagramBIO @@ -302,7 +303,7 @@ - UDP + UDP @@ -346,7 +347,7 @@ - Path And ConnDemultiplexer + Path And ConnDemultiplexer @@ -428,7 +429,7 @@ - CongestionController + CongestionController @@ -436,7 +437,7 @@ - New Reno + New Reno @@ -462,26 +463,26 @@ - - - - + + + + - - - - Timer AndEvent Queue + + + + Timer AndEvent Queue - - - - + + + + @@ -489,7 +490,7 @@ - Flow Controller AndStatistics Collector + Flow Controller AndStatistics Collector @@ -512,7 +513,7 @@ - ACK Handling AndLoss Detector + ACK Handling AndLoss Detector @@ -524,9 +525,9 @@ - - - + + + @@ -566,18 +567,18 @@ - - - - + + + + - - - - + + + + @@ -603,6 +604,35 @@ + + + + + + Frame-in-FlightManagement + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/designs/quic-design/quic-fifm.md b/doc/designs/quic-design/quic-fifm.md index 1de81318f3b..f03b5d7d368 100644 --- a/doc/designs/quic-design/quic-fifm.md +++ b/doc/designs/quic-design/quic-fifm.md @@ -133,7 +133,7 @@ metadata associated with it: to the `TX` state. If the packet it was sent in is subsequently lost, it is transitioned back to the `NEW` state. -Packets in the `NEW` state participate in a priority queue (the NEW queue) +Frames in the `NEW` state participate in a priority queue (the NEW queue) according to their priority and the CFQ's NEW queue can be iterated in priority order by callers. @@ -304,9 +304,9 @@ allocation made per transmitted packet. The TX packetiser will obtain a `QUIC_TXPIM_PKT` structure from the TXPIM, fill in the structure including the ACK Manager data, and submit it via the FIFD which we introduce below. -The TXPIM does not anything with the `QUIC_TXPIM_PKT` structure itself -other than managing its allocation and manipulation. Constructive -use of the data kept in the TXPIM is made by the FIFD. +The TXPIM does do not anything with the `QUIC_TXPIM_PKT` structure itself other +than managing its allocation and manipulation. Constructive use of the data kept +in the TXPIM is made by the FIFD. ### API @@ -328,10 +328,11 @@ typedef struct quic_txpim_pkt_st { QUIC_FIFD *fifd; /* Regenerate-strategy frames. */ - unsigned int had_handshake_done : 1; - unsigned int had_max_data_frame : 1; - unsigned int had_max_streams_frame : 1; - unsigned int had_ack_frame : 1; + unsigned int had_handshake_done : 1; + unsigned int had_max_data_frame : 1; + unsigned int had_max_streams_bidi_frame : 1; + unsigned int had_max_streams_uni_frame : 1; + unsigned int had_ack_frame : 1; /* Private data follows. */ } QUIC_TXPIM_PKT; @@ -392,6 +393,12 @@ const QUIC_TXPIM_CHUNK *ossl_quic_txpim_pkt_get_chunks(QUIC_TXPIM_PKT *fpkt); * ossl_quic_txpim_pkt_get_chunks(). */ size_t ossl_quic_txpim_pkt_get_num_chunks(QUIC_TXPIM_PKT *fpkt); + +/* + * Returns the number of QUIC_TXPIM_PKTs allocated by the given TXPIM that have + * yet to be returned to the TXPIM. + */ +size_t ossl_quic_txpim_get_in_use(QUIC_TXPIM *txpim); ``` The Frame-in-Flight Dispatcher (FIFD) @@ -432,7 +439,7 @@ simply glues all of these parts together. ### API ```c -typedef struct quic_fifm_st { +typedef struct quic_fifd_st { /* (internals) */ } QUIC_FIFD; @@ -476,9 +483,6 @@ Typical Intended TX Packetiser Usage all CFQ frames are considered of higher priority). For each such frame it places in a packet, it: - - informs the CFQ that the CFQ item has been transmitted, causing a - transition of the CFQ item to the `TX` state; - - calls `ossl_quic_txpim_pkt_add_cfq_item()` on the TXPIM to log the CFQ item as having been transmitted in the given packet, so that the CFQ item can be released or requeued depending on the ultimate fate of the packet. @@ -497,7 +501,9 @@ Typical Intended TX Packetiser Usage - TX Packetiser calls `ossl_quic_fifd_pkt_commit()`. The FIFD takes care of submitting the packet to the ACK Manager and provides its own callback - implementation. + implementation. It also takes care of informing the CFQ that any CFQ items + which were added via `ossl_quic_txpim_pkt_add_cfq_item()` have been + transmitted. In the event of packet loss, ACK or discard, the appropriate QUIC Send Stream, CFQ and regenerate callback calls are made. Regardless of the outcome, the diff --git a/doc/designs/quic-design/quic-overview.md b/doc/designs/quic-design/quic-overview.md index 141abcb9bca..2ef43cefc51 100644 --- a/doc/designs/quic-design/quic-overview.md +++ b/doc/designs/quic-design/quic-overview.md @@ -21,6 +21,13 @@ SSL_read and SSL_write functions. They will be bypassed with a single-copy API for read and write (_not for MVP_). +Frame in Flight Manager +----------------------- + +The frame in flight manager manages the queueing of frames which may need to be +retransmitted if the packets in which they were transmitted were lost. It is +[discussed in more detail here.](./quic-fifm.md) + Connection State Machine ------------------------ @@ -65,12 +72,17 @@ either as data or as events to the subsequent modules based on the frame type. Flow Controller And Statistics Collector is consulted for decisions and to record the statistics of the received stream data. -Flow Controller And Statistics Collector ----------------------------------------- +Flow Controller +--------------- + +This module is consulted by the TX Packetizer and RX Frame Handler for flow +control decisions at both the stream and connection levels. + +Statistics Collector +-------------------- -This module collects various statistics about send and received -stream data. It is also consulted by the TX Packetizer and RX Frame -Handler for flow control decisions. +This module maintains statistics about a connection, most notably the estimated +round trip time to the remote peer. QUIC Write Record Layer ----------------------- diff --git a/include/internal/quic_fifd.h b/include/internal/quic_fifd.h new file mode 100644 index 00000000000..15952e43d89 --- /dev/null +++ b/include/internal/quic_fifd.h @@ -0,0 +1,56 @@ +/* + * Copyright 2022 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the Apache License 2.0 (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#ifndef OSSL_QUIC_FIFD_H +# define OSSL_QUIC_FIFD_H + +# include +# include "internal/quic_types.h" +# include "internal/quic_cfq.h" +# include "internal/quic_ackm.h" +# include "internal/quic_txpim.h" +# include "internal/quic_stream.h" + +/* + * QUIC Frame-in-Flight Dispatcher (FIFD) + * ====================================== + */ +struct quic_fifd_st { + /* Internal data; use the ossl_quic_fifd functions. */ + QUIC_CFQ *cfq; + OSSL_ACKM *ackm; + QUIC_TXPIM *txpim; + QUIC_SSTREAM *(*get_sstream_by_id)(uint64_t stream_id, + void *arg); + void *get_sstream_by_id_arg; + void (*regen_frame)(uint64_t frame_type, + uint64_t stream_id, + void *arg); + void *regen_frame_arg; +}; + +int ossl_quic_fifd_init(QUIC_FIFD *fifd, + QUIC_CFQ *cfq, + OSSL_ACKM *ackm, + QUIC_TXPIM *txpim, + /* stream_id is UINT64_MAX for the crypto stream */ + QUIC_SSTREAM *(*get_sstream_by_id)(uint64_t stream_id, + void *arg), + void *get_sstream_by_id_arg, + /* stream_id is UINT64_MAX if not applicable */ + void (*regen_frame)(uint64_t frame_type, + uint64_t stream_id, + void *arg), + void *regen_frame_arg); + +void ossl_quic_fifd_cleanup(QUIC_FIFD *fifd); /* (no-op) */ + +int ossl_quic_fifd_pkt_commit(QUIC_FIFD *fifd, QUIC_TXPIM_PKT *pkt); + +#endif diff --git a/include/internal/quic_txpim.h b/include/internal/quic_txpim.h index 623004e79da..087d13363fb 100644 --- a/include/internal/quic_txpim.h +++ b/include/internal/quic_txpim.h @@ -35,7 +35,8 @@ typedef struct quic_txpim_pkt_st { /* Regenerate-strategy frames. */ unsigned int had_handshake_done_frame : 1; unsigned int had_max_data_frame : 1; - unsigned int had_max_streams_frame : 1; + unsigned int had_max_streams_bidi_frame : 1; + unsigned int had_max_streams_uni_frame : 1; unsigned int had_ack_frame : 1; /* Private data follows. */ @@ -106,4 +107,10 @@ const QUIC_TXPIM_CHUNK *ossl_quic_txpim_pkt_get_chunks(QUIC_TXPIM_PKT *fpkt); */ size_t ossl_quic_txpim_pkt_get_num_chunks(QUIC_TXPIM_PKT *fpkt); +/* + * Returns the number of QUIC_TXPIM_PKTs allocated by the given TXPIM that have + * yet to be returned to the TXPIM. + */ +size_t ossl_quic_txpim_get_in_use(QUIC_TXPIM *txpim); + #endif diff --git a/ssl/quic/build.info b/ssl/quic/build.info index 1f228cb6d74..45440384e11 100644 --- a/ssl/quic/build.info +++ b/ssl/quic/build.info @@ -5,4 +5,4 @@ SOURCE[$LIBSSL]=cc_dummy.c quic_demux.c quic_record_rx.c SOURCE[$LIBSSL]=quic_record_tx.c quic_record_util.c quic_record_shared.c quic_wire_pkt.c SOURCE[$LIBSSL]=quic_record_rx_wrap.c quic_rx_depack.c SOURCE[$LIBSSL]=quic_fc.c uint_set.c quic_stream.c -SOURCE[$LIBSSL]=quic_cfq.c quic_txpim.c +SOURCE[$LIBSSL]=quic_cfq.c quic_txpim.c quic_fifd.c diff --git a/ssl/quic/quic_cfq.c b/ssl/quic/quic_cfq.c index cdd621458c4..0b0651289a5 100644 --- a/ssl/quic/quic_cfq.c +++ b/ssl/quic/quic_cfq.c @@ -320,6 +320,9 @@ QUIC_CFQ_ITEM *ossl_quic_cfq_get_priority_head(QUIC_CFQ *cfq, uint32_t pn_space) for (; item != NULL && item->pn_space != pn_space; item = item->next); + if (item == NULL) + return NULL; /* ubsan */ + return &item->public; } @@ -335,5 +338,8 @@ QUIC_CFQ_ITEM *ossl_quic_cfq_item_get_priority_next(QUIC_CFQ_ITEM *item, for (; ex != NULL && ex->pn_space != pn_space; ex = ex->next); + if (ex == NULL) + return NULL; /* ubsan */ + return &ex->public; } diff --git a/ssl/quic/quic_fifd.c b/ssl/quic/quic_fifd.c new file mode 100644 index 00000000000..8f548520b1b --- /dev/null +++ b/ssl/quic/quic_fifd.c @@ -0,0 +1,202 @@ +/* + * Copyright 2022 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the Apache License 2.0 (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#include "internal/quic_fifd.h" +#include "internal/quic_wire.h" + +int ossl_quic_fifd_init(QUIC_FIFD *fifd, + QUIC_CFQ *cfq, + OSSL_ACKM *ackm, + QUIC_TXPIM *txpim, + /* stream_id is UINT64_MAX for the crypto stream */ + QUIC_SSTREAM *(*get_sstream_by_id)(uint64_t stream_id, + void *arg), + void *get_sstream_by_id_arg, + /* stream_id is UINT64_MAX if not applicable */ + void (*regen_frame)(uint64_t frame_type, + uint64_t stream_id, + void *arg), + void *regen_frame_arg) +{ + if (cfq == NULL || ackm == NULL || txpim == NULL + || get_sstream_by_id == NULL || regen_frame == NULL) + return 0; + + fifd->cfq = cfq; + fifd->ackm = ackm; + fifd->txpim = txpim; + fifd->get_sstream_by_id = get_sstream_by_id; + fifd->get_sstream_by_id_arg = get_sstream_by_id_arg; + fifd->regen_frame = regen_frame; + fifd->regen_frame_arg = regen_frame_arg; + return 1; +} + +void ossl_quic_fifd_cleanup(QUIC_FIFD *fifd) +{ + /* No-op. */ +} + +static void on_acked(void *arg) +{ + QUIC_TXPIM_PKT *pkt = arg; + QUIC_FIFD *fifd = pkt->fifd; + const QUIC_TXPIM_CHUNK *chunks = ossl_quic_txpim_pkt_get_chunks(pkt); + size_t i, num_chunks = ossl_quic_txpim_pkt_get_num_chunks(pkt); + QUIC_SSTREAM *sstream; + QUIC_CFQ_ITEM *cfq_item, *cfq_item_next; + + /* STREAM and CRYPTO stream chunks, FINs and stream FC frames */ + for (i = 0; i < num_chunks; ++i) { + sstream = fifd->get_sstream_by_id(chunks[i].stream_id, + fifd->get_sstream_by_id_arg); + if (sstream == NULL) + continue; + + if (chunks[i].end >= chunks[i].start) + ossl_quic_sstream_mark_acked(sstream, + chunks[i].start, chunks[i].end); + + if (chunks[i].has_fin && chunks[i].stream_id != UINT64_MAX) + ossl_quic_sstream_mark_acked_fin(sstream); + } + + /* GCR */ + for (cfq_item = pkt->retx_head; cfq_item != NULL; cfq_item = cfq_item_next) { + cfq_item_next = cfq_item->pkt_next; + ossl_quic_cfq_release(fifd->cfq, cfq_item); + } + + ossl_quic_txpim_pkt_release(fifd->txpim, pkt); +} + +static void on_lost(void *arg) +{ + QUIC_TXPIM_PKT *pkt = arg; + QUIC_FIFD *fifd = pkt->fifd; + const QUIC_TXPIM_CHUNK *chunks = ossl_quic_txpim_pkt_get_chunks(pkt); + size_t i, num_chunks = ossl_quic_txpim_pkt_get_num_chunks(pkt); + QUIC_SSTREAM *sstream; + QUIC_CFQ_ITEM *cfq_item, *cfq_item_next; + + /* STREAM and CRYPTO stream chunks, FIN and stream FC frames */ + for (i = 0; i < num_chunks; ++i) { + sstream = fifd->get_sstream_by_id(chunks[i].stream_id, + fifd->get_sstream_by_id_arg); + if (sstream == NULL) + continue; + + if (chunks[i].end >= chunks[i].start) + ossl_quic_sstream_mark_lost(sstream, + chunks[i].start, chunks[i].end); + + if (chunks[i].has_fin && chunks[i].stream_id != UINT64_MAX) + ossl_quic_sstream_mark_lost_fin(sstream); + + /* + * Inform caller that stream needs an FC frame. + * + * Note: We could track whether an FC frame was sent originally for the + * stream to determine if it really needs to be regenerated or not. + * However, if loss has occurred, it's probably better to ensure the + * peer has up-to-date flow control data for the stream. Given that + * these frames are extremely small, we may as well always send it when + * handling loss. + */ + fifd->regen_frame(OSSL_QUIC_FRAME_TYPE_MAX_STREAM_DATA, + chunks[i].stream_id, + fifd->regen_frame_arg); + } + + /* GCR */ + for (cfq_item = pkt->retx_head; cfq_item != NULL; cfq_item = cfq_item_next) { + cfq_item_next = cfq_item->pkt_next; + ossl_quic_cfq_mark_lost(fifd->cfq, cfq_item, UINT32_MAX); + } + + /* Regenerate flag frames */ + if (pkt->had_handshake_done_frame) + fifd->regen_frame(OSSL_QUIC_FRAME_TYPE_HANDSHAKE_DONE, + UINT64_MAX, + fifd->regen_frame_arg); + + if (pkt->had_max_data_frame) + fifd->regen_frame(OSSL_QUIC_FRAME_TYPE_MAX_DATA, + UINT64_MAX, + fifd->regen_frame_arg); + + if (pkt->had_max_streams_bidi_frame) + fifd->regen_frame(OSSL_QUIC_FRAME_TYPE_MAX_STREAMS_BIDI, + UINT64_MAX, + fifd->regen_frame_arg); + + if (pkt->had_max_streams_uni_frame) + fifd->regen_frame(OSSL_QUIC_FRAME_TYPE_MAX_STREAMS_UNI, + UINT64_MAX, + fifd->regen_frame_arg); + + if (pkt->had_ack_frame) + /* + * We always use the ACK_WITH_ECN frame type to represent the ACK frame + * type in our callback; we assume it is the caller's job to decide + * whether it wants to send ECN data or not. + */ + fifd->regen_frame(OSSL_QUIC_FRAME_TYPE_ACK_WITH_ECN, + UINT64_MAX, + fifd->regen_frame_arg); + + ossl_quic_txpim_pkt_release(fifd->txpim, pkt); +} + +static void on_discarded(void *arg) +{ + QUIC_TXPIM_PKT *pkt = arg; + QUIC_FIFD *fifd = pkt->fifd; + QUIC_CFQ_ITEM *cfq_item, *cfq_item_next; + + /* + * Don't need to do anything to SSTREAMs for STREAM and CRYPTO streams, as + * we assume caller will clean them up. + */ + + /* GCR */ + for (cfq_item = pkt->retx_head; cfq_item != NULL; cfq_item = cfq_item_next) { + cfq_item_next = cfq_item->pkt_next; + ossl_quic_cfq_release(fifd->cfq, cfq_item); + } + + ossl_quic_txpim_pkt_release(fifd->txpim, pkt); +} + +int ossl_quic_fifd_pkt_commit(QUIC_FIFD *fifd, QUIC_TXPIM_PKT *pkt) +{ + QUIC_CFQ_ITEM *cfq_item; + + pkt->fifd = fifd; + + pkt->ackm_pkt.on_lost = on_lost; + pkt->ackm_pkt.on_acked = on_acked; + pkt->ackm_pkt.on_discarded = on_discarded; + pkt->ackm_pkt.cb_arg = pkt; + + pkt->ackm_pkt.prev = pkt->ackm_pkt.next + = pkt->ackm_pkt.anext = pkt->ackm_pkt.lnext = NULL; + + /* + * Mark the CFQ items which have been added to this packet as having been + * transmitted. + */ + for (cfq_item = pkt->retx_head; + cfq_item != NULL; + cfq_item = cfq_item->pkt_next) + ossl_quic_cfq_mark_tx(fifd->cfq, cfq_item); + + /* Inform the ACKM. */ + return ossl_ackm_on_tx_packet(fifd->ackm, &pkt->ackm_pkt); +} diff --git a/ssl/quic/quic_txpim.c b/ssl/quic/quic_txpim.c index 38b16a45619..96937587699 100644 --- a/ssl/quic/quic_txpim.c +++ b/ssl/quic/quic_txpim.c @@ -111,7 +111,8 @@ static void txpim_clear(QUIC_TXPIM_PKT_EX *ex) ex->public.fifd = NULL; ex->public.had_handshake_done_frame = 0; ex->public.had_max_data_frame = 0; - ex->public.had_max_streams_frame = 0; + ex->public.had_max_streams_bidi_frame = 0; + ex->public.had_max_streams_uni_frame = 0; ex->public.had_ack_frame = 0; } @@ -202,6 +203,10 @@ const QUIC_TXPIM_CHUNK *ossl_quic_txpim_pkt_get_chunks(QUIC_TXPIM_PKT *fpkt) QUIC_TXPIM_PKT_EX *ex = (QUIC_TXPIM_PKT_EX *)fpkt; if (ex->chunks_need_sort) { + /* + * List of chunks will generally be very small so there is no issue + * simply sorting here. + */ qsort(ex->chunks, ex->num_chunks, sizeof(QUIC_TXPIM_CHUNK), compare); ex->chunks_need_sort = 0; } @@ -215,3 +220,8 @@ size_t ossl_quic_txpim_pkt_get_num_chunks(QUIC_TXPIM_PKT *fpkt) return ex->num_chunks; } + +size_t ossl_quic_txpim_get_in_use(QUIC_TXPIM *txpim) +{ + return txpim->in_use; +} diff --git a/test/build.info b/test/build.info index 728fc111667..370220806d4 100644 --- a/test/build.info +++ b/test/build.info @@ -308,6 +308,10 @@ IF[{- !$disabled{tests} -}] INCLUDE[quic_txpim_test]=../include ../apps/include DEPEND[quic_txpim_test]=../libcrypto.a ../libssl.a libtestutil.a + SOURCE[quic_fifd_test]=quic_fifd_test.c + INCLUDE[quic_fifd_test]=../include ../apps/include + DEPEND[quic_fifd_test]=../libcrypto.a ../libssl.a libtestutil.a + SOURCE[asynctest]=asynctest.c INCLUDE[asynctest]=../include ../apps/include DEPEND[asynctest]=../libcrypto @@ -1032,7 +1036,7 @@ ENDIF ENDIF IF[{- !$disabled{'quic'} -}] - PROGRAMS{noinst}=quicapitest quic_wire_test quic_ackm_test quic_record_test quic_fc_test quic_stream_test quic_cfq_test quic_txpim_test + PROGRAMS{noinst}=quicapitest quic_wire_test quic_ackm_test quic_record_test quic_fc_test quic_stream_test quic_cfq_test quic_txpim_test quic_fifd_test ENDIF SOURCE[quicapitest]=quicapitest.c helpers/ssltestlib.c diff --git a/test/quic_fifd_test.c b/test/quic_fifd_test.c new file mode 100644 index 00000000000..47eb0309301 --- /dev/null +++ b/test/quic_fifd_test.c @@ -0,0 +1,358 @@ +/* + * Copyright 2022 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the Apache License 2.0 (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#include "internal/packet.h" +#include "internal/quic_txpim.h" +#include "internal/quic_fifd.h" +#include "testutil.h" + +static OSSL_TIME cur_time; + +static OSSL_TIME fake_now(void *arg) { + return cur_time; +} + +static void step_time(uint64_t ms) { + cur_time = ossl_time_add(cur_time, ossl_ms2time(ms)); +} + +static QUIC_SSTREAM *(*get_sstream_by_id_p)(uint64_t stream_id, void *arg); + +static QUIC_SSTREAM *get_sstream_by_id(uint64_t stream_id, void *arg) +{ + return get_sstream_by_id_p(stream_id, arg); +} + +static void (*regen_frame_p)(uint64_t frame_type, uint64_t stream_id, void *arg); + +static void regen_frame(uint64_t frame_type, uint64_t stream_id, void *arg) +{ + regen_frame_p(frame_type, stream_id, arg); +} + +typedef struct info_st { + QUIC_FIFD fifd; + OSSL_ACKM *ackm; + QUIC_CFQ *cfq; + QUIC_TXPIM *txpim; + OSSL_STATM statm; + OSSL_CC_DATA *ccdata; + QUIC_SSTREAM *sstream[4]; +} INFO; + +static INFO *cur_info; +static int cb_fail; +static int cfq_freed; + +/* ---------------------------------------------------------------------- + * 1. Test that a submitted packet, on ack, acks all streams inside of it + * Test that a submitted packet, on ack, calls the get by ID function + * correctly + * Test that a submitted packet, on ack, acks all fins inside it + * Test that a submitted packet, on ack, releases the TXPIM packet + */ +static QUIC_SSTREAM *sstream_expect(uint64_t stream_id, void *arg) +{ + if (stream_id == 42 || stream_id == 43) + return cur_info->sstream[stream_id - 42]; + + cb_fail = 1; + return NULL; +} + +static uint64_t regen_frame_type[16]; +static uint64_t regen_stream_id[16]; +static size_t regen_count; + +static void regen_expect(uint64_t frame_type, uint64_t stream_id, void *arg) +{ + regen_frame_type[regen_count] = frame_type; + regen_stream_id[regen_count] = stream_id; + ++regen_count; +} + +static const unsigned char placeholder_data[] = "placeholder"; + +static void cfq_free_cb_(unsigned char *buf, size_t buf_len, void *arg) +{ + if (buf == placeholder_data && buf_len == sizeof(placeholder_data)) + cfq_freed = 1; +} + +#define TEST_KIND_ACK 0 +#define TEST_KIND_LOSS 1 +#define TEST_KIND_DISCARD 2 +#define TEST_KIND_NUM 3 + +static int test_generic(INFO *info, int kind) +{ + int testresult = 0; + size_t i, consumed = 0; + QUIC_TXPIM_PKT *pkt = NULL, *pkt2 = NULL; + OSSL_QUIC_FRAME_STREAM hdr = {0}; + OSSL_QTX_IOVEC iov[2]; + size_t num_iov; + QUIC_TXPIM_CHUNK chunk = {42, 0, 11, 0}; + OSSL_QUIC_FRAME_ACK ack = {0}; + OSSL_QUIC_ACK_RANGE ack_ranges[1] = {0}; + QUIC_CFQ_ITEM *cfq_item = NULL; + uint32_t pn_space = (kind == TEST_KIND_DISCARD) + ? QUIC_PN_SPACE_HANDSHAKE : QUIC_PN_SPACE_APP; + + cur_time = ossl_seconds2time(1000); + regen_count = 0; + + get_sstream_by_id_p = sstream_expect; + regen_frame_p = regen_expect; + + if (!TEST_ptr(pkt = ossl_quic_txpim_pkt_alloc(info->txpim))) + goto err; + + for (i = 0; i < 2; ++i) { + num_iov = OSSL_NELEM(iov); + if (!TEST_true(ossl_quic_sstream_append(info->sstream[i], + (unsigned char *)"Test message", + 12, &consumed)) + || !TEST_size_t_eq(consumed, 12)) + goto err; + + if (i == 1) + ossl_quic_sstream_fin(info->sstream[i]); + + if (!TEST_true(ossl_quic_sstream_get_stream_frame(info->sstream[i], 0, + &hdr, iov, &num_iov)) + || !TEST_int_eq(hdr.is_fin, i == 1) + || !TEST_uint64_t_eq(hdr.offset, 0) + || !TEST_uint64_t_eq(hdr.len, 12) + || !TEST_size_t_eq(ossl_quic_sstream_get_buffer_used(info->sstream[i]), 12) + || !TEST_true(ossl_quic_sstream_mark_transmitted(info->sstream[i], + hdr.offset, + hdr.offset + hdr.len - 1))) + goto err; + + if (i == 1 && !TEST_true(ossl_quic_sstream_mark_transmitted_fin(info->sstream[i], + hdr.offset + hdr.len))) + goto err; + + chunk.has_fin = hdr.is_fin; + chunk.stream_id = 42 + i; + if (!TEST_true(ossl_quic_txpim_pkt_append_chunk(pkt, &chunk))) + goto err; + } + + cfq_freed = 0; + if (!TEST_ptr(cfq_item = ossl_quic_cfq_add_frame(info->cfq, 10, + pn_space, + OSSL_QUIC_FRAME_TYPE_NEW_CONN_ID, + placeholder_data, + sizeof(placeholder_data), + cfq_free_cb_, NULL)) + || !TEST_ptr_eq(cfq_item, ossl_quic_cfq_get_priority_head(info->cfq, pn_space))) + goto err; + + ossl_quic_txpim_pkt_add_cfq_item(pkt, cfq_item); + + pkt->ackm_pkt.pkt_num = 0; + pkt->ackm_pkt.pkt_space = pn_space; + pkt->ackm_pkt.largest_acked = QUIC_PN_INVALID; + pkt->ackm_pkt.num_bytes = 50; + pkt->ackm_pkt.time = cur_time; + pkt->ackm_pkt.is_inflight = 1; + pkt->ackm_pkt.is_ack_eliciting = 1; + if (kind == TEST_KIND_LOSS) { + pkt->had_handshake_done_frame = 1; + pkt->had_max_data_frame = 1; + pkt->had_max_streams_bidi_frame = 1; + pkt->had_max_streams_uni_frame = 1; + pkt->had_ack_frame = 1; + } + + ack_ranges[0].start = 0; + ack_ranges[0].end = 0; + ack.ack_ranges = ack_ranges; + ack.num_ack_ranges = 1; + + if (!TEST_true(ossl_quic_fifd_pkt_commit(&info->fifd, pkt))) + goto err; + + /* CFQ item should have been marked as transmitted */ + if (!TEST_ptr_null(ossl_quic_cfq_get_priority_head(info->cfq, pn_space))) + goto err; + + switch (kind) { + case TEST_KIND_ACK: + if (!TEST_true(ossl_ackm_on_rx_ack_frame(info->ackm, &ack, + pn_space, + cur_time))) + goto err; + + for (i = 0; i < 2; ++i) + if (!TEST_size_t_eq(ossl_quic_sstream_get_buffer_used(info->sstream[i]), 0)) + goto err; + + /* This should fail, which proves the FIN was acked */ + if (!TEST_false(ossl_quic_sstream_mark_lost_fin(info->sstream[1]))) + goto err; + + /* CFQ item must have been released */ + if (!TEST_true(cfq_freed)) + goto err; + + /* No regen calls should have been made */ + if (!TEST_size_t_eq(regen_count, 0)) + goto err; + + break; + + case TEST_KIND_LOSS: + /* Trigger loss detection via packet threshold. */ + if (!TEST_ptr(pkt2 = ossl_quic_txpim_pkt_alloc(info->txpim))) + goto err; + + step_time(10000); + pkt2->ackm_pkt.pkt_num = 50; + pkt2->ackm_pkt.pkt_space = pn_space; + pkt2->ackm_pkt.largest_acked = QUIC_PN_INVALID; + pkt2->ackm_pkt.num_bytes = 50; + pkt2->ackm_pkt.time = cur_time; + pkt2->ackm_pkt.is_inflight = 1; + pkt2->ackm_pkt.is_ack_eliciting = 1; + + ack_ranges[0].start = 50; + ack_ranges[0].end = 50; + ack.ack_ranges = ack_ranges; + ack.num_ack_ranges = 1; + + if (!TEST_true(ossl_quic_fifd_pkt_commit(&info->fifd, pkt2)) + || !TEST_true(ossl_ackm_on_rx_ack_frame(info->ackm, &ack, + pn_space, cur_time))) + goto err; + + for (i = 0; i < 2; ++i) { + num_iov = OSSL_NELEM(iov); + /* + * Stream data we sent must have been marked as lost; check by + * ensuring it is returned again + */ + if (!TEST_true(ossl_quic_sstream_get_stream_frame(info->sstream[i], 0, + &hdr, iov, &num_iov)) + || !TEST_uint64_t_eq(hdr.offset, 0) + || !TEST_uint64_t_eq(hdr.len, 12)) + goto err; + } + + /* FC frame should have regenerated for each stream */ + if (!TEST_size_t_eq(regen_count, 7) + || !TEST_uint64_t_eq(regen_stream_id[0], 42) + || !TEST_uint64_t_eq(regen_frame_type[0], OSSL_QUIC_FRAME_TYPE_MAX_STREAM_DATA) + || !TEST_uint64_t_eq(regen_stream_id[1], 43) + || !TEST_uint64_t_eq(regen_frame_type[1], OSSL_QUIC_FRAME_TYPE_MAX_STREAM_DATA) + || !TEST_uint64_t_eq(regen_frame_type[2], OSSL_QUIC_FRAME_TYPE_HANDSHAKE_DONE) + || !TEST_uint64_t_eq(regen_stream_id[2], UINT64_MAX) + || !TEST_uint64_t_eq(regen_frame_type[3], OSSL_QUIC_FRAME_TYPE_MAX_DATA) + || !TEST_uint64_t_eq(regen_stream_id[3], UINT64_MAX) + || !TEST_uint64_t_eq(regen_frame_type[4], OSSL_QUIC_FRAME_TYPE_MAX_STREAMS_BIDI) + || !TEST_uint64_t_eq(regen_stream_id[4], UINT64_MAX) + || !TEST_uint64_t_eq(regen_frame_type[5], OSSL_QUIC_FRAME_TYPE_MAX_STREAMS_UNI) + || !TEST_uint64_t_eq(regen_stream_id[5], UINT64_MAX) + || !TEST_uint64_t_eq(regen_frame_type[6], OSSL_QUIC_FRAME_TYPE_ACK_WITH_ECN) + || !TEST_uint64_t_eq(regen_stream_id[6], UINT64_MAX)) + goto err; + + /* CFQ item should have been marked as lost */ + if (!TEST_ptr_eq(cfq_item, ossl_quic_cfq_get_priority_head(info->cfq, pn_space))) + goto err; + + /* FIN should have been marked as lost */ + num_iov = OSSL_NELEM(iov); + if (!TEST_true(ossl_quic_sstream_get_stream_frame(info->sstream[1], 10, + &hdr, iov, &num_iov)) + || !TEST_true(hdr.is_fin) + || !TEST_uint64_t_eq(hdr.len, 0)) + goto err; + + break; + + case TEST_KIND_DISCARD: + if (!TEST_true(ossl_ackm_on_pkt_space_discarded(info->ackm, pn_space))) + goto err; + + /* CFQ item must have been released */ + if (!TEST_true(cfq_freed)) + goto err; + + break; + + default: + goto err; + } + + /* TXPIM must have been released */ + if (!TEST_size_t_eq(ossl_quic_txpim_get_in_use(info->txpim), 0)) + goto err; + + testresult = 1; +err: + return testresult; +} + +static int test_fifd(int idx) +{ + int testresult = 0; + INFO info = {0}; + size_t i; + + cur_info = &info; + cb_fail = 0; + + if (!TEST_true(ossl_statm_init(&info.statm)) + || !TEST_ptr(info.ccdata = ossl_cc_dummy_method.new(NULL, NULL, NULL)) + || !TEST_ptr(info.ackm = ossl_ackm_new(fake_now, NULL, + &info.statm, + &ossl_cc_dummy_method, + info.ccdata)) + || !TEST_true(ossl_ackm_on_handshake_confirmed(info.ackm)) + || !TEST_ptr(info.cfq = ossl_quic_cfq_new()) + || !TEST_ptr(info.txpim = ossl_quic_txpim_new()) + || !TEST_true(ossl_quic_fifd_init(&info.fifd, info.cfq, info.ackm, + info.txpim, + get_sstream_by_id, NULL, + regen_frame, NULL))) + goto err; + + for (i = 0; i < OSSL_NELEM(info.sstream); ++i) + if (!TEST_ptr(info.sstream[i] = ossl_quic_sstream_new(1024))) + goto err; + + ossl_statm_update_rtt(&info.statm, ossl_time_zero(), ossl_ms2time(1)); + + if (!TEST_true(test_generic(&info, idx)) + || !TEST_false(cb_fail)) + goto err; + + testresult = 1; +err: + ossl_quic_fifd_cleanup(&info.fifd); + ossl_quic_cfq_free(info.cfq); + ossl_quic_txpim_free(info.txpim); + ossl_ackm_free(info.ackm); + ossl_statm_destroy(&info.statm); + if (info.ccdata != NULL) + ossl_cc_dummy_method.free(info.ccdata); + for (i = 0; i < OSSL_NELEM(info.sstream); ++i) + ossl_quic_sstream_free(info.sstream[i]); + cur_info = NULL; + return testresult; +} + +int setup_tests(void) +{ + ADD_ALL_TESTS(test_fifd, TEST_KIND_NUM); + return 1; +} diff --git a/test/recipes/70-test_quic_fifd.t b/test/recipes/70-test_quic_fifd.t new file mode 100644 index 00000000000..40245523ede --- /dev/null +++ b/test/recipes/70-test_quic_fifd.t @@ -0,0 +1,19 @@ +#! /usr/bin/env perl +# Copyright 2022 The OpenSSL Project Authors. All Rights Reserved. +# +# Licensed under the Apache License 2.0 (the "License"). You may not use +# this file except in compliance with the License. You can obtain a copy +# in the file LICENSE in the source distribution or at +# https://www.openssl.org/source/license.html + +use OpenSSL::Test; +use OpenSSL::Test::Utils; + +setup("test_quic_fifd"); + +plan skip_all => "QUIC protocol is not supported by this OpenSSL build" + if disabled('quic'); + +plan tests => 1; + +ok(run(test(["quic_fifd_test"])));