From 0ede517cfa73fd3566d2ecd32215b4b12dd1d3b5 Mon Sep 17 00:00:00 2001 From: Hugo Landau Date: Thu, 15 Sep 2022 12:48:50 +0100 Subject: [PATCH] QUIC FIFD Reviewed-by: Tomas Mraz Reviewed-by: Matt Caswell (Merged from https://github.com/openssl/openssl/pull/19206) --- .../quic-design/images/quic-overview.odg | Bin 32160 -> 22501 bytes .../quic-design/images/quic-overview.svg | 196 ++++++---- doc/designs/quic-design/quic-fifm.md | 32 +- doc/designs/quic-design/quic-overview.md | 22 +- include/internal/quic_fifd.h | 56 +++ include/internal/quic_txpim.h | 9 +- ssl/quic/build.info | 2 +- ssl/quic/quic_cfq.c | 6 + ssl/quic/quic_fifd.c | 202 ++++++++++ ssl/quic/quic_txpim.c | 12 +- test/build.info | 6 +- test/quic_fifd_test.c | 358 ++++++++++++++++++ test/recipes/70-test_quic_fifd.t | 19 + 13 files changed, 815 insertions(+), 105 deletions(-) create mode 100644 include/internal/quic_fifd.h create mode 100644 ssl/quic/quic_fifd.c create mode 100644 test/quic_fifd_test.c create mode 100644 test/recipes/70-test_quic_fifd.t diff --git a/doc/designs/quic-design/images/quic-overview.odg b/doc/designs/quic-design/images/quic-overview.odg index a844a0deb7100e1267d6ff42d2c5d930123b848b..c8a055760bb2cdbde439d4814707405b584eb39f 100644 GIT binary patch delta 20966 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 delta 30711 zcmaI7bx<9_vnad=cXxMp3+@^S76=5_;O=@3?(PKl5ZooWyE_DT*Fdl@zkA=Uy5IZb zb?sL7OxN^OZ_P~0_Uu=K@}5Dcs*2DsH~@eE06UDB38>1@|3L=a|BWb<3^b4ux)A

*8$_8T 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 - + - + - - + + - - - - - + + + + + - + - + - + - - + + - - - - - - - - + + + + + + + + - - - - - - + + + + + + - - - + + + + - + @@ -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 1de81318f3..f03b5d7d36 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 141abcb9bc..2ef43cefc5 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 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 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 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 0000000000..15952e43d8 --- /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 623004e79d..087d13363f 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 1f228cb6d7..45440384e1 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 cdd621458c..0b0651289a 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 0000000000..8f548520b1 --- /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 38b16a4561..9693758769 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 728fc11166..370220806d 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 0000000000..47eb030930 --- /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 0000000000..40245523ed --- /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"])));