blob: 4a69065ce3e32c1287aca3b2646f4a8dcce78b86 [file] [log] [blame]
/** @file
@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 "tscore/ink_config.h"
#include "P_Net.h"
#include "P_QUICPacketHandler.h"
#include "P_QUICNetProcessor.h"
#include "P_QUICNet.h"
#include "P_QUICClosedConCollector.h"
#include "QUICGlobals.h"
#include "QUICConfig.h"
#include "QUICPacket.h"
#include "QUICDebugNames.h"
#include "QUICEvents.h"
#include "QUICResetTokenTable.h"
#include "QUICMultiCertConfigLoader.h"
#include "QUICTLS.h"
static constexpr char debug_tag[] = "quic_sec";
static constexpr char v_debug_tag[] = "v_quic_sec";
#define QUICDebug(fmt, ...) Debug(debug_tag, fmt, ##__VA_ARGS__)
#define QUICQCDebug(qc, fmt, ...) Debug(debug_tag, "[%s] " fmt, qc->cids().data(), ##__VA_ARGS__)
// ["local dcid" - "local scid"]
#define QUICPHDebug(dcid, scid, fmt, ...) \
Debug(debug_tag, "[%08" PRIx32 "-%08" PRIx32 "] " fmt, dcid.h32(), scid.h32(), ##__VA_ARGS__)
#define QUICVPHDebug(dcid, scid, fmt, ...) \
Debug(v_debug_tag, "[%08" PRIx32 "-%08" PRIx32 "] " fmt, dcid.h32(), scid.h32(), ##__VA_ARGS__)
//
// QUICPacketHandler
//
QUICPacketHandler::QUICPacketHandler(QUICResetTokenTable &rtable) : _rtable(rtable)
{
this->_closed_con_collector = new QUICClosedConCollector;
this->_closed_con_collector->mutex = new_ProxyMutex();
}
QUICPacketHandler::~QUICPacketHandler()
{
if (this->_collector_event != nullptr) {
this->_collector_event->cancel();
this->_collector_event = nullptr;
}
if (this->_closed_con_collector != nullptr) {
delete this->_closed_con_collector;
this->_closed_con_collector = nullptr;
}
}
void
QUICPacketHandler::close_connection(QUICNetVConnection *conn)
{
int isin = ink_atomic_swap(&conn->in_closed_queue, 1);
if (!isin) {
this->_closed_con_collector->closedQueue.push(conn);
}
}
void
QUICPacketHandler::_send_packet(const QUICPacket &packet, UDPConnection *udp_con, IpEndpoint &addr, uint32_t pmtu,
const QUICPacketHeaderProtector *ph_protector, int dcil)
{
size_t udp_len;
Ptr<IOBufferBlock> udp_payload(new_IOBufferBlock());
udp_payload->alloc(iobuffer_size_to_index(pmtu, BUFFER_SIZE_INDEX_32K));
packet.store(reinterpret_cast<uint8_t *>(udp_payload->end()), &udp_len);
udp_payload->fill(udp_len);
if (ph_protector) {
ph_protector->protect(reinterpret_cast<uint8_t *>(udp_payload->start()), udp_len, dcil);
}
this->_send_packet(udp_con, addr, udp_payload);
}
void
QUICPacketHandler::_send_packet(UDPConnection *udp_con, IpEndpoint &addr, Ptr<IOBufferBlock> udp_payload)
{
UDPPacket *udp_packet = new_UDPPacket(addr, 0, udp_payload);
if (is_debug_tag_set(v_debug_tag)) {
ip_port_text_buffer ipb;
QUICConnectionId dcid = QUICConnectionId::ZERO();
QUICConnectionId scid = QUICConnectionId::ZERO();
const uint8_t *buf = reinterpret_cast<uint8_t *>(udp_payload->buf());
uint64_t buf_len = udp_payload->size();
if (!QUICInvariants::dcid(dcid, buf, buf_len)) {
ink_assert(false);
}
if (QUICInvariants::is_long_header(buf)) {
if (!QUICInvariants::scid(scid, buf, buf_len)) {
ink_assert(false);
}
}
QUICVPHDebug(dcid, scid, "send %s packet to %s from port %u size=%" PRId64, (QUICInvariants::is_long_header(buf) ? "LH" : "SH"),
ats_ip_nptop(&addr, ipb, sizeof(ipb)), udp_con->getPortNum(), buf_len);
}
udp_con->send(this->_get_continuation(), udp_packet);
get_UDPNetHandler(static_cast<UnixUDPConnection *>(udp_con)->ethread)->signalActivity();
}
QUICConnection *
QUICPacketHandler::_check_stateless_reset(const uint8_t *buf, size_t buf_len)
{
return this->_rtable.lookup({buf + (buf_len - 16)});
}
//
// QUICPacketHandlerIn
//
QUICPacketHandlerIn::QUICPacketHandlerIn(const NetProcessor::AcceptOptions &opt, QUICConnectionTable &ctable,
QUICResetTokenTable &rtable)
: NetAccept(opt), QUICPacketHandler(rtable), _ctable(ctable)
{
this->mutex = new_ProxyMutex();
// create Connection Table
QUICConfig::scoped_config params;
}
QUICPacketHandlerIn::~QUICPacketHandlerIn() {}
NetProcessor *
QUICPacketHandlerIn::getNetProcessor() const
{
return &quic_NetProcessor;
}
NetAccept *
QUICPacketHandlerIn::clone() const
{
NetAccept *na;
na = new QUICPacketHandlerIn(opt, this->_ctable, this->_rtable);
*na = *this;
return na;
}
int
QUICPacketHandlerIn::acceptEvent(int event, void *data)
{
// NetVConnection *netvc;
ink_release_assert(event == NET_EVENT_DATAGRAM_OPEN || event == NET_EVENT_DATAGRAM_READ_READY ||
event == NET_EVENT_DATAGRAM_ERROR);
ink_release_assert((event == NET_EVENT_DATAGRAM_OPEN) ? (data != nullptr) : (1));
ink_release_assert((event == NET_EVENT_DATAGRAM_READ_READY) ? (data != nullptr) : (1));
if (event == NET_EVENT_DATAGRAM_OPEN) {
// Nothing to do.
return EVENT_CONT;
} else if (event == NET_EVENT_DATAGRAM_READ_READY) {
if (this->_collector_event == nullptr) {
this->_collector_event = this_ethread()->schedule_every(this->_closed_con_collector, HRTIME_MSECONDS(100));
}
Queue<UDPPacket> *queue = static_cast<Queue<UDPPacket> *>(data);
UDPPacket *packet_r;
while ((packet_r = queue->dequeue())) {
this->_recv_packet(event, packet_r);
}
return EVENT_CONT;
}
/////////////////
// EVENT_ERROR //
/////////////////
if (((long)data) == -ECONNABORTED) {
}
ink_abort("QUIC accept received fatal error: errno = %d", -(static_cast<int>((intptr_t)data)));
return EVENT_CONT;
return 0;
}
void
QUICPacketHandlerIn::init_accept(EThread *t = nullptr)
{
SET_HANDLER(&QUICPacketHandlerIn::acceptEvent);
}
Continuation *
QUICPacketHandlerIn::_get_continuation()
{
return static_cast<NetAccept *>(this);
}
void
QUICPacketHandlerIn::_recv_packet(int event, UDPPacket *udp_packet)
{
// Assumption: udp_packet has only one IOBufferBlock
IOBufferBlock *block = udp_packet->getIOBlockChain();
const uint8_t *buf = reinterpret_cast<uint8_t *>(block->buf());
uint64_t buf_len = block->size();
QUICVersion version;
if (buf_len == 0) {
QUICDebug("Ignore packet - payload is too small");
udp_packet->free();
return;
}
QUICConnectionId dcid = QUICConnectionId::ZERO();
QUICConnectionId scid = QUICConnectionId::ZERO();
if (!QUICInvariants::dcid(dcid, buf, buf_len)) {
QUICDebug("Ignore packet - payload is too small");
udp_packet->free();
return;
}
if (QUICInvariants::is_long_header(buf)) {
if (!QUICInvariants::scid(scid, buf, buf_len)) {
QUICDebug("Ignore packet - payload is too small");
udp_packet->free();
return;
}
if (is_debug_tag_set(v_debug_tag)) {
ip_port_text_buffer ipb_from;
ip_port_text_buffer ipb_to;
QUICVPHDebug(scid, dcid, "recv LH packet from %s to %s size=%" PRId64,
ats_ip_nptop(&udp_packet->from.sa, ipb_from, sizeof(ipb_from)),
ats_ip_nptop(&udp_packet->to.sa, ipb_to, sizeof(ipb_to)), udp_packet->getPktLength());
}
if (unlikely(!QUICInvariants::version(version, buf, buf_len))) {
QUICDebug("Ignore packet - payload is too small");
udp_packet->free();
return;
}
if (!QUICInvariants::is_version_negotiation(version) && !QUICTypeUtil::is_supported_version(version)) {
QUICPHDebug(scid, dcid, "Unsupported version: 0x%x", version);
QUICPacketUPtr vn = QUICPacketFactory::create_version_negotiation_packet(scid, dcid, version);
this->_send_packet(*vn, udp_packet->getConnection(), udp_packet->from, 1200, nullptr, 0);
udp_packet->free();
return;
}
if (dcid == QUICConnectionId::ZERO()) {
// TODO: lookup DCID by 5-tuple when ATS omits SCID
return;
}
QUICPacketType type = QUICPacketType::UNINITIALIZED;
QUICLongHeaderPacketR::type(type, buf, buf_len);
if (type == QUICPacketType::INITIAL) {
// [draft-18] 7.2.
// When an Initial packet is sent by a client which has not previously received a Retry packet from the server, it populates
// the Destination Connection ID field with an unpredictable value. This MUST be at least 8 bytes in length.
if (dcid != QUICConnectionId::ZERO() && dcid.length() < QUICConnectionId::MIN_LENGTH_FOR_INITIAL) {
QUICDebug("Ignore packet - DCIL is too small for Initial packet");
udp_packet->free();
return;
}
}
} else {
// TODO: lookup DCID by 5-tuple when ATS omits SCID
if (is_debug_tag_set(v_debug_tag)) {
ip_port_text_buffer ipb_from;
ip_port_text_buffer ipb_to;
QUICVPHDebug(scid, dcid, "recv SH packet from %s to %s size=%" PRId64,
ats_ip_nptop(&udp_packet->from.sa, ipb_from, sizeof(ipb_from)),
ats_ip_nptop(&udp_packet->to.sa, ipb_to, sizeof(ipb_to)), udp_packet->getPktLength());
}
}
QUICConnection *qc = this->_ctable.lookup(dcid);
QUICNetVConnection *vc = static_cast<QUICNetVConnection *>(qc);
// Server Stateless Retry
QUICConfig::scoped_config params;
QUICConnectionId ocid_in_retry_token = QUICConnectionId::ZERO();
QUICConnectionId rcid_in_retry_token = QUICConnectionId::ZERO();
if (!vc && params->stateless_retry() && QUICInvariants::is_long_header(buf)) {
int ret = this->_stateless_retry(buf, buf_len, udp_packet->getConnection(), udp_packet->from, dcid, scid, &ocid_in_retry_token,
&rcid_in_retry_token, version);
if (ret < 0) {
udp_packet->free();
return;
}
}
// [draft-12] 6.1.2. Server Packet Handling
// Servers MUST drop incoming packets under all other circumstances. They SHOULD send a Stateless Reset (Section 6.10.4) if a
// connection ID is present in the header.
if (!vc && !QUICInvariants::is_long_header(buf)) {
auto connection = static_cast<QUICNetVConnection *>(this->_check_stateless_reset(buf, buf_len));
if (connection) {
QUICDebug("Stateless Reset has been received");
connection->thread->schedule_imm(connection, QUIC_EVENT_STATELESS_RESET);
return;
}
bool sent =
this->_send_stateless_reset(dcid, params->instance_id(), udp_packet->getConnection(), udp_packet->from, buf_len - 1);
udp_packet->free();
if (is_debug_tag_set(debug_tag) && sent) {
QUICPHDebug(scid, dcid, "sent Stateless Reset : connection not found, dcid=%s", dcid.hex().c_str());
}
return;
} else if (vc && vc->in_closed_queue) {
bool sent =
this->_send_stateless_reset(dcid, params->instance_id(), udp_packet->getConnection(), udp_packet->from, buf_len - 1);
udp_packet->free();
if (is_debug_tag_set(debug_tag) && sent) {
QUICPHDebug(scid, dcid, "sent Stateless Reset : connection is already closed, dcid=%s", dcid.hex().c_str());
}
return;
}
EThread *eth = nullptr;
if (!vc) {
// Create a new NetVConnection
Connection con;
con.setRemote(&udp_packet->from.sa);
eth = eventProcessor.assign_thread(ET_NET);
QUICConnectionId original_cid = dcid;
QUICConnectionId peer_cid = scid;
if (is_debug_tag_set("quic_sec")) {
QUICPHDebug(peer_cid, original_cid, "client initial dcid=%s", original_cid.hex().c_str());
}
vc = static_cast<QUICNetVConnection *>(getNetProcessor()->allocate_vc(nullptr));
vc->init(version, peer_cid, original_cid, ocid_in_retry_token, rcid_in_retry_token, udp_packet->getConnection(), this,
&this->_rtable, &this->_ctable);
vc->id = net_next_connection_number();
vc->con.move(con);
vc->submit_time = Thread::get_hrtime();
vc->thread = eth;
vc->mutex = new_ProxyMutex();
vc->action_ = *this->action_;
vc->set_is_transparent(this->opt.f_inbound_transparent);
vc->set_context(NET_VCONNECTION_IN);
vc->options.ip_proto = NetVCOptions::USE_UDP;
vc->options.ip_family = udp_packet->from.sa.sa_family;
qc = vc;
} else {
eth = vc->thread;
}
QUICPollEvent *qe = quicPollEventAllocator.alloc();
qe->init(qc, static_cast<UDPPacketInternal *>(udp_packet));
// Push the packet into QUICPollCont
get_QUICPollCont(eth)->inQueue.push(qe);
get_NetHandler(eth)->signalActivity();
return;
}
// TODO: Should be called via eventProcessor?
void
QUICPacketHandler::send_packet(const QUICPacket &packet, QUICNetVConnection *vc, const QUICPacketHeaderProtector &ph_protector)
{
this->_send_packet(packet, vc->get_udp_con(), vc->con.addr, vc->pmtu(), &ph_protector, vc->peer_connection_id().length());
}
void
QUICPacketHandler::send_packet(QUICNetVConnection *vc, const Ptr<IOBufferBlock> &udp_payload)
{
this->_send_packet(vc->get_udp_con(), vc->con.addr, udp_payload);
}
int
QUICPacketHandlerIn::_stateless_retry(const uint8_t *buf, uint64_t buf_len, UDPConnection *connection, IpEndpoint from,
QUICConnectionId dcid, QUICConnectionId scid, QUICConnectionId *original_cid,
QUICConnectionId *retry_cid, QUICVersion version)
{
QUICPacketType type = QUICPacketType::UNINITIALIZED;
QUICPacketR::type(type, buf, buf_len);
if (type != QUICPacketType::INITIAL) {
return 1;
}
// TODO: refine packet parsers in here, QUICPacketLongHeader, and QUICPacketReceiveQueue
size_t token_length = 0;
uint8_t token_length_field_len = 0;
size_t token_length_field_offset = 0;
if (!QUICInitialPacketR::token_length(token_length, token_length_field_len, token_length_field_offset, buf, buf_len)) {
return -1;
}
if (token_length == 0) {
QUICConnectionId local_cid;
local_cid.randomize();
QUICRetryToken token(from, dcid, local_cid);
QUICPacketUPtr retry_packet = QUICPacketFactory::create_retry_packet(version, scid, local_cid, token);
QUICDebug("[TX] %s packet ODCID=%" PRIx64 " RCID=%" PRIx64 " token_length=%u token=%02x%02x%02x%02x...",
QUICDebugNames::packet_type(retry_packet->type()), static_cast<uint64_t>(token.original_dcid()),
static_cast<uint64_t>(token.scid()), token.length(), token.buf()[0], token.buf()[1], token.buf()[2], token.buf()[3]);
this->_send_packet(*retry_packet, connection, from, 1200, nullptr, 0);
return -2;
} else {
size_t token_offset = token_length_field_offset + token_length_field_len;
if (QUICAddressValidationToken::type(buf + token_offset) == QUICAddressValidationToken::Type::RETRY) {
QUICRetryToken token(buf + token_offset, token_length);
if (token.is_valid(from)) {
*original_cid = token.original_dcid();
*retry_cid = token.scid();
QUICDebug("Retry Token is valid. ODCID=%" PRIx64 " RCID=%" PRIx64, static_cast<uint64_t>(*original_cid),
static_cast<uint64_t>(*retry_cid));
return 0;
} else {
QUICDebug("Retry token is invalid: ODCID=%" PRIx64 " RCID=%" PRIx64 " token_length=%u token=%02x%02x%02x%02x...",
static_cast<uint64_t>(token.original_dcid()), static_cast<uint64_t>(*retry_cid), token.length(), token.buf()[0],
token.buf()[1], token.buf()[2], token.buf()[3]);
this->_send_invalid_token_error(buf, buf_len, connection, from);
return -3;
}
} else {
// TODO Handle ResumptionToken
return -4;
}
}
return 0;
}
bool
QUICPacketHandlerIn::_send_stateless_reset(QUICConnectionId dcid, uint32_t instance_id, UDPConnection *udp_con, IpEndpoint &addr,
size_t maximum_size)
{
QUICStatelessResetToken token(dcid, instance_id);
auto packet = QUICPacketFactory::create_stateless_reset_packet(token, maximum_size);
if (packet) {
this->_send_packet(*packet, udp_con, addr, 1200, nullptr, 0);
return true;
}
return false;
}
void
QUICPacketHandlerIn::_send_invalid_token_error(const uint8_t *initial_packet, uint64_t initial_packet_len,
UDPConnection *connection, IpEndpoint from)
{
QUICConnectionId scid_in_initial;
QUICConnectionId dcid_in_initial;
QUICInvariants::scid(scid_in_initial, initial_packet, initial_packet_len);
QUICInvariants::dcid(dcid_in_initial, initial_packet, initial_packet_len);
QUICVersion version_in_initial;
QUICLongHeaderPacketR::version(version_in_initial, initial_packet, initial_packet_len);
// Create CONNECTION_CLOSE frame
auto error = std::make_unique<QUICConnectionError>(QUICTransErrorCode::INVALID_TOKEN);
uint8_t frame_buf[QUICFrame::MAX_INSTANCE_SIZE];
QUICFrame *frame = QUICFrameFactory::create_connection_close_frame(frame_buf, *error);
Ptr<IOBufferBlock> block = frame->to_io_buffer_block(1200);
size_t block_len = 0;
for (Ptr<IOBufferBlock> tmp = block; tmp; tmp = tmp->next) {
block_len += tmp->size();
}
frame->~QUICFrame();
// Prepare for packet protection
QUICPacketProtectionKeyInfo ppki;
ppki.set_context(QUICPacketProtectionKeyInfo::Context::SERVER);
QUICPacketFactory pf(ppki);
QUICPacketHeaderProtector php(ppki);
QUICCertConfig::scoped_config server_cert;
QUICTLS tls(ppki, server_cert->ssl_default.get(), NET_VCONNECTION_IN, {}, "", "");
tls.initialize_key_materials(dcid_in_initial, version_in_initial);
// Create INITIAL packet
QUICConnectionId scid;
scid.randomize();
uint8_t packet_buf[QUICPacket::MAX_INSTANCE_SIZE];
QUICPacketUPtr cc_packet = pf.create_initial_packet(packet_buf, scid_in_initial, scid, 0, block, block_len, false, false, true);
this->_send_packet(*cc_packet, connection, from, 0, &php, scid_in_initial);
}
//
// QUICPacketHandlerOut
//
QUICPacketHandlerOut::QUICPacketHandlerOut(QUICResetTokenTable &rtable) : Continuation(new_ProxyMutex()), QUICPacketHandler(rtable)
{
SET_HANDLER(&QUICPacketHandlerOut::event_handler);
}
void
QUICPacketHandlerOut::init(QUICNetVConnection *vc)
{
this->_vc = vc;
}
int
QUICPacketHandlerOut::event_handler(int event, Event *data)
{
switch (event) {
case NET_EVENT_DATAGRAM_OPEN: {
// Nothing to do.
return EVENT_CONT;
}
case NET_EVENT_DATAGRAM_READ_READY: {
Queue<UDPPacket> *queue = reinterpret_cast<Queue<UDPPacket> *>(data);
UDPPacket *packet_r;
while ((packet_r = queue->dequeue())) {
this->_recv_packet(event, packet_r);
}
return EVENT_CONT;
}
default:
Debug("quic_ph", "Unknown Event (%d)", event);
break;
}
return EVENT_DONE;
}
Continuation *
QUICPacketHandlerOut::_get_continuation()
{
return this;
}
void
QUICPacketHandlerOut::_recv_packet(int event, UDPPacket *udp_packet)
{
IOBufferBlock *block = udp_packet->getIOBlockChain();
const uint8_t *buf = reinterpret_cast<uint8_t *>(block->buf());
uint64_t buf_len = block->size();
if (is_debug_tag_set(debug_tag)) {
ip_port_text_buffer ipb_from;
ip_port_text_buffer ipb_to;
QUICQCDebug(this->_vc, "recv %s packet from %s to %s size=%" PRId64, (QUICInvariants::is_long_header(buf) ? "LH" : "SH"),
ats_ip_nptop(&udp_packet->from.sa, ipb_from, sizeof(ipb_from)),
ats_ip_nptop(&udp_packet->to.sa, ipb_to, sizeof(ipb_to)), udp_packet->getPktLength());
}
QUICConnectionId dcid;
if (!QUICInvariants::dcid(dcid, buf, buf_len)) {
QUICDebug("Ignore packet - payload is too small");
udp_packet->free();
return;
}
if (!QUICInvariants::is_long_header(buf) && dcid != this->_vc->connection_id()) {
auto connection = static_cast<QUICNetVConnection *>(this->_check_stateless_reset(buf, buf_len));
if (connection) {
if (connection->connection_id() == this->_vc->connection_id()) {
QUICDebug("Stateless Reset has been received");
this->_vc->thread->schedule_imm(this->_vc, QUIC_EVENT_STATELESS_RESET);
}
return;
}
}
this->_vc->handle_received_packet(udp_packet);
eventProcessor.schedule_imm(this->_vc, ET_CALL, QUIC_EVENT_PACKET_READ_READY, nullptr);
}