From cfdd201fcd7f1b873e873e078bbe662a6f212ef8 Mon Sep 17 00:00:00 2001 From: Vittorio Parrella Date: Mon, 20 May 2024 22:52:39 +0200 Subject: [PATCH] Introduce generic slowstart algorithms for congestion control --- CMakeLists.txt | 2 + include/quicly.h | 5 ++ include/quicly/cc.h | 33 ++++++++ include/quicly/defaults.h | 1 + include/quicly/ss.h | 55 ++++++++++++++ lib/cc-cubic.c | 12 +-- lib/cc-pico.c | 3 +- lib/cc-reno.c | 12 +-- lib/defaults.c | 6 +- lib/quicly.c | 5 ++ lib/ss-rfc2001.c | 49 ++++++++++++ lib/ss-search.c | 156 ++++++++++++++++++++++++++++++++++++++ src/cli.c | 13 ++++ t/e2e.t | 60 +++++++++++++++ 14 files changed, 397 insertions(+), 15 deletions(-) create mode 100644 include/quicly/ss.h create mode 100644 lib/ss-rfc2001.c create mode 100644 lib/ss-search.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 4d00892a..a4d1a457 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -61,6 +61,8 @@ SET(QUICLY_LIBRARY_FILES lib/remote_cid.c lib/retire_cid.c lib/sendstate.c + lib/ss-rfc2001.c + lib/ss-search.c lib/sentmap.c lib/streambuf.c ${CMAKE_CURRENT_BINARY_DIR}/quicly-tracer.h) diff --git a/include/quicly.h b/include/quicly.h index 22c10649..9e827c59 100644 --- a/include/quicly.h +++ b/include/quicly.h @@ -38,6 +38,7 @@ extern "C" { #include "quicly/local_cid.h" #include "quicly/linklist.h" #include "quicly/loss.h" +#include "quicly/ss.h" #include "quicly/cc.h" #include "quicly/rate.h" #include "quicly/recvstate.h" @@ -401,6 +402,10 @@ struct st_quicly_context_t { * initializes a congestion controller for given connection. */ quicly_init_cc_t *init_cc; + /** + * set the slowstart function for the connection + */ + quicly_ss_type_t *cc_slowstart; /** * optional refcount callback */ diff --git a/include/quicly/cc.h b/include/quicly/cc.h index eaa37da2..c64f87bb 100644 --- a/include/quicly/cc.h +++ b/include/quicly/cc.h @@ -40,6 +40,11 @@ extern "C" { #define QUICLY_MIN_CWND 2 #define QUICLY_RENO_BETA 0.7 +#define QUICLY_SEARCH_DELV_BIN_COUNT (10) // number of search delivered bytes bins +#define QUICLY_SEARCH_TOTAL_BIN_COUNT (25) // number of search sent bytes bins +#define QUICLY_SEARCH_WINDOW_MULTIPLIER (3.5) // search multiplier for window calculation +#define QUICLY_SEARCH_THRESH (0.35) // search threshold to exit slow start phase + /** * Holds pointers to concrete congestion control implementation functions. */ @@ -58,6 +63,30 @@ typedef struct st_quicly_cc_t { * Current slow start threshold. */ uint32_t ssthresh; + /** + * Slow-start specific data storage + */ + union { + struct { + /** + * Bins for the byte count sent and the byte count delivered (instantiated on init) + */ + uint64_t delv_bins[QUICLY_SEARCH_TOTAL_BIN_COUNT]; + /** + * Maintains the end time of the current bin + */ + int64_t bin_end; + /** + * Holds the size of each bin (based on the handshake RTT) + */ + uint32_t bin_time; + /** + * Counts the number of times that the bin has been incremented, so we know when to + * start trying to watch for congestion + */ + uint32_t bin_rounds; + } search; + } ss_state; /** * Packet number indicating end of recovery period, if in recovery. */ @@ -202,6 +231,10 @@ struct st_quicly_cc_type_t { * Switches the underlying algorithm of `cc` to that of `cc_switch`, returning a boolean if the operation was successful. */ int (*cc_switch)(quicly_cc_t *cc); + /* + * Defines a slowstart callback + */ + struct st_quicly_ss_type_t *cc_slowstart; /** * */ diff --git a/include/quicly/defaults.h b/include/quicly/defaults.h index 917c5ac1..443cfc78 100644 --- a/include/quicly/defaults.h +++ b/include/quicly/defaults.h @@ -59,6 +59,7 @@ extern quicly_crypto_engine_t quicly_default_crypto_engine; #define quicly_default_cc quicly_cc_type_reno #define quicly_default_init_cc quicly_cc_reno_init +#define quicly_default_ss quicly_ss_type_rfc2001 #ifdef __cplusplus } diff --git a/include/quicly/ss.h b/include/quicly/ss.h new file mode 100644 index 00000000..cca8f8da --- /dev/null +++ b/include/quicly/ss.h @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2024 Viasat Inc. + * Authors: Amber Cronin, Jae Won Chung, Mike Foxworthy, Vittorio Parrella, Feng Li, Mark Claypool + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#ifndef quicly_ss_h +#define quicly_ss_h + +#ifdef __cplusplus +extern "C" { +#endif + +#include "quicly/cc.h" +#include "quicly/constants.h" +#include +#include + +/** + * Holds pointers to concrete congestion control implementation functions. + */ +typedef struct st_quicly_ss_type_t quicly_ss_type_t; + +struct st_quicly_ss_type_t { + const char* name; + void (*ss)(quicly_cc_t *cc, const quicly_loss_t *loss, uint32_t bytes, uint64_t largest_acked, uint32_t inflight, + uint64_t next_pn, int64_t now, uint32_t max_udp_payload_size); +}; + +extern quicly_ss_type_t quicly_ss_type_rfc2001, quicly_ss_type_search, quicly_ss_type_disabled; + +extern quicly_ss_type_t* quicly_ss_all_types[]; + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/lib/cc-cubic.c b/lib/cc-cubic.c index 8a59c88a..c096b936 100644 --- a/lib/cc-cubic.c +++ b/lib/cc-cubic.c @@ -22,8 +22,10 @@ */ #include #include "quicly/cc.h" +#include "quicly/defaults.h" #include "quicly.h" #include "quicly/pacer.h" +#include "quicly/ss.h" #define QUICLY_MIN_CWND 2 @@ -73,13 +75,11 @@ static void cubic_on_acked(quicly_cc_t *cc, const quicly_loss_t *loss, uint32_t quicly_cc_jumpstart_on_acked(cc, 0, bytes, largest_acked, inflight, next_pn); - /* TODO: respect cc_limited */ - /* Slow start. */ if (cc->cwnd < cc->ssthresh) { - cc->cwnd += bytes; - if (cc->cwnd_maximum < cc->cwnd) - cc->cwnd_maximum = cc->cwnd; + if (cc_limited) { + cc->type->cc_slowstart->ss(cc, loss, bytes, largest_acked, inflight, next_pn, now, max_udp_payload_size); + } return; } @@ -208,5 +208,5 @@ static void cubic_init(quicly_init_cc_t *self, quicly_cc_t *cc, uint32_t initcwn quicly_cc_type_t quicly_cc_type_cubic = {"cubic", &quicly_cc_cubic_init, cubic_on_acked, cubic_on_lost, cubic_on_persistent_congestion, cubic_on_sent, - cubic_on_switch, quicly_cc_jumpstart_enter}; + cubic_on_switch, &quicly_default_ss, quicly_cc_jumpstart_enter}; quicly_init_cc_t quicly_cc_cubic_init = {cubic_init}; diff --git a/lib/cc-pico.c b/lib/cc-pico.c index a98e8d58..04f43291 100644 --- a/lib/cc-pico.c +++ b/lib/cc-pico.c @@ -194,5 +194,6 @@ static void pico_init(quicly_init_cc_t *self, quicly_cc_t *cc, uint32_t initcwnd quicly_cc_type_t quicly_cc_type_pico = {"pico", &quicly_cc_pico_init, pico_on_acked, pico_on_lost, pico_on_persistent_congestion, pico_on_sent, - pico_on_switch, quicly_cc_jumpstart_enter}; + pico_on_switch, &quicly_ss_type_disabled, + quicly_cc_jumpstart_enter}; quicly_init_cc_t quicly_cc_pico_init = {pico_init}; diff --git a/lib/cc-reno.c b/lib/cc-reno.c index 0af65f0a..c37eb0a0 100644 --- a/lib/cc-reno.c +++ b/lib/cc-reno.c @@ -20,6 +20,7 @@ * IN THE SOFTWARE. */ #include "quicly/cc.h" +#include "quicly/defaults.h" #include "quicly.h" /* TODO: Avoid increase if sender was application limited. */ @@ -38,12 +39,10 @@ static void reno_on_acked(quicly_cc_t *cc, const quicly_loss_t *loss, uint32_t b /* Slow start. */ if (cc->cwnd < cc->ssthresh) { - if (cc_limited) { - cc->cwnd += bytes; - if (cc->cwnd_maximum < cc->cwnd) - cc->cwnd_maximum = cc->cwnd; - } - return; + if (cc_limited) { + cc->type->cc_slowstart->ss(cc, loss, bytes, largest_acked, inflight, next_pn, now, max_udp_payload_size); + } + return; } /* Congestion avoidance. */ if (!cc_limited) @@ -143,6 +142,7 @@ quicly_cc_type_t quicly_cc_type_reno = {"reno", quicly_cc_reno_on_persistent_congestion, quicly_cc_reno_on_sent, reno_on_switch, + &quicly_default_ss, quicly_cc_jumpstart_enter}; quicly_init_cc_t quicly_cc_reno_init = {reno_init}; diff --git a/lib/defaults.c b/lib/defaults.c index cdf8d8a2..6918fbb4 100644 --- a/lib/defaults.c +++ b/lib/defaults.c @@ -68,7 +68,8 @@ const quicly_context_t quicly_spec_context = {NULL, NULL, NULL, &quicly_default_crypto_engine, - &quicly_default_init_cc}; + &quicly_default_init_cc, + &quicly_default_ss}; /* profile with a focus on reducing latency for the HTTP use case */ const quicly_context_t quicly_performant_context = {NULL, /* tls */ @@ -105,7 +106,8 @@ const quicly_context_t quicly_performant_context = {NULL, NULL, NULL, &quicly_default_crypto_engine, - &quicly_default_init_cc}; + &quicly_default_init_cc, + &quicly_default_ss}; /** * The context of the default CID encryptor. All the contexts being used here are ECB ciphers and therefore stateless - they can be diff --git a/lib/quicly.c b/lib/quicly.c index 4c0cfa08..f3e613fb 100644 --- a/lib/quicly.c +++ b/lib/quicly.c @@ -37,6 +37,7 @@ #include "quicly/sentmap.h" #include "quicly/pacer.h" #include "quicly/frame.h" +#include "quicly/ss.h" #include "quicly/streambuf.h" #include "quicly/cc.h" #if QUICLY_USE_DTRACE @@ -2570,6 +2571,10 @@ static quicly_conn_t *create_connection(quicly_context_t *ctx, uint32_t protocol conn->egress.send_ack_at = INT64_MAX; conn->egress.send_probe_at = INT64_MAX; conn->super.ctx->init_cc->cb(conn->super.ctx->init_cc, &conn->egress.cc, initcwnd, conn->stash.now); + if (conn->super.ctx->cc_slowstart != NULL) { + conn->egress.cc.type->cc_slowstart->name = conn->super.ctx->cc_slowstart->name; + conn->egress.cc.type->cc_slowstart->ss = conn->super.ctx->cc_slowstart->ss; + } if (pacer != NULL) { conn->egress.pacer = pacer; quicly_pacer_reset(conn->egress.pacer); diff --git a/lib/ss-rfc2001.c b/lib/ss-rfc2001.c new file mode 100644 index 00000000..8d0f179e --- /dev/null +++ b/lib/ss-rfc2001.c @@ -0,0 +1,49 @@ + /* + * Copyright (c) 2024 Viasat Inc. + * Authors: Amber Cronin, Jae Won Chung, Mike Foxworthy, Vittorio Parrella, Feng Li, Mark Claypool + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include "quicly/ss.h" + +/* + * Default TCP Slow start (RFC 2001) algorithm implementing exponential growth + * of congestion window (cwnd) -- doubling cwnd every RTT. + * Same slow start algorithm defined in RFC 9000 for QUIC + * +*/ + +void ss_quicly_default(quicly_cc_t *cc, const quicly_loss_t *loss, uint32_t bytes, uint64_t largest_acked, uint32_t inflight, + uint64_t next_pn, int64_t now, uint32_t max_udp_payload_size) +{ + cc->cwnd += bytes; + if (cc->cwnd_maximum < cc->cwnd) + cc->cwnd_maximum = cc->cwnd; +} + +quicly_ss_type_t quicly_ss_type_rfc2001= { "rfc2001", ss_quicly_default }; + +void ss_quicly_disabled(quicly_cc_t *cc, const quicly_loss_t *loss, uint32_t bytes, uint64_t largest_acked, uint32_t inflight, + uint64_t next_pn, int64_t now, uint32_t max_udp_payload_size) +{} + +quicly_ss_type_t quicly_ss_type_disabled = { "disabled", ss_quicly_disabled }; + +quicly_ss_type_t* quicly_ss_all_types[] = { &quicly_ss_type_disabled, &quicly_ss_type_rfc2001, &quicly_ss_type_search, NULL }; diff --git a/lib/ss-search.c b/lib/ss-search.c new file mode 100644 index 00000000..8166bb82 --- /dev/null +++ b/lib/ss-search.c @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2024 Viasat Inc. + * Authors: Amber Cronin, Jae Won Chung, Mike Foxworthy, Vittorio Parrella, Feng Li, Mark Claypool + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include "quicly/ss.h" +#include + +/* + * Slow start Exit At Right CHokepoint (SEARCH) is a slow start algorithm to detect + * the right exit point from slow start when reaching the maximum link capacity. It + * keeps tracking delivery rate and exits from slow start when delivery rate is not + * increasing as expected. SEARCH has been implemented and evaluated with in QUIC and + * Linux TCP (as a kernel module). + * + * References: + * [1] Amber Cronin, Maryam Ataei Kachooei, Jae Chung, Feng Li, Benjamin Peters, and Mark Claypool. + * Improving QUIC Slow Start Behavior in Wireless Networks with SEARCH, In Proceedings of the IEEE + * Local and Metropolitan Area Conference (LANMAN), Boston, MA, USA, July 2024. + * + * [2] Maryam Ataei Kachooei, Jae Chung, Feng Li, Benjamin Peters, Josh Chung, and + * Mark Claypool. Improving TCP Slow Start Performance in Wireless Networks with + * SEARCH, In Proceedings of the World of Wireless, Mobile and Multimedia Networks + * (WoWMoM), Perth, Australia June 2024. + * +*/ + +void ss_search_reset(quicly_cc_t *cc, const quicly_loss_t *loss, uint32_t bytes, int64_t now); + +void ss_search_reset(quicly_cc_t *cc, const quicly_loss_t *loss, uint32_t bytes, int64_t now) +{ + // Handy pointers to the cc struct + uint64_t* delv = cc->ss_state.search.delv_bins; + int64_t* bin_end = &cc->ss_state.search.bin_end; + uint32_t* bin_time = &cc->ss_state.search.bin_time; + uint32_t* bin_rounds = &cc->ss_state.search.bin_rounds; + + // bin time is the size of each of the sent/delv bins + uint32_t tmp_bin_time = (loss->rtt.latest * QUICLY_SEARCH_WINDOW_MULTIPLIER) / (QUICLY_SEARCH_DELV_BIN_COUNT); + *bin_time = tmp_bin_time < 1 ? 1 : tmp_bin_time; + *bin_end = now + *bin_time; + delv[0] = 0; + *bin_rounds = 0; +} + +// bytes is the number of bytes acked in the last ACK frame +// inflight is sentmap->bytes_in_flight + bytes +void ss_search(quicly_cc_t *cc, const quicly_loss_t *loss, uint32_t bytes, uint64_t largest_acked, uint32_t inflight, + uint64_t next_pn, int64_t now, uint32_t max_udp_payload_size) +{ + // Handy pointers to the cc struct + uint64_t* delv = cc->ss_state.search.delv_bins; + int64_t* bin_end = &cc->ss_state.search.bin_end; + uint32_t* bin_time = &cc->ss_state.search.bin_time; + uint32_t* bin_rounds = &cc->ss_state.search.bin_rounds; + + // struct initializations, everything else important has already been reset to 0 + if(*bin_time == 0) { + ss_search_reset(cc, loss, bytes, now); + } + + // bin_shift is the number of bins to shift backwards, based on the latest RTT + uint8_t bin_shift = loss->rtt.latest / *bin_time; + if(bin_shift == 0) { + bin_shift = 1; + } + else if(loss->rtt.latest % *bin_time > (*bin_time / 2)) { + // round to the nearest bin (not doing interpolation yet) + bin_shift++; + } + + // Possibly add some code here for dirty reset - run when no data has been sent on the connection + // for a very long time, but application never received a loss (and so is still in slow-start) + // This is likely handled by the prior binroll while loop, but that might add unnecessary latency + // dependant on how long ago the last packet was acknowledged. + if (((now - *bin_end) / *bin_time) > QUICLY_SEARCH_TOTAL_BIN_COUNT) { + ss_search_reset(cc, loss, bytes, now); + } + + + // perform prior binrolls before updating the latest bin to run SEARCH on if necessary + while((now - *bin_time) > (*bin_end)) { + *bin_end += *bin_time; + *bin_rounds += 1; + delv[(*bin_rounds % (QUICLY_SEARCH_TOTAL_BIN_COUNT))] = 0; + } + // perform current binroll + if((now > (*bin_end))) { + // only perform SEARCH if there is enough data in the sent bins with the current RTT + // bin_rounds tracks how many times we've rolled over, and a single window is the entire + // delivered bin count (because of the definition of how bin_time is calculated) + // thus, the number of rounds must be >= than the delv bin count + the bin shift + if((*bin_rounds) >= ((QUICLY_SEARCH_DELV_BIN_COUNT) + bin_shift) + && bin_shift < (QUICLY_SEARCH_TOTAL_BIN_COUNT - QUICLY_SEARCH_DELV_BIN_COUNT)) { + // do SEARCH + double shift_delv_sum = 0, delv_sum = 0; + for (int i = *bin_rounds; i > (*bin_rounds - (QUICLY_SEARCH_DELV_BIN_COUNT)); i--) { + // the value of bin_shift will always be at least 1, so the current sent bin is never used + shift_delv_sum += delv[((i - bin_shift) % (QUICLY_SEARCH_TOTAL_BIN_COUNT))]; + delv_sum += delv[(i % (QUICLY_SEARCH_TOTAL_BIN_COUNT))]; + } + if (shift_delv_sum >= 1) { + shift_delv_sum *= 2; + double normalized_diff = (shift_delv_sum - delv_sum) / shift_delv_sum; + if (normalized_diff > QUICLY_SEARCH_THRESH) { + // exit slow start + // TODO: Proposal to lower cwnd by tracked previously sent bytes + if (cc->cwnd_maximum < cc->cwnd) + cc->cwnd_maximum = cc->cwnd; + cc->ssthresh = cc->cwnd; + cc->cwnd_exiting_slow_start = cc->cwnd; + cc->exit_slow_start_at = now; + return; + } + } + } + else if(bin_shift >= (QUICLY_SEARCH_TOTAL_BIN_COUNT - QUICLY_SEARCH_DELV_BIN_COUNT)) { + /* TODO: Double bin_time and consolidate for high RTT operation */ + } + + *bin_end += *bin_time; + *bin_rounds += 1; + delv[(*bin_rounds % (QUICLY_SEARCH_TOTAL_BIN_COUNT))] = 0; + } + + // fill (updated) bin with latest acknowledged bytes + // TCP implementation has a method of tracking total delivered bytes to avoid this per-packet + // computation, but we aren't doing that (yet). loss->total_bytes_sent looks interesting, but + // does not seem to guarantee a match with conn->egress.max_data.sent (see loss.c) + delv[(*bin_rounds % (QUICLY_SEARCH_TOTAL_BIN_COUNT))] += bytes; + + // perform standard SS doubling + cc->cwnd += bytes; + if (cc->cwnd_maximum < cc->cwnd) + cc->cwnd_maximum = cc->cwnd; +} + +quicly_ss_type_t quicly_ss_type_search = { "search", ss_search }; diff --git a/src/cli.c b/src/cli.c index 5463095f..dec0541d 100644 --- a/src/cli.c +++ b/src/cli.c @@ -1230,6 +1230,8 @@ static void usage(const char *cmd) " -r [initial-pto] initial PTO (in milliseconds)\n" " -S [num-speculative-ptos] number of speculative PTOs\n" " -s session-file file to load / store the session ticket\n" + " --slowstart slowstart algorithm to use; \"rfc2001\" (default),\n" + " \"disabled\", \"search\"\n" " -u size initial size of UDP datagram payload\n" " -U size maximum size of UDP datagram payload\n" " -V verify peer using the default certificates\n" @@ -1298,6 +1300,7 @@ int main(int argc, char **argv) static const struct option longopts[] = {{"ech-key", required_argument, NULL, 0}, {"ech-configs", required_argument, NULL, 0}, {"disable-ecn", no_argument, NULL, 0}, + {"slowstart", required_argument, NULL, 0}, {"disregard-app-limited", no_argument, NULL, 0}, {"jumpstart-default", required_argument, NULL, 0}, {"jumpstart-max", required_argument, NULL, 0}, @@ -1312,6 +1315,16 @@ int main(int argc, char **argv) ech_setup_configs(optarg); } else if (strcmp(longopts[opt_index].name, "disable-ecn") == 0) { ctx.enable_ecn = 0; + } else if (strcmp(longopts[opt_index].name, "slowstart") == 0) { + quicly_ss_type_t **ss; + for (ss = quicly_ss_all_types; *ss != NULL; ++ss) + if (strcmp((*ss)->name, optarg) == 0) + break; + if (*ss != NULL) { + ctx.cc_slowstart = (*ss); + } else { + fprintf(stderr, "unknown slowstart algorithm: %s\n", optarg); + } } else if (strcmp(longopts[opt_index].name, "disregard-app-limited") == 0) { ctx.respect_app_limited = 0; } else if (strcmp(longopts[opt_index].name, "jumpstart-default") == 0) { diff --git a/t/e2e.t b/t/e2e.t index 88c8be62..924e8dc1 100755 --- a/t/e2e.t +++ b/t/e2e.t @@ -529,6 +529,66 @@ subtest "slow-start" => sub { }; }; +subtest "slow-start-search" => sub { + # spawn udpfw that applies 100ms RTT but otherwise nothing + my $udpfw_guard = spawn_process( + ["sh", "-c", "exec $udpfw -b 100 -i 1 -p 0 -B 100 -I 1 -P 100000 -l $udpfw_port 127.0.0.1 $port > /dev/null 2>&1"], + $udpfw_port, + ); + + # read first $size bytes from client $cli (which would be the payload received) and check RT + my $doit = sub { + my ($size, $rt_min, $rt_max) = @_; + subtest "${size}B" => sub { + my $start_at = time; + open my $fh, "-|", "$cli -p /$size 127.0.0.1 $udpfw_port 2>&1" + or die "failed to launch $cli:$!"; + for (my $total_read = 0; $total_read < $size;) { + IO::Select->new($fh)->can_read(); # block until the command writes something + my $nread = sysread $fh, my $buf, 65536; + die "failed to read from pipe, got $nread:$!" + unless $nread > 0; + $total_read += $nread; + } + my $elapsed = time - $start_at; + diag $elapsed; + cmp_ok $rt_min * 0.1, '<=', $elapsed, "RT >= $rt_min"; + cmp_ok $rt_max * 0.1, '>=', $elapsed, "RT <= $rt_max"; + }; + }; + + my $each_cc = sub { + my $cb = shift; + for my $cc (qw(cubic reno pico)) { + subtest $cc => sub { + $cb->($cc); + }; + } + }; + + subtest "search-test" => sub { + $each_cc->(sub { + my $cc = shift; + subtest "search-ss-enabled" => sub { + #test with slow start algorithm SEARCH + my $guard = spawn_server("-C", "$cc:10", "--slowstart", "search"); + # tail of 1st, and 2nd batch fits into both round trip + $doit->(@$_) + for ([14000, 2, 2.5], [45000, 3, 3.5], [72000, 4, 4.5]); + }; + subtest "search-ss-disable" => sub { + #test with SEARCH disabled using the default slow start algorithm defined in RFC 2001 + my $guard = spawn_server("-C", "$cc:10", "--slowstart", "rfc2001"); + # tail of 1st, 2nd, and 3rd batch fits into each round trip + $doit->(@$_) + for ([14000, 2, 2.5], [45000, 3, 3.5], [72000, 4, 4.5]); + }; + + }); + }; + +}; + done_testing; sub spawn_server {