blob: f1cb4078af79dc3d043681f89442480c6241d6ee [file] [log] [blame]
/** @file
A brief file description
@section license License
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#include <algorithm>
#include "tscore/ink_assert.h"
#include "tscore/ink_defs.h"
#include "QUICAltConnectionManager.h"
#include "QUICConnectionTable.h"
#include "QUICResetTokenTable.h"
static constexpr char V_DEBUG_TAG[] = "v_quic_alt_con";
#define QUICACMVDebug(fmt, ...) Debug(V_DEBUG_TAG, "[%s] " fmt, this->_qc->cids().data(), ##__VA_ARGS__)
QUICAltConnectionManager::QUICAltConnectionManager(QUICConnection *qc, QUICConnectionTable &ctable, QUICResetTokenTable &rtable,
const QUICConnectionId &peer_initial_cid, uint32_t instance_id,
uint8_t local_active_cid_limit, const IpEndpoint *preferred_endpoint_ipv4,
const IpEndpoint *preferred_endpoint_ipv6)
: _qc(qc), _ctable(ctable), _rtable(rtable), _instance_id(instance_id), _local_active_cid_limit(local_active_cid_limit)
{
// Sequence number of the initial CID is 0
this->_alt_quic_connection_ids_remote.push_back({0, peer_initial_cid, {}, {true}});
this->_alt_quic_connection_ids_local[0].seq_num = 0;
this->_alt_quic_connection_ids_local[0].advertised = true;
if ((preferred_endpoint_ipv4 && !ats_ip_addr_port_eq(*preferred_endpoint_ipv4, qc->five_tuple().source())) ||
(preferred_endpoint_ipv6 && !ats_ip_addr_port_eq(*preferred_endpoint_ipv6, qc->five_tuple().source()))) {
this->_alt_quic_connection_ids_local[1] = this->_generate_next_alt_con_info();
// This alt cid will be advertised via Transport Parameter, so no need to advertise it via NCID frame
this->_alt_quic_connection_ids_local[1].advertised = true;
IpEndpoint empty_endpoint_ipv4;
IpEndpoint empty_endpoint_ipv6;
empty_endpoint_ipv4.sa.sa_family = AF_UNSPEC;
empty_endpoint_ipv6.sa.sa_family = AF_UNSPEC;
if (preferred_endpoint_ipv4 == nullptr) {
preferred_endpoint_ipv4 = &empty_endpoint_ipv4;
}
if (preferred_endpoint_ipv6 == nullptr) {
preferred_endpoint_ipv6 = &empty_endpoint_ipv6;
}
// FIXME Check nullptr dereference
this->_local_preferred_address =
new QUICPreferredAddress(*preferred_endpoint_ipv4, *preferred_endpoint_ipv6, this->_alt_quic_connection_ids_local[1].id,
this->_alt_quic_connection_ids_local[1].token);
}
}
QUICAltConnectionManager::~QUICAltConnectionManager()
{
ats_free(this->_alt_quic_connection_ids_local);
delete this->_local_preferred_address;
}
const QUICPreferredAddress *
QUICAltConnectionManager::preferred_address() const
{
return this->_local_preferred_address;
}
std::vector<QUICFrameType>
QUICAltConnectionManager::interests()
{
return {QUICFrameType::NEW_CONNECTION_ID, QUICFrameType::RETIRE_CONNECTION_ID};
}
QUICConnectionErrorUPtr
QUICAltConnectionManager::handle_frame(QUICEncryptionLevel level, const QUICFrame &frame)
{
QUICConnectionErrorUPtr error = nullptr;
switch (frame.type()) {
case QUICFrameType::NEW_CONNECTION_ID:
error = this->_register_remote_connection_id(static_cast<const QUICNewConnectionIdFrame &>(frame));
break;
case QUICFrameType::RETIRE_CONNECTION_ID:
error = this->_retire_remote_connection_id(static_cast<const QUICRetireConnectionIdFrame &>(frame));
break;
default:
QUICACMVDebug("Unexpected frame type: %02x", static_cast<unsigned int>(frame.type()));
ink_assert(false);
break;
}
return error;
}
QUICAltConnectionManager::AltConnectionInfo
QUICAltConnectionManager::_generate_next_alt_con_info()
{
QUICConnectionId conn_id;
conn_id.randomize();
QUICStatelessResetToken token(conn_id, this->_instance_id);
AltConnectionInfo aci = {++this->_alt_quic_connection_id_seq_num, conn_id, token, {false}};
if (this->_qc->direction() == NET_VCONNECTION_IN) {
this->_ctable.insert(conn_id, this->_qc);
}
if (is_debug_tag_set(V_DEBUG_TAG)) {
QUICACMVDebug("alt-cid=%s", conn_id.hex().c_str());
}
return aci;
}
void
QUICAltConnectionManager::_init_alt_connection_ids()
{
for (int i = this->_alt_quic_connection_id_seq_num + 1; i < this->_remote_active_cid_limit; ++i) {
this->_alt_quic_connection_ids_local[i] = this->_generate_next_alt_con_info();
}
this->_need_advertise = true;
}
bool
QUICAltConnectionManager::_update_alt_connection_id(uint64_t chosen_seq_num)
{
for (int i = 0; i < this->_remote_active_cid_limit; ++i) {
if (this->_alt_quic_connection_ids_local[i].seq_num == chosen_seq_num) {
this->_alt_quic_connection_ids_local[i] = this->_generate_next_alt_con_info();
this->_need_advertise = true;
return true;
}
}
return false;
}
QUICConnectionErrorUPtr
QUICAltConnectionManager::_register_remote_connection_id(const QUICNewConnectionIdFrame &frame)
{
QUICConnectionErrorUPtr error = nullptr;
if (frame.connection_id() == QUICConnectionId::ZERO()) {
error = std::make_unique<QUICConnectionError>(QUICTransErrorCode::PROTOCOL_VIOLATION, "received zero-length cid",
QUICFrameType::NEW_CONNECTION_ID);
} else {
int unused = 0;
for (auto &&x : this->_alt_quic_connection_ids_remote) {
if (x.seq_num == frame.sequence()) {
return error;
}
if (x.used == false && x.seq_num != 1) {
++unused;
}
}
if (unused > this->_local_active_cid_limit) {
error = std::make_unique<QUICConnectionError>(QUICTransErrorCode::CONNECTION_ID_LIMIT_ERROR, "received too many alt CIDs",
QUICFrameType::NEW_CONNECTION_ID);
} else {
this->_alt_quic_connection_ids_remote.push_back(
{frame.sequence(), frame.connection_id(), frame.stateless_reset_token(), {false}});
}
}
return error;
}
QUICConnectionErrorUPtr
QUICAltConnectionManager::_retire_remote_connection_id(const QUICRetireConnectionIdFrame &frame)
{
QUICConnectionErrorUPtr error = nullptr;
if (!this->_update_alt_connection_id(frame.seq_num())) {
error = std::make_unique<QUICConnectionError>(QUICTransErrorCode::PROTOCOL_VIOLATION, "received unused sequence number",
QUICFrameType::RETIRE_CONNECTION_ID);
}
return error;
}
bool
QUICAltConnectionManager::is_ready_to_migrate() const
{
if (this->_alt_quic_connection_ids_remote.empty()) {
return false;
}
for (auto &info : this->_alt_quic_connection_ids_remote) {
if (!info.used) {
return true;
}
}
return false;
}
QUICConnectionId
QUICAltConnectionManager::migrate_to_alt_cid()
{
for (auto &info : this->_alt_quic_connection_ids_remote) {
if (info.used) {
continue;
}
info.used = true;
this->_rtable.insert(info.token, this->_qc);
return info.id;
}
ink_assert(!"Could not find CID available");
return QUICConnectionId::ZERO();
}
bool
QUICAltConnectionManager::migrate_to(const QUICConnectionId &cid, QUICStatelessResetToken &new_reset_token)
{
if (this->_local_preferred_address) {
if (cid == this->_local_preferred_address->cid()) {
new_reset_token = this->_local_preferred_address->token();
return true;
}
}
for (int i = 0; i < this->_remote_active_cid_limit; ++i) {
AltConnectionInfo &info = this->_alt_quic_connection_ids_local[i];
if (info.id == cid) {
// Migrate connection
new_reset_token = info.token;
return true;
}
}
return false;
}
void
QUICAltConnectionManager::drop_cid(const QUICConnectionId &cid)
{
for (auto it = this->_alt_quic_connection_ids_remote.begin(); it != this->_alt_quic_connection_ids_remote.end(); ++it) {
if (it->id == cid) {
QUICACMVDebug("Dropping advertised CID %" PRIx32 " seq# %" PRIu64, it->id.h32(), it->seq_num);
this->_retired_seq_nums.push(it->seq_num);
this->_rtable.erase(it->token);
this->_alt_quic_connection_ids_remote.erase(it);
return;
}
}
}
void
QUICAltConnectionManager::invalidate_alt_connections()
{
int n = this->_remote_active_cid_limit + ((this->_local_preferred_address == nullptr) ? 1 : 0);
for (int i = 0; i < n; ++i) {
this->_ctable.erase(this->_alt_quic_connection_ids_local[i].id, this->_qc);
}
}
void
QUICAltConnectionManager::set_remote_preferred_address(const QUICPreferredAddress &preferred_address)
{
ink_assert(preferred_address.is_available());
// Sequence number of the preferred address is 1 if available
this->_alt_quic_connection_ids_remote.push_back({1, preferred_address.cid(), preferred_address.token(), {false}});
}
void
QUICAltConnectionManager::set_remote_active_cid_limit(uint8_t active_cid_limit)
{
this->_remote_active_cid_limit =
std::min(static_cast<unsigned int>(active_cid_limit), countof(this->_alt_quic_connection_ids_local));
this->_init_alt_connection_ids();
}
bool
QUICAltConnectionManager::will_generate_frame(QUICEncryptionLevel level, size_t current_packet_size, bool ack_eliciting,
uint32_t seq_num)
{
if (!this->_is_level_matched(level)) {
return false;
}
return this->_need_advertise || !this->_retired_seq_nums.empty();
}
/**
* @param connection_credit This is not used. Because NEW_CONNECTION_ID frame is not flow-controlled
*/
QUICFrame *
QUICAltConnectionManager::generate_frame(uint8_t *buf, QUICEncryptionLevel level, uint64_t /* connection_credit */,
uint16_t maximum_frame_size, size_t current_packet_size, uint32_t seq_num)
{
QUICFrame *frame = nullptr;
if (!this->_is_level_matched(level)) {
return frame;
}
if (this->_need_advertise) {
for (int i = 0; i < this->_remote_active_cid_limit; ++i) {
if (!this->_alt_quic_connection_ids_local[i].advertised) {
// FIXME Should send a sequence number for retire_prior_to. Sending 0 for now.
frame = QUICFrameFactory::create_new_connection_id_frame(buf, this->_alt_quic_connection_ids_local[i].seq_num, 0,
this->_alt_quic_connection_ids_local[i].id,
this->_alt_quic_connection_ids_local[i].token);
if (frame && frame->size() > maximum_frame_size) {
// Cancel generating frame
frame = nullptr;
} else if (frame != nullptr) {
this->_records_new_connection_id_frame(level, static_cast<const QUICNewConnectionIdFrame &>(*frame));
this->_alt_quic_connection_ids_local[i].advertised = true;
}
return frame;
}
}
this->_need_advertise = false;
}
if (!this->_retired_seq_nums.empty()) {
auto s = this->_retired_seq_nums.front();
frame = QUICFrameFactory::create_retire_connection_id_frame(buf, s);
this->_records_retire_connection_id_frame(level, static_cast<const QUICRetireConnectionIdFrame &>(*frame));
this->_retired_seq_nums.pop();
return frame;
}
return frame;
}
void
QUICAltConnectionManager::_records_new_connection_id_frame(QUICEncryptionLevel level, const QUICNewConnectionIdFrame &frame)
{
QUICFrameInformationUPtr info = QUICFrameInformationUPtr(quicFrameInformationAllocator.alloc());
info->type = frame.type();
info->level = level;
AltConnectionInfo *frame_info = reinterpret_cast<AltConnectionInfo *>(info->data);
frame_info->seq_num = frame.sequence();
frame_info->token = frame.stateless_reset_token();
frame_info->id = frame.connection_id();
this->_records_frame(frame.id(), std::move(info));
}
void
QUICAltConnectionManager::_records_retire_connection_id_frame(QUICEncryptionLevel level, const QUICRetireConnectionIdFrame &frame)
{
QUICFrameInformationUPtr info = QUICFrameInformationUPtr(quicFrameInformationAllocator.alloc());
info->type = frame.type();
info->level = level;
*reinterpret_cast<int64_t *>(info->data) = frame.seq_num();
this->_records_frame(frame.id(), std::move(info));
}
void
QUICAltConnectionManager::_on_frame_lost(QUICFrameInformationUPtr &info)
{
switch (info->type) {
case QUICFrameType::NEW_CONNECTION_ID: {
AltConnectionInfo *frame_info = reinterpret_cast<AltConnectionInfo *>(info->data);
for (int i = 0; i < this->_remote_active_cid_limit; ++i) {
if (this->_alt_quic_connection_ids_local[i].seq_num == frame_info->seq_num) {
ink_assert(this->_alt_quic_connection_ids_local[i].advertised);
this->_alt_quic_connection_ids_local[i].advertised = false;
this->_need_advertise = true;
return;
}
}
break;
}
case QUICFrameType::RETIRE_CONNECTION_ID: {
this->_retired_seq_nums.push(*reinterpret_cast<int64_t *>(info->data));
break;
}
default:
ink_assert(0);
}
}