From bb1143e2f0149c3cb5de13401270e815540aaa7b Mon Sep 17 00:00:00 2001 From: huitema Date: Fri, 23 Aug 2024 20:07:59 -0700 Subject: [PATCH] Add test of initial PTO for retransmissions. --- UnitTest1/unittest1.cpp | 7 ++ picoquic/picoquic_internal.h | 2 + picoquic/sender.c | 3 +- picoquic_t/picoquic_t.c | 1 + picoquictest/edge_cases.c | 165 +++++++++++++++++++++++++++++++++++ picoquictest/picoquictest.h | 1 + picoquictest/tls_api_test.c | 8 +- 7 files changed, 183 insertions(+), 4 deletions(-) diff --git a/UnitTest1/unittest1.cpp b/UnitTest1/unittest1.cpp index af9c8310c..364bfca32 100644 --- a/UnitTest1/unittest1.cpp +++ b/UnitTest1/unittest1.cpp @@ -1834,6 +1834,13 @@ namespace UnitTest1 Assert::AreEqual(ret, 0); } + TEST_METHOD(initial_pto) + { + int ret = initial_pto_test(); + + Assert::AreEqual(ret, 0); + } + TEST_METHOD(ready_to_send) { int ret = ready_to_send_test(); diff --git a/picoquic/picoquic_internal.h b/picoquic/picoquic_internal.h index 97211b214..0daf60ce2 100644 --- a/picoquic/picoquic_internal.h +++ b/picoquic/picoquic_internal.h @@ -1727,6 +1727,8 @@ size_t picoquic_get_checksum_length(picoquic_cnx_t* cnx, picoquic_epoch_enum is_ void picoquic_protect_packet_header(uint8_t* send_buffer, size_t pn_offset, uint8_t first_mask, void* pn_enc); +size_t picoquic_protect_packet(picoquic_cnx_t* cnx, picoquic_packet_type_enum ptype, uint8_t* bytes, uint64_t sequence_number, size_t length, size_t header_length, uint8_t* send_buffer, size_t send_buffer_max, void* aead_context, void* pn_enc, picoquic_path_t* path_x, uint64_t current_time); + uint64_t picoquic_get_packet_number64(uint64_t highest, uint64_t mask, uint32_t pn); void picoquic_log_pn_dec_trial(picoquic_cnx_t* cnx); /* For debugging potential PN_ENC corruption */ diff --git a/picoquic/sender.c b/picoquic/sender.c index b60cf07d6..a1a567630 100644 --- a/picoquic/sender.c +++ b/picoquic/sender.c @@ -1,3 +1,4 @@ +#include "picoquic_internal.h" /* * Author: Christian Huitema * Copyright (c) 2017, Private Octopus, Inc. @@ -828,7 +829,7 @@ void picoquic_protect_packet_header(uint8_t * send_buffer, size_t pn_offset, uin } } -static size_t picoquic_protect_packet(picoquic_cnx_t* cnx, +size_t picoquic_protect_packet(picoquic_cnx_t* cnx, picoquic_packet_type_enum ptype, uint8_t * bytes, uint64_t sequence_number, diff --git a/picoquic_t/picoquic_t.c b/picoquic_t/picoquic_t.c index e41a43fe2..b0c3d5e99 100644 --- a/picoquic_t/picoquic_t.c +++ b/picoquic_t/picoquic_t.c @@ -304,6 +304,7 @@ static const picoquic_test_def_t test_table[] = { { "reset_need_max", reset_need_max_test }, { "reset_need_reset", reset_need_reset_test }, { "reset_need_stop", reset_need_stop_test }, + { "initial_pto", initial_pto_test }, { "ready_to_send", ready_to_send_test }, { "ready_to_skip", ready_to_skip_test }, { "ready_to_zfin", ready_to_zfin_test }, diff --git a/picoquictest/edge_cases.c b/picoquictest/edge_cases.c index c8d6b55ed..ece432e47 100644 --- a/picoquictest/edge_cases.c +++ b/picoquictest/edge_cases.c @@ -1231,4 +1231,169 @@ int reset_need_reset_test() int reset_need_stop_test() { return reset_repeat_test_one(reset_need_stop_sending); +} + +/* +* Initial PTO test: +* Test the scenario in which: +* - the client sends an initial message +* - the server sends an ACK +* - no further packets from the server, either because they are lost +* or because the server is hitting the amplification limit. +* Verify that the client sends a message after a PTO commensurate +* with the RTT. +*/ + +int initial_pto_prepare(picoquic_test_tls_api_ctx_t* test_ctx, uint64_t* p_simulated_time, + size_t *length) +{ + int ret = 0; + uint8_t buf[PICOQUIC_MAX_PACKET_SIZE]; + struct sockaddr_storage addr_to; + struct sockaddr_storage addr_from; + + ret = picoquic_prepare_packet(test_ctx->cnx_client, *p_simulated_time, + buf, PICOQUIC_MAX_PACKET_SIZE, length, + &addr_to, &addr_from, NULL); + + if (ret == 0) { + /* Submit initial packet to server context, so we get the + * crypto context created */ + ret = picoquic_incoming_packet_ex(test_ctx->qserver, buf, *length, + (struct sockaddr*)&addr_from, (struct sockaddr*)&addr_to, 0, 0, + &test_ctx->cnx_server, *p_simulated_time); + if (ret == 0 && test_ctx->cnx_server == NULL) { + ret = -1; + } + } + return ret; +} + +int initial_pto_wait(picoquic_test_tls_api_ctx_t* test_ctx, uint64_t* p_simulated_time, + uint64_t max_wait, size_t* length_sent) +{ + int ret = 0; + size_t length = 0; + *length_sent = 0; + while (ret == 0 && *p_simulated_time < max_wait) { + uint64_t client_departure = test_ctx->cnx_client->next_wake_time; + + if (client_departure >= max_wait) { + *p_simulated_time = max_wait; + break; + } + else { + if (*p_simulated_time < client_departure) { + *p_simulated_time = client_departure; + } + ret = initial_pto_prepare(test_ctx, p_simulated_time, &length); + if (length > 0 || test_ctx->cnx_client->cnx_state >= picoquic_state_handshake_failure) { + *length_sent = length; + break; + } + } + } + return ret; +} + +int initial_pto_ack(picoquic_test_tls_api_ctx_t* test_ctx, uint64_t* p_simulated_time) +{ + int ret = 0; + uint8_t buf[PICOQUIC_INITIAL_MTU_IPV6]; + uint8_t send_buffer[PICOQUIC_MAX_PACKET_SIZE]; + picoquic_epoch_enum epoch = picoquic_epoch_initial; + picoquic_packet_type_enum packet_type = picoquic_packet_initial; + picoquic_packet_context_enum pc = picoquic_packet_context_initial; + size_t checksum_overhead = 16; + uint8_t * bytes_max = buf + PICOQUIC_INITIAL_MTU_IPV6 - checksum_overhead; + uint8_t* bytes_next = buf; + uint64_t sequence_number = 0; + size_t length = 0; + size_t header_length = 0; + int more_data = 0; + size_t send_length = 0; + /* Format the header */ + header_length = picoquic_predict_packet_header_length(test_ctx->cnx_server, packet_type, + &test_ctx->cnx_server->pkt_ctx[pc]); + bytes_next += header_length; + /* add ack, function of client sequence number */ + bytes_next = picoquic_format_ack_frame(test_ctx->cnx_server, bytes_next, bytes_max, &more_data, + *p_simulated_time, pc, 0); + /* add padding to minimum length */ + length = picoquic_pad_to_target_length(buf, bytes_next - buf, + PICOQUIC_INITIAL_MTU_IPV6 - checksum_overhead); + + /* Finalize */ + send_length = picoquic_protect_packet(test_ctx->cnx_server, packet_type, buf, 0, + length, header_length, + send_buffer, PICOQUIC_MAX_PACKET_SIZE, + test_ctx->cnx_server->crypto_context[picoquic_epoch_initial].aead_encrypt, + test_ctx->cnx_server->crypto_context[picoquic_epoch_initial].pn_enc, + test_ctx->cnx_server->path[0], *p_simulated_time); + /* Submit to client. */ + if (send_length == 0) { + ret = -1; + } + else { + picoquic_cnx_t* last_cnx = NULL; + ret = picoquic_incoming_packet_ex(test_ctx->qclient, send_buffer, send_length, + (struct sockaddr*)&test_ctx->server_addr, (struct sockaddr*)&test_ctx->client_addr, 0, 0, + &last_cnx, *p_simulated_time); + } + return ret; +} + +int initial_pto_test() +{ + int ret = 0; + picoquic_test_tls_api_ctx_t *test_ctx = NULL; + size_t length = 0; + uint64_t simulated_time = 0; + uint64_t simulated_rtt = 20000; + uint64_t simulated_pto = 4*simulated_rtt; + picoquic_connection_id_t initial_cid = { { 0x94, 0x01, 0x41, 0, 0, 0, 0, 0}, 8 }; + + /* Create a client. */ + ret = tls_api_init_ctx_ex(&test_ctx, PICOQUIC_INTERNAL_TEST_VERSION_1, + PICOQUIC_TEST_SNI, PICOQUIC_TEST_ALPN, &simulated_time, NULL, NULL, 0, 1, 0, &initial_cid); + if (ret != 0) { + DBG_PRINTF("Cannot initialize context, ret = 0x%x", ret); + } + else { + /* Set the binlog */ + picoquic_set_binlog(test_ctx->qclient, "."); + /* start the client connection */ + ret = picoquic_start_client_cnx(test_ctx->cnx_client); + } + /* Send the initial packet */ + if (ret == 0) { + ret = initial_pto_prepare(test_ctx, &simulated_time, &length); + if (ret == 0 && length < 1200) { + length = -1; + } + } + /* get the initial message, wait until next client time >= time of ACK */ + if (ret == 0 && simulated_time < 20000) { + ret = initial_pto_wait(test_ctx, &simulated_time, simulated_rtt, &length); + } + /* format an ACK packet, apply initial protection, submit ACK to client */ + if (ret == 0) { + ret = initial_pto_ack(test_ctx, &simulated_time); + } + /* Wait until next client time >= expected response, or + * client is ready to send and does send. */ + if (ret == 0 && simulated_time < simulated_pto) { + ret = initial_pto_wait(test_ctx, &simulated_time, simulated_pto, &length); + if (ret == 0 && length < 1200) { + /* Did not send the PTO */ + ret = -1; + } + } + /* Clean up */ + if (test_ctx != NULL) { + tls_api_delete_ctx(test_ctx); + test_ctx = NULL; + } + + return ret; } \ No newline at end of file diff --git a/picoquictest/picoquictest.h b/picoquictest/picoquictest.h index 2bb5e84db..06fbb2b6c 100644 --- a/picoquictest/picoquictest.h +++ b/picoquictest/picoquictest.h @@ -246,6 +246,7 @@ int reset_extra_stop_test(); int reset_need_max_test(); int reset_need_reset_test(); int reset_need_stop_test(); +int initial_pto_test(); int ready_to_send_test(); int ready_to_skip_test(); int ready_to_zero_test(); diff --git a/picoquictest/tls_api_test.c b/picoquictest/tls_api_test.c index cf8a8dcce..3db04177c 100644 --- a/picoquictest/tls_api_test.c +++ b/picoquictest/tls_api_test.c @@ -10165,9 +10165,11 @@ int ddos_amplification_test_one(int use_0rtt, int do_8k) if (ret == 0) { /* Prepare a first packet from the client to the server */ - ret = picoquic_prepare_packet(test_ctx->cnx_client, simulated_time, - packet->bytes, PICOQUIC_MAX_PACKET_SIZE, &packet->length, - &packet->addr_to, &packet->addr_from, NULL); + { + ret = picoquic_prepare_packet(test_ctx->cnx_client, simulated_time, + packet->bytes, PICOQUIC_MAX_PACKET_SIZE, &packet->length, + &packet->addr_to, &packet->addr_from, NULL); + } if (packet->length == 0) { ret = PICOQUIC_ERROR_UNEXPECTED_ERROR;