1
0
Fork 0
Univerxel/deps/picoquic/packet.c

2222 lines
88 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.
*/
/*
* Processing of an incoming packet.
* - Has to find the proper context, based on either the 64 bit context ID
* or a combination of source address, source port and partial context value.
* - Has to find the sequence number, based on partial values and windows.
* - For initial packets, has to perform version checks.
*/
#include "picoquic_internal.h"
#include "picoquic_binlog.h"
#include "picoquic_unified_log.h"
#include "tls_api.h"
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#if 0
uint8_t* picoquic_frames_varint_decode(uint8_t* bytes, const uint8_t* bytes_max, uint64_t* n64);
uint8_t* picoquic_frames_varlen_decode(uint8_t* bytes, const uint8_t* bytes_max, size_t* n);
uint8_t* picoquic_frames_uint8_decode(uint8_t* bytes, const uint8_t* bytes_max, uint8_t* n);
uint8_t* picoquic_frames_uint16_decode(uint8_t* bytes, const uint8_t* bytes_max, uint16_t* n);
uint8_t* picoquic_frames_uint32_decode(uint8_t* bytes, const uint8_t* bytes_max, uint32_t* n);
uint8_t* picoquic_frames_uint64_decode(uint8_t* bytes, const uint8_t* bytes_max, uint64_t* n);
uint8_t* picoquic_frames_cid_decode(uint8_t* bytes, const uint8_t* bytes_max, picoquic_connection_id_t* n);
#endif
int picoquic_parse_long_packet_header(
picoquic_quic_t* quic,
const uint8_t* bytes,
size_t length,
const struct sockaddr* addr_from,
picoquic_packet_header* ph,
picoquic_cnx_t** pcnx)
{
int ret = 0;
const uint8_t* bytes_start = bytes;
const uint8_t* bytes_max = bytes + length;
uint8_t flags = 0;
if ((bytes = picoquic_frames_uint8_decode(bytes, bytes_max, &flags)) == NULL ||
(bytes = picoquic_frames_uint32_decode(bytes, bytes_max, &ph->vn)) == NULL)
{
ret = -1;
}
else if (ph->vn != 0) {
ph->version_index = picoquic_get_version_index(ph->vn);
if (ph->version_index < 0) {
DBG_PRINTF("Version is not recognized: 0x%08x\n", ph->vn);
ph->ptype = picoquic_packet_error;
ph->pc = 0;
ret = PICOQUIC_ERROR_VERSION_NOT_SUPPORTED;
}
}
if (ret == 0 && (
(bytes = picoquic_frames_cid_decode(bytes, bytes_max, &ph->dest_cnx_id)) == NULL ||
(bytes = picoquic_frames_cid_decode(bytes, bytes_max, &ph->srce_cnx_id)) == NULL)) {
ret = -1;
}
if (ret == 0) {
ph->offset = bytes - bytes_start;
if (ph->vn == 0) {
/* VN = zero identifies a version negotiation packet */
ph->ptype = picoquic_packet_version_negotiation;
ph->pc = picoquic_packet_context_initial;
ph->payload_length = (uint16_t)((length > ph->offset) ? length - ph->offset : 0);
ph->pl_val = ph->payload_length; /* saving the value found in the packet */
if (*pcnx == NULL && quic != NULL) {
/* The version negotiation should always include the cnx-id sent by the client */
if (quic->local_cnxid_length == 0) {
*pcnx = picoquic_cnx_by_net(quic, addr_from);
}
else if (ph->dest_cnx_id.id_len == quic->local_cnxid_length) {
*pcnx = picoquic_cnx_by_id(quic, ph->dest_cnx_id);
}
}
}
else {
size_t payload_length = 0;
/* If the version is supported now, the format field in the version table
* describes the encoding. */
ph->spin = 0;
ph->has_spin_bit = 0;
ph->quic_bit_is_zero = (flags & 0x40) == 0;
switch ((flags >> 4) & 3) {
case 0: /* Initial */
{
/* special case of the initial packets. They contain a retry token between the header
* and the encrypted payload */
size_t tok_len = 0;
bytes = picoquic_frames_varlen_decode(bytes, bytes_max, &tok_len);
size_t bytes_left = bytes_max - bytes;
ph->epoch = picoquic_epoch_initial;
if (bytes == NULL || bytes_left < tok_len) {
/* packet is malformed */
ph->ptype = picoquic_packet_error;
ph->pc = 0;
ph->offset = length;
}
else {
ph->ptype = picoquic_packet_initial;
ph->pc = picoquic_packet_context_initial;
ph->token_length = tok_len;
ph->token_bytes = bytes;
bytes += tok_len;
ph->offset = bytes - bytes_start;
}
break;
}
case 1: /* 0-RTT Protected */
ph->ptype = picoquic_packet_0rtt_protected;
ph->pc = picoquic_packet_context_application;
ph->epoch = picoquic_epoch_0rtt;
break;
case 2: /* Handshake */
ph->ptype = picoquic_packet_handshake;
ph->pc = picoquic_packet_context_handshake;
ph->epoch = picoquic_epoch_handshake;
break;
case 3: /* Retry */
ph->ptype = picoquic_packet_retry;
ph->pc = picoquic_packet_context_initial;
ph->epoch = picoquic_epoch_initial;
break;
default: /* Not a valid packet type */
DBG_PRINTF("Packet type is not recognized: 0x%02x\n", flags);
ph->ptype = picoquic_packet_error;
ph->version_index = -1;
ph->pc = 0;
break;
}
if (ph->ptype == picoquic_packet_retry) {
/* No segment length or sequence number in retry packets */
if (length > ph->offset) {
payload_length = length - ph->offset;
}
else {
payload_length = 0;
ph->ptype = picoquic_packet_error;
}
}
else if (ph->ptype != picoquic_packet_error) {
bytes = picoquic_frames_varlen_decode(bytes, bytes_max, &payload_length);
size_t bytes_left = (bytes_max > bytes) ? bytes_max - bytes : 0;
if (bytes == NULL || bytes_left < payload_length || ph->version_index < 0) {
ph->ptype = picoquic_packet_error;
ph->payload_length = (uint16_t)((length > ph->offset) ? length - ph->offset : 0);
ph->pl_val = ph->payload_length;
}
}
if (ph->ptype != picoquic_packet_error)
{
ph->pl_val = (uint16_t)payload_length;
ph->payload_length = (uint16_t)payload_length;
ph->offset = bytes - bytes_start;
ph->pn_offset = ph->offset;
/* Retrieve the connection context */
if (*pcnx == NULL) {
if (quic->local_cnxid_length == 0) {
*pcnx = picoquic_cnx_by_net(quic, addr_from);
}
else
{
if (ph->dest_cnx_id.id_len == quic->local_cnxid_length) {
*pcnx = picoquic_cnx_by_id(quic, ph->dest_cnx_id);
}
if (*pcnx == NULL && (ph->ptype == picoquic_packet_initial || ph->ptype == picoquic_packet_0rtt_protected)) {
*pcnx = picoquic_cnx_by_icid(quic, &ph->dest_cnx_id, addr_from);
}
else if (*pcnx == NULL) {
DBG_PRINTF("Dropped packet of type %d, no connection", ph->ptype);
}
}
}
if (ph->quic_bit_is_zero && *pcnx != NULL && !(*pcnx)->local_parameters.do_grease_quic_bit) {
ph->ptype = picoquic_packet_error;
}
}
else {
/* Try to find the connection context, for logging purpose. */
if (*pcnx == NULL) {
if (quic->local_cnxid_length == 0) {
*pcnx = picoquic_cnx_by_net(quic, addr_from);
}
else if (ph->dest_cnx_id.id_len == quic->local_cnxid_length) {
*pcnx = picoquic_cnx_by_id(quic, ph->dest_cnx_id);
}
}
}
}
}
return ret;
}
int picoquic_parse_short_packet_header(
picoquic_quic_t* quic,
const uint8_t* bytes,
size_t length,
const struct sockaddr* addr_from,
picoquic_packet_header* ph,
picoquic_cnx_t** pcnx,
int receiving)
{
int ret = 0;
/* If this is a short header, it should be possible to retrieve the connection
* context. This depends on whether the quic context requires cnx_id or not.
*/
uint8_t cnxid_length = (receiving == 0 && *pcnx != NULL) ? (*pcnx)->path[0]->remote_cnxid.id_len : quic->local_cnxid_length;
ph->pc = picoquic_packet_context_application;
ph->pl_val = 0; /* No actual payload length in short headers */
if ((int)length >= 1 + cnxid_length) {
/* We can identify the connection by its ID */
ph->offset = (size_t)1 + picoquic_parse_connection_id(bytes + 1, cnxid_length, &ph->dest_cnx_id);
/* TODO: should consider using combination of CNX ID and ADDR_FROM */
if (*pcnx == NULL)
{
if (quic->local_cnxid_length > 0) {
*pcnx = picoquic_cnx_by_id(quic, ph->dest_cnx_id);
}
else {
*pcnx = picoquic_cnx_by_net(quic, addr_from);
}
}
}
else {
ph->ptype = picoquic_packet_error;
ph->offset = length;
ph->payload_length = 0;
}
if (*pcnx != NULL) {
int has_loss_bit = (receiving && (*pcnx)->is_loss_bit_enabled_incoming) || ((!receiving && (*pcnx)->is_loss_bit_enabled_outgoing));
ph->epoch = picoquic_epoch_1rtt;
ph->version_index = (*pcnx)->version_index;
ph->quic_bit_is_zero = (bytes[0] & 0x40) == 0;
if (!ph->quic_bit_is_zero ||(*pcnx)->local_parameters.do_grease_quic_bit) {
/* We do not check the quic bit if the local endpoint advertised greasing. */
ph->ptype = picoquic_packet_1rtt_protected;
} else {
/* Check for QUIC bit failed! */
ph->ptype = picoquic_packet_error;
}
ph->has_spin_bit = 1;
ph->spin = (bytes[0] >> 5) & 1;
ph->pn_offset = ph->offset;
ph->pn = 0;
ph->pnmask = 0;
ph->key_phase = ((bytes[0] >> 2) & 1); /* Initialize here so that simple tests with unencrypted headers can work */
if (has_loss_bit) {
ph->has_loss_bits = 1;
ph->loss_bit_L = (bytes[0] >> 3) & 1;
ph->loss_bit_Q = (bytes[0] >> 4) & 1;
}
if (length < ph->offset || ph->ptype == picoquic_packet_error) {
ret = -1;
ph->payload_length = 0;
}
else {
ph->payload_length = (uint16_t)(length - ph->offset);
}
}
else {
/* This may be a packet to a forgotten connection */
ph->ptype = picoquic_packet_1rtt_protected;
ph->payload_length = (uint16_t)((length > ph->offset) ? length - ph->offset : 0);
}
return ret;
}
int picoquic_parse_packet_header(
picoquic_quic_t* quic,
const uint8_t* bytes,
size_t length,
const struct sockaddr* addr_from,
picoquic_packet_header* ph,
picoquic_cnx_t** pcnx,
int receiving)
{
int ret = 0;
/* Initialize the PH structure to zero, but version index to -1 (error) */
memset(ph, 0, sizeof(picoquic_packet_header));
ph->version_index = -1;
/* Is this a long header or a short header? -- in any case, we need at least 17 bytes */
if ((bytes[0] & 0x80) == 0x80) {
ret = picoquic_parse_long_packet_header(quic, bytes, length, addr_from, ph, pcnx);
} else {
ret = picoquic_parse_short_packet_header(quic, bytes, length, addr_from, ph, pcnx, receiving);
}
return ret;
}
/* The packet number logic */
uint64_t picoquic_get_packet_number64(uint64_t highest, uint64_t mask, uint32_t pn)
{
uint64_t expected = highest + 1;
uint64_t not_mask_plus_one = (~mask) + 1;
uint64_t pn64 = (expected & mask) | pn;
if (pn64 < expected) {
uint64_t delta1 = expected - pn64;
uint64_t delta2 = not_mask_plus_one - delta1;
if (delta2 < delta1) {
pn64 += not_mask_plus_one;
}
} else {
uint64_t delta1 = pn64 - expected;
uint64_t delta2 = not_mask_plus_one - delta1;
if (delta2 <= delta1 && (pn64 & mask) > 0) {
/* Out of sequence packet from previous roll */
pn64 -= not_mask_plus_one;
}
}
return pn64;
}
/* Debug code used to test whether the PN decryption works as expected.
*/
void picoquic_log_pn_dec_trial(picoquic_cnx_t* cnx)
{
if (cnx->quic->log_pn_dec && (cnx->quic->F_log != NULL || cnx->f_binlog != NULL)){
void* pn_dec = cnx->crypto_context[picoquic_epoch_1rtt].pn_dec;
void* pn_enc = cnx->crypto_context[picoquic_epoch_1rtt].pn_enc;
uint8_t test_iv[32] = {
0, 1, 3, 4, 4, 6, 7, 8, 9,
0, 1, 3, 4, 4, 6, 7, 8, 9,
0, 1, 3, 4, 4, 6, 7, 8, 9,
0, 1 };
size_t mask_length = 5;
uint8_t mask_bytes[5] = { 0, 0, 0, 0, 0 };
uint8_t demask_bytes[5] = { 0, 0, 0, 0, 0 };
if (pn_enc != NULL) {
picoquic_pn_encrypt(pn_enc, test_iv, mask_bytes, mask_bytes, mask_length);
}
if (pn_dec != NULL) {
picoquic_pn_encrypt(pn_dec, test_iv, demask_bytes, demask_bytes, mask_length);
}
picoquic_log_app_message(cnx, "1RTT PN ENC/DEC, Phi: %d, signature = %02x%02x%02x%02x%02x, %02x%02x%02x%02x%02x",
cnx->key_phase_enc,
mask_bytes[0], mask_bytes[1], mask_bytes[2], mask_bytes[3], mask_bytes[4],
demask_bytes[0], demask_bytes[1], demask_bytes[2], demask_bytes[3], demask_bytes[4]);
}
}
/*
* Remove header protection
*/
int picoquic_remove_header_protection(picoquic_cnx_t* cnx,
uint8_t* bytes, picoquic_packet_header* ph)
{
int ret = 0;
size_t length = ph->offset + ph->payload_length; /* this may change after decrypting the PN */
void * pn_enc = NULL;
pn_enc = cnx->crypto_context[ph->epoch].pn_dec;
if (pn_enc != NULL)
{
/* The header length is not yet known, will only be known after the sequence number is decrypted */
size_t mask_length = 5;
size_t sample_offset = ph->pn_offset + 4;
size_t sample_size = picoquic_pn_iv_size(pn_enc);
uint8_t mask_bytes[5] = { 0, 0, 0, 0, 0 };
if (sample_offset + sample_size > length)
{
/* return an error */
/* Invalid packet format. Avoid crash! */
ph->pn = 0xFFFFFFFF;
ph->pnmask = 0xFFFFFFFF00000000ull;
ph->offset = ph->pn_offset;
DBG_PRINTF("Invalid packet length, type: %d, epoch: %d, pc: %d, pn-offset: %d, length: %d\n",
ph->ptype, ph->epoch, ph->pc, (int)ph->pn_offset, (int)length);
}
else
{ /* Decode */
uint8_t first_byte = bytes[0];
uint8_t first_mask = ((first_byte & 0x80) == 0x80) ? 0x0F : (cnx->is_loss_bit_enabled_incoming)?0x07:0x1F;
uint8_t pn_l;
uint32_t pn_val = 0;
picoquic_pn_encrypt(pn_enc, bytes + sample_offset, mask_bytes, mask_bytes, mask_length);
/* Decode the first byte */
first_byte ^= (mask_bytes[0] & first_mask);
pn_l = (first_byte & 3) + 1;
ph->pnmask = (0xFFFFFFFFFFFFFFFFull);
bytes[0] = first_byte;
/* Packet encoding is 1 to 4 bytes */
for (uint8_t i = 1; i <= pn_l; i++) {
pn_val <<= 8;
bytes[ph->offset] ^= mask_bytes[i];
pn_val += bytes[ph->offset++];
ph->pnmask <<= 8;
}
ph->pn = pn_val;
ph->payload_length -= pn_l;
/* Only set the key phase byte if short header */
if (ph->ptype == picoquic_packet_1rtt_protected) {
ph->key_phase = ((first_byte >> 2) & 1);
}
/* Build a packet number to 64 bits */
ph->pn64 = picoquic_get_packet_number64(
cnx->pkt_ctx[ph->pc].first_sack_item.end_of_sack_range, ph->pnmask, ph->pn);
/* Check the reserved bits */
ph->has_reserved_bit_set = ((first_byte & 0x80) == 0 && !cnx->is_loss_bit_enabled_incoming &&
(first_byte & 0x18) != 0);
}
}
else {
/* The pn_enc algorithm was not initialized. Avoid crash! */
ph->pn = 0xFFFFFFFF;
ph->pnmask = 0xFFFFFFFF00000000ull;
ph->offset = ph->pn_offset;
ph->pn64 = 0xFFFFFFFFFFFFFFFFull;
DBG_PRINTF("PN dec not ready, type: %d, epoch: %d, pc: %d, pn: %d\n",
ph->ptype, ph->epoch, ph->pc, (int)ph->pn);
ret = PICOQUIC_ERROR_AEAD_NOT_READY;
}
return ret;
}
/*
* Remove packet protection
*/
size_t picoquic_remove_packet_protection(picoquic_cnx_t* cnx,
uint8_t* bytes, picoquic_packet_header* ph,
uint64_t current_time, int * already_received)
{
size_t decoded;
int ret = 0;
/* verify that the packet is new */
if (already_received != NULL) {
if (picoquic_is_pn_already_received(cnx, ph->pc, ph->pn64) != 0) {
/* Set error type: already received */
*already_received = 1;
}
else {
*already_received = 0;
}
}
if (ph->epoch == picoquic_epoch_1rtt) {
int need_integrity_check = 1;
/* Manage key rotation */
if (ph->key_phase == cnx->key_phase_dec) {
/* AEAD Decrypt, in place */
decoded = picoquic_aead_decrypt_generic(bytes + ph->offset,
bytes + ph->offset, ph->payload_length, ph->pn64, bytes, ph->offset, cnx->crypto_context[picoquic_epoch_1rtt].aead_decrypt);
}
else if (ph->pn64 < cnx->crypto_rotation_sequence) {
/* This packet claims to be encoded with the old key */
if (current_time > cnx->crypto_rotation_time_guard) {
/* Too late. Ignore the packet. Could be some kind of attack. */
decoded = ph->payload_length + 1;
need_integrity_check = 0;
}
else if (cnx->crypto_context_old.aead_decrypt != NULL) {
decoded = picoquic_aead_decrypt_generic(bytes + ph->offset,
bytes + ph->offset, ph->payload_length, ph->pn64, bytes, ph->offset, cnx->crypto_context_old.aead_decrypt);
}
else {
/* old context is either not yet available, or already removed */
decoded = ph->payload_length + 1;
need_integrity_check = 0;
}
}
else {
/* TODO: check that this is larger than last received with current key */
/* These could only be a new key */
if (cnx->crypto_context_new.aead_decrypt == NULL &&
cnx->crypto_context_new.aead_encrypt == NULL) {
/* If the new context was already computed, don't do it again */
ret = picoquic_compute_new_rotated_keys(cnx);
}
/* if decoding succeeds, the rotation should be validated */
if (ret == 0 && cnx->crypto_context_new.aead_decrypt != NULL) {
decoded = picoquic_aead_decrypt_generic(bytes + ph->offset,
bytes + ph->offset, ph->payload_length, ph->pn64, bytes, ph->offset, cnx->crypto_context_new.aead_decrypt);
if (decoded <= ph->payload_length) {
/* Rotation only if the packet was correctly decrypted with the new key */
cnx->crypto_rotation_time_guard = current_time + cnx->path[0]->retransmit_timer;
cnx->crypto_rotation_sequence = ph->pn64;
picoquic_apply_rotated_keys(cnx, 0);
cnx->nb_crypto_key_rotations++;
if (cnx->crypto_context_new.aead_encrypt != NULL) {
/* If that move was not already validated, move to the new encryption keys */
picoquic_apply_rotated_keys(cnx, 1);
}
}
}
else {
/* new context could not be computed */
decoded = ph->payload_length + 1;
need_integrity_check = 0;
}
}
if (need_integrity_check && decoded > ph->payload_length) {
cnx->crypto_failure_count++;
if (cnx->crypto_failure_count > picoquic_aead_integrity_limit(cnx->crypto_context[picoquic_epoch_1rtt].aead_decrypt)) {
picoquic_log_app_message(cnx, "AEAD Integrity limit reached after 0x%" PRIx64 " failed decryptions.", cnx->crypto_failure_count);
(void)picoquic_connection_error(cnx, PICOQUIC_TRANSPORT_AEAD_LIMIT_REACHED, 0);
}
}
}
else {
/* TODO: get rid of handshake some time after handshake complete */
/* For all the other epochs, there is a single crypto context and no key rotation */
if (cnx->crypto_context[ph->epoch].aead_decrypt != NULL) {
decoded = picoquic_aead_decrypt_generic(bytes + ph->offset,
bytes + ph->offset, ph->payload_length, ph->pn64, bytes, ph->offset, cnx->crypto_context[ph->epoch].aead_decrypt);
}
else {
decoded = ph->payload_length + 1;
}
}
/* Add here a check that the PN key is still valid. */
if (decoded > ph->payload_length) {
picoquic_log_pn_dec_trial(cnx);
}
/* by conventions, values larger than input indicate error */
return decoded;
}
int picoquic_parse_header_and_decrypt(
picoquic_quic_t* quic,
const uint8_t* bytes,
size_t length,
size_t packet_length,
const struct sockaddr* addr_from,
uint64_t current_time,
picoquic_packet_header* ph,
picoquic_cnx_t** pcnx,
size_t * consumed,
int * new_ctx_created)
{
/* Parse the clear text header. Ret == 0 means an incorrect packet that could not be parsed */
int already_received = 0;
size_t decoded_length = 0;
int ret = picoquic_parse_packet_header(quic, bytes, length, addr_from, ph, pcnx, 1);
*new_ctx_created = 0;
if (ret == 0 ) {
if (ph->ptype != picoquic_packet_version_negotiation &&
ph->ptype != picoquic_packet_retry && ph->ptype != picoquic_packet_error) {
/* TODO: clarify length, payload length, packet length -- special case of initial packet */
length = ph->offset + ph->payload_length;
*consumed = length;
if (*pcnx == NULL) {
if (ph->ptype == picoquic_packet_initial) {
/* Create a connection context if the CI is acceptable */
if (packet_length < PICOQUIC_ENFORCED_INITIAL_MTU) {
/* Unexpected packet. Reject, drop and log. */
ret = PICOQUIC_ERROR_INITIAL_TOO_SHORT;
}
else if (ph->dest_cnx_id.id_len < PICOQUIC_ENFORCED_INITIAL_CID_LENGTH) {
/* Initial CID too short -- ignore the packet */
ret = PICOQUIC_ERROR_INITIAL_CID_TOO_SHORT;
}
else {
/* if listening is OK, listen */
*pcnx = picoquic_create_cnx(quic, ph->dest_cnx_id, ph->srce_cnx_id, addr_from, current_time, ph->vn, NULL, NULL, 0);
/* If an incoming connection was created, register the ICID */
*new_ctx_created = (*pcnx == NULL) ? 0 : 1;
if (*pcnx == NULL) {
DBG_PRINTF("%s", "Cannot create connection context\n");
}
}
}
}
else if (!(*pcnx)->client_mode && ph->ptype == picoquic_packet_initial && packet_length < PICOQUIC_ENFORCED_INITIAL_MTU) {
/* Unexpected packet. Reject, drop and log. */
ret = PICOQUIC_ERROR_INITIAL_TOO_SHORT;
}
if (ret == 0) {
if (*pcnx != NULL) {
/* Remove header protection at this point -- values of bytes will change */
ret = picoquic_remove_header_protection(*pcnx, (uint8_t *)bytes, ph);
if (ret == 0) {
decoded_length = picoquic_remove_packet_protection(*pcnx, (uint8_t *) bytes, ph, current_time, &already_received);
}
else {
decoded_length = ph->payload_length + 1;
}
if (decoded_length > (length - ph->offset)) {
if (ph->ptype == picoquic_packet_1rtt_protected &&
length >= PICOQUIC_RESET_PACKET_MIN_SIZE &&
memcmp(bytes + length - PICOQUIC_RESET_SECRET_SIZE,
(*pcnx)->path[0]->reset_secret, PICOQUIC_RESET_SECRET_SIZE) == 0) {
ret = PICOQUIC_ERROR_STATELESS_RESET;
}
else {
if (ret != PICOQUIC_ERROR_AEAD_NOT_READY) {
ret = PICOQUIC_ERROR_AEAD_CHECK;
}
if (*new_ctx_created) {
picoquic_delete_cnx(*pcnx);
*pcnx = NULL;
*new_ctx_created = 0;
}
}
}
else if (already_received != 0) {
ret = PICOQUIC_ERROR_DUPLICATE;
}
else {
ph->payload_length = (uint16_t)decoded_length;
}
}
else if (ph->ptype == picoquic_packet_1rtt_protected)
{
/* This may be a stateless reset.
* We test the address + putative reset secret pair against the hash table
* of registered secrets. If there is a match, the corresponding connection is
* found and the packet is marked as Stateless Reset */
if (length >= PICOQUIC_RESET_PACKET_MIN_SIZE) {
*pcnx = picoquic_cnx_by_secret(quic, bytes + length - PICOQUIC_RESET_SECRET_SIZE, addr_from);
if (*pcnx != NULL) {
ret = PICOQUIC_ERROR_STATELESS_RESET;
}
}
}
}
}
else {
*consumed = length;
}
}
return ret;
}
/*
* Processing of a version renegotiation packet.
*
* From the specification: When the client receives a Version Negotiation packet
* from the server, it should select an acceptable protocol version. If the server
* lists an acceptable version, the client selects that version and reattempts to
* create a connection using that version. Though the contents of a packet might
* not change in response to version negotiation, a client MUST increase the packet
* number it uses on every packet it sends. Packets MUST continue to use long headers
* and MUST include the new negotiated protocol version.
*/
int picoquic_incoming_version_negotiation(
picoquic_cnx_t* cnx,
uint8_t* bytes,
size_t length,
struct sockaddr* addr_from,
picoquic_packet_header* ph,
uint64_t current_time)
{
int ret = 0;
#ifdef _WINDOWS
UNREFERENCED_PARAMETER(addr_from);
UNREFERENCED_PARAMETER(current_time);
#endif
/* Check the connection state */
if (cnx->cnx_state != picoquic_state_client_init_sent) {
/* This is an unexpected packet. Log and drop.*/
DBG_PRINTF("Unexpected VN packet (%d), state %d, type: %d, epoch: %d, pc: %d, pn: %d\n",
cnx->client_mode, cnx->cnx_state, ph->ptype, ph->epoch, ph->pc, (int)ph->pn);
} else if (picoquic_compare_connection_id(&ph->dest_cnx_id, &cnx->path[0]->p_local_cnxid->cnx_id) != 0 || ph->vn != 0) {
/* Packet that do not match the "echo" checks should be logged and ignored */
DBG_PRINTF("VN packet (%d), does not pass echo test.\n", cnx->client_mode);
ret = PICOQUIC_ERROR_DETECTED;
} else {
/* Add DOS resilience */
const uint8_t * v_bytes = bytes + ph->offset;
const uint8_t* bytes_max = bytes + length;
int nb_vn = 0;
while (v_bytes < bytes_max) {
uint32_t vn = 0;
if ((v_bytes = picoquic_frames_uint32_decode(v_bytes, bytes_max, &vn)) == NULL){
DBG_PRINTF("VN packet (%d), length %zu, coding error after %d version numbers.\n",
cnx->client_mode, length, nb_vn);
ret = PICOQUIC_ERROR_DETECTED;
break;
} else if (vn == cnx->proposed_version) {
DBG_PRINTF("VN packet (%d), proposed_version[%d] = 0x%08x.\n", cnx->client_mode, nb_vn, vn);
ret = PICOQUIC_ERROR_DETECTED;
break;
}
nb_vn++;
}
if (ret == 0) {
if (nb_vn == 0) {
DBG_PRINTF("VN packet (%d), does not propose any version.\n", cnx->client_mode);
ret = PICOQUIC_ERROR_DETECTED;
}
else {
/* Signal VN to the application */
if (cnx->callback_fn && length > ph->offset) {
(void)(cnx->callback_fn)(cnx, 0, bytes + ph->offset, length - ph->offset,
picoquic_callback_version_negotiation, cnx->callback_ctx, NULL);
}
/* TODO: consider rewriting the version negotiation code */
DBG_PRINTF("%s", "Disconnect upon receiving version negotiation.\n");
cnx->cnx_state = picoquic_state_disconnected;
ret = 0;
}
}
}
return ret;
}
/*
* Send a version negotiation packet in response to an incoming packet
* sporting the wrong version number. This assumes that the original packet
* is at least 517 bytes long.
*/
void picoquic_prepare_version_negotiation(
picoquic_quic_t* quic,
struct sockaddr* addr_from,
struct sockaddr* addr_to,
unsigned long if_index_to,
picoquic_packet_header* ph,
uint8_t* original_bytes)
{
picoquic_cnx_t* cnx = NULL;
uint8_t dcid_length = original_bytes[5];
uint8_t * dcid = original_bytes + 6;
uint8_t scid_length = original_bytes[6 + dcid_length];
uint8_t* scid = original_bytes + 6 + dcid_length + 1;
/* Verify that this is not a spurious error by checking whether a connection context
* already exists */
if (dcid_length <= PICOQUIC_CONNECTION_ID_MAX_SIZE) {
(void) picoquic_parse_connection_id(dcid, dcid_length, &ph->dest_cnx_id);
if (ph->dest_cnx_id.id_len == quic->local_cnxid_length) {
if (quic->local_cnxid_length == 0) {
cnx = picoquic_cnx_by_net(quic, addr_from);
}
else {
cnx = picoquic_cnx_by_id(quic, ph->dest_cnx_id);
}
}
if (cnx == NULL) {
cnx = picoquic_cnx_by_icid(quic, &ph->dest_cnx_id, addr_from);
}
}
else {
(void)picoquic_parse_connection_id(dcid, PICOQUIC_CONNECTION_ID_MAX_SIZE, &ph->dest_cnx_id);
}
/* If no connection context exists, send back a version negotiation */
if (cnx == NULL) {
picoquic_stateless_packet_t* sp = picoquic_create_stateless_packet(quic);
if (sp != NULL) {
uint8_t* bytes = sp->bytes;
size_t byte_index = 0;
uint32_t rand_vn;
/* Packet type set to random value for version negotiation */
picoquic_public_random(bytes + byte_index, 1);
bytes[byte_index++] |= 0x80;
/* Set the version number to zero */
picoformat_32(bytes + byte_index, 0);
byte_index += 4;
/* Copy the connection identifiers */
bytes[byte_index++] = scid_length;
memcpy(bytes + byte_index, scid, scid_length);
byte_index += scid_length;
bytes[byte_index++] = dcid_length;
memcpy(bytes + byte_index, dcid, dcid_length);
byte_index += dcid_length;
/* Set the payload to the list of versions */
for (size_t i = 0; i < picoquic_nb_supported_versions; i++) {
picoformat_32(bytes + byte_index, picoquic_supported_versions[i].version);
byte_index += 4;
}
/* Add random reserved value as grease, but be careful to not match proposed version */
do {
rand_vn = (((uint32_t)picoquic_public_random_64()) & 0xF0F0F0F0) | 0x0A0A0A0A;
} while (rand_vn == ph->vn);
picoformat_32(bytes + byte_index, rand_vn);
byte_index += 4;
/* Set length and addresses, and queue. */
sp->length = byte_index;
picoquic_store_addr(&sp->addr_to, addr_from);
picoquic_store_addr(&sp->addr_local, addr_to);
sp->if_index_local = if_index_to;
sp->initial_cid = ph->dest_cnx_id;
sp->cnxid_log64 = picoquic_val64_connection_id(sp->initial_cid);
sp->ptype = picoquic_packet_version_negotiation;
picoquic_log_quic_pdu(quic, 1, picoquic_get_quic_time(quic), 0, addr_to, addr_from, sp->length);
picoquic_queue_stateless_packet(quic, sp);
}
}
}
/*
* Process an unexpected connection ID. This could be an old packet from a
* previous connection. If the packet type correspond to an encrypted value,
* the server can respond with a public reset.
*
* Per draft 14, the stateless reset starts with the packet code 0K110000.
* The packet has after the first byte at least 23 random bytes, and then
* the 16 bytes reset token.
*/
void picoquic_process_unexpected_cnxid(
picoquic_quic_t* quic,
size_t length,
struct sockaddr* addr_from,
struct sockaddr* addr_to,
unsigned long if_index_to,
picoquic_packet_header* ph)
{
if (length > PICOQUIC_RESET_PACKET_MIN_SIZE &&
ph->ptype == picoquic_packet_1rtt_protected) {
picoquic_stateless_packet_t* sp = picoquic_create_stateless_packet(quic);
if (sp != NULL) {
size_t pad_size = length - PICOQUIC_RESET_SECRET_SIZE -1;
uint8_t* bytes = sp->bytes;
size_t byte_index = 0;
if (pad_size > PICOQUIC_RESET_PACKET_PAD_SIZE) {
pad_size = (size_t)picoquic_public_uniform_random(pad_size - PICOQUIC_RESET_PACKET_PAD_SIZE)
+ PICOQUIC_RESET_PACKET_PAD_SIZE;
}
else {
pad_size = PICOQUIC_RESET_PACKET_PAD_SIZE;
}
/* Packet type set to short header, randomize the 5 lower bits */
bytes[byte_index++] = 0x30 | (uint8_t)(picoquic_public_random_64() & 0x1F);
/* Add the random bytes */
picoquic_public_random(bytes + byte_index, pad_size);
byte_index += pad_size;
/* Add the public reset secret */
(void)picoquic_create_cnxid_reset_secret(quic, &ph->dest_cnx_id, bytes + byte_index);
byte_index += PICOQUIC_RESET_SECRET_SIZE;
sp->length = byte_index;
sp->ptype = picoquic_packet_1rtt_protected;
picoquic_store_addr(&sp->addr_to, addr_from);
picoquic_store_addr(&sp->addr_local, addr_to);
sp->if_index_local = if_index_to;
sp->initial_cid = ph->dest_cnx_id;
sp->cnxid_log64 = picoquic_val64_connection_id(sp->initial_cid);
picoquic_log_context_free_app_message(quic, &sp->initial_cid, "Unexpected connection ID, sending stateless reset.\n");
picoquic_queue_stateless_packet(quic, sp);
}
}
}
/*
* Queue a stateless retry packet
*/
void picoquic_queue_stateless_retry(picoquic_cnx_t* cnx,
picoquic_packet_header* ph, struct sockaddr* addr_from,
struct sockaddr* addr_to,
unsigned long if_index_to,
uint8_t * token,
size_t token_length)
{
picoquic_stateless_packet_t* sp = picoquic_create_stateless_packet(cnx->quic);
void * integrity_aead = picoquic_find_retry_protection_context(cnx, 1);
size_t checksum_length = (integrity_aead == NULL) ? 0 : picoquic_aead_get_checksum_length(integrity_aead);
if (sp != NULL) {
uint8_t* bytes = sp->bytes;
size_t byte_index = 0;
size_t header_length = 0;
size_t pn_offset;
size_t pn_length;
cnx->path[0]->remote_cnxid = ph->srce_cnx_id;
byte_index = header_length = picoquic_create_packet_header(cnx, picoquic_packet_retry,
0, &cnx->path[0]->remote_cnxid, &cnx->path[0]->p_local_cnxid->cnx_id, 0,
bytes, &pn_offset, &pn_length);
/* In the old drafts, there is no header protection and the sender copies the ODCID
* in the packet. In the recent draft, the ODCID is not sent but
* is verified as part of integrity checksum */
if (integrity_aead == NULL) {
bytes[byte_index++] = cnx->initial_cnxid.id_len;
byte_index += picoquic_format_connection_id(bytes + byte_index,
PICOQUIC_MAX_PACKET_SIZE - byte_index - checksum_length, cnx->initial_cnxid);
}
/* Add the token */
memcpy(&bytes[byte_index], token, token_length);
byte_index += token_length;
/* Encode the retry integrity protection if required. */
byte_index = picoquic_encode_retry_protection(integrity_aead, bytes, PICOQUIC_MAX_PACKET_SIZE, byte_index, &cnx->initial_cnxid);
sp->length = byte_index;
sp->ptype = picoquic_packet_1rtt_protected;
picoquic_store_addr(&sp->addr_to, addr_from);
picoquic_store_addr(&sp->addr_local, addr_to);
sp->if_index_local = if_index_to;
sp->cnxid_log64 = picoquic_val64_connection_id(picoquic_get_logging_cnxid(cnx));
picoquic_log_outgoing_packet(cnx,
bytes, 0, pn_length, sp->length,
bytes, sp->length, picoquic_get_quic_time(cnx->quic));
picoquic_queue_stateless_packet(cnx->quic, sp);
}
}
/*
* Processing of initial or handshake messages when they are not expected
* any more. These messages could be used in a DOS attack against the
* connection, but they could also be legit messages sent by a peer
* that does not implement implicit ACK. They are processed to not
* cause any side effect, but to still generate ACK if the client
* needs them.
*/
void picoquic_ignore_incoming_handshake(
picoquic_cnx_t* cnx,
uint8_t* bytes,
picoquic_packet_header* ph,
uint64_t current_time)
{
/* The data starts at ph->index, and its length
* is ph->payload_length. */
int ret = 0;
size_t byte_index = 0;
int ack_needed = 0;
picoquic_packet_context_enum pc;
if (ph->ptype == picoquic_packet_initial) {
pc = picoquic_packet_context_initial;
}
else if (ph->ptype == picoquic_packet_handshake) {
pc = picoquic_packet_context_handshake;
}
else {
/* Not expected! */
return;
}
bytes += ph->offset;
while (ret == 0 && byte_index < ph->payload_length) {
size_t frame_length = 0;
int frame_is_pure_ack = 0;
ret = picoquic_skip_frame(&bytes[byte_index],
ph->payload_length - byte_index, &frame_length, &frame_is_pure_ack);
byte_index += frame_length;
if (frame_is_pure_ack == 0) {
ack_needed = 1;
}
}
/* If the packet contains ackable data, mark ack needed
* in the relevant packet context */
if (ret == 0 && ack_needed) {
picoquic_set_ack_needed(cnx, current_time, pc);
}
}
/*
* Processing of an incoming client initial packet,
* on an unknown connection context.
*/
int picoquic_incoming_client_initial(
picoquic_cnx_t** pcnx,
uint8_t* bytes,
size_t packet_length,
struct sockaddr* addr_from,
struct sockaddr* addr_to,
unsigned long if_index_to,
picoquic_packet_header* ph,
uint64_t current_time,
int new_context_created)
{
int ret = 0;
/* Logic to test the retry token.
* the "check token" value may change between the time a previous client connection
* attempt triggered a "retry" and the time that retry arrive, so it is not wrong
* to receive a retry token even if not required, as long as it decrypts correctly.
*/
if ((*pcnx)->cnx_state == picoquic_state_server_init &&
!(*pcnx)->quic->server_busy) {
int is_new_token = 0;
int is_wrong_token = 0;
if (ph->token_length > 0) {
if (picoquic_verify_retry_token((*pcnx)->quic, addr_from, current_time,
&is_new_token, &(*pcnx)->original_cnxid, &ph->dest_cnx_id, ph->pn,
ph->token_bytes, ph->token_length, new_context_created) != 0) {
is_wrong_token = 1;
}
else {
(*pcnx)->initial_validated = 1;
}
}
if (is_wrong_token && !is_new_token) {
(void)picoquic_connection_error(*pcnx, PICOQUIC_TRANSPORT_INVALID_TOKEN, 0);
ret = PICOQUIC_ERROR_INVALID_TOKEN;
}
else if ((*pcnx)->quic->check_token && (ph->token_length == 0 || is_wrong_token)){
uint8_t token_buffer[256];
size_t token_size;
if (picoquic_prepare_retry_token((*pcnx)->quic, addr_from,
current_time + PICOQUIC_TOKEN_DELAY_SHORT, &ph->dest_cnx_id,
&(*pcnx)->path[0]->p_local_cnxid->cnx_id, ph->pn,
token_buffer, sizeof(token_buffer), &token_size) != 0) {
ret = PICOQUIC_ERROR_MEMORY;
}
else {
picoquic_queue_stateless_retry(*pcnx, ph,
addr_from, addr_to, if_index_to, token_buffer, token_size);
ret = PICOQUIC_ERROR_RETRY;
}
}
}
if (ret == 0) {
if (picoquic_compare_connection_id(&ph->dest_cnx_id, &(*pcnx)->path[0]->p_local_cnxid->cnx_id) == 0) {
(*pcnx)->initial_validated = 1;
}
if (!(*pcnx)->initial_validated && (*pcnx)->pkt_ctx[picoquic_packet_context_initial].retransmit_oldest != NULL
&& packet_length >= PICOQUIC_ENFORCED_INITIAL_MTU) {
/* In most cases, receiving more than 1 initial packets before validation indicates that the
* client is repeating data that it believes is lost. We set the initial_repeat_needed flag
* to trigger such repetitions. There are exceptions, e.g., clients sending large client hellos
* that require multiple packets. These exceptions are detected and handled during packet
* processing. */
(*pcnx)->initial_repeat_needed = 1;
}
if ((*pcnx)->cnx_state == picoquic_state_server_init &&
(*pcnx)->quic->server_busy) {
(*pcnx)->local_error = PICOQUIC_TRANSPORT_SERVER_BUSY;
(*pcnx)->cnx_state = picoquic_state_handshake_failure;
}
else if ((*pcnx)->cnx_state == picoquic_state_server_init &&
(*pcnx)->initial_cnxid.id_len < PICOQUIC_ENFORCED_INITIAL_CID_LENGTH) {
(*pcnx)->local_error = PICOQUIC_TRANSPORT_PROTOCOL_VIOLATION;
(*pcnx)->cnx_state = picoquic_state_handshake_failure;
}
else if ((*pcnx)->cnx_state < picoquic_state_server_almost_ready) {
/* Document the incoming addresses */
if ((*pcnx)->path[0]->local_addr.ss_family == 0 && addr_to != NULL) {
picoquic_store_addr(&(*pcnx)->path[0]->local_addr, addr_to);
}
if ((*pcnx)->path[0]->peer_addr.ss_family == 0 && addr_from != NULL) {
picoquic_store_addr(&(*pcnx)->path[0]->peer_addr, addr_from);
}
(*pcnx)->path[0]->if_index_dest = if_index_to;
/* decode the incoming frames */
if (ret == 0) {
ret = picoquic_decode_frames(*pcnx, (*pcnx)->path[0],
bytes + ph->offset, ph->payload_length, ph->epoch, addr_from, addr_to, current_time);
}
/* processing of client initial packet */
if (ret == 0) {
int data_consumed = 0;
/* initialization of context & creation of data */
ret = picoquic_tls_stream_process(*pcnx, &data_consumed);
/* The "initial_repeat_needed" flag is set if multiple initial packets are
* received while the connection is not yet validated. In most cases, this indicates
* that the client repeated some initial packets, or sent some gratuitous initial
* packets, because it believes its own initial packet was lost. The flag forces
* immediate retransmission of initial packets. However, there are cases when the
* client sent large client hello messages that do not fit on a single packets. In
* those cases, the flag should not be set. We detect that by testing whether new
* TLS data was received in the packet. */
if (data_consumed) {
(*pcnx)->initial_repeat_needed = 0;
}
}
}
else if ((*pcnx)->cnx_state < picoquic_state_ready) {
/* Require an acknowledgement if the packet contains ackable frames */
picoquic_ignore_incoming_handshake(*pcnx, bytes, ph, current_time);
}
else {
/* Initial keys should have been discarded, treat packet as unexpected */
ret = PICOQUIC_ERROR_UNEXPECTED_PACKET;
}
}
if (ret == PICOQUIC_ERROR_INVALID_TOKEN && (*pcnx)->cnx_state == picoquic_state_handshake_failure) {
ret = 0;
}
if (ret != 0 || (*pcnx)->cnx_state == picoquic_state_disconnected) {
/* This is bad. If this is an initial attempt, delete the connection */
if (new_context_created) {
picoquic_delete_cnx(*pcnx);
*pcnx = NULL;
ret = PICOQUIC_ERROR_CONNECTION_DELETED;
}
}
return ret;
}
/*
* Processing of a server retry
*
* The packet number and connection ID fields echo the corresponding fields from the
* triggering client packet. This allows a client to verify that the server received its packet.
*
* A Server Stateless Retry packet is never explicitly acknowledged in an ACK frame by a client.
* Receiving another Client Initial packet implicitly acknowledges a Server Stateless Retry packet.
*
* After receiving a Server Stateless Retry packet, the client uses a new Client Initial packet
* containing the next token. In effect, the next cryptographic
* handshake message is sent on a new connection.
*/
int picoquic_incoming_retry(
picoquic_cnx_t* cnx,
uint8_t* bytes,
picoquic_packet_header* ph,
uint64_t current_time)
{
int ret = 0;
size_t token_length = 0;
uint8_t * token = NULL;
if ((cnx->cnx_state != picoquic_state_client_init_sent && cnx->cnx_state != picoquic_state_client_init_resent) ||
cnx->original_cnxid.id_len != 0) {
ret = PICOQUIC_ERROR_UNEXPECTED_PACKET;
} else {
/* Verify that the header is a proper echo of what was sent */
if (ph->vn != picoquic_supported_versions[cnx->version_index].version) {
/* Packet that do not match the "echo" checks should be logged and ignored */
ret = PICOQUIC_ERROR_UNEXPECTED_PACKET;
} else if (ph->pn64 != 0) {
/* after draft-12, PN is required to be 0 */
ret = PICOQUIC_ERROR_UNEXPECTED_PACKET;
}
}
if (ret == 0) {
/* Parse the retry frame */
void * integrity_aead = picoquic_find_retry_protection_context(cnx, 0);
size_t byte_index = ph->offset;
size_t data_length = ph->offset + ph->payload_length;
/* Assume that is aead context is null, this is the old format and the
* integrity shall be verifed by checking the ODCID */
if (integrity_aead == NULL) {
uint8_t odcil = bytes[byte_index++];
if (odcil != cnx->initial_cnxid.id_len || (size_t)odcil + 1u > ph->payload_length ||
memcmp(cnx->initial_cnxid.id, &bytes[byte_index], odcil) != 0) {
/* malformed ODCIL, or does not match initial cid; ignore */
ret = PICOQUIC_ERROR_UNEXPECTED_PACKET;
picoquic_log_app_message(cnx, "Retry packet rejected: odcid check failed");
}
else {
byte_index += odcil;
}
}
else {
ret = picoquic_verify_retry_protection(integrity_aead, bytes, &data_length, byte_index, &cnx->initial_cnxid);
picoquic_log_app_message(cnx, "Retry packet rejected: integrity check failed");
}
if (ret == 0) {
token_length = data_length - byte_index;
if (token_length > 0) {
token = malloc(token_length);
if (token == NULL) {
ret = PICOQUIC_ERROR_MEMORY;
}
else {
memcpy(token, &bytes[byte_index], token_length);
}
}
}
}
if (ret == 0) {
/* Close the log, because it is keyed by initial_cnxid */
picoquic_log_close_connection(cnx);
/* if this is the first reset, reset the original cid */
if (cnx->original_cnxid.id_len == 0) {
cnx->original_cnxid = cnx->initial_cnxid;
}
/* reset the initial CNX_ID to the version sent by the server */
cnx->initial_cnxid = ph->srce_cnx_id;
/* keep a copy of the retry token */
if (cnx->retry_token != NULL) {
free(cnx->retry_token);
}
cnx->retry_token = token;
cnx->retry_token_length = (uint16_t)token_length;
picoquic_reset_cnx(cnx, current_time);
/* Mark the packet as not required for ack */
ret = PICOQUIC_ERROR_RETRY;
}
return ret;
}
/*
* Processing of a server clear text packet.
*/
int picoquic_incoming_server_initial(
picoquic_cnx_t* cnx,
uint8_t* bytes,
struct sockaddr* addr_to,
unsigned long if_index_to,
picoquic_packet_header* ph,
uint64_t current_time)
{
int ret = 0;
if (cnx->cnx_state == picoquic_state_client_init_sent || cnx->cnx_state == picoquic_state_client_init_resent) {
cnx->cnx_state = picoquic_state_client_handshake_start;
}
/* Check the server cnx id */
if ((!picoquic_is_connection_id_null(&cnx->path[0]->remote_cnxid) || cnx->cnx_state > picoquic_state_client_handshake_start) &&
picoquic_compare_connection_id(&cnx->path[0]->remote_cnxid, &ph->srce_cnx_id) != 0) {
ret = PICOQUIC_ERROR_CNXID_CHECK; /* protocol error */
}
if (ret == 0) {
if (cnx->cnx_state <= picoquic_state_client_handshake_start) {
/* Document local address if not present */
if (cnx->path[0]->local_addr.ss_family == 0 && addr_to != NULL) {
picoquic_store_addr(&cnx->path[0]->local_addr, addr_to);
}
cnx->path[0]->if_index_dest = if_index_to;
/* Accept the incoming frames */
if (ph->payload_length == 0) {
/* empty payload! */
ret = picoquic_connection_error(cnx, PICOQUIC_TRANSPORT_PROTOCOL_VIOLATION, 0);
}
else {
ret = picoquic_decode_frames(cnx, cnx->path[0],
bytes + ph->offset, ph->payload_length, ph->epoch, NULL, addr_to, current_time);
}
/* processing of initial packet */
if (ret == 0) {
ret = picoquic_tls_stream_process(cnx, NULL);
}
}
else if (cnx->cnx_state < picoquic_state_ready) {
/* Require an acknowledgement if the packet contains ackable frames */
picoquic_ignore_incoming_handshake(cnx, bytes, ph, current_time);
}
else {
/* Initial keys should have been discarded, treat packet as unexpected */
ret = PICOQUIC_ERROR_UNEXPECTED_PACKET;
}
}
return ret;
}
int picoquic_incoming_server_handshake(
picoquic_cnx_t* cnx,
uint8_t* bytes,
struct sockaddr* addr_to,
unsigned long if_index_to,
picoquic_packet_header* ph,
uint64_t current_time)
{
int ret = 0;
#ifdef _WINDOWS
UNREFERENCED_PARAMETER(addr_to);
UNREFERENCED_PARAMETER(if_index_to);
#endif
int restricted = cnx->cnx_state != picoquic_state_client_handshake_start;
if (picoquic_compare_connection_id(&cnx->path[0]->remote_cnxid, &ph->srce_cnx_id) != 0) {
ret = PICOQUIC_ERROR_CNXID_CHECK; /* protocol error */
}
if (ret == 0) {
if (cnx->cnx_state < picoquic_state_ready) {
/* Accept the incoming frames */
if (ph->payload_length == 0) {
/* empty payload! */
ret = picoquic_connection_error(cnx, PICOQUIC_TRANSPORT_PROTOCOL_VIOLATION, 0);
}
else {
ret = picoquic_decode_frames(cnx, cnx->path[0],
bytes + ph->offset, ph->payload_length, ph->epoch, NULL, addr_to, current_time);
}
/* processing of initial packet */
if (ret == 0 && restricted == 0) {
ret = picoquic_tls_stream_process(cnx, NULL);
}
}
else {
/* Initial keys should have been discarded, treat packet as unexpected */
ret = PICOQUIC_ERROR_UNEXPECTED_PACKET;
}
}
return ret;
}
/*
* Processing of client handshake packet.
*/
int picoquic_incoming_client_handshake(
picoquic_cnx_t* cnx,
uint8_t* bytes,
picoquic_packet_header* ph,
uint64_t current_time)
{
int ret = 0;
cnx->initial_validated = 1;
cnx->initial_repeat_needed = 0;
if (cnx->cnx_state < picoquic_state_server_almost_ready) {
if (picoquic_compare_connection_id(&ph->srce_cnx_id, &cnx->path[0]->remote_cnxid) != 0) {
ret = PICOQUIC_ERROR_CNXID_CHECK;
} else {
/* Accept the incoming frames */
if (ph->payload_length == 0) {
/* empty payload! */
ret = picoquic_connection_error(cnx, PICOQUIC_TRANSPORT_PROTOCOL_VIOLATION, 0);
}
else {
ret = picoquic_decode_frames(cnx, cnx->path[0],
bytes + ph->offset, ph->payload_length, ph->epoch, NULL, NULL, current_time);
}
/* processing of client clear text packet */
if (ret == 0) {
/* Any successful handshake packet is an explicit ack of initial packets */
picoquic_implicit_handshake_ack(cnx, picoquic_packet_context_initial, current_time);
picoquic_crypto_context_free(&cnx->crypto_context[picoquic_epoch_initial]);
/* If TLS data present, progress the TLS state */
ret = picoquic_tls_stream_process(cnx, NULL);
/* If TLS FIN has been received, the server side handshake is ready */
if (!cnx->client_mode && cnx->cnx_state < picoquic_state_ready && picoquic_is_tls_complete(cnx)) {
picoquic_ready_state_transition(cnx, current_time);
}
}
}
}
else if (cnx->cnx_state <= picoquic_state_ready) {
/* Because the client is never guaranteed to discard handshake keys,
* we need to keep it for the duration of the connection.
* Process the incoming frames, ignore them, but
* require an acknowledgement if the packet contains ackable frames */
picoquic_ignore_incoming_handshake(cnx, bytes, ph, current_time);
}
else {
/* Not expected. Log and ignore. */
ret = PICOQUIC_ERROR_UNEXPECTED_PACKET;
}
return ret;
}
/*
* Processing of stateless reset packet.
*/
int picoquic_incoming_stateless_reset(
picoquic_cnx_t* cnx)
{
/* Stateless reset. The connection should be abandonned */
cnx->cnx_state = picoquic_state_disconnected;
if (cnx->callback_fn) {
(void)(cnx->callback_fn)(cnx, 0, NULL, 0, picoquic_callback_stateless_reset, cnx->callback_ctx, NULL);
}
return PICOQUIC_ERROR_AEAD_CHECK;
}
/*
* Processing of 0-RTT packet
*/
int picoquic_incoming_0rtt(
picoquic_cnx_t* cnx,
uint8_t* bytes,
picoquic_packet_header* ph,
uint64_t current_time)
{
int ret = 0;
if (!(picoquic_compare_connection_id(&ph->dest_cnx_id, &cnx->initial_cnxid) == 0 ||
picoquic_compare_connection_id(&ph->dest_cnx_id, &cnx->path[0]->p_local_cnxid->cnx_id) == 0) ||
picoquic_compare_connection_id(&ph->srce_cnx_id, &cnx->path[0]->remote_cnxid) != 0) {
ret = PICOQUIC_ERROR_CNXID_CHECK;
} else if (cnx->cnx_state == picoquic_state_server_almost_ready ||
cnx->cnx_state == picoquic_state_server_false_start ||
(cnx->cnx_state == picoquic_state_ready && !cnx->is_1rtt_received)) {
if (ph->vn != picoquic_supported_versions[cnx->version_index].version) {
ret = picoquic_connection_error(cnx, PICOQUIC_TRANSPORT_PROTOCOL_VIOLATION, 0);
} else {
/* Accept the incoming frames */
if (ph->payload_length == 0) {
/* empty payload! */
ret = picoquic_connection_error(cnx, PICOQUIC_TRANSPORT_PROTOCOL_VIOLATION, 0);
}
else {
cnx->nb_zero_rtt_received++;
ret = picoquic_decode_frames(cnx, cnx->path[0],
bytes + ph->offset, ph->payload_length, ph->epoch, NULL, NULL, current_time);
}
if (ret == 0) {
/* Processing of TLS messages -- EOED */
ret = picoquic_tls_stream_process(cnx, NULL);
}
}
} else {
/* Not expected. Log and ignore. */
ret = PICOQUIC_ERROR_UNEXPECTED_PACKET;
}
return ret;
}
/*
Find path of incoming packet
A path is defined by a pair of addresses. The path is created by the client
when it learns about a new local or remote address. It is created by the
server when it receives data from a not yet identified address pair.
We associate a local CID with a path. This is the CID that the peer uses
to send packet. This is a loose association. When a packet is received, the
packet is associated with a path based on the address tuple. If this is a
new tuple, a new path should be created, unless too many paths have been
created already (some heuristics needed there).
Different scenarios play here:
- If the incoming CID has not yet been seen, we treat arrival as a
migration attempt and pursue the validation sequence.
- If this is the same incoming CID as an existing path, we treat it
as an indication of NAT rebinding. We may need some heuristic to
decide whether this is legit or an attack. If this may be legit, we
create a new path and send challenges on both the new and the old path.
- If this is the same tuple and a different incoming CID, we treat that
as an attempt by the peer to change the CID for privacy reason. On this
event, the server picks a new CID for the path if available. (May need
some safety there, e.g. only pick a new CID if the incoming CID sequence
is higher than the old one.)
NAT rebinding should only happen if the address was changed in the
network, either by a NAT or by an attacker. NATs are:
- rare but not unheard of in front of servers
- rare with IPv6
- rare if the connection is sustained
A small problem here is that the QUIC test suite include some pretty
unrealistic NAT rebinding simulations, so we cannot be too strict. In
order to pass the test suites, we will accept the first rebinding
attempt as genuine, and be more picky with the next ones. They may have
to wait until validation timers expire.
Local CID are kept in a list, and are associated with paths by a reference.
If a local CID is retired, the reference is zeroed. When a new packet arrives
on path with a new CID, the reference is reset.
If we cannot associate an existing path with a packet and also
cannot create a new path, we treat the packet as arriving on the
default path.
*/
int picoquic_find_incoming_path(picoquic_cnx_t* cnx, picoquic_packet_header* ph,
struct sockaddr* addr_from,
struct sockaddr* addr_to,
uint64_t current_time,
int* p_path_id)
{
int ret = 0;
int partial_match_path = -1;
int nat_rebinding_path = -1;
int nat_rebinding_total = 0;
int path_id = picoquic_find_path_by_address(cnx, addr_to, addr_from, &partial_match_path);
if (path_id < 0 && partial_match_path >= 0) {
/* Document the source address and promote to full match. */
path_id = partial_match_path;
picoquic_store_addr(&cnx->path[path_id]->local_addr, addr_to);
}
if (path_id >= 0) {
/* Packet arriving on an existing path */
if (cnx->path[path_id]->p_local_cnxid == NULL) {
/* First packet from the peer. Remember the CNX ID. No further action */
cnx->path[path_id]->p_local_cnxid = picoquic_find_local_cnxid(cnx, &ph->dest_cnx_id);
} else if (picoquic_compare_connection_id(&cnx->path[path_id]->p_local_cnxid->cnx_id, &ph->dest_cnx_id) != 0) {
/* The peer switched to a new CID */
cnx->path[path_id]->p_local_cnxid = picoquic_find_local_cnxid(cnx, &ph->dest_cnx_id);
if (cnx->client_mode == 0 && cnx->cnxid_stash_first != NULL && path_id == 0) {
/* If on a server, dereference the current CID, and pick a new one */
(void)picoquic_renew_connection_id(cnx, path_id);
}
}
}
else {
/* No valid path. Need to create one, but only if this is
* within our resource boundaries. This is the place where
* we might want to do some heuristics.
*
* Check whether this is a duplicate of an existing path.
*/
for (int i = 0; i < cnx->nb_paths; i++) {
if (cnx->path[i]->p_local_cnxid != NULL &&
picoquic_compare_connection_id(&cnx->path[i]->p_local_cnxid->cnx_id, &ph->dest_cnx_id) == 0) {
if (nat_rebinding_total == 0) {
nat_rebinding_path = i;
}
nat_rebinding_total++;
}
}
if (cnx->nb_paths < PICOQUIC_NB_PATH_TARGET
&& picoquic_create_path(cnx, current_time, addr_to, addr_from) > 0) {
/* The peer is probing for a new path, or there was a path rebinding */
path_id = cnx->nb_paths - 1;
if (!cnx->client_mode && cnx->local_parameters.prefered_address.is_defined) {
struct sockaddr_storage dest_addr;
memset(&dest_addr, 0, sizeof(struct sockaddr_storage));
/* program a migration. */
if (addr_to->sa_family== AF_INET) {
/* configure an IPv4 sockaddr */
struct sockaddr_in* d4 = (struct sockaddr_in*) & dest_addr;
d4->sin_family = AF_INET;
d4->sin_port = htons(cnx->local_parameters.prefered_address.ipv4Port);
memcpy(&d4->sin_addr, cnx->local_parameters.prefered_address.ipv4Address, 4);
} else if (addr_to->sa_family == AF_INET6){
/* configure an IPv6 sockaddr */
struct sockaddr_in6* d6 = (struct sockaddr_in6*) & dest_addr;
d6->sin6_family = AF_INET6;
d6->sin6_port = htons(cnx->local_parameters.prefered_address.ipv6Port);
memcpy(&d6->sin6_addr, cnx->local_parameters.prefered_address.ipv6Address, 16);
}
if (picoquic_compare_addr(addr_to, (struct sockaddr*) & dest_addr) == 0) {
cnx->path[path_id]->path_is_preferred_path = 1;
}
}
if (picoquic_assign_peer_cnxid_to_path(cnx, path_id) != 0){
/* Copy the destination ID from an existing path */
int alt_path = (nat_rebinding_path >= 0) ? nat_rebinding_path : 0;
cnx->path[path_id]->remote_cnxid = cnx->path[alt_path]->remote_cnxid;
cnx->path[path_id]->remote_cnxid_sequence = cnx->path[alt_path]->remote_cnxid_sequence;
memcpy(cnx->path[path_id]->reset_secret, cnx->path[alt_path]->reset_secret,
PICOQUIC_RESET_SECRET_SIZE);
}
cnx->path[path_id]->path_is_published = 1;
cnx->path[path_id]->p_local_cnxid = picoquic_find_local_cnxid(cnx, &ph->dest_cnx_id);
picoquic_register_path(cnx, cnx->path[path_id]);
picoquic_set_path_challenge(cnx, path_id, current_time);
/* If this is a NAT rebinding, also set a challenge on the original path */
if (nat_rebinding_path >= 0) {
/* Treat this as a NAT rebinding. Mark the old path for validation */
picoquic_set_path_challenge(cnx, nat_rebinding_path, current_time);
}
}
else {
DBG_PRINTF("%s", "Cannot create new path for incoming packet");
if (nat_rebinding_path >= 0) {
path_id = nat_rebinding_path;
}
else {
path_id = 0;
}
}
}
#if 1
if (cnx->nb_paths > 2 && cnx->path[1] == cnx->path[2]) {
DBG_PRINTF("%s", "Bug");
}
#endif
*p_path_id = path_id;
return ret;
}
/*
* ECN Accounting. This is only called if the packet was processed successfully.
*/
void picoquic_ecn_accounting(picoquic_cnx_t* cnx,
unsigned char received_ecn, picoquic_packet_context_enum pc)
{
switch (received_ecn & 0x03) {
case 0x00:
break;
case 0x01: /* ECN_ECT_1 */
cnx->pkt_ctx[pc].ecn_ect1_total_local++;
cnx->pkt_ctx[pc].sending_ecn_ack |= 1;
break;
case 0x02: /* ECN_ECT_0 */
cnx->pkt_ctx[pc].ecn_ect0_total_local++;
cnx->pkt_ctx[pc].sending_ecn_ack |= 1;
break;
case 0x03: /* ECN_CE */
cnx->pkt_ctx[pc].ecn_ce_total_local++;
cnx->pkt_ctx[pc].sending_ecn_ack |= 1;
break;
}
}
/*
* Processing of client encrypted packet.
*/
int picoquic_incoming_encrypted(
picoquic_cnx_t* cnx,
uint8_t* bytes,
picoquic_packet_header* ph,
struct sockaddr* addr_from,
struct sockaddr* addr_to,
int if_index_to,
unsigned char received_ecn,
uint64_t current_time)
{
int ret = 0;
int path_id = -1;
/* Check the packet */
if (cnx->cnx_state < picoquic_state_client_almost_ready) {
/* handshake is not complete. Just ignore the packet */
ret = PICOQUIC_ERROR_UNEXPECTED_PACKET;
}
else if (cnx->cnx_state == picoquic_state_disconnected) {
/* Connection is disconnected. Just ignore the packet */
ret = PICOQUIC_ERROR_UNEXPECTED_PACKET;
}
else {
/* Packet is correct */
/* TODO: consider treatment of migration during closing mode */
/* Do not process data in closing or draining modes */
if (cnx->cnx_state >= picoquic_state_closing_received) {
/* only look for closing frames in closing modes */
if (cnx->cnx_state == picoquic_state_closing) {
int closing_received = 0;
ret = picoquic_decode_closing_frames(
bytes + ph->offset, ph->payload_length, &closing_received);
if (ret == 0) {
if (closing_received) {
if (cnx->client_mode) {
cnx->cnx_state = picoquic_state_disconnected;
}
else {
cnx->cnx_state = picoquic_state_draining;
}
}
else {
picoquic_set_ack_needed(cnx, current_time, ph->pc);
}
}
}
else {
/* Just ignore the packets in closing received or draining mode */
ret = PICOQUIC_ERROR_UNEXPECTED_PACKET;
}
}
else {
if (ph->payload_length == 0) {
/* empty payload! */
ret = picoquic_connection_error(cnx, PICOQUIC_TRANSPORT_PROTOCOL_VIOLATION, 0);
}
else if (ph->has_reserved_bit_set) {
/* Reserved bits were not set to zero */
ret = picoquic_connection_error(cnx, PICOQUIC_TRANSPORT_PROTOCOL_VIOLATION, 0);
}
else {
/* Find the arrival path and update its state */
ret = picoquic_find_incoming_path(cnx, ph, addr_from, addr_to, current_time, &path_id);
}
if (ret == 0) {
picoquic_path_t * path_x = cnx->path[path_id];
path_x->if_index_dest = if_index_to;
cnx->is_1rtt_received = 1;
picoquic_spin_function_table[cnx->spin_policy].spinbit_incoming(cnx, path_x, ph);
/* Accept the incoming frames */
ret = picoquic_decode_frames(cnx, cnx->path[path_id],
bytes + ph->offset, ph->payload_length, ph->epoch, addr_from, addr_to, current_time);
}
if (ret == 0) {
/* Compute receive bandwidth */
cnx->path[path_id]->received += ph->offset + ph->payload_length +
picoquic_get_checksum_length(cnx, picoquic_epoch_1rtt);
if (cnx->path[path_id]->receive_rate_epoch == 0) {
cnx->path[path_id]->received_prior = cnx->path[path_id]->received;
cnx->path[path_id]->receive_rate_epoch = current_time;
}
else {
uint64_t delta = current_time - cnx->path[path_id]->receive_rate_epoch;
if (delta > cnx->path[path_id]->smoothed_rtt && delta > PICOQUIC_BANDWIDTH_TIME_INTERVAL_MIN) {
cnx->path[path_id]->receive_rate_estimate = ((cnx->path[path_id]->received - cnx->path[path_id]->received_prior)*1000000) / delta;
cnx->path[path_id]->received_prior = cnx->path[path_id]->received;
cnx->path[path_id]->receive_rate_epoch = current_time;
if (cnx->path[path_id]->receive_rate_estimate > cnx->path[path_id]->receive_rate_max) {
cnx->path[path_id]->receive_rate_max = cnx->path[path_id]->receive_rate_estimate;
if (path_id == 0 && !cnx->is_ack_frequency_negotiated) {
cnx->ack_gap_remote = picoquic_compute_ack_gap(cnx, cnx->path[0]->receive_rate_max);
}
}
}
}
/* Processing of TLS messages */
ret = picoquic_tls_stream_process(cnx, NULL);
}
if (ret == 0 && picoquic_cnx_is_still_logging(cnx)) {
picoquic_log_cc_dump(cnx, current_time);
}
}
}
return ret;
}
/* Processing of packets received before they could be fully decrypted
*/
int picoquic_incoming_not_decrypted(
picoquic_cnx_t* cnx,
picoquic_packet_header* ph,
uint64_t current_time,
uint8_t * bytes,
size_t length,
struct sockaddr * addr_from,
struct sockaddr* addr_to,
int if_index_to,
unsigned char received_ecn)
{
int buffered = 0;
if (cnx->cnx_state < picoquic_state_ready) {
if (cnx->path[0]->p_local_cnxid->cnx_id.id_len > 0 &&
picoquic_compare_connection_id(&cnx->path[0]->p_local_cnxid->cnx_id, &ph->dest_cnx_id) == 0)
{
/* verifying the destination cnx id is a strong hint that the peer is responding */
if (cnx->path[0]->smoothed_rtt == PICOQUIC_INITIAL_RTT
&& cnx->path[0]->rtt_variant == 0 &&
current_time - cnx->start_time < cnx->path[0]->smoothed_rtt) {
/* We received a first packet from the peer! */
picoquic_update_path_rtt(cnx, cnx->path[0], cnx->start_time, current_time, 0);
}
if (length <= PICOQUIC_MAX_PACKET_SIZE &&
((ph->ptype == picoquic_packet_handshake && cnx->client_mode) || ph->ptype == picoquic_packet_1rtt_protected)) {
/* stash a copy of the incoming message for processing once the keys are available */
picoquic_stateless_packet_t* packet = picoquic_create_stateless_packet(cnx->quic);
if (packet != NULL) {
packet->length = length;
packet->ptype = ph->ptype;
memcpy(packet->bytes, bytes, length);
packet->next_packet = cnx->first_sooner;
cnx->first_sooner = packet;
picoquic_store_addr(&packet->addr_local, addr_to);
picoquic_store_addr(&packet->addr_to, addr_from);
packet->if_index_local = if_index_to;
packet->received_ecn = received_ecn;
packet->receive_time = current_time;
buffered = 1;
}
}
}
}
return buffered;
}
/*
* Processing of the packet that was just received from the network.
*/
int picoquic_incoming_segment(
picoquic_quic_t* quic,
uint8_t* bytes,
size_t length,
size_t packet_length,
size_t* consumed,
struct sockaddr* addr_from,
struct sockaddr* addr_to,
int if_index_to,
unsigned char received_ecn,
uint64_t current_time,
uint64_t receive_time,
picoquic_connection_id_t* previous_dest_id)
{
int ret = 0;
picoquic_cnx_t* cnx = NULL;
picoquic_packet_header ph;
int new_context_created = 0;
int is_first_segment = 0;
int is_buffered = 0;
/* Parse the header and decrypt the segment */
ret = picoquic_parse_header_and_decrypt(quic, bytes, length, packet_length, addr_from,
current_time, &ph, &cnx, consumed, &new_context_created);
/* Verify that the segment coalescing is for the same destination ID */
if (picoquic_is_connection_id_null(previous_dest_id)) {
/* This is the first segment in the incoming packet */
*previous_dest_id = ph.dest_cnx_id;
is_first_segment = 1;
/* if needed, log that the packet is received */
if (cnx != NULL) {
picoquic_log_pdu(cnx, 1, current_time, addr_from, addr_to, packet_length);
}
else {
picoquic_log_quic_pdu(quic, 1, current_time, picoquic_val64_connection_id(ph.dest_cnx_id),
addr_from, addr_to, packet_length);
}
}
else {
if (ret == 0 && picoquic_compare_connection_id(previous_dest_id, &ph.dest_cnx_id) != 0) {
ret = PICOQUIC_ERROR_CNXID_SEGMENT;
}
}
/* Store packet if received in advance of encryption keys */
if (ret == PICOQUIC_ERROR_AEAD_NOT_READY &&
cnx != NULL) {
is_buffered = picoquic_incoming_not_decrypted(cnx, &ph, current_time, bytes, length, addr_from, addr_to, if_index_to, received_ecn);
}
/* Log the incoming packet */
if (cnx != NULL) {
if (ret == 0) {
picoquic_log_packet(cnx, 1, current_time, &ph, bytes, *consumed);
}
else if (is_buffered) {
picoquic_log_buffered_packet(cnx, ph.ptype, current_time);
} else {
picoquic_log_dropped_packet(cnx, &ph, length, ret, bytes, current_time);
}
}
if (ret == PICOQUIC_ERROR_VERSION_NOT_SUPPORTED) {
if (packet_length >= PICOQUIC_ENFORCED_INITIAL_MTU) {
/* use the result of parsing to consider version negotiation */
picoquic_prepare_version_negotiation(quic, addr_from, addr_to, if_index_to, &ph, bytes);
}
} else if (ret == 0) {
if (cnx == NULL) {
/* Unexpected packet. Reject, drop and log. */
if (!picoquic_is_connection_id_null(&ph.dest_cnx_id)) {
picoquic_process_unexpected_cnxid(quic, length, addr_from, addr_to, if_index_to, &ph);
}
ret = PICOQUIC_ERROR_DETECTED;
}
else {
cnx->quic_bit_received_0 |= ph.quic_bit_is_zero;
switch (ph.ptype) {
case picoquic_packet_version_negotiation:
ret = picoquic_incoming_version_negotiation(
cnx, bytes, length, addr_from, &ph, current_time);
break;
case picoquic_packet_initial:
/* Initial packet: either crypto handshakes or acks. */
if ((!cnx->client_mode && picoquic_compare_connection_id(&ph.dest_cnx_id, &cnx->initial_cnxid) == 0) ||
picoquic_compare_connection_id(&ph.dest_cnx_id, &cnx->path[0]->p_local_cnxid->cnx_id) == 0) {
/* Verify that the source CID matches expectation */
if (picoquic_is_connection_id_null(&cnx->path[0]->remote_cnxid)) {
cnx->path[0]->remote_cnxid = ph.srce_cnx_id;
} else if (picoquic_compare_connection_id(&cnx->path[0]->remote_cnxid, &ph.srce_cnx_id) != 0) {
DBG_PRINTF("Error wrong srce cnxid (%d), type: %d, epoch: %d, pc: %d, pn: %d\n",
cnx->client_mode, ph.ptype, ph.epoch, ph.pc, (int)ph.pn);
ret = PICOQUIC_ERROR_UNEXPECTED_PACKET;
}
if (ret == 0) {
if (cnx->client_mode == 0) {
if (is_first_segment) {
/* Account for the data received in handshake, but only
* count the packet once. Do not count it again if it is not
* the first segment in packet */
cnx->initial_data_received += packet_length;
}
ret = picoquic_incoming_client_initial(&cnx, bytes, packet_length,
addr_from, addr_to, if_index_to, &ph, current_time, new_context_created);
}
else {
/* TODO: this really depends on the current receive epoch */
ret = picoquic_incoming_server_initial(cnx, bytes, addr_to, if_index_to, &ph, current_time);
}
}
} else {
DBG_PRINTF("Error detected (%d), type: %d, epoch: %d, pc: %d, pn: %d\n",
cnx->client_mode, ph.ptype, ph.epoch, ph.pc, (int)ph.pn);
ret = PICOQUIC_ERROR_DETECTED;
}
break;
case picoquic_packet_retry:
ret = picoquic_incoming_retry(cnx, bytes, &ph, current_time);
break;
case picoquic_packet_handshake:
if (cnx->client_mode)
{
ret = picoquic_incoming_server_handshake(cnx, bytes, addr_to, if_index_to, &ph, current_time);
}
else
{
ret = picoquic_incoming_client_handshake(cnx, bytes, &ph, current_time);
}
break;
case picoquic_packet_0rtt_protected:
if (is_first_segment) {
/* Account for the data received in handshake, but only
* count the packet once. Do not count it again if it is not
* the first segment in packet */
cnx->initial_data_received += packet_length;
}
ret = picoquic_incoming_0rtt(cnx, bytes, &ph, current_time);
break;
case picoquic_packet_1rtt_protected:
ret = picoquic_incoming_encrypted(cnx, bytes, &ph, addr_from, addr_to, if_index_to, received_ecn, current_time);
break;
default:
/* Packet type error. Log and ignore */
DBG_PRINTF("Unexpected packet type (%d), type: %d, epoch: %d, pc: %d, pn: %d\n",
cnx->client_mode, ph.ptype, ph.epoch, ph.pc, (int) ph.pn);
ret = PICOQUIC_ERROR_DETECTED;
break;
}
}
} else if (ret == PICOQUIC_ERROR_STATELESS_RESET) {
ret = picoquic_incoming_stateless_reset(cnx);
}
else if (ret == PICOQUIC_ERROR_AEAD_CHECK &&
ph.ptype == picoquic_packet_handshake &&
cnx != NULL &&
(cnx->cnx_state == picoquic_state_client_init_sent || cnx->cnx_state == picoquic_state_client_init_resent))
{
/* Indicates that the server probably sent initial and handshake but initial was lost */
if (cnx->pkt_ctx[picoquic_packet_context_initial].retransmit_oldest != NULL &&
cnx->pkt_ctx[picoquic_packet_context_initial].nb_retransmit == 0) {
/* Reset the retransmit timer to start retransmission immediately */
cnx->path[0]->retransmit_timer = current_time -
cnx->pkt_ctx[picoquic_packet_context_initial].retransmit_oldest->send_time;
}
}
if (ret == 0) {
if (cnx != NULL && cnx->cnx_state != picoquic_state_disconnected &&
ph.ptype != picoquic_packet_version_negotiation) {
cnx->nb_packets_received++;
/* Mark the sequence number as received */
ret = picoquic_record_pn_received(cnx, ph.pc, ph.pn64, receive_time);
/* Perform ECN accounting */
picoquic_ecn_accounting(cnx, received_ecn, ph.pc);
}
if (cnx != NULL) {
picoquic_reinsert_by_wake_time(cnx->quic, cnx, current_time);
}
} else if (ret == PICOQUIC_ERROR_DUPLICATE) {
/* Bad packets are dropped silently, but duplicates should be acknowledged */
if (cnx != NULL) {
picoquic_set_ack_needed(cnx, current_time, ph.pc);
}
ret = -1;
} else if (ret == PICOQUIC_ERROR_AEAD_CHECK || ret == PICOQUIC_ERROR_INITIAL_TOO_SHORT ||
ret == PICOQUIC_ERROR_INITIAL_CID_TOO_SHORT ||
ret == PICOQUIC_ERROR_UNEXPECTED_PACKET ||
ret == PICOQUIC_ERROR_CNXID_CHECK ||
ret == PICOQUIC_ERROR_RETRY || ret == PICOQUIC_ERROR_DETECTED ||
ret == PICOQUIC_ERROR_CONNECTION_DELETED ||
ret == PICOQUIC_ERROR_CNXID_SEGMENT ||
ret == PICOQUIC_ERROR_VERSION_NOT_SUPPORTED ||
ret == PICOQUIC_ERROR_AEAD_NOT_READY) {
/* Bad packets are dropped silently */
if (ret == PICOQUIC_ERROR_AEAD_CHECK ||
ret == PICOQUIC_ERROR_AEAD_NOT_READY ||
ret == PICOQUIC_ERROR_VERSION_NOT_SUPPORTED) {
ret = 0;
}
else {
ret = -1;
}
if (cnx != NULL) {
picoquic_reinsert_by_wake_time(cnx->quic, cnx, current_time);
}
} else if (ret == 1) {
/* wonder what happened ! */
DBG_PRINTF("Packet (%d) get ret=1, t: %d, e: %d, pc: %d, pn: %d, l: %zu\n",
(cnx == NULL) ? -1 : cnx->client_mode, ph.ptype, ph.epoch, ph.pc, (int)ph.pn, length);
ret = -1;
}
else if (ret != 0) {
DBG_PRINTF("Packet (%d) error, t: %d, e: %d, pc: %d, pn: %d, l: %zu, ret : 0x%x\n",
(cnx == NULL) ? -1 : cnx->client_mode, ph.ptype, ph.epoch, ph.pc, (int)ph.pn, length, ret);
ret = -1;
}
return ret;
}
int picoquic_incoming_packet(
picoquic_quic_t* quic,
uint8_t* bytes,
size_t packet_length,
struct sockaddr* addr_from,
struct sockaddr* addr_to,
int if_index_to,
unsigned char received_ecn,
uint64_t current_time)
{
size_t consumed_index = 0;
int ret = 0;
picoquic_connection_id_t previous_destid = picoquic_null_connection_id;
while (consumed_index < packet_length) {
size_t consumed = 0;
ret = picoquic_incoming_segment(quic, bytes + consumed_index,
packet_length - consumed_index, packet_length,
&consumed, addr_from, addr_to, if_index_to, received_ecn, current_time, current_time, &previous_destid);
if (ret == 0) {
consumed_index += consumed;
if (consumed == 0) {
DBG_PRINTF("%s", "Receive bug, ret = 0 && consumed = 0\n");
break;
}
} else {
ret = 0;
break;
}
}
return ret;
}
/* Processing of stashed packets after acquiring encryption context */
void picoquic_process_sooner_packets(picoquic_cnx_t* cnx, uint64_t current_time)
{
picoquic_stateless_packet_t* packet = cnx->first_sooner;
picoquic_stateless_packet_t* previous = NULL;
cnx->recycle_sooner_needed = 0;
while (packet != NULL) {
picoquic_stateless_packet_t* next_packet = packet->next_packet;
int could_try_now = 1;
picoquic_epoch_enum epoch = 0;
switch (packet->ptype) {
case picoquic_packet_handshake:
epoch = picoquic_epoch_handshake;
break;
case picoquic_packet_1rtt_protected:
epoch = picoquic_epoch_1rtt;
break;
default:
could_try_now = 0;
break;
}
if (could_try_now &&
(cnx->crypto_context[epoch].aead_decrypt != NULL || cnx->crypto_context[epoch].pn_dec != NULL))
{
#if 0
int ret;
DBG_PRINTF("De-stashing packet type %d, %d bytes", (int)packet->ptype, (int)packet->length);
ret = picoquic_incoming_packet(cnx->quic, packet->bytes, packet->length,
(struct sockaddr*) & packet->addr_to, (struct sockaddr*) & packet->addr_local, packet->if_index_local, packet->received_ecn, current_time);
#else
size_t consumed_index = 0;
int ret = 0;
picoquic_connection_id_t previous_destid = picoquic_null_connection_id;
while (consumed_index < packet->length) {
size_t consumed = 0;
ret = picoquic_incoming_segment(cnx->quic, packet->bytes + consumed_index,
packet->length - consumed_index, packet->length,
&consumed, (struct sockaddr*) & packet->addr_to, (struct sockaddr*) & packet->addr_local, packet->if_index_local,
packet->received_ecn, current_time, packet->receive_time, &previous_destid);
if (ret == 0 && consumed > 0) {
consumed_index += consumed;
}
else {
break;
}
}
#endif
if (ret != 0) {
DBG_PRINTF("Processing sooner packet type %d returns %d (0x%d)", (int)packet->ptype, ret, ret);
}
if (previous == NULL) {
cnx->first_sooner = packet->next_packet;
}
else {
previous->next_packet = packet->next_packet;
}
picoquic_delete_stateless_packet(packet);
}
else {
previous = packet;
}
packet = next_packet;
}
}