3805 lines
130 KiB
C
3805 lines
130 KiB
C
|
|
/*
|
|
* Author: Christian Huitema
|
|
* Copyright (c) 2017, Private Octopus, Inc.
|
|
* All rights reserved.
|
|
*
|
|
* Permission to use, copy, modify, and distribute this software for any
|
|
* purpose with or without fee is hereby granted, provided that the above
|
|
* copyright notice and this permission notice appear in all copies.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
* DISCLAIMED. IN NO EVENT SHALL Private Octopus, Inc. BE LIABLE FOR ANY
|
|
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
#include "picoquic.h"
|
|
#include "picoquic_internal.h"
|
|
#include "picoquic_unified_log.h"
|
|
#include "tls_api.h"
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#ifndef _WINDOWS
|
|
#include <sys/time.h>
|
|
#endif
|
|
|
|
|
|
/*
|
|
* Supported versions. Specific versions may mandate different processing of different
|
|
* formats.
|
|
* The first version in the list is the preferred version.
|
|
* The protection of clear text packets will be a function of the version negotiation.
|
|
*/
|
|
|
|
static uint8_t picoquic_cleartext_internal_test_1_salt[] = {
|
|
0x30, 0x67, 0x16, 0xd7, 0x63, 0x75, 0xd5, 0x55,
|
|
0x4b, 0x2f, 0x60, 0x5e, 0xef, 0x78, 0xd8, 0x33,
|
|
0x3d, 0xc1, 0xca, 0x36
|
|
};
|
|
|
|
static uint8_t picoquic_cleartext_draft_23_salt[] = {
|
|
0xc3, 0xee, 0xf7, 0x12, 0xc7, 0x2e, 0xbb, 0x5a,
|
|
0x11, 0xa7, 0xd2, 0x43, 0x2b, 0xb4, 0x63, 0x65,
|
|
0xbe, 0xf9, 0xf5, 0x02
|
|
};
|
|
|
|
uint8_t picoquic_retry_protection_key_25[32] = {
|
|
0x65, 0x6e, 0x61, 0xe3, 0x36, 0xae, 0x94, 0x17, 0xf7, 0xf0, 0xed, 0xd8, 0xd7, 0x8d, 0x46, 0x1e,
|
|
0x2a, 0xa7, 0x08, 0x4a, 0xba, 0x7a, 0x14, 0xc1, 0xe9, 0xf7, 0x26, 0xd5, 0x57, 0x09, 0x16, 0x9a };
|
|
|
|
static uint8_t picoquic_cleartext_draft_29_salt[] = {
|
|
0xaf, 0xbf, 0xec, 0x28, 0x99, 0x93, 0xd2, 0x4c,
|
|
0x9e, 0x97, 0x86, 0xf1, 0x9c, 0x61, 0x11, 0xe0,
|
|
0x43, 0x90, 0xa8, 0x99
|
|
};
|
|
|
|
uint8_t picoquic_retry_protection_key_29[32] = {
|
|
0x8b, 0x0d, 0x37, 0xeb, 0x85, 0x35, 0x02, 0x2e, 0xbc, 0x8d, 0x76, 0xa2, 0x07, 0xd8, 0x0d, 0xf2,
|
|
0x26, 0x46, 0xec, 0x06, 0xdc, 0x80, 0x96, 0x42, 0xc3, 0x0a, 0x8b, 0xaa, 0x2b, 0xaa, 0xff, 0x4c };
|
|
|
|
const picoquic_version_parameters_t picoquic_supported_versions[] = {
|
|
{ PICOQUIC_TWENTIETH_INTEROP_VERSION,
|
|
sizeof(picoquic_cleartext_draft_29_salt),
|
|
picoquic_cleartext_draft_29_salt,
|
|
sizeof(picoquic_retry_protection_key_29),
|
|
picoquic_retry_protection_key_29 },
|
|
{ PICOQUIC_TWENTIETH_PRE_INTEROP_VERSION,
|
|
sizeof(picoquic_cleartext_draft_29_salt),
|
|
picoquic_cleartext_draft_29_salt,
|
|
sizeof(picoquic_retry_protection_key_29),
|
|
picoquic_retry_protection_key_29 },
|
|
{ PICOQUIC_NINETEENTH_INTEROP_VERSION,
|
|
sizeof(picoquic_cleartext_draft_29_salt),
|
|
picoquic_cleartext_draft_29_salt,
|
|
sizeof(picoquic_retry_protection_key_29),
|
|
picoquic_retry_protection_key_29 },
|
|
{ PICOQUIC_NINETEENTH_BIS_INTEROP_VERSION,
|
|
sizeof(picoquic_cleartext_draft_29_salt),
|
|
picoquic_cleartext_draft_29_salt,
|
|
sizeof(picoquic_retry_protection_key_29),
|
|
picoquic_retry_protection_key_29 },
|
|
{ PICOQUIC_EIGHTEENTH_INTEROP_VERSION,
|
|
sizeof(picoquic_cleartext_draft_23_salt),
|
|
picoquic_cleartext_draft_23_salt,
|
|
sizeof(picoquic_retry_protection_key_25),
|
|
picoquic_retry_protection_key_25 },
|
|
{ PICOQUIC_SEVENTEENTH_INTEROP_VERSION,
|
|
sizeof(picoquic_cleartext_draft_23_salt),
|
|
picoquic_cleartext_draft_23_salt,
|
|
sizeof(picoquic_retry_protection_key_25),
|
|
picoquic_retry_protection_key_25 },
|
|
{ PICOQUIC_INTERNAL_TEST_VERSION_2,
|
|
sizeof(picoquic_cleartext_internal_test_1_salt),
|
|
picoquic_cleartext_internal_test_1_salt,
|
|
sizeof(picoquic_retry_protection_key_25),
|
|
picoquic_retry_protection_key_25},
|
|
{ PICOQUIC_INTERNAL_TEST_VERSION_1,
|
|
sizeof(picoquic_cleartext_internal_test_1_salt),
|
|
picoquic_cleartext_internal_test_1_salt,
|
|
sizeof(picoquic_retry_protection_key_25),
|
|
picoquic_retry_protection_key_25 }
|
|
};
|
|
|
|
const size_t picoquic_nb_supported_versions = sizeof(picoquic_supported_versions) / sizeof(picoquic_version_parameters_t);
|
|
|
|
/*
|
|
* Structures used in the hash table of connections
|
|
*/
|
|
typedef struct st_picoquic_cnx_id_key_t {
|
|
picoquic_connection_id_t cnx_id;
|
|
picoquic_cnx_t* cnx;
|
|
picoquic_local_cnxid_t* l_cid;
|
|
struct st_picoquic_cnx_id_key_t* next_cnx_id;
|
|
} picoquic_cnx_id_key_t;
|
|
|
|
typedef struct st_picoquic_net_id_key_t {
|
|
struct sockaddr_storage saddr;
|
|
picoquic_cnx_t* cnx;
|
|
picoquic_path_t* path;
|
|
struct st_picoquic_net_id_key_t* next_net_id;
|
|
} picoquic_net_id_key_t;
|
|
|
|
typedef struct st_picoquic_net_icid_key_t {
|
|
struct sockaddr_storage saddr;
|
|
picoquic_connection_id_t icid;
|
|
picoquic_cnx_t* cnx;
|
|
} picoquic_net_icid_key_t;
|
|
|
|
typedef struct st_picoquic_net_secret_key_t {
|
|
struct sockaddr_storage saddr;
|
|
uint8_t reset_secret[PICOQUIC_RESET_SECRET_SIZE];
|
|
picoquic_cnx_t* cnx;
|
|
} picoquic_net_secret_key_t;
|
|
|
|
/* Hash and compare for CNX hash tables */
|
|
static uint64_t picoquic_cnx_id_hash(const void* key)
|
|
{
|
|
const picoquic_cnx_id_key_t* cid = (const picoquic_cnx_id_key_t*)key;
|
|
return picoquic_connection_id_hash(&cid->cnx_id);
|
|
}
|
|
|
|
static int picoquic_cnx_id_compare(const void* key1, const void* key2)
|
|
{
|
|
const picoquic_cnx_id_key_t* cid1 = (const picoquic_cnx_id_key_t*)key1;
|
|
const picoquic_cnx_id_key_t* cid2 = (const picoquic_cnx_id_key_t*)key2;
|
|
|
|
return picoquic_compare_connection_id(&cid1->cnx_id, &cid2->cnx_id);
|
|
}
|
|
|
|
static uint64_t picoquic_net_id_hash(const void* key)
|
|
{
|
|
const picoquic_net_id_key_t* net = (const picoquic_net_id_key_t*)key;
|
|
|
|
// return picohash_bytes((uint8_t*)&net->saddr, sizeof(net->saddr));
|
|
return picoquic_hash_addr((struct sockaddr*) & net->saddr);
|
|
}
|
|
|
|
static int picoquic_net_id_compare(const void* key1, const void* key2)
|
|
{
|
|
const picoquic_net_id_key_t* net1 = (const picoquic_net_id_key_t*)key1;
|
|
const picoquic_net_id_key_t* net2 = (const picoquic_net_id_key_t*)key2;
|
|
|
|
return picoquic_compare_addr((struct sockaddr*) & net1->saddr, (struct sockaddr*) & net2->saddr);
|
|
}
|
|
|
|
static uint64_t picoquic_net_icid_hash(const void* key)
|
|
{
|
|
const picoquic_net_icid_key_t* net_icid = (const picoquic_net_icid_key_t*)key;
|
|
|
|
return picohash_hash_mix(picoquic_hash_addr((struct sockaddr*) & net_icid->saddr),
|
|
picoquic_connection_id_hash(&net_icid->icid));
|
|
|
|
}
|
|
|
|
static int picoquic_net_icid_compare(const void* key1, const void* key2)
|
|
{
|
|
const picoquic_net_icid_key_t* net_icid1 = (const picoquic_net_icid_key_t*)key1;
|
|
const picoquic_net_icid_key_t* net_icid2 = (const picoquic_net_icid_key_t*)key2;
|
|
int ret = picoquic_compare_addr((struct sockaddr*) & net_icid1->saddr, (struct sockaddr*) & net_icid2->saddr);
|
|
if (ret == 0) {
|
|
ret = picoquic_compare_connection_id(&net_icid1->icid, &net_icid2->icid);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static uint64_t picoquic_net_secret_hash(const void* key)
|
|
{
|
|
const picoquic_net_secret_key_t* net_secret = (const picoquic_net_secret_key_t*)key;
|
|
|
|
return picohash_hash_mix(picoquic_hash_addr((struct sockaddr*) & net_secret->saddr),
|
|
picohash_bytes(net_secret->reset_secret, PICOQUIC_RESET_SECRET_SIZE));
|
|
|
|
}
|
|
|
|
static int picoquic_net_secret_compare(const void* key1, const void* key2)
|
|
{
|
|
const picoquic_net_secret_key_t* net_secret1 = (const picoquic_net_secret_key_t*)key1;
|
|
const picoquic_net_secret_key_t* net_secret2 = (const picoquic_net_secret_key_t*)key2;
|
|
int ret = picoquic_compare_addr((struct sockaddr*) & net_secret1->saddr, (struct sockaddr*) & net_secret2->saddr);
|
|
|
|
if (ret == 0) {
|
|
#ifdef PICOQUIC_USE_CONSTANT_TIME_MEMCMP
|
|
ret = picoquic_constant_time_memcmp(net_secret1->reset_secret, net_secret2->reset_secret, PICOQUIC_RESET_SECRET_SIZE);
|
|
#else
|
|
ret = memcmp(net_secret1->reset_secret, net_secret2->reset_secret, PICOQUIC_RESET_SECRET_SIZE);
|
|
#endif
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
picoquic_packet_context_enum picoquic_context_from_epoch(int epoch)
|
|
{
|
|
static picoquic_packet_context_enum const pc[4] = {
|
|
picoquic_packet_context_initial,
|
|
picoquic_packet_context_application,
|
|
picoquic_packet_context_handshake,
|
|
picoquic_packet_context_application
|
|
};
|
|
|
|
return (epoch >= 0 && epoch < 4) ? pc[epoch] : 0;
|
|
}
|
|
|
|
/* Token reuse management */
|
|
|
|
static int64_t picoquic_registered_token_compare(void* l, void* r)
|
|
{
|
|
/* STream values are from 0 to 2^62-1, which means we are not worried with rollover */
|
|
picoquic_registered_token_t* rt_l = (picoquic_registered_token_t*)l;
|
|
picoquic_registered_token_t* rt_r = (picoquic_registered_token_t*)r;
|
|
int64_t ret = rt_l->token_time - rt_r->token_time;
|
|
if (ret == 0) {
|
|
ret = rt_l->token_hash - rt_r->token_hash;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static picosplay_node_t* picoquic_registered_token_create(void* value)
|
|
{
|
|
return &((picoquic_registered_token_t*)value)->registered_token_node;
|
|
}
|
|
|
|
|
|
static void* picoquic_registered_token_value(picosplay_node_t* node)
|
|
{
|
|
return (void*)((char*)node - offsetof(struct st_picoquic_registered_token_t, registered_token_node));
|
|
}
|
|
|
|
static void picoquic_registered_token_delete(void* tree, picosplay_node_t* node)
|
|
{
|
|
picoquic_registered_token_t* rt = (picoquic_registered_token_t*)picoquic_registered_token_value(node);
|
|
free(rt);
|
|
}
|
|
|
|
int picoquic_registered_token_check_reuse(picoquic_quic_t * quic,
|
|
const uint8_t * token, size_t token_length, uint64_t expiry_time)
|
|
{
|
|
int ret = -1;
|
|
if (token_length >= 8) {
|
|
picoquic_registered_token_t* rt = (picoquic_registered_token_t*)malloc(sizeof(picoquic_registered_token_t));
|
|
if (rt != NULL) {
|
|
picosplay_node_t* rt_n = NULL;
|
|
memset(rt, 0, sizeof(picoquic_registered_token_t));
|
|
rt->token_time = expiry_time;
|
|
rt->token_hash = PICOPARSE_64(token + token_length - 8);
|
|
rt->count = 1;
|
|
rt_n = picosplay_find(&quic->token_reuse_tree, rt);
|
|
if (rt_n != NULL) {
|
|
free(rt);
|
|
rt = (picoquic_registered_token_t*)picoquic_registered_token_value(rt_n);
|
|
rt->count++;
|
|
DBG_PRINTF("Token reuse detected, count=%d", rt->count);
|
|
}
|
|
else {
|
|
(void)picosplay_insert(&quic->token_reuse_tree, rt);
|
|
ret = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
void picoquic_registered_token_clear(picoquic_quic_t* quic, uint64_t expiry_time_max)
|
|
{
|
|
int end_reached = 0;
|
|
do {
|
|
picoquic_registered_token_t* rt_first = (picoquic_registered_token_t*)
|
|
picoquic_registered_token_value(picosplay_first(&quic->token_reuse_tree));
|
|
if (rt_first == NULL || rt_first->token_time >= expiry_time_max) {
|
|
end_reached = 1;
|
|
}
|
|
else {
|
|
picosplay_delete_hint(&quic->token_reuse_tree, &rt_first->registered_token_node);
|
|
}
|
|
} while (!end_reached);
|
|
}
|
|
|
|
/* Forward reference */
|
|
static void picoquic_wake_list_init(picoquic_quic_t* quic);
|
|
|
|
/* QUIC context create and dispose */
|
|
picoquic_quic_t* picoquic_create(uint32_t nb_connections,
|
|
char const* cert_file_name,
|
|
char const* key_file_name,
|
|
char const * cert_root_file_name,
|
|
char const* default_alpn,
|
|
picoquic_stream_data_cb_fn default_callback_fn,
|
|
void* default_callback_ctx,
|
|
picoquic_connection_id_cb_fn cnx_id_callback,
|
|
void* cnx_id_callback_ctx,
|
|
uint8_t reset_seed[PICOQUIC_RESET_SECRET_SIZE],
|
|
uint64_t current_time,
|
|
uint64_t* p_simulated_time,
|
|
char const* ticket_file_name,
|
|
const uint8_t* ticket_encryption_key,
|
|
size_t ticket_encryption_key_length)
|
|
{
|
|
picoquic_quic_t* quic = (picoquic_quic_t*)malloc(sizeof(picoquic_quic_t));
|
|
int ret = 0;
|
|
|
|
if (quic != NULL) {
|
|
/* TODO: winsock init */
|
|
/* TODO: open UDP sockets - maybe */
|
|
memset(quic, 0, sizeof(picoquic_quic_t));
|
|
|
|
quic->default_callback_fn = default_callback_fn;
|
|
quic->default_callback_ctx = default_callback_ctx;
|
|
quic->default_congestion_alg = PICOQUIC_DEFAULT_CONGESTION_ALGORITHM;
|
|
quic->default_alpn = picoquic_string_duplicate(default_alpn);
|
|
quic->cnx_id_callback_fn = cnx_id_callback;
|
|
quic->cnx_id_callback_ctx = cnx_id_callback_ctx;
|
|
quic->p_simulated_time = p_simulated_time;
|
|
quic->local_cnxid_length = 8; /* TODO: should be lower on clients-only implementation */
|
|
quic->padding_multiple_default = 0; /* TODO: consider default = 128 */
|
|
quic->padding_minsize_default = PICOQUIC_RESET_PACKET_MIN_SIZE;
|
|
quic->crypto_epoch_length_max = 0;
|
|
quic->max_simultaneous_logs = PICOQUIC_DEFAULT_SIMULTANEOUS_LOGS;
|
|
quic->max_half_open_before_retry = PICOQUIC_DEFAULT_HALF_OPEN_RETRY_THRESHOLD;
|
|
picoquic_wake_list_init(quic);
|
|
|
|
if (cnx_id_callback != NULL) {
|
|
quic->unconditional_cnx_id = 1;
|
|
}
|
|
|
|
if (ticket_file_name != NULL) {
|
|
quic->ticket_file_name = ticket_file_name;
|
|
ret = picoquic_load_tickets(&quic->p_first_ticket, current_time, ticket_file_name);
|
|
|
|
if (ret == PICOQUIC_ERROR_NO_SUCH_FILE) {
|
|
DBG_PRINTF("Ticket file <%s> not created yet.\n", ticket_file_name);
|
|
ret = 0;
|
|
}
|
|
else if (ret != 0) {
|
|
DBG_PRINTF("Cannot load tickets from <%s>\n", ticket_file_name);
|
|
ret = 0;
|
|
}
|
|
}
|
|
|
|
if (ret == 0) {
|
|
quic->table_cnx_by_id = picohash_create((size_t)nb_connections * 4,
|
|
picoquic_cnx_id_hash, picoquic_cnx_id_compare);
|
|
|
|
quic->table_cnx_by_net = picohash_create((size_t)nb_connections * 4,
|
|
picoquic_net_id_hash, picoquic_net_id_compare);
|
|
|
|
quic->table_cnx_by_icid = picohash_create((size_t)nb_connections,
|
|
picoquic_net_icid_hash, picoquic_net_icid_compare);
|
|
|
|
quic->table_cnx_by_secret = picohash_create((size_t)nb_connections * 4,
|
|
picoquic_net_secret_hash, picoquic_net_secret_compare);
|
|
|
|
picosplay_init_tree(&quic->token_reuse_tree, picoquic_registered_token_compare,
|
|
picoquic_registered_token_create, picoquic_registered_token_delete, picoquic_registered_token_value);
|
|
|
|
if (quic->table_cnx_by_id == NULL || quic->table_cnx_by_net == NULL ||
|
|
quic->table_cnx_by_icid == NULL || quic->table_cnx_by_secret == NULL) {
|
|
ret = -1;
|
|
DBG_PRINTF("%s", "Cannot initialize hash tables\n");
|
|
}
|
|
else if (picoquic_master_tlscontext(quic, cert_file_name, key_file_name, cert_root_file_name, ticket_encryption_key, ticket_encryption_key_length) != 0) {
|
|
ret = -1;
|
|
DBG_PRINTF("%s", "Cannot create TLS context \n");
|
|
}
|
|
else {
|
|
/* the random generator was initialized as part of the TLS context.
|
|
* Use it to create the seed for generating the per context stateless
|
|
* resets and the retry tokens */
|
|
|
|
if (!reset_seed)
|
|
picoquic_crypto_random(quic, quic->reset_seed, sizeof(quic->reset_seed));
|
|
else
|
|
memcpy(quic->reset_seed, reset_seed, sizeof(quic->reset_seed));
|
|
|
|
picoquic_crypto_random(quic, quic->retry_seed, sizeof(quic->retry_seed));
|
|
|
|
/* If there is no root certificate context specified, use a null certifier. */
|
|
}
|
|
}
|
|
|
|
if (ret != 0) {
|
|
picoquic_free(quic);
|
|
quic = NULL;
|
|
}
|
|
}
|
|
|
|
return quic;
|
|
}
|
|
|
|
int picoquic_load_token_file(picoquic_quic_t* quic, char const * token_file_name)
|
|
{
|
|
uint64_t current_time = picoquic_get_quic_time(quic);
|
|
int ret = picoquic_load_tokens(&quic->p_first_token, current_time, token_file_name);
|
|
|
|
if (ret == PICOQUIC_ERROR_NO_SUCH_FILE) {
|
|
DBG_PRINTF("Ticket file <%s> not created yet.\n", token_file_name);
|
|
ret = 0;
|
|
}
|
|
else if (ret != 0) {
|
|
DBG_PRINTF("Cannot load tickets from <%s>\n", token_file_name);
|
|
}
|
|
|
|
if (ret == 0) {
|
|
quic->token_file_name = token_file_name;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
int picoquic_set_default_tp(picoquic_quic_t* quic, picoquic_tp_t * tp)
|
|
{
|
|
int ret = 0;
|
|
|
|
if (quic->default_tp == NULL) {
|
|
quic->default_tp = (picoquic_tp_t *)malloc(sizeof(picoquic_tp_t));
|
|
}
|
|
|
|
if (quic->default_tp == NULL) {
|
|
ret = PICOQUIC_ERROR_MEMORY;
|
|
}
|
|
else {
|
|
memcpy(quic->default_tp, tp, sizeof(picoquic_tp_t));
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
void picoquic_set_default_padding(picoquic_quic_t* quic, uint32_t padding_multiple, uint32_t padding_minsize)
|
|
{
|
|
quic->padding_minsize_default = padding_minsize;
|
|
quic->padding_multiple_default = padding_multiple;
|
|
}
|
|
|
|
void picoquic_set_default_spinbit_policy(picoquic_quic_t * quic, picoquic_spinbit_version_enum default_spinbit_policy)
|
|
{
|
|
quic->default_spin_policy = default_spinbit_policy;
|
|
}
|
|
|
|
void picoquic_set_default_crypto_epoch_length(picoquic_quic_t* quic, uint64_t crypto_epoch_length_max)
|
|
{
|
|
quic->crypto_epoch_length_max = (crypto_epoch_length_max == 0) ?
|
|
PICOQUIC_DEFAULT_CRYPTO_EPOCH_LENGTH : crypto_epoch_length_max;
|
|
}
|
|
|
|
uint64_t picoquic_get_default_crypto_epoch_length(picoquic_quic_t* quic)
|
|
{
|
|
return quic->crypto_epoch_length_max;
|
|
}
|
|
|
|
void picoquic_set_crypto_epoch_length(picoquic_cnx_t* cnx, uint64_t crypto_epoch_length_max)
|
|
{
|
|
cnx->crypto_epoch_length_max = (crypto_epoch_length_max == 0) ?
|
|
PICOQUIC_DEFAULT_CRYPTO_EPOCH_LENGTH : crypto_epoch_length_max;
|
|
}
|
|
|
|
uint64_t picoquic_get_crypto_epoch_length(picoquic_cnx_t* cnx)
|
|
{
|
|
return cnx->crypto_epoch_length_max;
|
|
}
|
|
|
|
|
|
uint8_t picoquic_get_local_cid_length(picoquic_quic_t* quic)
|
|
{
|
|
return quic->local_cnxid_length;
|
|
}
|
|
|
|
int picoquic_is_local_cid(picoquic_quic_t* quic, picoquic_connection_id_t* cid)
|
|
{
|
|
return (cid->id_len == quic->local_cnxid_length &&
|
|
picoquic_cnx_by_id(quic, *cid) != NULL);
|
|
}
|
|
|
|
void picoquic_set_max_simultaneous_logs(picoquic_quic_t* quic, uint32_t max_simultaneous_logs)
|
|
{
|
|
quic->max_simultaneous_logs = max_simultaneous_logs;
|
|
}
|
|
|
|
uint32_t picoquic_get_max_simultaneous_logs(picoquic_quic_t* quic)
|
|
{
|
|
return quic->max_simultaneous_logs;
|
|
}
|
|
|
|
void picoquic_free(picoquic_quic_t* quic)
|
|
{
|
|
if (quic != NULL) {
|
|
|
|
/* delete all the connection contexts -- do this before any other
|
|
* action, as deleting connections may add packets to queues or
|
|
* change connection lists */
|
|
while (quic->cnx_list != NULL) {
|
|
picoquic_delete_cnx(quic->cnx_list);
|
|
}
|
|
|
|
/* Delete TLS and AEAD cntexts */
|
|
picoquic_delete_retry_protection_contexts(quic);
|
|
|
|
if (quic->aead_encrypt_ticket_ctx != NULL) {
|
|
picoquic_aead_free(quic->aead_encrypt_ticket_ctx);
|
|
quic->aead_encrypt_ticket_ctx = NULL;
|
|
}
|
|
|
|
if (quic->aead_decrypt_ticket_ctx != NULL) {
|
|
picoquic_aead_free(quic->aead_decrypt_ticket_ctx);
|
|
quic->aead_decrypt_ticket_ctx = NULL;
|
|
}
|
|
|
|
if (quic->default_alpn != NULL) {
|
|
free((void*)quic->default_alpn);
|
|
quic->default_alpn = NULL;
|
|
}
|
|
|
|
/* delete the stored tickets */
|
|
picoquic_free_tickets(&quic->p_first_ticket);
|
|
|
|
/* Deelete the reused tokens tree */
|
|
picosplay_empty_tree(&quic->token_reuse_tree);
|
|
|
|
/* delete packets in pool */
|
|
while (quic->p_first_packet != NULL) {
|
|
picoquic_packet_t * p = quic->p_first_packet->next_packet;
|
|
free(quic->p_first_packet);
|
|
quic->p_first_packet = p;
|
|
}
|
|
|
|
/* delete all pending stateless packets */
|
|
while (quic->pending_stateless_packet != NULL) {
|
|
picoquic_stateless_packet_t* to_delete = quic->pending_stateless_packet;
|
|
quic->pending_stateless_packet = to_delete->next_packet;
|
|
free(to_delete);
|
|
}
|
|
|
|
if (quic->table_cnx_by_id != NULL) {
|
|
picohash_delete(quic->table_cnx_by_id, 1);
|
|
}
|
|
|
|
if (quic->table_cnx_by_net != NULL) {
|
|
picohash_delete(quic->table_cnx_by_net, 1);
|
|
}
|
|
|
|
if (quic->table_cnx_by_icid != NULL) {
|
|
picohash_delete(quic->table_cnx_by_icid, 1);
|
|
}
|
|
|
|
if (quic->table_cnx_by_secret != NULL) {
|
|
picohash_delete(quic->table_cnx_by_secret, 1);
|
|
}
|
|
|
|
if (quic->verify_certificate_ctx != NULL &&
|
|
quic->free_verify_certificate_callback_fn != NULL) {
|
|
(quic->free_verify_certificate_callback_fn)(quic->verify_certificate_ctx);
|
|
quic->verify_certificate_ctx = NULL;
|
|
}
|
|
|
|
if (quic->verify_certificate_callback_fn != NULL) {
|
|
picoquic_dispose_verify_certificate_callback(quic, 1);
|
|
}
|
|
|
|
if (quic->default_tp != NULL) {
|
|
free(quic->default_tp);
|
|
quic->default_tp = NULL;
|
|
}
|
|
|
|
/* Delete the picotls context */
|
|
if (quic->tls_master_ctx != NULL) {
|
|
picoquic_master_tlscontext_free(quic);
|
|
|
|
free(quic->tls_master_ctx);
|
|
quic->tls_master_ctx = NULL;
|
|
}
|
|
|
|
quic->binlog_dir = picoquic_string_free(quic->binlog_dir);
|
|
quic->qlog_dir = picoquic_string_free(quic->qlog_dir);
|
|
|
|
free(quic);
|
|
}
|
|
}
|
|
|
|
void picoquic_set_null_verifier(picoquic_quic_t* quic) {
|
|
picoquic_dispose_verify_certificate_callback(quic, quic->verify_certificate_callback_fn != NULL);
|
|
}
|
|
|
|
void picoquic_set_cookie_mode(picoquic_quic_t* quic, int cookie_mode)
|
|
{
|
|
if (cookie_mode&1) {
|
|
quic->force_check_token = 1;
|
|
} else {
|
|
quic->force_check_token = 0;
|
|
}
|
|
|
|
if (cookie_mode & 2) {
|
|
quic->provide_token = 1;
|
|
}
|
|
else {
|
|
quic->provide_token = 0;
|
|
}
|
|
|
|
quic->check_token = (quic->force_check_token || quic->max_half_open_before_retry <= quic->current_number_half_open);
|
|
}
|
|
|
|
void picoquic_set_max_half_open_retry_threshold(picoquic_quic_t* quic, uint32_t max_half_open_before_retry)
|
|
{
|
|
quic->max_half_open_before_retry = max_half_open_before_retry;
|
|
}
|
|
|
|
uint32_t picoquic_get_max_half_open_retry_threshold(picoquic_quic_t* quic)
|
|
{
|
|
return quic->max_half_open_before_retry;
|
|
}
|
|
|
|
picoquic_stateless_packet_t* picoquic_create_stateless_packet(picoquic_quic_t* quic)
|
|
{
|
|
#ifdef _WINDOWS
|
|
UNREFERENCED_PARAMETER(quic);
|
|
#endif
|
|
return (picoquic_stateless_packet_t*)malloc(sizeof(picoquic_stateless_packet_t));
|
|
}
|
|
|
|
void picoquic_delete_stateless_packet(picoquic_stateless_packet_t* sp)
|
|
{
|
|
free(sp);
|
|
}
|
|
|
|
void picoquic_queue_stateless_packet(picoquic_quic_t* quic, picoquic_stateless_packet_t* sp)
|
|
{
|
|
picoquic_stateless_packet_t** pnext = &quic->pending_stateless_packet;
|
|
|
|
while ((*pnext) != NULL) {
|
|
pnext = &(*pnext)->next_packet;
|
|
}
|
|
|
|
*pnext = sp;
|
|
sp->next_packet = NULL;
|
|
}
|
|
|
|
picoquic_stateless_packet_t* picoquic_dequeue_stateless_packet(picoquic_quic_t* quic)
|
|
{
|
|
picoquic_stateless_packet_t* sp = quic->pending_stateless_packet;
|
|
|
|
if (sp != NULL) {
|
|
quic->pending_stateless_packet = sp->next_packet;
|
|
sp->next_packet = NULL;
|
|
picoquic_log_quic_pdu(quic, 0, picoquic_get_quic_time(quic), sp->cnxid_log64,
|
|
(struct sockaddr*) & sp->addr_to, (struct sockaddr*) & sp->addr_local, sp->length);
|
|
}
|
|
|
|
return sp;
|
|
}
|
|
|
|
int picoquic_cnx_is_still_logging(picoquic_cnx_t* cnx)
|
|
{
|
|
int ret =
|
|
(cnx->nb_packets_logged < PICOQUIC_LOG_PACKET_MAX_SEQUENCE || cnx->quic->use_long_log);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* Connection context creation and registration */
|
|
int picoquic_register_cnx_id(picoquic_quic_t* quic, picoquic_cnx_t* cnx, picoquic_local_cnxid_t* l_cid)
|
|
{
|
|
int ret = 0;
|
|
picohash_item* item;
|
|
picoquic_cnx_id_key_t* key = (picoquic_cnx_id_key_t*)malloc(sizeof(picoquic_cnx_id_key_t));
|
|
|
|
if (key == NULL) {
|
|
ret = -1;
|
|
} else {
|
|
key->cnx_id = l_cid->cnx_id;
|
|
key->cnx = cnx;
|
|
key->l_cid = l_cid;
|
|
key->next_cnx_id = NULL;
|
|
|
|
item = picohash_retrieve(quic->table_cnx_by_id, key);
|
|
|
|
if (item != NULL) {
|
|
ret = -1;
|
|
} else {
|
|
ret = picohash_insert(quic->table_cnx_by_id, key);
|
|
|
|
if (ret == 0) {
|
|
key->next_cnx_id = l_cid->first_cnx_id;
|
|
l_cid->first_cnx_id = key;
|
|
}
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
int picoquic_register_net_id(picoquic_quic_t* quic, picoquic_cnx_t* cnx, picoquic_path_t * path_x, struct sockaddr* addr)
|
|
{
|
|
int ret = 0;
|
|
picohash_item* item;
|
|
picoquic_net_id_key_t* key = (picoquic_net_id_key_t*)malloc(sizeof(picoquic_net_id_key_t));
|
|
|
|
if (key == NULL) {
|
|
ret = -1;
|
|
} else {
|
|
memset(key, 0, sizeof(picoquic_net_id_key_t));
|
|
picoquic_store_addr(&key->saddr, addr);
|
|
|
|
key->cnx = cnx;
|
|
key->path = path_x;
|
|
|
|
item = picohash_retrieve(quic->table_cnx_by_net, key);
|
|
|
|
if (item != NULL) {
|
|
ret = -1;
|
|
} else {
|
|
ret = picohash_insert(quic->table_cnx_by_net, key);
|
|
|
|
if (ret == 0) {
|
|
key->next_net_id = path_x->first_net_id;
|
|
path_x->first_net_id = key;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (key != NULL && ret != 0) {
|
|
free(key);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* The initial CID and the reset secret are tracked in specific tables:
|
|
*
|
|
* - quic->table_cnx_by_icid: keyed by client address and initial CID. Created
|
|
* when the connection for the specified initial CID and address is created
|
|
* by the server (or the peer receiving the connection in P2P cases)
|
|
* - quic->table_cnx_by_secret: keyed by peer address and reset secret for
|
|
* the default path of the connection (cnx->path[0]).
|
|
*
|
|
* In both cases, the address is that associated to the default path. The
|
|
* path can be updated after migration, either by an address change or by
|
|
* a change of CID and secret while keeping the address constant.
|
|
*
|
|
* If either the default address or the default reset secret changes, the
|
|
* old table entry is updated to track the new address and secret. The
|
|
* entry is kept up to date until the connection closes.
|
|
*
|
|
* Migration can only happen after a connection is established, but
|
|
* packets could still arrive after that, maybe due to network delays.
|
|
* In order to keep the design simple, the ICID entry is created once, and
|
|
* kept for the duration of the connection.
|
|
*
|
|
* To facilitate management, the hash table keys are remembered in the
|
|
* connection context as:
|
|
*
|
|
* - cnx->reset_secret_key
|
|
* - cnx->net_icid_key
|
|
*/
|
|
|
|
int picoquic_register_net_icid(picoquic_cnx_t* cnx)
|
|
{
|
|
int ret = 0;
|
|
picohash_item* item;
|
|
picoquic_net_icid_key_t* key = (picoquic_net_icid_key_t*)malloc(sizeof(picoquic_net_icid_key_t));
|
|
|
|
if (key == NULL) {
|
|
ret = -1;
|
|
}
|
|
else {
|
|
memset(key, 0, sizeof(picoquic_net_icid_key_t));
|
|
picoquic_store_addr(&key->saddr, (struct sockaddr *)&cnx->path[0]->peer_addr);
|
|
key->icid = cnx->initial_cnxid;
|
|
|
|
key->cnx = cnx;
|
|
|
|
item = picohash_retrieve(cnx->quic->table_cnx_by_icid, key);
|
|
|
|
if (item != NULL) {
|
|
ret = -1;
|
|
}
|
|
else {
|
|
ret = picohash_insert(cnx->quic->table_cnx_by_icid, key);
|
|
|
|
if (ret == 0) {
|
|
cnx->net_icid_key = key;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (key != NULL && ret != 0) {
|
|
free(key);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
int picoquic_register_net_secret(picoquic_cnx_t* cnx)
|
|
{
|
|
int ret = 0;
|
|
picohash_item* item;
|
|
picoquic_net_secret_key_t* key = (picoquic_net_secret_key_t*)malloc(sizeof(picoquic_net_secret_key_t));
|
|
|
|
if (key == NULL) {
|
|
ret = PICOQUIC_ERROR_MEMORY;
|
|
}
|
|
else {
|
|
memset(key, 0, sizeof(picoquic_net_secret_key_t));
|
|
picoquic_store_addr(&key->saddr, (struct sockaddr *)&cnx->path[0]->peer_addr);
|
|
memcpy(key->reset_secret, cnx->path[0]->reset_secret, PICOQUIC_RESET_SECRET_SIZE);
|
|
|
|
key->cnx = cnx;
|
|
|
|
item = picohash_retrieve(cnx->quic->table_cnx_by_secret, key);
|
|
|
|
if (item != NULL) {
|
|
ret = -1;
|
|
}
|
|
else {
|
|
ret = picohash_insert(cnx->quic->table_cnx_by_secret, key);
|
|
|
|
if (ret == 0) {
|
|
if (cnx->reset_secret_key != NULL) {
|
|
picohash_delete_key(cnx->quic->table_cnx_by_secret, cnx->reset_secret_key, 1);
|
|
}
|
|
cnx->reset_secret_key = key;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (key != NULL && ret != 0) {
|
|
free(key);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
void picoquic_init_transport_parameters(picoquic_tp_t* tp, int client_mode)
|
|
{
|
|
tp->initial_max_stream_data_bidi_local = 0x200000;
|
|
tp->initial_max_stream_data_bidi_remote = 65635;
|
|
tp->initial_max_stream_data_uni = 65535;
|
|
tp->initial_max_data = 0x100000;
|
|
if (client_mode) {
|
|
tp->initial_max_stream_id_bidir = 2049;
|
|
tp->initial_max_stream_id_unidir = 2051;
|
|
} else {
|
|
tp->initial_max_stream_id_bidir = 2048;
|
|
tp->initial_max_stream_id_unidir = 2050;
|
|
}
|
|
tp->idle_timeout = PICOQUIC_MICROSEC_HANDSHAKE_MAX/1000;
|
|
tp->max_packet_size = PICOQUIC_PRACTICAL_MAX_MTU;
|
|
tp->max_datagram_frame_size = 0;
|
|
tp->ack_delay_exponent = 3;
|
|
tp->active_connection_id_limit = PICOQUIC_NB_PATH_TARGET;
|
|
tp->max_ack_delay = PICOQUIC_ACK_DELAY_MAX;
|
|
tp->enable_loss_bit = 2;
|
|
tp->min_ack_delay = PICOQUIC_ACK_DELAY_MIN;
|
|
tp->enable_time_stamp = 0;
|
|
}
|
|
|
|
|
|
/* management of the list of connections in context */
|
|
|
|
picoquic_quic_t* picoquic_get_quic_ctx(picoquic_cnx_t* cnx)
|
|
{
|
|
return (cnx == NULL)?NULL:cnx->quic;
|
|
}
|
|
|
|
picoquic_cnx_t* picoquic_get_first_cnx(picoquic_quic_t* quic)
|
|
{
|
|
return quic->cnx_list;
|
|
}
|
|
|
|
picoquic_cnx_t* picoquic_get_next_cnx(picoquic_cnx_t* cnx)
|
|
{
|
|
return cnx->next_in_table;
|
|
}
|
|
|
|
static void picoquic_insert_cnx_in_list(picoquic_quic_t* quic, picoquic_cnx_t* cnx)
|
|
{
|
|
if (quic->cnx_list != NULL) {
|
|
quic->cnx_list->previous_in_table = cnx;
|
|
cnx->next_in_table = quic->cnx_list;
|
|
} else {
|
|
quic->cnx_last = cnx;
|
|
cnx->next_in_table = NULL;
|
|
}
|
|
quic->cnx_list = cnx;
|
|
cnx->previous_in_table = NULL;
|
|
}
|
|
|
|
static void picoquic_remove_cnx_from_list(picoquic_cnx_t* cnx)
|
|
{
|
|
if (cnx->next_in_table == NULL) {
|
|
cnx->quic->cnx_last = cnx->previous_in_table;
|
|
} else {
|
|
cnx->next_in_table->previous_in_table = cnx->previous_in_table;
|
|
}
|
|
|
|
if (cnx->previous_in_table == NULL) {
|
|
cnx->quic->cnx_list = cnx->next_in_table;
|
|
}
|
|
else {
|
|
cnx->previous_in_table->next_in_table = cnx->next_in_table;
|
|
}
|
|
|
|
if (cnx->net_icid_key != NULL) {
|
|
picohash_delete_key(cnx->quic->table_cnx_by_icid, cnx->net_icid_key, 1);
|
|
cnx->net_icid_key = NULL;
|
|
}
|
|
|
|
if (cnx->reset_secret_key != NULL) {
|
|
picohash_delete_key(cnx->quic->table_cnx_by_secret, cnx->reset_secret_key, 1);
|
|
cnx->reset_secret_key = NULL;
|
|
}
|
|
}
|
|
|
|
/* Management of the list of connections, sorted by wake time */
|
|
|
|
static void* picoquic_wake_list_node_value(picosplay_node_t* cnx_wake_node)
|
|
{
|
|
return (cnx_wake_node == NULL)?NULL:(void*)((char*)cnx_wake_node - offsetof(struct st_picoquic_cnx_t, cnx_wake_node));
|
|
}
|
|
|
|
static int64_t picoquic_wake_list_compare(void* l, void* r) {
|
|
return (int64_t)((picoquic_cnx_t*)l)->next_wake_time - ((picoquic_cnx_t*)r)->next_wake_time;
|
|
}
|
|
|
|
static picosplay_node_t* picoquic_wake_list_create_node(void* v_cnx)
|
|
{
|
|
return &((picoquic_cnx_t*)v_cnx)->cnx_wake_node;
|
|
}
|
|
|
|
static void picoquic_wake_list_delete_node(void* tree, picosplay_node_t* node)
|
|
{
|
|
#ifdef _WINDOWS
|
|
UNREFERENCED_PARAMETER(tree);
|
|
#endif
|
|
memset(node, 0, sizeof(picosplay_node_t));
|
|
}
|
|
|
|
static void picoquic_wake_list_init(picoquic_quic_t * quic)
|
|
{
|
|
picosplay_init_tree(&quic->cnx_wake_tree, picoquic_wake_list_compare,
|
|
picoquic_wake_list_create_node, picoquic_wake_list_delete_node, picoquic_wake_list_node_value);
|
|
}
|
|
|
|
static void picoquic_remove_cnx_from_wake_list(picoquic_cnx_t* cnx)
|
|
{
|
|
picosplay_delete_hint(&cnx->quic->cnx_wake_tree, &cnx->cnx_wake_node);
|
|
}
|
|
|
|
static void picoquic_insert_cnx_by_wake_time(picoquic_quic_t* quic, picoquic_cnx_t* cnx)
|
|
{
|
|
picosplay_insert(&quic->cnx_wake_tree, cnx);
|
|
}
|
|
|
|
void picoquic_reinsert_by_wake_time(picoquic_quic_t* quic, picoquic_cnx_t* cnx, uint64_t next_time)
|
|
{
|
|
picoquic_remove_cnx_from_wake_list(cnx);
|
|
cnx->next_wake_time = next_time;
|
|
picoquic_insert_cnx_by_wake_time(quic, cnx);
|
|
}
|
|
|
|
picoquic_cnx_t* picoquic_get_earliest_cnx_to_wake(picoquic_quic_t* quic, uint64_t max_wake_time)
|
|
{
|
|
picoquic_cnx_t* cnx = (picoquic_cnx_t *)picoquic_wake_list_node_value(picosplay_first(&quic->cnx_wake_tree));
|
|
if (cnx != NULL && max_wake_time != 0 && cnx->next_wake_time > max_wake_time)
|
|
{
|
|
cnx = NULL;
|
|
}
|
|
|
|
return cnx;
|
|
}
|
|
|
|
uint64_t picoquic_get_next_wake_time(picoquic_quic_t* quic, uint64_t current_time)
|
|
{
|
|
uint64_t wake_time = UINT64_MAX;
|
|
|
|
if (quic->pending_stateless_packet != NULL) {
|
|
wake_time = current_time;
|
|
}
|
|
else{
|
|
picoquic_cnx_t* cnx_wake_first = (picoquic_cnx_t*)picoquic_wake_list_node_value(
|
|
picosplay_first(&quic->cnx_wake_tree));
|
|
|
|
if (cnx_wake_first != NULL) {
|
|
wake_time = cnx_wake_first->next_wake_time;
|
|
}
|
|
}
|
|
|
|
return wake_time;
|
|
}
|
|
|
|
int64_t picoquic_get_next_wake_delay(picoquic_quic_t* quic,
|
|
uint64_t current_time, int64_t delay_max)
|
|
{
|
|
uint64_t next_wake_time = picoquic_get_next_wake_time(quic, current_time);
|
|
int64_t wake_delay = next_wake_time - current_time;
|
|
|
|
if (wake_delay > delay_max || next_wake_time == UINT64_MAX) {
|
|
wake_delay = delay_max;
|
|
}
|
|
|
|
return wake_delay;
|
|
}
|
|
|
|
/* Other context management functions */
|
|
|
|
int picoquic_get_version_index(uint32_t proposed_version)
|
|
{
|
|
int ret = -1;
|
|
|
|
for (size_t i = 0; i < picoquic_nb_supported_versions; i++) {
|
|
if (picoquic_supported_versions[i].version == proposed_version) {
|
|
ret = (int)i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
static void picoquic_create_random_cnx_id(picoquic_quic_t* quic, picoquic_connection_id_t * cnx_id, uint8_t id_length)
|
|
{
|
|
if (id_length > 0) {
|
|
picoquic_crypto_random(quic, cnx_id->id, id_length);
|
|
}
|
|
if (id_length < sizeof(cnx_id->id)) {
|
|
memset(cnx_id->id + id_length, 0, sizeof(cnx_id->id) - id_length);
|
|
}
|
|
cnx_id->id_len = id_length;
|
|
}
|
|
|
|
/* Path management -- returns the index of the path that was created. */
|
|
|
|
int picoquic_create_path(picoquic_cnx_t* cnx, uint64_t start_time, const struct sockaddr* local_addr, const struct sockaddr* peer_addr)
|
|
{
|
|
int ret = -1;
|
|
|
|
if (cnx->nb_paths >= cnx->nb_path_alloc)
|
|
{
|
|
int new_alloc = (cnx->nb_path_alloc == 0) ? 1 : 2 * cnx->nb_path_alloc;
|
|
picoquic_path_t ** new_path = (picoquic_path_t **)malloc(new_alloc * sizeof(picoquic_path_t *));
|
|
|
|
if (new_path != NULL)
|
|
{
|
|
if (cnx->path != NULL)
|
|
{
|
|
if (cnx->nb_paths > 0)
|
|
{
|
|
memcpy(new_path, cnx->path, cnx->nb_paths * sizeof(picoquic_path_t *));
|
|
}
|
|
free(cnx->path);
|
|
}
|
|
cnx->path = new_path;
|
|
cnx->nb_path_alloc = new_alloc;
|
|
}
|
|
}
|
|
|
|
if (cnx->nb_paths < cnx->nb_path_alloc)
|
|
{
|
|
picoquic_path_t * path_x = (picoquic_path_t *)malloc(sizeof(picoquic_path_t));
|
|
|
|
if (path_x != NULL)
|
|
{
|
|
memset(path_x, 0, sizeof(picoquic_path_t));
|
|
/* Register the sequence number */
|
|
path_x->path_sequence = cnx->path_sequence_next;
|
|
cnx->path_sequence_next++;
|
|
|
|
/* Set the addresses */
|
|
picoquic_store_addr(&path_x->peer_addr, peer_addr);
|
|
picoquic_store_addr(&path_x->local_addr, local_addr);
|
|
|
|
/* Set the challenge used for this path */
|
|
for (int ichal = 0; ichal < PICOQUIC_CHALLENGE_REPEAT_MAX; ichal++) {
|
|
path_x->challenge[ichal] = picoquic_public_random_64();
|
|
}
|
|
|
|
/* Initialize the reset secret to a random value. This
|
|
* will prevent spurious matches to an all zero value, for example.
|
|
* The real value will be set when receiving the transport parameters.
|
|
*/
|
|
picoquic_public_random(path_x->reset_secret, PICOQUIC_RESET_SECRET_SIZE);
|
|
|
|
/* Initialize per path time measurement */
|
|
path_x->smoothed_rtt = PICOQUIC_INITIAL_RTT;
|
|
path_x->rtt_variant = 0;
|
|
path_x->retransmit_timer = PICOQUIC_INITIAL_RETRANSMIT_TIMER;
|
|
path_x->rtt_min = 0;
|
|
|
|
/* Initialize per path congestion control state */
|
|
path_x->cwin = PICOQUIC_CWIN_INITIAL;
|
|
path_x->bytes_in_transit = 0;
|
|
path_x->congestion_alg_state = NULL;
|
|
|
|
/* Initialize per path pacing state */
|
|
path_x->pacing_evaluation_time = start_time;
|
|
path_x->pacing_bucket_nanosec = 16;
|
|
path_x->pacing_bucket_max = 16;
|
|
path_x->pacing_packet_time_nanosec = 1;
|
|
path_x->pacing_packet_time_microsec = 1;
|
|
|
|
/* Initialize the MTU */
|
|
path_x->send_mtu = (peer_addr == NULL || peer_addr->sa_family == AF_INET) ? PICOQUIC_INITIAL_MTU_IPV4 : PICOQUIC_INITIAL_MTU_IPV6;
|
|
|
|
/* Record the path */
|
|
cnx->path[cnx->nb_paths] = path_x;
|
|
ret = cnx->nb_paths++;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Register the path in the hash tables.
|
|
* This only registers the address associated with the path.
|
|
*/
|
|
void picoquic_register_path(picoquic_cnx_t* cnx, picoquic_path_t * path_x)
|
|
{
|
|
|
|
if (path_x->peer_addr.ss_family != 0) {
|
|
(void)picoquic_register_net_id(cnx->quic, cnx, cnx->path[0], (struct sockaddr *)&path_x->peer_addr);
|
|
}
|
|
|
|
path_x->path_is_registered = 1;
|
|
}
|
|
|
|
/* To delete a path, we need to delete the data allocated to the path: search items in
|
|
* the hash tables, and congestion algorithm context. Then delete the path data itself,
|
|
* and finally remove the path reference from the table of paths in the connection
|
|
* context.
|
|
*/
|
|
|
|
static void picoquic_clear_path_data(picoquic_cnx_t* cnx, picoquic_path_t * path_x)
|
|
{
|
|
while (path_x->first_net_id != NULL) {
|
|
picohash_item* item;
|
|
picoquic_net_id_key_t* net_id_key = path_x->first_net_id;
|
|
path_x->first_net_id = net_id_key->next_net_id;
|
|
net_id_key->next_net_id = NULL;
|
|
|
|
item = picohash_retrieve(cnx->quic->table_cnx_by_net, net_id_key);
|
|
if (item != NULL) {
|
|
picohash_delete_item(cnx->quic->table_cnx_by_net, item, 1);
|
|
}
|
|
}
|
|
/* Remove the congestion data */
|
|
if (cnx->congestion_alg != NULL) {
|
|
cnx->congestion_alg->alg_delete(path_x);
|
|
}
|
|
|
|
/* Free the record */
|
|
free(path_x);
|
|
}
|
|
|
|
void picoquic_delete_path(picoquic_cnx_t* cnx, int path_index)
|
|
{
|
|
picoquic_path_t * path_x = cnx->path[path_index];
|
|
picoquic_packet_t* p = NULL;
|
|
|
|
if (cnx->quic->F_log != NULL) {
|
|
fflush(cnx->quic->F_log);
|
|
}
|
|
|
|
/* Remove old path data from retransmit queue */
|
|
for (picoquic_packet_context_enum pc = 0; pc < picoquic_nb_packet_context; pc++)
|
|
{
|
|
p = cnx->pkt_ctx[pc].retransmit_newest;
|
|
|
|
while (p != NULL) {
|
|
if (p->send_path == path_x) {
|
|
DBG_PRINTF("Erase path for packet pc: %d, seq:%" PRIu64 "\n", pc, p->sequence_number);
|
|
p->send_path = NULL;
|
|
}
|
|
p = p->next_packet;
|
|
}
|
|
|
|
p = cnx->pkt_ctx[pc].retransmitted_newest;
|
|
while (p != NULL) {
|
|
if (p->send_path == path_x) {
|
|
DBG_PRINTF("Erase path for old packet pc: %d, seq:%" PRIu64 "\n", pc, p->sequence_number);
|
|
p->send_path = NULL;
|
|
}
|
|
p = p->next_packet;
|
|
}
|
|
}
|
|
/* Free the data */
|
|
picoquic_clear_path_data(cnx, path_x);
|
|
|
|
|
|
/* Compact the path table */
|
|
for (int i = path_index + 1; i < cnx->nb_paths; i++) {
|
|
cnx->path[i-1] = cnx->path[i];
|
|
}
|
|
|
|
cnx->nb_paths--;
|
|
cnx->path[cnx->nb_paths] = NULL;
|
|
}
|
|
|
|
/*
|
|
* Path challenges may be abandoned if they are tried too many times without success.
|
|
*/
|
|
|
|
void picoquic_delete_abandoned_paths(picoquic_cnx_t* cnx, uint64_t current_time, uint64_t * next_wake_time)
|
|
{
|
|
int path_index_good = 1;
|
|
int path_index_current = 1;
|
|
unsigned int is_demotion_in_progress = 0;
|
|
|
|
while (path_index_current < cnx->nb_paths) {
|
|
if (cnx->path[path_index_current]->challenge_failed ||
|
|
(cnx->path[path_index_current]->path_is_demoted &&
|
|
current_time >= cnx->path[path_index_current]->demotion_time) ||
|
|
(path_index_current > 0 && cnx->path[path_index_current]->challenge_verified &&
|
|
current_time - cnx->path[path_index_current]->latest_sent_time >= cnx->idle_timeout)) {
|
|
/* Demote any failed path */
|
|
if (!cnx->path[path_index_current]->path_is_demoted) {
|
|
picoquic_demote_path(cnx, path_index_current, current_time);
|
|
}
|
|
/* Only increment the current index */
|
|
is_demotion_in_progress |= cnx->path[path_index_current]->path_is_demoted;
|
|
path_index_current++;
|
|
} else {
|
|
if (cnx->path[path_index_current]->path_is_demoted &&
|
|
current_time < cnx->path[path_index_current]->demotion_time){
|
|
is_demotion_in_progress |= 1;
|
|
if (*next_wake_time > cnx->path[path_index_current]->demotion_time) {
|
|
*next_wake_time = cnx->path[path_index_current]->demotion_time;
|
|
SET_LAST_WAKE(cnx->quic, PICOQUIC_QUICCTX);
|
|
}
|
|
}
|
|
|
|
if (path_index_current > path_index_good) {
|
|
/* swap the path indexed good with current */
|
|
picoquic_path_t * path_x = cnx->path[path_index_current];
|
|
cnx->path[path_index_current] = cnx->path[path_index_good];
|
|
cnx->path[path_index_good] = path_x;
|
|
}
|
|
/* increment both indices */
|
|
path_index_current++;
|
|
path_index_good++;
|
|
}
|
|
}
|
|
|
|
while (cnx->nb_paths > path_index_good) {
|
|
int d_path = cnx->nb_paths - 1;
|
|
if (!picoquic_is_connection_id_null(&cnx->path[d_path]->remote_cnxid)) {
|
|
(void)picoquic_queue_retire_connection_id_frame(cnx, cnx->path[d_path]->remote_cnxid_sequence);
|
|
}
|
|
picoquic_delete_path(cnx, d_path);
|
|
}
|
|
|
|
/* TODO: what if there are no paths left? */
|
|
cnx->path_demotion_needed = is_demotion_in_progress;
|
|
}
|
|
|
|
/*
|
|
* Demote path, compute the effective time for demotion.
|
|
*/
|
|
void picoquic_demote_path(picoquic_cnx_t* cnx, int path_index, uint64_t current_time)
|
|
{
|
|
if (!cnx->path[path_index]->path_is_demoted) {
|
|
uint64_t demote_timer = cnx->path[path_index]->retransmit_timer;
|
|
|
|
if (demote_timer < PICOQUIC_INITIAL_MAX_RETRANSMIT_TIMER) {
|
|
demote_timer = PICOQUIC_INITIAL_MAX_RETRANSMIT_TIMER;
|
|
}
|
|
|
|
cnx->path[path_index]->path_is_demoted = 1;
|
|
cnx->path[path_index]->demotion_time = current_time + 3* demote_timer;
|
|
cnx->path_demotion_needed = 1;
|
|
}
|
|
}
|
|
|
|
/* Promote path to default. This happens when a new path is verified, at the end
|
|
* of a migration, and becomes the new default path.
|
|
*/
|
|
|
|
void picoquic_promote_path_to_default(picoquic_cnx_t* cnx, int path_index, uint64_t current_time)
|
|
{
|
|
if (path_index > 0 && path_index < cnx->nb_paths) {
|
|
picoquic_path_t * path_x = cnx->path[path_index];
|
|
|
|
if (cnx->path[path_index]->path_is_preferred_path) {
|
|
/* this is a migration to the preferred path requested by the server */
|
|
if (cnx->client_mode) {
|
|
cnx->remote_parameters.migration_disabled = 0;
|
|
}
|
|
else {
|
|
cnx->local_parameters.migration_disabled = 0;
|
|
}
|
|
}
|
|
|
|
if (cnx->quic->F_log != NULL || cnx->f_binlog != NULL) {
|
|
char src_ip[128];
|
|
char dst_ip[128];
|
|
|
|
picoquic_log_app_message(cnx, "Path %d promoted to default at T=%fs, Local: %s, Remote: %s",
|
|
path_index, (double)(current_time - cnx->start_time) / 1000000.0,
|
|
picoquic_addr_text((struct sockaddr*) & cnx->path[path_index]->local_addr, src_ip, sizeof(src_ip)),
|
|
picoquic_addr_text((struct sockaddr*) & cnx->path[path_index]->peer_addr, dst_ip, sizeof(dst_ip)));
|
|
}
|
|
|
|
/* Set the congestion algorithm for the new path */
|
|
if (cnx->congestion_alg != NULL) {
|
|
cnx->congestion_alg->alg_init(path_x, current_time);
|
|
}
|
|
|
|
/* Mark old path as demoted */
|
|
picoquic_demote_path(cnx, 0, current_time);
|
|
|
|
/* Swap */
|
|
cnx->path[path_index] = cnx->path[0];
|
|
cnx->path[0] = path_x;
|
|
|
|
/* Update the secret */
|
|
(void)picoquic_register_net_secret(cnx);
|
|
}
|
|
}
|
|
|
|
/* Set or renew challenge for a path */
|
|
void picoquic_set_path_challenge(picoquic_cnx_t* cnx, int path_id, uint64_t current_time)
|
|
{
|
|
if (!cnx->path[path_id]->challenge_required || cnx->path[path_id]->challenge_verified) {
|
|
/* Reset the path challenge */
|
|
cnx->path[path_id]->challenge_required = 1;
|
|
for (int ichal = 0; ichal < PICOQUIC_CHALLENGE_REPEAT_MAX; ichal++) {
|
|
cnx->path[path_id]->challenge[ichal] = picoquic_public_random_64();
|
|
}
|
|
cnx->path[path_id]->challenge_verified = 0;
|
|
cnx->path[path_id]->challenge_time = current_time;
|
|
cnx->path[path_id]->challenge_repeat_count = 0;
|
|
}
|
|
}
|
|
|
|
/* Find path by address pair
|
|
*/
|
|
int picoquic_find_path_by_address(picoquic_cnx_t* cnx, const struct sockaddr* addr_to,
|
|
const struct sockaddr* addr_from, int * partial_match)
|
|
{
|
|
int path_id = -1;
|
|
int is_null_from = 0;
|
|
struct sockaddr_storage null_addr;
|
|
|
|
*partial_match = -1;
|
|
|
|
if (addr_from != NULL || addr_to != NULL) {
|
|
if (addr_from == NULL || addr_to == NULL) {
|
|
memset(&null_addr, 0, sizeof(struct sockaddr_storage));
|
|
if (addr_from == NULL) {
|
|
addr_from = (struct sockaddr*) & null_addr;
|
|
}
|
|
else {
|
|
addr_to = (struct sockaddr*) & null_addr;
|
|
}
|
|
is_null_from = 1;
|
|
}
|
|
|
|
/* Find whether an existing path matches the pair of addresses */
|
|
for (int i = 0; i < cnx->nb_paths; i++) {
|
|
if (picoquic_compare_addr((struct sockaddr*) & cnx->path[i]->peer_addr,
|
|
addr_from) == 0) {
|
|
if (cnx->path[i]->local_addr.ss_family == 0) {
|
|
*partial_match = i;
|
|
}
|
|
else if (picoquic_compare_addr((struct sockaddr*) & cnx->path[i]->local_addr,
|
|
addr_to) == 0) {
|
|
path_id = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (path_id < 0 && is_null_from) {
|
|
path_id = *partial_match;
|
|
*partial_match = -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
return path_id;
|
|
}
|
|
|
|
/* Assign CID to path */
|
|
int picoquic_assign_peer_cnxid_to_path(picoquic_cnx_t* cnx, int path_id)
|
|
{
|
|
int ret = -1;
|
|
picoquic_cnxid_stash_t* available_cnxid = picoquic_dequeue_cnxid_stash(cnx);
|
|
|
|
if (available_cnxid != NULL) {
|
|
cnx->path[path_id]->remote_cnxid = available_cnxid->cnx_id;
|
|
cnx->path[path_id]->remote_cnxid_sequence = available_cnxid->sequence;
|
|
memcpy(cnx->path[path_id]->reset_secret, available_cnxid->reset_secret,
|
|
PICOQUIC_RESET_SECRET_SIZE);
|
|
free(available_cnxid);
|
|
ret = 0;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* Create a new path in order to trigger a migration */
|
|
int picoquic_probe_new_path_ex(picoquic_cnx_t* cnx, const struct sockaddr* addr_from,
|
|
const struct sockaddr* addr_to, uint64_t current_time, int to_preferred_address)
|
|
{
|
|
int ret = 0;
|
|
int partial_match_path = -1;
|
|
int path_id = -1;
|
|
|
|
if ((cnx->remote_parameters.migration_disabled && !to_preferred_address ) ||
|
|
cnx->local_parameters.migration_disabled) {
|
|
/* Do not create new paths if migration is disabled */
|
|
ret = PICOQUIC_ERROR_MIGRATION_DISABLED;
|
|
DBG_PRINTF("Tried to create probe with migration disabled = %d", cnx->remote_parameters.migration_disabled);
|
|
}
|
|
else if ((path_id = picoquic_find_path_by_address(cnx, addr_to, addr_from, &partial_match_path)) >= 0) {
|
|
/* This path already exists. Will not create it, but will restore it in working order if disabled. */
|
|
ret = -1;
|
|
}
|
|
else if (partial_match_path >= 0 && addr_from->sa_family == 0) {
|
|
/* This path already exists. Will not create it, but will restore it in working order if disabled. */
|
|
ret = -1;
|
|
}
|
|
else if (cnx->cnxid_stash_first == NULL) {
|
|
/* No CNXID available yet. */
|
|
ret = -1;
|
|
}
|
|
else if (cnx->nb_paths >= PICOQUIC_NB_PATH_TARGET) {
|
|
/* Too many paths created already */
|
|
ret = -1;
|
|
}
|
|
else if (picoquic_create_path(cnx, current_time, addr_to, addr_from) > 0) {
|
|
path_id = cnx->nb_paths - 1;
|
|
ret = picoquic_assign_peer_cnxid_to_path(cnx, path_id);
|
|
|
|
if (ret != 0) {
|
|
/* delete the path that was just created! */
|
|
picoquic_delete_path(cnx, path_id);
|
|
}
|
|
else {
|
|
cnx->path[path_id]->path_is_published = 1;
|
|
picoquic_register_path(cnx, cnx->path[path_id]);
|
|
picoquic_set_path_challenge(cnx, path_id, current_time);
|
|
cnx->path[path_id]->path_is_preferred_path = to_preferred_address;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
int picoquic_probe_new_path(picoquic_cnx_t* cnx, const struct sockaddr* addr_from,
|
|
const struct sockaddr* addr_to, uint64_t current_time)
|
|
{
|
|
return picoquic_probe_new_path_ex(cnx, addr_from, addr_to, current_time, 0);
|
|
}
|
|
|
|
/* Reset the path MTU, for example if too many packet losses are detected */
|
|
void picoquic_reset_path_mtu(picoquic_path_t* path_x)
|
|
{
|
|
/* Re-initialize the MTU */
|
|
path_x->send_mtu = (path_x->peer_addr.ss_family == 0 || path_x->peer_addr.ss_family == AF_INET) ?
|
|
PICOQUIC_INITIAL_MTU_IPV4 : PICOQUIC_INITIAL_MTU_IPV6;
|
|
/* Reset the MTU discovery context */
|
|
path_x->send_mtu_max_tried = 0;
|
|
path_x->mtu_probe_sent = 0;
|
|
}
|
|
|
|
/*
|
|
* Manage the stash of connection IDs sent by the peer
|
|
*/
|
|
|
|
picoquic_cnxid_stash_t * picoquic_dequeue_cnxid_stash(picoquic_cnx_t * cnx)
|
|
{
|
|
picoquic_cnxid_stash_t * stashed = NULL;
|
|
|
|
if (cnx != NULL && cnx->cnxid_stash_first != NULL) {
|
|
stashed = cnx->cnxid_stash_first;
|
|
cnx->cnxid_stash_first = stashed->next_in_stash;
|
|
}
|
|
|
|
return stashed;
|
|
}
|
|
|
|
int picoquic_enqueue_cnxid_stash(picoquic_cnx_t* cnx,
|
|
const uint64_t sequence, const uint8_t cid_length, const uint8_t* cnxid_bytes,
|
|
const uint8_t* secret_bytes, picoquic_cnxid_stash_t** pstashed)
|
|
{
|
|
int ret = 0;
|
|
int is_duplicate = 0;
|
|
size_t nb_cid_received = 0;
|
|
picoquic_connection_id_t cnx_id;
|
|
picoquic_cnxid_stash_t* next_stash = cnx->cnxid_stash_first;
|
|
picoquic_cnxid_stash_t* last_stash = NULL;
|
|
picoquic_cnxid_stash_t* stashed = NULL;
|
|
uint64_t cnxid_mask = 0;
|
|
|
|
/* verify the format */
|
|
if (picoquic_parse_connection_id(cnxid_bytes, cid_length, &cnx_id) == 0) {
|
|
ret = PICOQUIC_TRANSPORT_FRAME_FORMAT_ERROR;
|
|
}
|
|
|
|
if (ret == 0 && cnx->path[0]->remote_cnxid.id_len == 0) {
|
|
/* Protocol error. The peer is using null length cnx_id */
|
|
ret = PICOQUIC_TRANSPORT_PROTOCOL_VIOLATION;
|
|
}
|
|
|
|
/* Verify that the proposed CID is not already in use */
|
|
for (int i = 0; ret == 0 && i < cnx->nb_paths; i++) {
|
|
if (cnx->path[i]->remote_cnxid.id_len > 0) {
|
|
if (sequence == cnx->path[i]->remote_cnxid_sequence) {
|
|
if (picoquic_compare_connection_id(&cnx_id, &cnx->path[i]->remote_cnxid) == 0)
|
|
{
|
|
if (memcmp(secret_bytes, cnx->path[i]->reset_secret, PICOQUIC_RESET_SECRET_SIZE) == 0) {
|
|
is_duplicate = 1;
|
|
break;
|
|
}
|
|
else {
|
|
DBG_PRINTF("Path %d, Cnx_id: %02x%02x%02x%02x..., Reset = %02x%02x%02x%02x... vs %02x%02x%02x%02x...\n",
|
|
i,
|
|
cnx->path[i]->remote_cnxid.id[0], cnx->path[i]->remote_cnxid.id[1],
|
|
cnx->path[i]->remote_cnxid.id[2], cnx->path[i]->remote_cnxid.id[3],
|
|
secret_bytes[0], secret_bytes[1], secret_bytes[2], secret_bytes[3],
|
|
cnx->path[i]->reset_secret[0], cnx->path[i]->reset_secret[1],
|
|
cnx->path[i]->reset_secret[2], cnx->path[i]->reset_secret[3]);
|
|
ret = PICOQUIC_TRANSPORT_PROTOCOL_VIOLATION;
|
|
}
|
|
break;
|
|
}
|
|
else {
|
|
DBG_PRINTF("Path %d, Sequence %d, Cnx_id: %02x%02x%02x%02x..., vs %02x%02x%02x%02x...\n",
|
|
i, (int)sequence,
|
|
cnx->path[i]->remote_cnxid.id[0], cnx->path[i]->remote_cnxid.id[1],
|
|
cnx->path[i]->remote_cnxid.id[2], cnx->path[i]->remote_cnxid.id[3],
|
|
cnx_id.id[0], cnx_id.id[1], cnx_id.id[2], cnx_id.id[3]);
|
|
ret = PICOQUIC_TRANSPORT_PROTOCOL_VIOLATION;
|
|
}
|
|
}
|
|
else if (picoquic_compare_connection_id(&cnx_id, &cnx->path[i]->remote_cnxid) == 0) {
|
|
DBG_PRINTF("Path %d, Cnx_id: %02x%02x%02x%02x..., Sequence %d vs. %d\n",
|
|
i, cnx_id.id[0], cnx_id.id[1], cnx_id.id[2], cnx_id.id[3],
|
|
(int)sequence, (int)cnx->path[i]->remote_cnxid_sequence);
|
|
ret = PICOQUIC_TRANSPORT_PROTOCOL_VIOLATION;
|
|
}
|
|
else if (memcmp(secret_bytes, &cnx->path[i]->reset_secret, PICOQUIC_RESET_SECRET_SIZE) == 0) {
|
|
DBG_PRINTF("Path %d, Cnx_id: %02x%02x%02x%02x..., Sequence %d vs. %d, same secret\n",
|
|
i, cnx_id.id[0], cnx_id.id[1], cnx_id.id[2], cnx_id.id[3],
|
|
(int)sequence, (int)cnx->path[i]->remote_cnxid_sequence);
|
|
ret = PICOQUIC_TRANSPORT_PROTOCOL_VIOLATION;
|
|
}
|
|
else {
|
|
if (!cnx->path[i]->path_is_demoted) {
|
|
uint64_t check = ((uint64_t)1) << cnx->path[i]->remote_cnxid_sequence;
|
|
if ((cnxid_mask & check) == 0) {
|
|
nb_cid_received++;
|
|
cnxid_mask |= check;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
while (ret == 0 && is_duplicate == 0 && next_stash != NULL) {
|
|
if (picoquic_compare_connection_id(&cnx_id, &next_stash->cnx_id) == 0)
|
|
{
|
|
if (next_stash->sequence == sequence &&
|
|
memcmp(secret_bytes, next_stash->reset_secret, PICOQUIC_RESET_SECRET_SIZE) == 0) {
|
|
is_duplicate = 1;
|
|
}
|
|
else {
|
|
ret = PICOQUIC_TRANSPORT_PROTOCOL_VIOLATION;
|
|
}
|
|
break;
|
|
}
|
|
else if (next_stash->sequence == sequence) {
|
|
ret = PICOQUIC_TRANSPORT_PROTOCOL_VIOLATION;
|
|
}
|
|
else if (memcmp(secret_bytes, next_stash->reset_secret, PICOQUIC_RESET_SECRET_SIZE) == 0) {
|
|
ret = PICOQUIC_TRANSPORT_PROTOCOL_VIOLATION;
|
|
}
|
|
else {
|
|
nb_cid_received++;
|
|
}
|
|
last_stash = next_stash;
|
|
next_stash = next_stash->next_in_stash;
|
|
}
|
|
|
|
if (ret == 0 && is_duplicate == 0) {
|
|
if (nb_cid_received >= cnx->local_parameters.active_connection_id_limit) {
|
|
ret = PICOQUIC_TRANSPORT_CONNECTION_ID_LIMIT_ERROR;
|
|
}
|
|
else {
|
|
stashed = (picoquic_cnxid_stash_t*)malloc(sizeof(picoquic_cnxid_stash_t));
|
|
|
|
if (stashed == NULL) {
|
|
ret = PICOQUIC_TRANSPORT_INTERNAL_ERROR;
|
|
}
|
|
else {
|
|
(void)picoquic_parse_connection_id(cnxid_bytes, cid_length, &stashed->cnx_id);
|
|
stashed->sequence = sequence;
|
|
memcpy(stashed->reset_secret, secret_bytes, PICOQUIC_RESET_SECRET_SIZE);
|
|
stashed->next_in_stash = NULL;
|
|
|
|
if (last_stash == NULL) {
|
|
cnx->cnxid_stash_first = stashed;
|
|
}
|
|
else {
|
|
last_stash->next_in_stash = stashed;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* the return argument is only used in tests */
|
|
|
|
if (pstashed != NULL) {
|
|
*pstashed = stashed;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
int picoquic_remove_not_before_cid(picoquic_cnx_t* cnx, uint64_t not_before, uint64_t current_time)
|
|
{
|
|
int ret = 0;
|
|
picoquic_cnxid_stash_t * next_stash = cnx->cnxid_stash_first;
|
|
picoquic_cnxid_stash_t * previous_stash = NULL;
|
|
|
|
while (ret == 0 && next_stash != NULL) {
|
|
if (next_stash->sequence < not_before) {
|
|
ret = picoquic_queue_retire_connection_id_frame(cnx, next_stash->sequence);
|
|
if (ret == 0){
|
|
next_stash = next_stash->next_in_stash;
|
|
if (previous_stash == NULL) {
|
|
cnx->cnxid_stash_first = next_stash;
|
|
}
|
|
else {
|
|
previous_stash->next_in_stash = next_stash;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
previous_stash = next_stash;
|
|
next_stash = next_stash->next_in_stash;
|
|
}
|
|
}
|
|
|
|
/* We need to stop transmitting data to the old CID. But we cannot just delete
|
|
* the correspondng paths,because there may be some data in transit. We must
|
|
* also ensure that at least one default path migrates successfully to a
|
|
* valid CID. As long as new CID are available, we can simply replace the
|
|
* old one by a new one. If no CID is available, the old path should be marked
|
|
* as failing, and thus scheduled for deletion after a time-out */
|
|
|
|
for (int i = 0; ret == 0 && i < cnx->nb_paths; i++) {
|
|
if (cnx->path[i]->remote_cnxid_sequence < not_before &&
|
|
cnx->path[i]->remote_cnxid.id_len > 0 &&
|
|
!cnx->path[i]->path_is_demoted) {
|
|
ret = picoquic_renew_connection_id(cnx, i);
|
|
if (ret != 0) {
|
|
DBG_PRINTF("Renew CNXID returns %x\n", ret);
|
|
if (i == 0) {
|
|
ret = PICOQUIC_TRANSPORT_PROTOCOL_VIOLATION;
|
|
}
|
|
else {
|
|
ret = 0;
|
|
picoquic_demote_path(cnx, i, current_time);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* Start using a new connection ID for the existing path
|
|
*/
|
|
int picoquic_renew_path_connection_id(picoquic_cnx_t* cnx, picoquic_path_t* path_x)
|
|
{
|
|
int ret = 0;
|
|
picoquic_cnxid_stash_t * stashed = NULL;
|
|
|
|
if (cnx->remote_parameters.migration_disabled != 0 ||
|
|
cnx->local_parameters.migration_disabled != 0) {
|
|
/* Do not switch cnx_id if migration is disabled */
|
|
ret = PICOQUIC_ERROR_MIGRATION_DISABLED;
|
|
}
|
|
else {
|
|
stashed = picoquic_dequeue_cnxid_stash(cnx);
|
|
|
|
if (stashed == NULL) {
|
|
ret = PICOQUIC_ERROR_CNXID_NOT_AVAILABLE;
|
|
} else {
|
|
/* Count the references to the remote cnxid */
|
|
int nb_cnxid_ref = 0;
|
|
|
|
for (int i = 0; i < cnx->nb_paths; i++) {
|
|
if (cnx->path[i]->remote_cnxid_sequence == path_x->remote_cnxid_sequence) {
|
|
nb_cnxid_ref++;
|
|
}
|
|
}
|
|
|
|
if (nb_cnxid_ref <= 1) {
|
|
/* if this was the last reference, retire the old cnxid */
|
|
if (picoquic_queue_retire_connection_id_frame(cnx, path_x->remote_cnxid_sequence) != 0) {
|
|
DBG_PRINTF("Could not properly retire CID[%" PRIu64 "]", path_x->remote_cnxid_sequence);
|
|
}
|
|
}
|
|
|
|
/* Install the new value */
|
|
path_x->remote_cnxid = stashed->cnx_id;
|
|
path_x->remote_cnxid_sequence = stashed->sequence;
|
|
memcpy(path_x->reset_secret, stashed->reset_secret,
|
|
PICOQUIC_RESET_SECRET_SIZE);
|
|
free(stashed);
|
|
|
|
/* If default path, reset the secret pointer */
|
|
if (path_x == cnx->path[0]) {
|
|
ret = picoquic_register_net_secret(cnx);
|
|
}
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
int picoquic_renew_connection_id(picoquic_cnx_t* cnx, int path_id)
|
|
{
|
|
int ret;
|
|
|
|
if (path_id >= cnx->nb_paths) {
|
|
ret = -1;
|
|
}
|
|
else {
|
|
ret = picoquic_renew_path_connection_id(cnx, cnx->path[path_id]);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* stream data splay management */
|
|
int64_t picoquic_stream_data_node_compare(void* l, void* r)
|
|
{
|
|
/* Offset values are from 0 to 2^62-1, which means we are not worried with rollover */
|
|
return ((picoquic_stream_data_node_t*)l)->offset - ((picoquic_stream_data_node_t*)r)->offset;
|
|
}
|
|
|
|
picosplay_node_t* picoquic_stream_data_node_create(void* value)
|
|
{
|
|
return &((picoquic_stream_data_node_t*)value)->stream_data_node;
|
|
}
|
|
|
|
|
|
void* picoquic_stream_data_node_value(picosplay_node_t* node)
|
|
{
|
|
return (void*)((char*)node - offsetof(struct st_picoquic_stream_data_node_t, stream_data_node));
|
|
}
|
|
|
|
|
|
void picoquic_stream_data_node_delete(void* tree, picosplay_node_t* node)
|
|
{
|
|
picoquic_stream_data_node_t* stream_data = (picoquic_stream_data_node_t*)picoquic_stream_data_node_value(node);
|
|
|
|
if (stream_data->bytes != NULL) {
|
|
free(stream_data->bytes);
|
|
stream_data->bytes = NULL;
|
|
}
|
|
|
|
free(stream_data);
|
|
}
|
|
|
|
/* Stream splay management */
|
|
|
|
static int64_t picoquic_stream_node_compare(void *l, void *r)
|
|
{
|
|
/* STream values are from 0 to 2^62-1, which means we are not worried with rollover */
|
|
return ((picoquic_stream_head_t*)l)->stream_id - ((picoquic_stream_head_t*)r)->stream_id;
|
|
}
|
|
|
|
static picosplay_node_t * picoquic_stream_node_create(void * value)
|
|
{
|
|
return &((picoquic_stream_head_t *)value)->stream_node;
|
|
}
|
|
|
|
|
|
static void * picoquic_stream_node_value(picosplay_node_t * node)
|
|
{
|
|
return (void*)((char*)node - offsetof(struct st_picoquic_stream_head_t, stream_node));
|
|
}
|
|
|
|
void picoquic_clear_stream(picoquic_stream_head_t* stream)
|
|
{
|
|
picoquic_stream_data_node_t* ready = stream->send_queue;
|
|
picoquic_stream_data_node_t* next;
|
|
|
|
while ((next = ready) != NULL) {
|
|
ready = next->next_stream_data;
|
|
|
|
if (next->bytes != NULL) {
|
|
free(next->bytes);
|
|
}
|
|
free(next);
|
|
}
|
|
|
|
picosplay_empty_tree(&stream->stream_data_tree);
|
|
|
|
while (stream->first_sack_item.next_sack != NULL) {
|
|
picoquic_sack_item_t * sack = stream->first_sack_item.next_sack;
|
|
stream->first_sack_item.next_sack = sack->next_sack;
|
|
free(sack);
|
|
}
|
|
}
|
|
|
|
|
|
static void picoquic_stream_node_delete(void * tree, picosplay_node_t * node)
|
|
{
|
|
picoquic_stream_head_t * stream = picoquic_stream_node_value(node);
|
|
|
|
picoquic_clear_stream(stream);
|
|
|
|
free(stream);
|
|
}
|
|
|
|
/* Management of streams */
|
|
|
|
picoquic_stream_head_t * picoquic_stream_from_node(picosplay_node_t * node)
|
|
{
|
|
#ifdef TOO_CAUTIOUS
|
|
return(picoquic_stream_head_t *)((node == NULL)?NULL:picoquic_stream_node_value(node));
|
|
#else
|
|
return (picoquic_stream_head_t *)node;
|
|
#endif
|
|
}
|
|
|
|
picoquic_stream_head_t * picoquic_first_stream(picoquic_cnx_t* cnx)
|
|
{
|
|
#ifdef TOO_CAUTIOUS
|
|
return picoquic_stream_from_node(picosplay_first(&cnx->stream_tree));
|
|
#else
|
|
return (picoquic_stream_head_t *)picosplay_first(&cnx->stream_tree);
|
|
#endif
|
|
}
|
|
|
|
picoquic_stream_head_t * picoquic_last_stream(picoquic_cnx_t* cnx)
|
|
{
|
|
#ifdef TOO_CAUTIOUS
|
|
return picoquic_stream_from_node(picosplay_last(&cnx->stream_tree));
|
|
#else
|
|
return (picoquic_stream_head_t *)picosplay_last(&cnx->stream_tree);
|
|
#endif
|
|
}
|
|
|
|
void picoquic_insert_output_stream(picoquic_cnx_t* cnx, picoquic_stream_head_t * stream)
|
|
{
|
|
if (stream->is_output_stream == 0) {
|
|
if (stream->stream_id == cnx->high_priority_stream_id) {
|
|
/* insert in front */
|
|
stream->previous_output_stream = NULL;
|
|
stream->next_output_stream = cnx->first_output_stream;
|
|
if (cnx->first_output_stream != NULL) {
|
|
cnx->first_output_stream->previous_output_stream = stream;
|
|
}
|
|
cnx->first_output_stream = stream;
|
|
} else {
|
|
stream->previous_output_stream = cnx->last_output_stream;
|
|
stream->next_output_stream = NULL;
|
|
if (cnx->last_output_stream == NULL) {
|
|
cnx->first_output_stream = stream;
|
|
cnx->last_output_stream = stream;
|
|
}
|
|
else {
|
|
cnx->last_output_stream->next_output_stream = stream;
|
|
cnx->last_output_stream = stream;
|
|
}
|
|
}
|
|
stream->is_output_stream = 1;
|
|
}
|
|
}
|
|
|
|
void picoquic_remove_output_stream(picoquic_cnx_t* cnx, picoquic_stream_head_t * stream, picoquic_stream_head_t * previous_stream)
|
|
{
|
|
if (stream->is_output_stream) {
|
|
stream->is_output_stream = 0;
|
|
|
|
if (stream->previous_output_stream == NULL) {
|
|
cnx->first_output_stream = stream->next_output_stream;
|
|
}
|
|
else {
|
|
stream->previous_output_stream->next_output_stream = stream->next_output_stream;
|
|
}
|
|
|
|
if (stream->next_output_stream == NULL) {
|
|
cnx->last_output_stream = stream->previous_output_stream;
|
|
}
|
|
else {
|
|
stream->next_output_stream->previous_output_stream = stream->previous_output_stream;
|
|
}
|
|
}
|
|
}
|
|
|
|
picoquic_stream_head_t * picoquic_next_stream(picoquic_stream_head_t * stream)
|
|
{
|
|
return (picoquic_stream_head_t *)picosplay_next((picosplay_node_t *)stream);
|
|
}
|
|
|
|
picoquic_stream_head_t* picoquic_find_stream(picoquic_cnx_t* cnx, uint64_t stream_id)
|
|
{
|
|
picoquic_stream_head_t target;
|
|
target.stream_id = stream_id;
|
|
|
|
return (picoquic_stream_head_t *)picosplay_find(&cnx->stream_tree, (void*)&target);
|
|
}
|
|
|
|
void picoquic_add_output_streams(picoquic_cnx_t* cnx, uint64_t old_limit, uint64_t new_limit, unsigned int is_bidir)
|
|
{
|
|
uint64_t old_rank = STREAM_RANK_FROM_ID(old_limit);
|
|
uint64_t first_new_id = STREAM_ID_FROM_RANK(old_rank + 1ull, !cnx->client_mode, !is_bidir);
|
|
picoquic_stream_head_t* stream = picoquic_find_stream(cnx, first_new_id );
|
|
|
|
while (stream) {
|
|
if (stream->stream_id > old_limit) {
|
|
if (stream->stream_id > new_limit) {
|
|
break;
|
|
}
|
|
if (IS_LOCAL_STREAM_ID(stream->stream_id, cnx->client_mode) && IS_BIDIR_STREAM_ID(stream->stream_id) == is_bidir) {
|
|
picoquic_insert_output_stream(cnx, stream);
|
|
}
|
|
}
|
|
stream = picoquic_next_stream(stream);
|
|
}
|
|
}
|
|
|
|
picoquic_stream_head_t* picoquic_create_stream(picoquic_cnx_t* cnx, uint64_t stream_id)
|
|
{
|
|
picoquic_stream_head_t* stream = (picoquic_stream_head_t*)malloc(sizeof(picoquic_stream_head_t));
|
|
if (stream != NULL) {
|
|
int is_output_stream = 0;
|
|
memset(stream, 0, sizeof(picoquic_stream_head_t));
|
|
stream->stream_id = stream_id;
|
|
|
|
if (IS_LOCAL_STREAM_ID(stream_id, cnx->client_mode)) {
|
|
if (IS_BIDIR_STREAM_ID(stream_id)) {
|
|
stream->maxdata_local = cnx->local_parameters.initial_max_stream_data_bidi_local;
|
|
stream->maxdata_remote = cnx->remote_parameters.initial_max_stream_data_bidi_remote;
|
|
is_output_stream = stream->stream_id <= cnx->max_stream_id_bidir_remote;
|
|
|
|
}
|
|
else {
|
|
stream->maxdata_local = 0;
|
|
stream->maxdata_remote = cnx->remote_parameters.initial_max_stream_data_uni;
|
|
is_output_stream = stream->stream_id <= cnx->max_stream_id_unidir_remote;
|
|
}
|
|
}
|
|
else {
|
|
if (IS_BIDIR_STREAM_ID(stream_id)) {
|
|
stream->maxdata_local = cnx->local_parameters.initial_max_stream_data_bidi_remote;
|
|
stream->maxdata_remote = cnx->remote_parameters.initial_max_stream_data_bidi_local;
|
|
is_output_stream = 1;
|
|
}
|
|
else {
|
|
stream->maxdata_local = cnx->local_parameters.initial_max_stream_data_uni;
|
|
stream->maxdata_remote = 0;
|
|
is_output_stream = 0;
|
|
}
|
|
}
|
|
|
|
picosplay_init_tree(&stream->stream_data_tree, picoquic_stream_data_node_compare, picoquic_stream_data_node_create, picoquic_stream_data_node_delete, picoquic_stream_data_node_value);
|
|
|
|
picosplay_insert(&cnx->stream_tree, stream);
|
|
if (is_output_stream) {
|
|
picoquic_insert_output_stream(cnx, stream);
|
|
}
|
|
else {
|
|
picoquic_remove_output_stream(cnx, stream, NULL);
|
|
picoquic_delete_stream_if_closed(cnx, stream);
|
|
}
|
|
|
|
if (stream_id >= cnx->next_stream_id[STREAM_TYPE_FROM_ID(stream_id)]) {
|
|
cnx->next_stream_id[STREAM_TYPE_FROM_ID(stream_id)] = NEXT_STREAM_ID_FOR_TYPE(stream_id);
|
|
}
|
|
}
|
|
|
|
return stream;
|
|
}
|
|
|
|
void picoquic_delete_stream(picoquic_cnx_t * cnx, picoquic_stream_head_t* stream)
|
|
{
|
|
picosplay_delete(&cnx->stream_tree, stream);
|
|
}
|
|
|
|
int picoquic_mark_direct_receive_stream(picoquic_cnx_t* cnx, uint64_t stream_id, picoquic_stream_direct_receive_fn direct_receive_fn, void* direct_receive_ctx)
|
|
{
|
|
int ret = 0;
|
|
picoquic_stream_head_t* stream = picoquic_find_stream(cnx, stream_id);
|
|
picoquic_stream_data_node_t* data;
|
|
|
|
if (stream == NULL) {
|
|
ret = PICOQUIC_ERROR_INVALID_STREAM_ID;
|
|
}
|
|
else if (!IS_BIDIR_STREAM_ID(stream_id) && IS_LOCAL_STREAM_ID(stream_id, cnx->client_mode)) {
|
|
ret = PICOQUIC_ERROR_INVALID_STREAM_ID;
|
|
}
|
|
else if (direct_receive_fn == NULL) {
|
|
/* This is illegal! */
|
|
ret = PICOQUIC_ERROR_NO_CALLBACK_PROVIDED;
|
|
}
|
|
else {
|
|
stream->direct_receive_fn = direct_receive_fn;
|
|
stream->direct_receive_ctx = direct_receive_ctx;
|
|
/* If there is pending data, pass it. */
|
|
while ((data = (picoquic_stream_data_node_t*)picosplay_first(&stream->stream_data_tree)) != NULL) {
|
|
size_t length = data->length;
|
|
uint64_t offset = data->offset;
|
|
uint8_t* bytes = data->bytes;
|
|
|
|
if (offset < stream->consumed_offset) {
|
|
if (offset + length < stream->consumed_offset) {
|
|
length = 0;
|
|
}
|
|
else {
|
|
size_t delta_offset = (size_t)(stream->consumed_offset - offset);
|
|
length -= delta_offset;
|
|
offset += delta_offset;
|
|
}
|
|
}
|
|
|
|
if (length > 0) {
|
|
ret = direct_receive_fn(cnx, stream_id, 0, bytes, offset, length, direct_receive_ctx);
|
|
}
|
|
|
|
if (ret == 0) {
|
|
picosplay_delete_hint(&stream->stream_data_tree, &data->stream_data_node);
|
|
}
|
|
else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* If there is a fin offset, pass it. */
|
|
if (ret == 0 && stream->fin_received && !stream->fin_signalled) {
|
|
uint8_t fin_bytes[8];
|
|
ret = direct_receive_fn(cnx, stream_id, 1, fin_bytes, stream->fin_offset, 0, direct_receive_ctx);
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
/* Management of local CID.
|
|
* Local CID are created and registered on demand.
|
|
*/
|
|
|
|
picoquic_local_cnxid_t* picoquic_create_local_cnxid(picoquic_cnx_t* cnx, picoquic_connection_id_t* suggested_value)
|
|
{
|
|
picoquic_local_cnxid_t* l_cid = NULL;
|
|
int is_unique = 0;
|
|
|
|
l_cid = (picoquic_local_cnxid_t*)malloc(sizeof(picoquic_local_cnxid_t));
|
|
|
|
if (l_cid != NULL) {
|
|
memset(l_cid, 0, sizeof(picoquic_local_cnxid_t));
|
|
if (cnx->quic->local_cnxid_length == 0) {
|
|
is_unique = 1;
|
|
}
|
|
else {
|
|
for (int i = 0; i < 32; i++) {
|
|
if (i == 0 && suggested_value != NULL) {
|
|
l_cid->cnx_id = *suggested_value;
|
|
}
|
|
else {
|
|
picoquic_create_random_cnx_id(cnx->quic, &l_cid->cnx_id, cnx->quic->local_cnxid_length);
|
|
|
|
if (cnx->quic->cnx_id_callback_fn) {
|
|
cnx->quic->cnx_id_callback_fn(cnx->quic, l_cid->cnx_id, cnx->initial_cnxid,
|
|
cnx->quic->cnx_id_callback_ctx, &l_cid->cnx_id);
|
|
}
|
|
}
|
|
|
|
if (picoquic_cnx_by_id(cnx->quic, l_cid->cnx_id) == NULL) {
|
|
is_unique = 1;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (is_unique) {
|
|
picoquic_local_cnxid_t* previous = NULL;
|
|
picoquic_local_cnxid_t* next = cnx->local_cnxid_first;
|
|
|
|
while (next != NULL) {
|
|
previous = next;
|
|
next = next->next;
|
|
}
|
|
|
|
if (previous == NULL) {
|
|
cnx->local_cnxid_first = l_cid;
|
|
}
|
|
else {
|
|
previous->next = l_cid;
|
|
}
|
|
|
|
l_cid->sequence = cnx->local_cnxid_sequence_next++;
|
|
cnx->nb_local_cnxid++;
|
|
|
|
if (cnx->quic->local_cnxid_length > 0) {
|
|
picoquic_register_cnx_id(cnx->quic, cnx, l_cid);
|
|
}
|
|
}
|
|
else {
|
|
free(l_cid);
|
|
l_cid = NULL;
|
|
}
|
|
}
|
|
|
|
return l_cid;
|
|
}
|
|
|
|
void picoquic_delete_local_cnxid(picoquic_cnx_t* cnx, picoquic_local_cnxid_t* l_cid)
|
|
{
|
|
picoquic_local_cnxid_t* previous = NULL;
|
|
picoquic_local_cnxid_t* next = cnx->local_cnxid_first;
|
|
|
|
|
|
/* Set l_cid references to NULL in path contexts */
|
|
for (int i = 0; i < cnx->nb_paths; i++) {
|
|
if (cnx->path[i]->p_local_cnxid == l_cid) {
|
|
cnx->path[i]->p_local_cnxid = NULL;
|
|
}
|
|
}
|
|
|
|
/* Remove from list */
|
|
while (next != NULL) {
|
|
if (next == l_cid) {
|
|
if (previous == NULL) {
|
|
cnx->local_cnxid_first = next->next;
|
|
}
|
|
else {
|
|
previous->next = next->next;
|
|
}
|
|
cnx->nb_local_cnxid--;
|
|
break;
|
|
}
|
|
else {
|
|
previous = next;
|
|
next = next->next;
|
|
}
|
|
}
|
|
|
|
if (l_cid->cnx_id.id_len > 0) {
|
|
/* Remove the registration in hash tables */
|
|
if (l_cid->first_cnx_id != NULL) {
|
|
picohash_item* item;
|
|
picoquic_cnx_id_key_t* cnx_id_key = l_cid->first_cnx_id;
|
|
|
|
item = picohash_retrieve(cnx->quic->table_cnx_by_id, cnx_id_key);
|
|
if (item != NULL) {
|
|
picohash_delete_item(cnx->quic->table_cnx_by_id, item, 1);
|
|
}
|
|
|
|
l_cid->first_cnx_id = NULL;
|
|
}
|
|
}
|
|
|
|
/* Delete and done */
|
|
free(l_cid);
|
|
}
|
|
|
|
void picoquic_retire_local_cnxid(picoquic_cnx_t* cnx, uint64_t sequence)
|
|
{
|
|
picoquic_local_cnxid_t* local_cnxid = cnx->local_cnxid_first;
|
|
|
|
while (local_cnxid != NULL) {
|
|
if (local_cnxid->sequence == sequence) {
|
|
break;
|
|
}
|
|
else {
|
|
local_cnxid = local_cnxid->next;
|
|
}
|
|
}
|
|
|
|
if (local_cnxid != NULL) {
|
|
picoquic_delete_local_cnxid(cnx, local_cnxid);
|
|
}
|
|
}
|
|
|
|
picoquic_local_cnxid_t* picoquic_find_local_cnxid(picoquic_cnx_t* cnx, picoquic_connection_id_t* cnxid)
|
|
{
|
|
picoquic_local_cnxid_t* local_cnxid = cnx->local_cnxid_first;
|
|
|
|
while (local_cnxid != NULL) {
|
|
if (picoquic_compare_connection_id(&local_cnxid->cnx_id, cnxid) == 0) {
|
|
break;
|
|
}
|
|
else {
|
|
local_cnxid = local_cnxid->next;
|
|
}
|
|
}
|
|
|
|
return local_cnxid;
|
|
}
|
|
|
|
/* Connection management
|
|
*/
|
|
|
|
picoquic_cnx_t* picoquic_create_cnx(picoquic_quic_t* quic,
|
|
picoquic_connection_id_t initial_cnx_id, picoquic_connection_id_t remote_cnx_id,
|
|
const struct sockaddr* addr_to, uint64_t start_time, uint32_t preferred_version,
|
|
char const* sni, char const* alpn, char client_mode)
|
|
{
|
|
picoquic_cnx_t* cnx = (picoquic_cnx_t*)malloc(sizeof(picoquic_cnx_t));
|
|
|
|
if (cnx != NULL) {
|
|
int ret;
|
|
picoquic_local_cnxid_t* cnxid0;
|
|
|
|
memset(cnx, 0, sizeof(picoquic_cnx_t));
|
|
cnx->start_time = start_time;
|
|
cnx->client_mode = client_mode;
|
|
if (client_mode) {
|
|
if (picoquic_is_connection_id_null(&initial_cnx_id)) {
|
|
picoquic_create_random_cnx_id(quic, &initial_cnx_id, 8);
|
|
}
|
|
}
|
|
cnx->initial_cnxid = initial_cnx_id;
|
|
cnx->quic = quic;
|
|
/* Create the connection ID number 0 */
|
|
cnxid0 = picoquic_create_local_cnxid(cnx, NULL);
|
|
|
|
/* Should return 0, since this is the first path */
|
|
ret = picoquic_create_path(cnx, start_time, NULL, addr_to);
|
|
|
|
if (ret != 0 || cnxid0 == NULL) {
|
|
free(cnx);
|
|
cnx = NULL;
|
|
} else {
|
|
cnx->next_wake_time = start_time;
|
|
SET_LAST_WAKE(quic, PICOQUIC_QUICCTX);
|
|
picoquic_insert_cnx_in_list(quic, cnx);
|
|
picoquic_insert_cnx_by_wake_time(quic, cnx);
|
|
/* Do not require verification for default path */
|
|
cnx->path[0]->p_local_cnxid = cnxid0;
|
|
cnx->path[0]->challenge_verified = 1;
|
|
|
|
cnx->high_priority_stream_id = (uint64_t)((int64_t)-1);
|
|
for (int i = 0; i < 4; i++) {
|
|
cnx->next_stream_id[i] = i;
|
|
}
|
|
picoquic_register_path(cnx, cnx->path[0]);
|
|
}
|
|
}
|
|
|
|
if (cnx != NULL) {
|
|
if (quic->default_tp == NULL) {
|
|
picoquic_init_transport_parameters(&cnx->local_parameters, cnx->client_mode);
|
|
} else {
|
|
memcpy(&cnx->local_parameters, quic->default_tp, sizeof(picoquic_tp_t));
|
|
/* If the default parameters include preferred address, document it */
|
|
if (cnx->local_parameters.prefered_address.is_defined) {
|
|
/* Create an additional CID */
|
|
picoquic_local_cnxid_t* cnxid1 = picoquic_create_local_cnxid(cnx, NULL);
|
|
if (cnxid1 != NULL){
|
|
/* copy the connection ID into the local parameter */
|
|
cnx->local_parameters.prefered_address.connection_id = cnxid1->cnx_id;
|
|
/* Create the reset secret */
|
|
(void)picoquic_create_cnxid_reset_secret(cnx->quic, &cnxid1->cnx_id,
|
|
cnx->local_parameters.prefered_address.statelessResetToken);
|
|
}
|
|
}
|
|
}
|
|
/* If local connection ID size is null, don't allow migration */
|
|
if (quic->local_cnxid_length == 0) {
|
|
cnx->local_parameters.migration_disabled = 1;
|
|
}
|
|
|
|
if (cnx->quic->mtu_max > 0)
|
|
{
|
|
cnx->local_parameters.max_packet_size = cnx->quic->mtu_max;
|
|
}
|
|
|
|
/* Initialize local flow control variables to advertised values */
|
|
cnx->maxdata_local = ((uint64_t)cnx->local_parameters.initial_max_data);
|
|
cnx->max_stream_id_bidir_local = cnx->local_parameters.initial_max_stream_id_bidir;
|
|
cnx->max_stream_id_bidir_local_computed = STREAM_TYPE_FROM_ID(cnx->local_parameters.initial_max_stream_id_bidir);
|
|
cnx->max_stream_id_unidir_local = cnx->local_parameters.initial_max_stream_id_unidir;
|
|
cnx->max_stream_id_unidir_local_computed = STREAM_TYPE_FROM_ID(cnx->local_parameters.initial_max_stream_id_unidir);
|
|
|
|
/* Initialize padding policy to default for context */
|
|
cnx->padding_multiple = quic->padding_multiple_default;
|
|
cnx->padding_minsize = quic->padding_minsize_default;
|
|
|
|
/* Initialize spin policy, ensure that at least 1/8th of connections do not spin */
|
|
cnx->spin_policy = quic->default_spin_policy;
|
|
if (cnx->spin_policy == picoquic_spinbit_basic) {
|
|
uint8_t rand256 = (uint8_t)picoquic_public_random_64();
|
|
if (rand256 < PICOQUIC_SPIN_RESERVE_MOD_256) {
|
|
cnx->spin_policy = picoquic_spinbit_null;
|
|
}
|
|
}
|
|
else if (cnx->spin_policy == picoquic_spinbit_on) {
|
|
/* Option used in test to avoid randomizing spin bit on/off */
|
|
cnx->spin_policy = picoquic_spinbit_basic;
|
|
}
|
|
|
|
if (sni != NULL) {
|
|
cnx->sni = picoquic_string_duplicate(sni);
|
|
}
|
|
|
|
if (alpn != NULL) {
|
|
cnx->alpn = picoquic_string_duplicate(alpn);
|
|
}
|
|
|
|
cnx->callback_fn = quic->default_callback_fn;
|
|
cnx->callback_ctx = quic->default_callback_ctx;
|
|
cnx->congestion_alg = quic->default_congestion_alg;
|
|
|
|
/* Initialize key rotation interval to default value */
|
|
cnx->crypto_epoch_length_max = quic->crypto_epoch_length_max;
|
|
|
|
/* Perform different initializations for clients and servers */
|
|
if (cnx->client_mode) {
|
|
if (preferred_version == 0) {
|
|
cnx->proposed_version = picoquic_supported_versions[0].version;
|
|
cnx->version_index = 0;
|
|
} else {
|
|
cnx->version_index = picoquic_get_version_index(preferred_version);
|
|
if (cnx->version_index < 0) {
|
|
cnx->version_index = PICOQUIC_INTEROP_VERSION_INDEX;
|
|
if ((preferred_version & 0x0A0A0A0A) == 0x0A0A0A0A) {
|
|
/* This is a hack, to allow greasing the cnx ID */
|
|
cnx->proposed_version = preferred_version;
|
|
|
|
} else {
|
|
cnx->proposed_version = picoquic_supported_versions[PICOQUIC_INTEROP_VERSION_INDEX].version;
|
|
}
|
|
} else {
|
|
cnx->proposed_version = preferred_version;
|
|
}
|
|
}
|
|
|
|
cnx->cnx_state = picoquic_state_client_init;
|
|
|
|
if (!quic->is_cert_store_not_empty || sni == NULL) {
|
|
/* This is a hack. The open SSL certifier crashes if no name is specified,
|
|
* and always fails if no certificate is stored, so we just use a NULL verifier */
|
|
picoquic_log_app_message(cnx, "%s -- certificate will not be verified.\n",
|
|
(sni == NULL) ? "No server name specified" : "No root crt list specified");
|
|
|
|
picoquic_set_null_verifier(quic);
|
|
}
|
|
} else {
|
|
cnx->is_half_open = 1;
|
|
cnx->quic->current_number_half_open += 1;
|
|
if (cnx->quic->current_number_half_open > cnx->quic->max_half_open_before_retry) {
|
|
cnx->quic->check_token = 1;
|
|
}
|
|
for (int epoch = 0; epoch < PICOQUIC_NUMBER_OF_EPOCHS; epoch++) {
|
|
cnx->tls_stream[epoch].send_queue = NULL;
|
|
}
|
|
cnx->cnx_state = picoquic_state_server_init;
|
|
cnx->initial_cnxid = initial_cnx_id;
|
|
cnx->path[0]->remote_cnxid = remote_cnx_id;
|
|
|
|
cnx->version_index = picoquic_get_version_index(preferred_version);
|
|
if (cnx->version_index < 0) {
|
|
/* TODO: this is an internal error condition, should not happen */
|
|
cnx->version_index = 0;
|
|
cnx->proposed_version = picoquic_supported_versions[0].version;
|
|
} else {
|
|
cnx->proposed_version = preferred_version;
|
|
}
|
|
}
|
|
|
|
for (picoquic_packet_context_enum pc = 0;
|
|
pc < picoquic_nb_packet_context; pc++) {
|
|
cnx->pkt_ctx[pc].first_sack_item.start_of_sack_range = (uint64_t)((int64_t)-1);
|
|
cnx->pkt_ctx[pc].first_sack_item.end_of_sack_range = 0;
|
|
cnx->pkt_ctx[pc].first_sack_item.next_sack = NULL;
|
|
cnx->pkt_ctx[pc].highest_ack_sent = 0;
|
|
cnx->pkt_ctx[pc].highest_ack_sent_time = start_time;
|
|
cnx->pkt_ctx[pc].time_stamp_largest_received = (uint64_t)((int64_t)-1);
|
|
if (quic->random_initial) {
|
|
cnx->pkt_ctx[pc].send_sequence = picoquic_crypto_uniform_random(quic, PICOQUIC_PN_RANDOM_RANGE) +
|
|
PICOQUIC_PN_RANDOM_MIN;
|
|
}
|
|
else {
|
|
cnx->pkt_ctx[pc].send_sequence = 0;
|
|
}
|
|
cnx->pkt_ctx[pc].nb_retransmit = 0;
|
|
cnx->pkt_ctx[pc].latest_retransmit_time = 0;
|
|
cnx->pkt_ctx[pc].retransmit_newest = NULL;
|
|
cnx->pkt_ctx[pc].retransmit_oldest = NULL;
|
|
cnx->pkt_ctx[pc].highest_acknowledged = cnx->pkt_ctx[pc].send_sequence - 1;
|
|
cnx->pkt_ctx[pc].latest_time_acknowledged = start_time;
|
|
cnx->pkt_ctx[pc].highest_acknowledged_time = start_time;
|
|
cnx->pkt_ctx[pc].ack_needed = 0;
|
|
}
|
|
|
|
cnx->latest_progress_time = start_time;
|
|
|
|
for (int epoch = 0; epoch < PICOQUIC_NUMBER_OF_EPOCHS; epoch++) {
|
|
cnx->tls_stream[epoch].stream_id = 0;
|
|
cnx->tls_stream[epoch].consumed_offset = 0;
|
|
cnx->tls_stream[epoch].fin_offset = 0;
|
|
cnx->tls_stream[epoch].stream_node.left = NULL;
|
|
cnx->tls_stream[epoch].stream_node.parent = NULL;
|
|
cnx->tls_stream[epoch].stream_node.right = NULL;
|
|
cnx->tls_stream[epoch].sent_offset = 0;
|
|
cnx->tls_stream[epoch].local_error = 0;
|
|
cnx->tls_stream[epoch].remote_error = 0;
|
|
cnx->tls_stream[epoch].maxdata_local = (uint64_t)((int64_t)-1);
|
|
cnx->tls_stream[epoch].maxdata_remote = (uint64_t)((int64_t)-1);
|
|
|
|
picosplay_init_tree(&cnx->tls_stream[epoch].stream_data_tree, picoquic_stream_data_node_compare, picoquic_stream_data_node_create, picoquic_stream_data_node_delete, picoquic_stream_data_node_value);
|
|
|
|
/* No need to reset the state flags, as they are not used for the crypto stream */
|
|
}
|
|
|
|
cnx->ack_frequency_sequence_local = (uint64_t)((int64_t)-1);
|
|
cnx->ack_gap_local = 2;
|
|
cnx->ack_frequency_delay_local = PICOQUIC_ACK_DELAY_MAX_DEFAULT;
|
|
cnx->ack_frequency_sequence_remote = (uint64_t)((int64_t)-1);
|
|
cnx->ack_gap_remote = 2;
|
|
cnx->ack_delay_remote = PICOQUIC_ACK_DELAY_MAX_DEFAULT;
|
|
|
|
picosplay_init_tree(&cnx->stream_tree, picoquic_stream_node_compare, picoquic_stream_node_create, picoquic_stream_node_delete, picoquic_stream_node_value);
|
|
|
|
cnx->congestion_alg = cnx->quic->default_congestion_alg;
|
|
if (cnx->congestion_alg != NULL) {
|
|
cnx->congestion_alg->alg_init(cnx->path[0], start_time);
|
|
}
|
|
}
|
|
|
|
/* Only initialize TLS after all parameters have been set */
|
|
if (cnx != NULL && picoquic_tlscontext_create(quic, cnx, start_time) != 0) {
|
|
/* Cannot just do partial creation! */
|
|
picoquic_delete_cnx(cnx);
|
|
cnx = NULL;
|
|
}
|
|
|
|
if (cnx != NULL) {
|
|
if (picoquic_setup_initial_traffic_keys(cnx)) {
|
|
/* Cannot initialize aead for initial packets */
|
|
picoquic_delete_cnx(cnx);
|
|
cnx = NULL;
|
|
}
|
|
}
|
|
|
|
if (cnx != NULL && !client_mode && quic->local_cnxid_length > 0) {
|
|
if (picoquic_register_net_icid(cnx) != 0) {
|
|
DBG_PRINTF("%s", "Could not register the ICID in table.\n");
|
|
picoquic_delete_cnx(cnx);
|
|
cnx = NULL;
|
|
}
|
|
}
|
|
|
|
if (cnx != NULL) {
|
|
picoquic_log_new_connection(cnx);
|
|
}
|
|
|
|
return cnx;
|
|
}
|
|
|
|
picoquic_cnx_t* picoquic_create_client_cnx(picoquic_quic_t* quic,
|
|
struct sockaddr* addr, uint64_t start_time, uint32_t preferred_version,
|
|
char const* sni, char const* alpn, picoquic_stream_data_cb_fn callback_fn, void* callback_ctx)
|
|
{
|
|
picoquic_cnx_t* cnx = picoquic_create_cnx(quic, picoquic_null_connection_id, picoquic_null_connection_id, addr, start_time, preferred_version, sni, alpn, 1);
|
|
|
|
if (cnx != NULL) {
|
|
int ret;
|
|
|
|
if (callback_fn != NULL)
|
|
cnx->callback_fn = callback_fn;
|
|
if (callback_ctx != NULL)
|
|
cnx->callback_ctx = callback_ctx;
|
|
ret = picoquic_start_client_cnx(cnx);
|
|
if (ret != 0) {
|
|
/* Cannot just do partial initialization! */
|
|
picoquic_delete_cnx(cnx);
|
|
cnx = NULL;
|
|
}
|
|
}
|
|
|
|
return cnx;
|
|
}
|
|
|
|
int picoquic_start_client_cnx(picoquic_cnx_t * cnx)
|
|
{
|
|
int ret = picoquic_initialize_tls_stream(cnx, picoquic_get_quic_time(cnx->quic));
|
|
/* A remote session ticket may have been loaded as part of initializing TLS,
|
|
* and remote parameters may have been initialized to the initial value
|
|
* of the previous session. Apply these new parameters. */
|
|
cnx->maxdata_remote = cnx->remote_parameters.initial_max_data;
|
|
cnx->max_stream_id_bidir_remote = cnx->remote_parameters.initial_max_stream_id_bidir;
|
|
cnx->max_stream_id_unidir_remote = cnx->remote_parameters.initial_max_stream_id_unidir;
|
|
|
|
picoquic_reinsert_by_wake_time(cnx->quic, cnx, picoquic_get_quic_time(cnx->quic));
|
|
|
|
return ret;
|
|
}
|
|
|
|
void picoquic_set_transport_parameters(picoquic_cnx_t * cnx, picoquic_tp_t const * tp)
|
|
{
|
|
cnx->local_parameters = *tp;
|
|
|
|
if (cnx->quic->mtu_max > 0)
|
|
{
|
|
cnx->local_parameters.max_packet_size = cnx->quic->mtu_max;
|
|
}
|
|
|
|
/* Initialize local flow control variables to advertised values */
|
|
|
|
cnx->maxdata_local = ((uint64_t)cnx->local_parameters.initial_max_data);
|
|
cnx->max_stream_id_bidir_local = cnx->local_parameters.initial_max_stream_id_bidir;
|
|
cnx->max_stream_id_unidir_local = cnx->local_parameters.initial_max_stream_id_unidir;
|
|
}
|
|
|
|
picoquic_tp_t const* picoquic_get_transport_parameters(picoquic_cnx_t* cnx, int get_local)
|
|
{
|
|
return(get_local) ? &cnx->local_parameters : &cnx->remote_parameters;
|
|
}
|
|
|
|
void picoquic_get_peer_addr(picoquic_cnx_t* cnx, struct sockaddr** addr)
|
|
{
|
|
*addr = (struct sockaddr*)&cnx->path[0]->peer_addr;
|
|
}
|
|
|
|
void picoquic_get_local_addr(picoquic_cnx_t* cnx, struct sockaddr** addr)
|
|
{
|
|
*addr = (struct sockaddr*)&cnx->path[0]->local_addr;
|
|
}
|
|
|
|
unsigned long picoquic_get_local_if_index(picoquic_cnx_t* cnx)
|
|
{
|
|
return cnx->path[0]->if_index_dest;
|
|
}
|
|
|
|
picoquic_connection_id_t picoquic_get_local_cnxid(picoquic_cnx_t* cnx)
|
|
{
|
|
return cnx->path[0]->p_local_cnxid->cnx_id;
|
|
}
|
|
|
|
picoquic_connection_id_t picoquic_get_remote_cnxid(picoquic_cnx_t* cnx)
|
|
{
|
|
return cnx->path[0]->remote_cnxid;
|
|
}
|
|
|
|
picoquic_connection_id_t picoquic_get_initial_cnxid(picoquic_cnx_t* cnx)
|
|
{
|
|
return cnx->initial_cnxid;
|
|
}
|
|
|
|
picoquic_connection_id_t picoquic_get_client_cnxid(picoquic_cnx_t* cnx)
|
|
{
|
|
return (cnx->client_mode)?cnx->path[0]->p_local_cnxid->cnx_id : cnx->path[0]->remote_cnxid;
|
|
}
|
|
|
|
picoquic_connection_id_t picoquic_get_server_cnxid(picoquic_cnx_t* cnx)
|
|
{
|
|
return (cnx->client_mode) ? cnx->path[0]->remote_cnxid : cnx->path[0]->p_local_cnxid->cnx_id;
|
|
}
|
|
|
|
picoquic_connection_id_t picoquic_get_logging_cnxid(picoquic_cnx_t* cnx)
|
|
{
|
|
return cnx->initial_cnxid;
|
|
}
|
|
|
|
uint64_t picoquic_get_cnx_start_time(picoquic_cnx_t* cnx)
|
|
{
|
|
return cnx->start_time;
|
|
}
|
|
|
|
picoquic_state_enum picoquic_get_cnx_state(picoquic_cnx_t* cnx)
|
|
{
|
|
return cnx->cnx_state;
|
|
}
|
|
|
|
uint64_t picoquic_is_0rtt_available(picoquic_cnx_t* cnx)
|
|
{
|
|
return (cnx->crypto_context[picoquic_epoch_0rtt].aead_encrypt == NULL) ? 0 : 1;
|
|
}
|
|
|
|
void picoquic_cnx_set_padding_policy(picoquic_cnx_t * cnx, uint32_t padding_multiple, uint32_t padding_minsize)
|
|
{
|
|
cnx->padding_multiple = padding_multiple;
|
|
cnx->padding_minsize = padding_minsize;
|
|
}
|
|
|
|
void picoquic_cnx_get_padding_policy(picoquic_cnx_t * cnx, uint32_t * padding_multiple, uint32_t * padding_minsize)
|
|
{
|
|
*padding_multiple = cnx->padding_multiple;
|
|
*padding_minsize = cnx->padding_minsize;
|
|
}
|
|
|
|
void picoquic_cnx_set_spinbit_policy(picoquic_cnx_t * cnx, picoquic_spinbit_version_enum spinbit_policy)
|
|
{
|
|
cnx->spin_policy = spinbit_policy;
|
|
}
|
|
|
|
void picoquic_cnx_set_pmtud_required(picoquic_cnx_t* cnx, int is_pmtud_required)
|
|
{
|
|
cnx->is_pmtud_required = is_pmtud_required;
|
|
}
|
|
|
|
/*
|
|
* Provide clock time
|
|
*/
|
|
uint64_t picoquic_current_time()
|
|
{
|
|
uint64_t now;
|
|
#ifdef _WINDOWS
|
|
FILETIME ft;
|
|
/*
|
|
* The GetSystemTimeAsFileTime API returns the number
|
|
* of 100-nanosecond intervals since January 1, 1601 (UTC),
|
|
* in FILETIME format.
|
|
*/
|
|
GetSystemTimePreciseAsFileTime(&ft);
|
|
|
|
/*
|
|
* Convert to plain 64 bit format, without making
|
|
* assumptions about the FILETIME structure alignment.
|
|
*/
|
|
now = ft.dwHighDateTime;
|
|
now <<= 32;
|
|
now |= ft.dwLowDateTime;
|
|
/*
|
|
* Convert units from 100ns to 1us
|
|
*/
|
|
now /= 10;
|
|
/*
|
|
* Account for microseconds elapsed between 1601 and 1970.
|
|
*/
|
|
now -= 11644473600000000ULL;
|
|
#else
|
|
struct timeval tv;
|
|
(void)gettimeofday(&tv, NULL);
|
|
now = (tv.tv_sec * 1000000ull) + tv.tv_usec;
|
|
#endif
|
|
return now;
|
|
}
|
|
|
|
/*
|
|
* Get the same time simulation as used for TLS
|
|
*/
|
|
|
|
uint64_t picoquic_get_quic_time(picoquic_quic_t* quic)
|
|
{
|
|
uint64_t now;
|
|
if (quic->p_simulated_time == NULL) {
|
|
now = picoquic_current_time();
|
|
}
|
|
else {
|
|
now = *quic->p_simulated_time;
|
|
}
|
|
|
|
return now;
|
|
}
|
|
|
|
void picoquic_connection_id_callback(picoquic_quic_t * quic, picoquic_connection_id_t cnx_id_local, picoquic_connection_id_t cnx_id_remote, void * cnx_id_cb_data, picoquic_connection_id_t * cnx_id_returned)
|
|
{
|
|
picoquic_connection_id_callback_ctx_t* ctx = (picoquic_connection_id_callback_ctx_t*)cnx_id_cb_data;
|
|
|
|
quic->local_cnxid_length = ctx->cnx_id_val.id_len;
|
|
|
|
/* Initialize with either random value or */
|
|
memset(cnx_id_returned, 0, sizeof(picoquic_connection_id_t));
|
|
if (ctx->cnx_id_select == picoquic_connection_id_remote) {
|
|
/* Keeping this for compatibility with old buggy version */
|
|
cnx_id_local = cnx_id_remote;
|
|
} else {
|
|
/* setting value to random data */
|
|
picoquic_public_random(cnx_id_local.id, quic->local_cnxid_length);
|
|
}
|
|
cnx_id_local.id_len = quic->local_cnxid_length;
|
|
|
|
/* Apply substitution under mask */
|
|
for (uint8_t i = 0; i < cnx_id_local.id_len; i++) {
|
|
cnx_id_returned->id[i] = (cnx_id_local.id[i] & ctx->cnx_id_mask.id[i]) | ctx->cnx_id_val.id[i];
|
|
}
|
|
cnx_id_returned->id_len = quic->local_cnxid_length;
|
|
|
|
/* Apply encryption if required */
|
|
switch (ctx->cnx_id_select) {
|
|
case picoquic_connection_id_encrypt_basic:
|
|
/* encryption under mask */
|
|
if (ctx->cid_enc == NULL) {
|
|
int ret = picoquic_cid_get_under_mask_ctx(&ctx->cid_enc, quic->reset_seed);
|
|
if (ret != 0) {
|
|
DBG_PRINTF("Cannot create CID encryption context, ret=%d\n", ret);
|
|
}
|
|
}
|
|
if (ctx->cid_enc != NULL) {
|
|
picoquic_cid_encrypt_under_mask(ctx->cid_enc, cnx_id_returned, &ctx->cnx_id_mask, cnx_id_returned);
|
|
}
|
|
break;
|
|
default:
|
|
/* Leave it unencrypted */
|
|
break;
|
|
}
|
|
}
|
|
|
|
picoquic_connection_id_callback_ctx_t * picoquic_connection_id_callback_create_ctx(
|
|
char const * select_type, char const * default_value_hex, char const * mask_hex)
|
|
{
|
|
picoquic_connection_id_callback_ctx_t* ctx = (picoquic_connection_id_callback_ctx_t*)
|
|
malloc(sizeof(picoquic_connection_id_callback_ctx_t));
|
|
|
|
if (ctx != NULL) {
|
|
size_t lv, lm;
|
|
memset(ctx, 0, sizeof(picoquic_connection_id_callback_ctx_t));
|
|
ctx->cnx_id_select = atoi(select_type);
|
|
/* TODO: find an alternative to parsing a 64 bit integer */
|
|
lv = picoquic_parse_connection_id_hexa(default_value_hex, strlen(default_value_hex), &ctx->cnx_id_val);
|
|
lm = picoquic_parse_connection_id_hexa(mask_hex, strlen(mask_hex), &ctx->cnx_id_val);
|
|
|
|
if (lm == 0 || lv == 0 || lm != lv) {
|
|
free(ctx);
|
|
ctx = NULL;
|
|
}
|
|
}
|
|
|
|
return ctx;
|
|
}
|
|
|
|
void picoquic_connection_id_callback_free_ctx(void * cnx_id_cb_data)
|
|
{
|
|
picoquic_connection_id_callback_ctx_t* ctx = (picoquic_connection_id_callback_ctx_t*)cnx_id_cb_data;
|
|
|
|
if (ctx != NULL && ctx->cid_enc != NULL) {
|
|
switch (ctx->cnx_id_select) {
|
|
case picoquic_connection_id_encrypt_basic:
|
|
/* encryption under mask */
|
|
picoquic_cid_free_under_mask_ctx(ctx->cid_enc);
|
|
break;
|
|
default:
|
|
/* Guessing for the most common, assuming free will work... */
|
|
picoquic_cid_free_under_mask_ctx(ctx->cid_enc);
|
|
break;
|
|
}
|
|
ctx->cid_enc = NULL;
|
|
}
|
|
free(cnx_id_cb_data);
|
|
}
|
|
|
|
void picoquic_set_fuzz(picoquic_quic_t * quic, picoquic_fuzz_fn fuzz_fn, void * fuzz_ctx)
|
|
{
|
|
quic->fuzz_fn = fuzz_fn;
|
|
quic->fuzz_ctx = fuzz_ctx;
|
|
}
|
|
|
|
void picoquic_set_log_level(picoquic_quic_t* quic, int log_level)
|
|
{
|
|
/* Only two level for now: log first 100 packets, or log everything. */
|
|
quic->use_long_log = (log_level > 0) ? 1 : 0;
|
|
}
|
|
|
|
void picoquic_set_random_initial(picoquic_quic_t* quic, int random_initial)
|
|
{
|
|
/* If set, triggers randomization of initial PN numbers. */
|
|
quic->random_initial = (random_initial > 0) ? 1 : 0;
|
|
}
|
|
|
|
void picoquic_set_packet_train_mode(picoquic_quic_t* quic, int train_mode)
|
|
{
|
|
/* TODO: consider setting high water mark for pacing. */
|
|
/* If set, wait until pacing bucket is full enough to allow further transmissions. */
|
|
quic->packet_train_mode = (train_mode > 0) ? 1 : 0;
|
|
}
|
|
|
|
void picoquic_set_padding_policy(picoquic_quic_t* quic, uint32_t padding_min_size, uint32_t padding_multiple)
|
|
{
|
|
quic->padding_minsize_default = padding_min_size;
|
|
quic->padding_multiple_default = padding_multiple;
|
|
}
|
|
|
|
int picoquic_set_default_connection_id_length(picoquic_quic_t* quic, uint8_t cid_length)
|
|
{
|
|
int ret = 0;
|
|
|
|
if (cid_length != quic->local_cnxid_length) {
|
|
if (cid_length > PICOQUIC_CONNECTION_ID_MAX_SIZE) {
|
|
ret = PICOQUIC_ERROR_CNXID_CHECK;
|
|
}
|
|
else if (quic->cnx_list != NULL) {
|
|
ret = PICOQUIC_ERROR_CANNOT_CHANGE_ACTIVE_CONTEXT;
|
|
}
|
|
else {
|
|
quic->local_cnxid_length = cid_length;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
void picoquic_set_mtu_max(picoquic_quic_t* quic, uint32_t mtu_max)
|
|
{
|
|
quic->mtu_max = mtu_max;
|
|
}
|
|
|
|
void picoquic_set_alpn_select_fn(picoquic_quic_t* quic, picoquic_alpn_select_fn alpn_select_fn)
|
|
{
|
|
if (quic->default_alpn != NULL) {
|
|
free((void *)quic->default_alpn);
|
|
quic->default_alpn = NULL;
|
|
}
|
|
quic->alpn_select_fn = alpn_select_fn;
|
|
}
|
|
|
|
void picoquic_set_default_callback(picoquic_quic_t* quic,
|
|
picoquic_stream_data_cb_fn callback_fn, void* callback_ctx)
|
|
{
|
|
quic->default_callback_fn = callback_fn;
|
|
quic->default_callback_ctx = callback_ctx;
|
|
}
|
|
|
|
void picoquic_set_callback(picoquic_cnx_t* cnx,
|
|
picoquic_stream_data_cb_fn callback_fn, void* callback_ctx)
|
|
{
|
|
cnx->callback_fn = callback_fn;
|
|
cnx->callback_ctx = callback_ctx;
|
|
}
|
|
|
|
picoquic_stream_data_cb_fn picoquic_get_default_callback_function(picoquic_quic_t* quic)
|
|
{
|
|
return quic->default_callback_fn;
|
|
}
|
|
|
|
void * picoquic_get_default_callback_context(picoquic_quic_t* quic)
|
|
{
|
|
return quic->default_callback_ctx;
|
|
}
|
|
|
|
picoquic_stream_data_cb_fn picoquic_get_callback_function(picoquic_cnx_t * cnx)
|
|
{
|
|
return cnx->callback_fn;
|
|
}
|
|
|
|
void * picoquic_get_callback_context(picoquic_cnx_t * cnx)
|
|
{
|
|
return cnx->callback_ctx;
|
|
}
|
|
|
|
picoquic_misc_frame_header_t* picoquic_create_misc_frame(const uint8_t* bytes, size_t length, int is_pure_ack)
|
|
{
|
|
size_t l_alloc = sizeof(picoquic_misc_frame_header_t) + length;
|
|
|
|
if (l_alloc < sizeof(picoquic_misc_frame_header_t)) {
|
|
return NULL;
|
|
}
|
|
else {
|
|
picoquic_misc_frame_header_t* head = (picoquic_misc_frame_header_t*)malloc(l_alloc);
|
|
if (head != NULL) {
|
|
memset(head, 0, sizeof(picoquic_misc_frame_header_t));
|
|
head->length = length;
|
|
head->is_pure_ack = is_pure_ack;
|
|
memcpy(((uint8_t *)head) + sizeof(picoquic_misc_frame_header_t), bytes, length);
|
|
}
|
|
return head;
|
|
}
|
|
}
|
|
|
|
int picoquic_queue_misc_or_dg_frame(picoquic_cnx_t * cnx, picoquic_misc_frame_header_t** first,
|
|
picoquic_misc_frame_header_t** last, const uint8_t* bytes, size_t length, int is_pure_ack)
|
|
{
|
|
int ret = 0;
|
|
picoquic_misc_frame_header_t* misc_frame = picoquic_create_misc_frame(bytes, length, is_pure_ack);
|
|
|
|
if (misc_frame == NULL) {
|
|
ret = PICOQUIC_ERROR_MEMORY;
|
|
} else {
|
|
if (*last == NULL) {
|
|
*first = misc_frame;
|
|
*last = misc_frame;
|
|
}
|
|
else {
|
|
(*last)->next_misc_frame = misc_frame;
|
|
misc_frame->previous_misc_frame = *last;
|
|
*last = misc_frame;
|
|
}
|
|
}
|
|
|
|
picoquic_reinsert_by_wake_time(cnx->quic, cnx, picoquic_get_quic_time(cnx->quic));
|
|
|
|
return ret;
|
|
}
|
|
|
|
int picoquic_queue_misc_frame(picoquic_cnx_t* cnx, const uint8_t* bytes, size_t length, int is_pure_ack)
|
|
{
|
|
return picoquic_queue_misc_or_dg_frame(cnx, &cnx->first_misc_frame, &cnx->last_misc_frame, bytes, length, is_pure_ack);
|
|
}
|
|
|
|
void picoquic_delete_misc_or_dg(picoquic_misc_frame_header_t** first, picoquic_misc_frame_header_t** last, picoquic_misc_frame_header_t* frame)
|
|
{
|
|
if (frame->next_misc_frame) {
|
|
frame->next_misc_frame->previous_misc_frame = frame->previous_misc_frame;
|
|
}
|
|
else {
|
|
*last = frame->previous_misc_frame;
|
|
}
|
|
|
|
if (frame->previous_misc_frame) {
|
|
frame->previous_misc_frame->next_misc_frame = frame->next_misc_frame;
|
|
}
|
|
else {
|
|
*first = frame->next_misc_frame;
|
|
}
|
|
|
|
free(frame);
|
|
}
|
|
|
|
void picoquic_reset_packet_context(picoquic_cnx_t* cnx,
|
|
picoquic_packet_context_enum pc)
|
|
{
|
|
/* TODO: special case for 0-RTT packets! */
|
|
picoquic_packet_context_t * pkt_ctx = &cnx->pkt_ctx[pc];
|
|
|
|
while (pkt_ctx->retransmit_newest != NULL) {
|
|
(void)picoquic_dequeue_retransmit_packet(cnx, pkt_ctx->retransmit_newest, 1);
|
|
}
|
|
|
|
while (pkt_ctx->retransmitted_newest != NULL) {
|
|
picoquic_dequeue_retransmitted_packet(cnx, pkt_ctx->retransmitted_newest);
|
|
}
|
|
|
|
pkt_ctx->retransmitted_oldest = NULL;
|
|
|
|
while (pkt_ctx->first_sack_item.next_sack != NULL) {
|
|
picoquic_sack_item_t * next = pkt_ctx->first_sack_item.next_sack;
|
|
pkt_ctx->first_sack_item.next_sack = next->next_sack;
|
|
free(next);
|
|
}
|
|
|
|
pkt_ctx->first_sack_item.start_of_sack_range = (uint64_t)((int64_t)-1);
|
|
pkt_ctx->first_sack_item.end_of_sack_range = 0;
|
|
/* Reset the ECN data */
|
|
pkt_ctx->ecn_ect0_total_local = 0;
|
|
pkt_ctx->ecn_ect1_total_local = 0;
|
|
pkt_ctx->ecn_ce_total_local = 0;
|
|
pkt_ctx->ecn_ect0_total_remote = 0;
|
|
pkt_ctx->ecn_ect1_total_remote = 0;
|
|
pkt_ctx->ecn_ce_total_remote = 0;
|
|
}
|
|
|
|
/*
|
|
* Reset the connection after an incoming retry packet.
|
|
*
|
|
* Can only happen after sending the client init packet.
|
|
* Result of reset:
|
|
*
|
|
* - connection ID is not changed.
|
|
* - sequence number is not changed.
|
|
* - all queued 0-RTT retransmission will be considered lost (to do with 0-RTT)
|
|
* - Client Initial packet is considered lost, free. A new one will have to be formatted.
|
|
* - TLS stream is reset, all TLS data is freed.
|
|
* - TLS API is called again.
|
|
* - State changes.
|
|
*/
|
|
|
|
int picoquic_reset_cnx(picoquic_cnx_t* cnx, uint64_t current_time)
|
|
{
|
|
int ret = 0;
|
|
|
|
/* Delete the packets queued for retransmission */
|
|
for (picoquic_packet_context_enum pc = 0;
|
|
pc < picoquic_nb_packet_context; pc++) {
|
|
/* Do not reset the application context, in order to keep the 0-RTT
|
|
* packets, and to keep using the same sequence number space in
|
|
* the new connection */
|
|
if (pc != picoquic_packet_context_application) {
|
|
picoquic_reset_packet_context(cnx, pc);
|
|
}
|
|
}
|
|
|
|
/* Reset the crypto stream */
|
|
for (int epoch = 0; epoch < PICOQUIC_NUMBER_OF_EPOCHS; epoch++) {
|
|
picoquic_clear_stream(&cnx->tls_stream[epoch]);
|
|
cnx->tls_stream[epoch].consumed_offset = 0;
|
|
cnx->tls_stream[epoch].fin_offset = 0;
|
|
cnx->tls_stream[epoch].sent_offset = 0;
|
|
/* No need to reset the state flags, are they are not used for the crypto stream */
|
|
}
|
|
|
|
for (int k = 0; k < 4; k++) {
|
|
picoquic_crypto_context_free(&cnx->crypto_context[k]);
|
|
}
|
|
|
|
picoquic_crypto_context_free(&cnx->crypto_context_new);
|
|
|
|
ret = picoquic_setup_initial_traffic_keys(cnx);
|
|
|
|
/* Reset the TLS context, Re-initialize the tls connection */
|
|
if (cnx->tls_ctx != NULL) {
|
|
picoquic_tlscontext_free(cnx->tls_ctx);
|
|
cnx->tls_ctx = NULL;
|
|
}
|
|
|
|
picoquic_log_new_connection(cnx);
|
|
|
|
if (ret == 0) {
|
|
ret = picoquic_tlscontext_create(cnx->quic, cnx, current_time);
|
|
}
|
|
if (ret == 0) {
|
|
ret = picoquic_initialize_tls_stream(cnx, current_time);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
int picoquic_connection_error(picoquic_cnx_t* cnx, uint16_t local_error, uint64_t frame_type)
|
|
{
|
|
if (cnx->cnx_state == picoquic_state_ready ||
|
|
cnx->cnx_state == picoquic_state_client_ready_start || cnx->cnx_state == picoquic_state_server_false_start) {
|
|
if (local_error > PICOQUIC_ERROR_CLASS) {
|
|
cnx->local_error = PICOQUIC_TRANSPORT_INTERNAL_ERROR;
|
|
}
|
|
else {
|
|
cnx->local_error = local_error;
|
|
}
|
|
cnx->cnx_state = picoquic_state_disconnecting;
|
|
|
|
picoquic_log_app_message(cnx, "Protocol error 0x%x", local_error);
|
|
DBG_PRINTF("Protocol error (%x)", local_error);
|
|
} else if (cnx->cnx_state < picoquic_state_server_false_start) {
|
|
if (cnx->cnx_state != picoquic_state_handshake_failure &&
|
|
cnx->cnx_state != picoquic_state_handshake_failure_resend) {
|
|
cnx->local_error = local_error;
|
|
cnx->cnx_state = picoquic_state_handshake_failure;
|
|
|
|
DBG_PRINTF("Protocol error %x", local_error);
|
|
}
|
|
}
|
|
|
|
cnx->offending_frame_type = frame_type;
|
|
|
|
return PICOQUIC_ERROR_DETECTED;
|
|
}
|
|
|
|
int picoquic_start_key_rotation(picoquic_cnx_t* cnx)
|
|
{
|
|
int ret = 0;
|
|
|
|
/* Verify that a packet of the previous rotation was acked */
|
|
if (cnx->cnx_state != picoquic_state_ready ||
|
|
cnx->crypto_epoch_sequence >
|
|
cnx->pkt_ctx[picoquic_packet_context_application].first_sack_item.end_of_sack_range) {
|
|
ret = PICOQUIC_ERROR_KEY_ROTATION_NOT_READY;
|
|
}
|
|
else {
|
|
ret = picoquic_compute_new_rotated_keys(cnx);
|
|
}
|
|
|
|
if (ret == 0) {
|
|
picoquic_apply_rotated_keys(cnx, 1);
|
|
picoquic_crypto_context_free(&cnx->crypto_context_old);
|
|
cnx->crypto_epoch_sequence = cnx->pkt_ctx[picoquic_packet_context_application].send_sequence;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
void picoquic_delete_sooner_packets(picoquic_cnx_t* cnx)
|
|
{
|
|
picoquic_stateless_packet_t* packet = cnx->first_sooner;
|
|
|
|
while (packet != NULL) {
|
|
picoquic_stateless_packet_t* next_packet = packet->next_packet;
|
|
picoquic_delete_stateless_packet(packet);
|
|
packet = next_packet;
|
|
}
|
|
cnx->first_sooner = NULL;
|
|
}
|
|
|
|
void picoquic_delete_cnx(picoquic_cnx_t* cnx)
|
|
{
|
|
picoquic_cnxid_stash_t* stashed_cnxid;
|
|
|
|
if (cnx != NULL) {
|
|
picoquic_log_close_connection(cnx);
|
|
|
|
if (cnx->is_half_open && cnx->quic->current_number_half_open > 0) {
|
|
cnx->quic->current_number_half_open--;
|
|
cnx->is_half_open = 0;
|
|
}
|
|
|
|
if (cnx->cnx_state < picoquic_state_disconnected) {
|
|
/* Give the application a chance to clean up its state */
|
|
cnx->cnx_state = picoquic_state_disconnected;
|
|
if (cnx->callback_fn) {
|
|
(void)(cnx->callback_fn)(cnx, 0, NULL, 0, picoquic_callback_close, cnx->callback_ctx, NULL);
|
|
}
|
|
}
|
|
|
|
if (cnx->alpn != NULL) {
|
|
free((void*)cnx->alpn);
|
|
cnx->alpn = NULL;
|
|
}
|
|
|
|
if (cnx->sni != NULL) {
|
|
free((void*)cnx->sni);
|
|
cnx->sni = NULL;
|
|
}
|
|
|
|
if (cnx->retry_token != NULL) {
|
|
free(cnx->retry_token);
|
|
cnx->retry_token = NULL;
|
|
}
|
|
|
|
picoquic_delete_sooner_packets(cnx);
|
|
|
|
picoquic_remove_cnx_from_list(cnx);
|
|
picoquic_remove_cnx_from_wake_list(cnx);
|
|
|
|
for (int i = 0; i < 4; i++) {
|
|
picoquic_crypto_context_free(&cnx->crypto_context[i]);
|
|
}
|
|
|
|
picoquic_crypto_context_free(&cnx->crypto_context_new);
|
|
|
|
for (picoquic_packet_context_enum pc = 0;
|
|
pc < picoquic_nb_packet_context; pc++) {
|
|
picoquic_reset_packet_context(cnx, pc);
|
|
}
|
|
|
|
while (cnx->first_misc_frame != NULL) {
|
|
picoquic_delete_misc_or_dg(&cnx->first_misc_frame, &cnx->last_misc_frame, cnx->first_misc_frame);
|
|
}
|
|
|
|
while (cnx->first_datagram != NULL) {
|
|
picoquic_delete_misc_or_dg(&cnx->first_datagram, &cnx->last_datagram, cnx->first_datagram);
|
|
}
|
|
|
|
for (int epoch = 0; epoch < PICOQUIC_NUMBER_OF_EPOCHS; epoch++) {
|
|
picoquic_clear_stream(&cnx->tls_stream[epoch]);
|
|
}
|
|
|
|
picosplay_empty_tree(&cnx->stream_tree);
|
|
|
|
if (cnx->tls_ctx != NULL) {
|
|
picoquic_tlscontext_free(cnx->tls_ctx);
|
|
cnx->tls_ctx = NULL;
|
|
}
|
|
|
|
if (cnx->path != NULL)
|
|
{
|
|
while (cnx->nb_paths > 0) {
|
|
picoquic_delete_path(cnx, cnx->nb_paths - 1);
|
|
}
|
|
|
|
free(cnx->path);
|
|
cnx->path = NULL;
|
|
}
|
|
|
|
while (cnx->local_cnxid_first != NULL) {
|
|
picoquic_delete_local_cnxid(cnx, cnx->local_cnxid_first);
|
|
}
|
|
|
|
while ((stashed_cnxid = picoquic_dequeue_cnxid_stash(cnx)) != NULL) {
|
|
free(stashed_cnxid);
|
|
}
|
|
|
|
free(cnx);
|
|
}
|
|
}
|
|
|
|
int picoquic_is_handshake_error(uint16_t error_code)
|
|
{
|
|
return ((error_code & 0xFF00) == PICOQUIC_TRANSPORT_CRYPTO_ERROR(0) ||
|
|
error_code == PICOQUIC_TLS_HANDSHAKE_FAILED);
|
|
}
|
|
|
|
/* Context retrieval functions */
|
|
picoquic_cnx_t* picoquic_cnx_by_id(picoquic_quic_t* quic, picoquic_connection_id_t cnx_id)
|
|
{
|
|
picoquic_cnx_t* ret = NULL;
|
|
picohash_item* item;
|
|
picoquic_cnx_id_key_t key;
|
|
|
|
memset(&key, 0, sizeof(key));
|
|
key.cnx_id = cnx_id;
|
|
|
|
item = picohash_retrieve(quic->table_cnx_by_id, &key);
|
|
|
|
if (item != NULL) {
|
|
ret = ((picoquic_cnx_id_key_t*)item->key)->cnx;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
picoquic_cnx_t* picoquic_cnx_by_net(picoquic_quic_t* quic, const struct sockaddr* addr)
|
|
{
|
|
picoquic_cnx_t* ret = NULL;
|
|
picohash_item* item;
|
|
picoquic_net_id_key_t key;
|
|
|
|
memset(&key, 0, sizeof(key));
|
|
picoquic_store_addr(&key.saddr, addr);
|
|
|
|
item = picohash_retrieve(quic->table_cnx_by_net, &key);
|
|
|
|
if (item != NULL) {
|
|
ret = ((picoquic_net_id_key_t*)item->key)->cnx;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
picoquic_cnx_t* picoquic_cnx_by_icid(picoquic_quic_t* quic, picoquic_connection_id_t* icid,
|
|
const struct sockaddr* addr)
|
|
{
|
|
picoquic_cnx_t* ret = NULL;
|
|
picohash_item* item;
|
|
picoquic_net_icid_key_t key;
|
|
|
|
memset(&key, 0, sizeof(key));
|
|
picoquic_store_addr(&key.saddr, addr);
|
|
key.icid = *icid;
|
|
|
|
item = picohash_retrieve(quic->table_cnx_by_icid, &key);
|
|
|
|
if (item != NULL) {
|
|
ret = ((picoquic_net_icid_key_t*)item->key)->cnx;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
picoquic_cnx_t* picoquic_cnx_by_secret(picoquic_quic_t* quic, const uint8_t* reset_secret, const struct sockaddr* addr)
|
|
{
|
|
picoquic_cnx_t* ret = NULL;
|
|
picohash_item* item;
|
|
picoquic_net_secret_key_t key;
|
|
|
|
memset(&key, 0, sizeof(key));
|
|
picoquic_store_addr(&key.saddr, addr);
|
|
memcpy(key.reset_secret, reset_secret, PICOQUIC_RESET_SECRET_SIZE);
|
|
|
|
item = picohash_retrieve(quic->table_cnx_by_secret, &key);
|
|
|
|
if (item != NULL) {
|
|
ret = ((picoquic_net_secret_key_t*)item->key)->cnx;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/* Get congestion control algorithm by name */
|
|
picoquic_congestion_algorithm_t const* picoquic_get_congestion_algorithm(char const* alg_name)
|
|
{
|
|
picoquic_congestion_algorithm_t const* alg = NULL;
|
|
if (alg_name != NULL) {
|
|
if (strcmp(alg_name, "reno") == 0) {
|
|
alg = picoquic_newreno_algorithm;
|
|
}
|
|
else if (strcmp(alg_name, "cubic") == 0) {
|
|
alg = picoquic_cubic_algorithm;
|
|
}
|
|
else if (strcmp(alg_name, "dcubic") == 0) {
|
|
alg = picoquic_dcubic_algorithm;
|
|
}
|
|
else if (strcmp(alg_name, "fast") == 0) {
|
|
alg = picoquic_fastcc_algorithm;
|
|
}
|
|
else if (strcmp(alg_name, "bbr") == 0) {
|
|
alg = picoquic_bbr_algorithm;
|
|
}
|
|
else {
|
|
alg = NULL;
|
|
}
|
|
}
|
|
return alg;
|
|
}
|
|
/*
|
|
* Set or reset the congestion control algorithm
|
|
*/
|
|
|
|
void picoquic_set_default_congestion_algorithm(picoquic_quic_t* quic, picoquic_congestion_algorithm_t const* alg)
|
|
{
|
|
quic->default_congestion_alg = alg;
|
|
}
|
|
|
|
void picoquic_set_default_congestion_algorithm_by_name(picoquic_quic_t* quic, char const * alg_name)
|
|
{
|
|
quic->default_congestion_alg = picoquic_get_congestion_algorithm(alg_name);
|
|
}
|
|
|
|
/*
|
|
* Set the optimistic ack policy
|
|
*/
|
|
|
|
void picoquic_set_optimistic_ack_policy(picoquic_quic_t* quic, uint32_t sequence_hole_pseudo_period)
|
|
{
|
|
quic->sequence_hole_pseudo_period = sequence_hole_pseudo_period;
|
|
}
|
|
|
|
void picoquic_set_congestion_algorithm(picoquic_cnx_t* cnx, picoquic_congestion_algorithm_t const* alg)
|
|
{
|
|
if (cnx->congestion_alg != NULL) {
|
|
if (cnx->path != NULL) {
|
|
for (int i = 0; i < cnx->nb_paths; i++) {
|
|
cnx->congestion_alg->alg_delete(cnx->path[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
cnx->congestion_alg = alg;
|
|
|
|
if (cnx->congestion_alg != NULL) {
|
|
if (cnx->path != NULL) {
|
|
for (int i = 0; i < cnx->nb_paths; i++) {
|
|
cnx->congestion_alg->alg_init(cnx->path[i], picoquic_get_quic_time(cnx->quic));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void picoquic_subscribe_pacing_rate_updates(picoquic_cnx_t* cnx, uint64_t decrease_threshold, uint64_t increase_threshold)
|
|
{
|
|
cnx->pacing_decrease_threshold = decrease_threshold;
|
|
cnx->pacing_increase_threshold = increase_threshold;
|
|
cnx->is_pacing_update_requested = (decrease_threshold != UINT64_MAX || increase_threshold != UINT64_MAX);
|
|
}
|
|
|
|
uint64_t picoquic_get_pacing_rate(picoquic_cnx_t* cnx)
|
|
{
|
|
return cnx->path[0]->pacing_rate;
|
|
}
|
|
|
|
uint64_t picoquic_get_cwin(picoquic_cnx_t* cnx)
|
|
{
|
|
return cnx->path[0]->cwin;
|
|
}
|
|
|
|
uint64_t picoquic_get_rtt(picoquic_cnx_t* cnx)
|
|
{
|
|
return cnx->path[0]->smoothed_rtt;
|
|
}
|
|
|
|
int picoquic_set_local_addr(picoquic_cnx_t* cnx, struct sockaddr* addr)
|
|
{
|
|
int ret = 0;
|
|
|
|
if (cnx != NULL && cnx->path[0] != NULL && cnx->path[0]->local_addr.ss_family == 0) {
|
|
picoquic_store_addr(&cnx->path[0]->local_addr, addr);
|
|
ret = (cnx->path[0]->local_addr.ss_family == 0) ? -1 : 0;
|
|
}
|
|
else {
|
|
ret = -1;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
void picoquic_enable_keep_alive(picoquic_cnx_t* cnx, uint64_t interval)
|
|
{
|
|
if (interval == 0) {
|
|
/* Use the negotiated value */
|
|
uint64_t idle_timeout = cnx->idle_timeout;
|
|
if (idle_timeout == 0) {
|
|
/* Idle timeout is only initialized after parameters are negotiated */
|
|
idle_timeout = cnx->local_parameters.idle_timeout * 1000ull;
|
|
}
|
|
/* Ensure at least 3 PTO*/
|
|
if (idle_timeout < 3 * cnx->path[0]->retransmit_timer) {
|
|
idle_timeout = 3 * cnx->path[0]->retransmit_timer;
|
|
}
|
|
/* set interval to half that value */
|
|
cnx->keep_alive_interval = idle_timeout / 2;
|
|
} else {
|
|
cnx->keep_alive_interval = interval;
|
|
}
|
|
}
|
|
|
|
void picoquic_disable_keep_alive(picoquic_cnx_t* cnx)
|
|
{
|
|
cnx->keep_alive_interval = 0;
|
|
}
|
|
|
|
int picoquic_set_verify_certificate_callback(picoquic_quic_t* quic, picoquic_verify_certificate_cb_fn cb, void* ctx,
|
|
picoquic_free_verify_certificate_ctx free_fn) {
|
|
picoquic_dispose_verify_certificate_callback(quic, quic->verify_certificate_callback_fn != NULL);
|
|
|
|
quic->verify_certificate_callback_fn = cb;
|
|
quic->free_verify_certificate_callback_fn = free_fn;
|
|
quic->verify_certificate_ctx = ctx;
|
|
|
|
return picoquic_enable_custom_verify_certificate_callback(quic);
|
|
}
|
|
|
|
int picoquic_is_client(picoquic_cnx_t* cnx)
|
|
{
|
|
return cnx->client_mode;
|
|
}
|
|
|
|
int picoquic_get_local_error(picoquic_cnx_t* cnx)
|
|
{
|
|
return cnx->local_error;
|
|
}
|
|
|
|
int picoquic_get_remote_error(picoquic_cnx_t* cnx)
|
|
{
|
|
return cnx->remote_error;
|
|
}
|
|
|
|
uint64_t picoquic_get_remote_stream_error(picoquic_cnx_t* cnx, uint64_t stream_id)
|
|
{
|
|
uint64_t remote_error = 0;
|
|
picoquic_stream_head_t* stream = picoquic_find_stream(cnx, stream_id);
|
|
if (stream != NULL) {
|
|
remote_error = stream->remote_error;
|
|
}
|
|
return remote_error;
|
|
}
|
|
|
|
uint64_t picoquic_get_data_sent(picoquic_cnx_t* cnx)
|
|
{
|
|
return cnx->data_sent;
|
|
}
|
|
|
|
uint64_t picoquic_get_data_received(picoquic_cnx_t* cnx)
|
|
{
|
|
return cnx->data_received;
|
|
}
|
|
|
|
void picoquic_set_client_authentication(picoquic_quic_t* quic, int client_authentication) {
|
|
picoquic_tls_set_client_authentication(quic, client_authentication);
|
|
}
|
|
|
|
/* Load balancer support is defined in https://datatracker.ietf.org/doc/draft-ietf-quic-load-balancers/
|
|
* The draft defines methods for encoding a server ID in a connection identifier, and optionally
|
|
* obfuscating or encrypting the CID value. The CID are generated by the individual servers,
|
|
* based on configuration options provided by the load balancer. The draft also defines
|
|
* methods for generating retry tokens either by a protection box colocated with the
|
|
* load balancer, or at the individual server, with methods for letting individual
|
|
* servers retrieve information from the tokens.
|
|
*/
|
|
|
|
static void picoquic_lb_compat_cid_generate_clear(picoquic_quic_t* quic,
|
|
picoquic_load_balancer_cid_context_t * lb_ctx, picoquic_connection_id_t* cnx_id_returned)
|
|
{
|
|
cnx_id_returned->id[0] = lb_ctx->first_byte;
|
|
memcpy(cnx_id_returned->id + 1, lb_ctx->server_id, lb_ctx->server_id_length);
|
|
}
|
|
|
|
static void picoquic_lb_compat_cid_generate_obfuscated(picoquic_quic_t* quic,
|
|
picoquic_load_balancer_cid_context_t* lb_ctx, picoquic_connection_id_t* cnx_id_returned)
|
|
{
|
|
uint64_t obfuscation_max = (UINT64_MAX - lb_ctx->server_id64) / lb_ctx->divider;
|
|
uint64_t obfuscator = 0;
|
|
uint64_t obfuscated;
|
|
|
|
cnx_id_returned->id[0] = lb_ctx->first_byte;
|
|
for (size_t i = 0; i < lb_ctx->routing_bits_length; i++) {
|
|
obfuscator <<= 8;
|
|
obfuscator += cnx_id_returned->id[i + 1];
|
|
obfuscator %= obfuscation_max;
|
|
}
|
|
obfuscated = obfuscator* lb_ctx->divider;
|
|
obfuscated += lb_ctx->server_id64;
|
|
|
|
for (size_t i = 0; i < lb_ctx->routing_bits_length; i++) {
|
|
size_t j = lb_ctx->routing_bits_length - i; /* varies from lb_ctx->routing_bits_length to 1 */
|
|
cnx_id_returned->id[j] = (uint8_t)obfuscated;
|
|
obfuscated >>= 8;
|
|
}
|
|
}
|
|
|
|
static void picoquic_lb_compat_cid_one_pass_stream(void * enc_ctx, uint8_t * nonce, size_t nonce_length, uint8_t * target, size_t target_length)
|
|
{
|
|
uint8_t mask[16];
|
|
/* Set the obfuscation value */
|
|
memset(mask, 0, sizeof(mask));
|
|
memcpy(mask, nonce, nonce_length);
|
|
/* Encrypt with ECB */
|
|
picoquic_aes128_ecb_encrypt(enc_ctx, mask, mask, sizeof(mask));
|
|
/* Apply the mask */
|
|
for (size_t i = 0; i < target_length; i++) {
|
|
target[i] ^= mask[i];
|
|
}
|
|
}
|
|
|
|
static void picoquic_lb_compat_cid_generate_stream_cipher(picoquic_quic_t* quic,
|
|
picoquic_load_balancer_cid_context_t* lb_ctx, picoquic_connection_id_t* cnx_id_returned)
|
|
{
|
|
size_t id_offset = ((size_t)1) + lb_ctx->nonce_length;
|
|
/* Prepare a clear text server ID */
|
|
cnx_id_returned->id[0] = lb_ctx->first_byte;
|
|
memcpy(cnx_id_returned->id + id_offset, lb_ctx->server_id, lb_ctx->server_id_length);
|
|
/* First pass -- obtain intermediate server ID */
|
|
picoquic_lb_compat_cid_one_pass_stream(lb_ctx->cid_encryption_context, cnx_id_returned->id + 1, lb_ctx->nonce_length,
|
|
cnx_id_returned->id + id_offset, lb_ctx->server_id_length);
|
|
/* Second pass -- obtain encrypted nonce */
|
|
picoquic_lb_compat_cid_one_pass_stream(lb_ctx->cid_encryption_context,
|
|
cnx_id_returned->id + id_offset, lb_ctx->server_id_length,
|
|
cnx_id_returned->id + 1, lb_ctx->nonce_length);
|
|
/* Third pass -- obtain encrypted server-id */
|
|
picoquic_lb_compat_cid_one_pass_stream(lb_ctx->cid_encryption_context, cnx_id_returned->id + 1, lb_ctx->nonce_length,
|
|
cnx_id_returned->id + id_offset, lb_ctx->server_id_length);
|
|
}
|
|
|
|
static void picoquic_lb_compat_cid_generate_block_cipher(picoquic_quic_t* quic,
|
|
picoquic_load_balancer_cid_context_t* lb_ctx, picoquic_connection_id_t* cnx_id_returned)
|
|
{
|
|
cnx_id_returned->id[0] = lb_ctx->first_byte;
|
|
/* Copy the server ID */
|
|
memcpy(cnx_id_returned->id + 1, lb_ctx->server_id, lb_ctx->server_id_length);
|
|
/* Set the zeropad value */
|
|
memset(cnx_id_returned->id + 1 + lb_ctx->server_id_length, 0, lb_ctx->zero_pad_length);
|
|
/* encrypt 16 bytes */
|
|
picoquic_aes128_ecb_encrypt(lb_ctx->cid_encryption_context, cnx_id_returned->id + 1, cnx_id_returned->id + 1, 16);
|
|
cnx_id_returned->id[0] = lb_ctx->first_byte;
|
|
}
|
|
|
|
void picoquic_lb_compat_cid_generate(picoquic_quic_t* quic, picoquic_connection_id_t cnx_id_local,
|
|
picoquic_connection_id_t cnx_id_remote, void* cnx_id_cb_data, picoquic_connection_id_t* cnx_id_returned)
|
|
{
|
|
picoquic_load_balancer_cid_context_t* lb_ctx = (picoquic_load_balancer_cid_context_t*)cnx_id_cb_data;
|
|
#ifdef _WINDOWS
|
|
UNREFERENCED_PARAMETER(cnx_id_local);
|
|
UNREFERENCED_PARAMETER(cnx_id_remote);
|
|
#endif
|
|
switch (lb_ctx->method) {
|
|
case picoquic_load_balancer_cid_clear:
|
|
picoquic_lb_compat_cid_generate_clear(quic, lb_ctx, cnx_id_returned);
|
|
break;
|
|
case picoquic_load_balancer_cid_obfuscated:
|
|
picoquic_lb_compat_cid_generate_obfuscated(quic, lb_ctx, cnx_id_returned);
|
|
break;
|
|
case picoquic_load_balancer_cid_stream_cipher:
|
|
picoquic_lb_compat_cid_generate_stream_cipher(quic, lb_ctx, cnx_id_returned);
|
|
break;
|
|
case picoquic_load_balancer_cid_block_cipher:
|
|
picoquic_lb_compat_cid_generate_block_cipher(quic, lb_ctx, cnx_id_returned);
|
|
break;
|
|
default:
|
|
/* Error, unknown method */
|
|
break;
|
|
}
|
|
}
|
|
|
|
static uint64_t picoquic_lb_compat_cid_verify_clear(picoquic_quic_t* quic,
|
|
picoquic_load_balancer_cid_context_t* lb_ctx, picoquic_connection_id_t const* cnx_id)
|
|
{
|
|
uint64_t s_id64 = 0;
|
|
|
|
for (size_t i = 0; i < lb_ctx->server_id_length; i++) {
|
|
s_id64 <<= 8;
|
|
s_id64 += cnx_id->id[i + 1];
|
|
}
|
|
|
|
return s_id64;
|
|
}
|
|
|
|
static uint64_t picoquic_lb_compat_cid_verify_obfuscated(picoquic_quic_t* quic,
|
|
picoquic_load_balancer_cid_context_t* lb_ctx, picoquic_connection_id_t const* cnx_id)
|
|
{
|
|
uint64_t s_id64 = 0;
|
|
|
|
for (size_t i = 0; i < lb_ctx->routing_bits_length; i++) {
|
|
s_id64 <<= 8;
|
|
s_id64 += cnx_id->id[i + 1];
|
|
s_id64 %= lb_ctx->divider;
|
|
}
|
|
|
|
return s_id64;
|
|
}
|
|
|
|
static uint64_t picoquic_lb_compat_cid_verify_stream_cipher(picoquic_quic_t* quic,
|
|
picoquic_load_balancer_cid_context_t* lb_ctx, picoquic_connection_id_t const* cnx_id)
|
|
{
|
|
size_t id_offset = ((size_t)1) + lb_ctx->nonce_length;
|
|
uint64_t s_id64 = 0;
|
|
picoquic_connection_id_t target = *cnx_id;
|
|
/* First pass -- obtain intermediate server ID */
|
|
picoquic_lb_compat_cid_one_pass_stream(lb_ctx->cid_encryption_context, target.id + 1, lb_ctx->nonce_length,
|
|
target.id + id_offset, lb_ctx->server_id_length);
|
|
/* Second pass -- obtain nonce */
|
|
picoquic_lb_compat_cid_one_pass_stream(lb_ctx->cid_encryption_context,
|
|
target.id + id_offset, lb_ctx->server_id_length, target.id + 1, lb_ctx->nonce_length);
|
|
/* First pass -- obtain server-id */
|
|
picoquic_lb_compat_cid_one_pass_stream(lb_ctx->cid_encryption_context, target.id + 1, lb_ctx->nonce_length,
|
|
target.id + id_offset, lb_ctx->server_id_length);
|
|
|
|
/* decode the server ID */
|
|
for (size_t i = 0; i < lb_ctx->server_id_length; i++) {
|
|
s_id64 <<= 8;
|
|
s_id64 += target.id[id_offset + i];
|
|
}
|
|
|
|
return s_id64;
|
|
}
|
|
|
|
static uint64_t picoquic_lb_compat_cid_verify_block_cipher(picoquic_quic_t* quic,
|
|
picoquic_load_balancer_cid_context_t* lb_ctx, picoquic_connection_id_t const* cnx_id)
|
|
{
|
|
uint8_t decoded[16];
|
|
uint64_t s_id64 = 0;
|
|
|
|
/* decrypt 16 bytes */
|
|
picoquic_aes128_ecb_encrypt(lb_ctx->cid_decryption_context, decoded, cnx_id->id + 1, 16);
|
|
|
|
/* Check that the nonce is all zeros */
|
|
for (size_t i = 0; i < lb_ctx->zero_pad_length; i++) {
|
|
if (decoded[i + lb_ctx->server_id_length] != 0) {
|
|
s_id64 = UINT64_MAX;
|
|
break;
|
|
}
|
|
}
|
|
/* Decode the server ID */
|
|
if (s_id64 == 0) {
|
|
for (size_t i = 0; i < lb_ctx->server_id_length; i++) {
|
|
s_id64 <<= 8;
|
|
s_id64 += decoded[i];
|
|
}
|
|
}
|
|
|
|
return s_id64;
|
|
}
|
|
|
|
uint64_t picoquic_lb_compat_cid_verify(picoquic_quic_t* quic, void* cnx_id_cb_data, picoquic_connection_id_t const* cnx_id)
|
|
{
|
|
picoquic_load_balancer_cid_context_t* lb_ctx = (picoquic_load_balancer_cid_context_t*)cnx_id_cb_data;
|
|
uint64_t server_id64;
|
|
|
|
if (cnx_id->id_len != lb_ctx->connection_id_length) {
|
|
server_id64 = UINT64_MAX;
|
|
}
|
|
else {
|
|
switch (lb_ctx->method) {
|
|
case picoquic_load_balancer_cid_clear:
|
|
server_id64 = picoquic_lb_compat_cid_verify_clear(quic, lb_ctx, cnx_id);
|
|
break;
|
|
case picoquic_load_balancer_cid_obfuscated:
|
|
server_id64 = picoquic_lb_compat_cid_verify_obfuscated(quic, lb_ctx, cnx_id);
|
|
break;
|
|
case picoquic_load_balancer_cid_stream_cipher:
|
|
server_id64 = picoquic_lb_compat_cid_verify_stream_cipher(quic, lb_ctx, cnx_id);
|
|
break;
|
|
case picoquic_load_balancer_cid_block_cipher:
|
|
server_id64 = picoquic_lb_compat_cid_verify_block_cipher(quic, lb_ctx, cnx_id);
|
|
break;
|
|
default:
|
|
/* Error, unknown method */
|
|
server_id64 = UINT64_MAX;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return server_id64;
|
|
}
|
|
|
|
int picoquic_lb_compat_cid_config(picoquic_quic_t* quic, picoquic_load_balancer_config_t * lb_config)
|
|
{
|
|
int ret = 0;
|
|
|
|
if (quic->cnx_list != NULL && quic->local_cnxid_length != lb_config->connection_id_length) {
|
|
/* Error. Changing the CID length now will break existing connections */
|
|
ret = -1;
|
|
}
|
|
else if (quic->cnx_id_callback_fn != NULL && quic->cnx_id_callback_ctx != NULL){
|
|
/* Error. Some other CID generation is configured, cannot be changed */
|
|
ret = -1;
|
|
}
|
|
else {
|
|
/* Verify that the method is supported and the parameters are compatible.
|
|
* If valid, configure the connection ID generation */
|
|
if (lb_config->connection_id_length > PICOQUIC_CONNECTION_ID_MAX_SIZE) {
|
|
ret = -1;
|
|
}
|
|
else {
|
|
switch (lb_config->method) {
|
|
case picoquic_load_balancer_cid_clear:
|
|
/* Require at least 2 bytes to make the CID unique */
|
|
if (lb_config->server_id_length + 1 + 2 > lb_config->connection_id_length) {
|
|
ret = -1;
|
|
}
|
|
break;
|
|
case picoquic_load_balancer_cid_obfuscated:
|
|
/* Require at least 2 bytes to obfuscate the server CID,
|
|
* cannot handle undivided values larger than 8 bytes */
|
|
if (lb_config->routing_bits_length + 1 > lb_config->connection_id_length ||
|
|
lb_config->server_id_length + 2 > lb_config->routing_bits_length ||
|
|
lb_config->routing_bits_length > 8 ||
|
|
lb_config->divider == 0) {
|
|
ret = -1;
|
|
}
|
|
break;
|
|
case picoquic_load_balancer_cid_stream_cipher:
|
|
/* Nonce length must be 8 to 16 bytes, CID should be long enough */
|
|
if (lb_config->nonce_length < 8 || lb_config->nonce_length > 16 ||
|
|
lb_config->nonce_length + lb_config->server_id_length + 1 > lb_config->connection_id_length) {
|
|
ret = -1;
|
|
}
|
|
break;
|
|
case picoquic_load_balancer_cid_block_cipher:
|
|
/* CID should include a whole AES-ECB block,
|
|
* there should be at least 2 bytes available for uniqueness,
|
|
* zero padding length should be 4 bytes for security */
|
|
if (lb_config->connection_id_length < 17 ||
|
|
lb_config->server_id_length + lb_config->zero_pad_length + 1 + 2 > lb_config->connection_id_length ||
|
|
lb_config->zero_pad_length < 4 ) {
|
|
ret = -1;
|
|
}
|
|
break;
|
|
default:
|
|
/* Error, unknown method */
|
|
ret = -1;
|
|
break;
|
|
}
|
|
}
|
|
if (ret == 0) {
|
|
/* Create a copy */
|
|
picoquic_load_balancer_cid_context_t* lb_ctx = (picoquic_load_balancer_cid_context_t*)malloc(sizeof(picoquic_load_balancer_cid_context_t));
|
|
|
|
if (lb_ctx == NULL) {
|
|
ret = -1;
|
|
}
|
|
else {
|
|
/* if allocated, create the necessary encryption contexts or variables */
|
|
uint64_t s_id64 = lb_config->server_id64;
|
|
memset(lb_ctx, 0, sizeof(picoquic_load_balancer_cid_context_t));
|
|
lb_ctx->method = lb_config->method;
|
|
lb_ctx->server_id_length = lb_config->server_id_length;
|
|
lb_ctx->routing_bits_length = lb_config->routing_bits_length;
|
|
lb_ctx->nonce_length = lb_config->nonce_length;
|
|
lb_ctx->zero_pad_length = lb_config->zero_pad_length;
|
|
lb_ctx->connection_id_length = lb_config->connection_id_length;
|
|
lb_ctx->first_byte = lb_config->first_byte;
|
|
lb_ctx->server_id64 = lb_config->server_id64;
|
|
lb_ctx->divider = lb_config->divider;
|
|
lb_ctx->cid_encryption_context = NULL;
|
|
lb_ctx->cid_decryption_context = NULL;
|
|
/* Compute the server ID bytes and set encryption contexts */
|
|
for (size_t i = 0; i < lb_ctx->server_id_length; i++) {
|
|
size_t j = lb_ctx->server_id_length - i - 1;
|
|
lb_ctx->server_id[j] = (uint8_t)s_id64;
|
|
s_id64 >>= 8;
|
|
}
|
|
if (s_id64 != 0) {
|
|
/* Server ID not long enough to encode actual value */
|
|
ret = -1;
|
|
} else if (lb_config->method == picoquic_load_balancer_cid_stream_cipher ||
|
|
lb_config->method == picoquic_load_balancer_cid_block_cipher) {
|
|
lb_ctx->cid_encryption_context = picoquic_aes128_ecb_create(1, lb_config->cid_encryption_key);
|
|
if (lb_ctx->cid_encryption_context == NULL) {
|
|
ret = -1;
|
|
}
|
|
else if (lb_config->method == picoquic_load_balancer_cid_block_cipher) {
|
|
lb_ctx->cid_decryption_context = picoquic_aes128_ecb_create(0, lb_config->cid_encryption_key);
|
|
if (lb_ctx->cid_decryption_context == NULL) {
|
|
picoquic_aes128_ecb_free(lb_ctx->cid_encryption_context);
|
|
lb_ctx->cid_encryption_context = NULL;
|
|
ret = -1;
|
|
}
|
|
}
|
|
}
|
|
if (ret != 0) {
|
|
/* if context allocation failed, free the copy */
|
|
free(lb_ctx);
|
|
lb_ctx = NULL;
|
|
} else {
|
|
/* Configure the CID generation */
|
|
quic->local_cnxid_length = lb_ctx->connection_id_length;
|
|
quic->cnx_id_callback_fn = picoquic_lb_compat_cid_generate;
|
|
quic->cnx_id_callback_ctx = (void*)lb_ctx;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
void picoquic_lb_compat_cid_config_free(picoquic_quic_t* quic)
|
|
{
|
|
if (quic->cnx_id_callback_fn == picoquic_lb_compat_cid_generate &&
|
|
quic->cnx_id_callback_ctx != NULL) {
|
|
picoquic_load_balancer_config_t* lb_config = (picoquic_load_balancer_config_t*)quic->cnx_id_callback_ctx;
|
|
/* Release the encryption contexts so as to avoid memory leaks */
|
|
/* Free the data */
|
|
free(lb_config);
|
|
/* Reset the Quic context */
|
|
quic->cnx_id_callback_fn = NULL;
|
|
quic->cnx_id_callback_ctx = NULL;
|
|
}
|
|
} |