blob: 6f9edb8ac857afa5ca0110518f0e752c470a5d4a [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 "P_QUICNetVConnection_quiche.h"
#include "P_QUICPacketHandler_quiche.h"
#include "quic/QUICStream_quiche.h"
#include <quiche.h>
static constexpr ink_hrtime WRITE_READY_INTERVAL = HRTIME_MSECONDS(2);
#define QUICConDebug(fmt, ...) Debug("quic_net", "[%s] " fmt, this->cids().data(), ##__VA_ARGS__)
#define QUICConVDebug(fmt, ...) Debug("v_quic_net", "[%s] " fmt, this->cids().data(), ##__VA_ARGS__)
ClassAllocator<QUICNetVConnection> quicNetVCAllocator("quicNetVCAllocator");
QUICNetVConnection::QUICNetVConnection() {}
QUICNetVConnection::~QUICNetVConnection() {}
void
QUICNetVConnection::init(QUICVersion version, QUICConnectionId peer_cid, QUICConnectionId original_cid, UDPConnection *,
QUICPacketHandler *)
{
}
void
QUICNetVConnection::init(QUICVersion version, QUICConnectionId peer_cid, QUICConnectionId original_cid, QUICConnectionId first_cid,
QUICConnectionId retry_cid, UDPConnection *udp_con, quiche_conn *quiche_con,
QUICPacketHandler *packet_handler, QUICConnectionTable *ctable)
{
SET_HANDLER((NetVConnHandler)&QUICNetVConnection::acceptEvent);
this->_udp_con = udp_con;
this->_quiche_con = quiche_con;
this->_packet_handler = packet_handler;
this->_original_quic_connection_id = original_cid;
this->_quic_connection_id.randomize();
this->_initial_source_connection_id = this->_quic_connection_id;
if (ctable) {
this->_ctable = ctable;
this->_ctable->insert(this->_quic_connection_id, this);
this->_ctable->insert(this->_original_quic_connection_id, this);
}
}
void
QUICNetVConnection::free()
{
this->free(this_ethread());
}
// called by ET_UDP
void
QUICNetVConnection::remove_connection_ids()
{
}
// called by ET_UDP
void
QUICNetVConnection::destroy(EThread *t)
{
QUICConDebug("Destroy connection");
if (from_accept_thread) {
quicNetVCAllocator.free(this);
} else {
THREAD_FREE(this, quicNetVCAllocator, t);
}
}
void
QUICNetVConnection::set_local_addr()
{
}
void
QUICNetVConnection::free(EThread *t)
{
QUICConDebug("Free connection");
this->_udp_con = nullptr;
quiche_conn_free(this->_quiche_con);
delete this->_application_map;
this->_application_map = nullptr;
delete this->_stream_manager;
this->_stream_manager = nullptr;
super::clear();
this->_context->trigger(QUICContext::CallbackEvent::CONNECTION_CLOSE);
ALPNSupport::clear();
TLSBasicSupport::clear();
this->_packet_handler->close_connection(this);
this->_packet_handler = nullptr;
}
void
QUICNetVConnection::reenable(VIO *vio)
{
}
int
QUICNetVConnection::state_handshake(int event, Event *data)
{
if (quiche_conn_is_established(this->_quiche_con)) {
this->_switch_to_established_state();
return this->handleEvent(event, data);
}
switch (event) {
case QUIC_EVENT_PACKET_READ_READY:
this->_handle_read_ready();
break;
case QUIC_EVENT_PACKET_WRITE_READY:
this->_close_packet_write_ready(data);
this->_handle_write_ready();
// Reschedule WRITE_READY
this->_schedule_packet_write_ready(true);
break;
case EVENT_INTERVAL:
this->_handle_interval();
break;
case VC_EVENT_EOS:
case VC_EVENT_ERROR:
case VC_EVENT_ACTIVE_TIMEOUT:
case VC_EVENT_INACTIVITY_TIMEOUT:
_unschedule_packet_write_ready();
this->closed = 1;
break;
default:
QUICConDebug("Unhandleed event: %d", event);
break;
}
return EVENT_DONE;
}
int
QUICNetVConnection::state_established(int event, Event *data)
{
switch (event) {
case QUIC_EVENT_PACKET_READ_READY:
this->_handle_read_ready();
break;
case QUIC_EVENT_PACKET_WRITE_READY:
this->_close_packet_write_ready(data);
this->_handle_write_ready();
// Reschedule WRITE_READY
this->_schedule_packet_write_ready(true);
break;
case EVENT_INTERVAL:
this->_handle_interval();
break;
case VC_EVENT_EOS:
case VC_EVENT_ERROR:
case VC_EVENT_ACTIVE_TIMEOUT:
case VC_EVENT_INACTIVITY_TIMEOUT:
_unschedule_packet_write_ready();
this->closed = 1;
break;
default:
QUICConDebug("Unhandleed event: %d", event);
break;
}
return EVENT_DONE;
}
void
QUICNetVConnection::_switch_to_established_state()
{
QUICConDebug("Enter state_connection_established");
this->_record_tls_handshake_end_time();
SET_HANDLER((NetVConnHandler)&QUICNetVConnection::state_established);
this->_start_application();
this->_handshake_completed = true;
}
void
QUICNetVConnection::_start_application()
{
if (!this->_application_started) {
this->_application_started = true;
const uint8_t *app_name;
size_t app_name_len = 0;
quiche_conn_application_proto(this->_quiche_con, &app_name, &app_name_len);
if (app_name == nullptr) {
app_name = reinterpret_cast<const uint8_t *>(IP_PROTO_TAG_HTTP_QUIC.data());
app_name_len = IP_PROTO_TAG_HTTP_QUIC.size();
}
this->set_negotiated_protocol_id({reinterpret_cast<const char *>(app_name), static_cast<size_t>(app_name_len)});
if (netvc_context == NET_VCONNECTION_IN) {
if (!this->setSelectedProtocol(app_name, app_name_len)) {
// this->_handle_error(std::make_unique<QUICConnectionError>(QUICTransErrorCode::PROTOCOL_VIOLATION));
} else {
this->endpoint()->handleEvent(NET_EVENT_ACCEPT, this);
}
} else {
this->action_.continuation->handleEvent(NET_EVENT_OPEN, this);
}
}
}
bool
QUICNetVConnection::shouldDestroy()
{
return this->refcount() == 0;
}
VIO *
QUICNetVConnection::do_io_read(Continuation *c, int64_t nbytes, MIOBuffer *buf)
{
ink_assert(false);
return nullptr;
}
VIO *
QUICNetVConnection::do_io_write(Continuation *c, int64_t nbytes, IOBufferReader *buf, bool owner)
{
ink_assert(false);
return nullptr;
}
int
QUICNetVConnection::acceptEvent(int event, Event *e)
{
EThread *t = (e == nullptr) ? this_ethread() : e->ethread;
NetHandler *h = get_NetHandler(t);
MUTEX_TRY_LOCK(lock, h->mutex, t);
if (!lock.is_locked()) {
if (event == EVENT_NONE) {
t->schedule_in(this, HRTIME_MSECONDS(net_retry_delay));
return EVENT_DONE;
} else {
e->schedule_in(HRTIME_MSECONDS(net_retry_delay));
return EVENT_CONT;
}
}
this->_context = std::make_unique<QUICContext>(this);
this->_application_map = new QUICApplicationMap();
this->_stream_manager = new QUICStreamManagerImpl(this->_context.get(), this->_application_map);
// this->thread is already assigned by QUICPacketHandlerIn::_recv_packet
ink_assert(this->thread == this_ethread());
// Send this NetVC to NetHandler and start to polling read & write event.
if (h->startIO(this) < 0) {
free(t);
return EVENT_DONE;
}
// FIXME: complete do_io_xxxx instead
this->read.enabled = 1;
// Handshake callback handler.
SET_HANDLER((NetVConnHandler)&QUICNetVConnection::state_handshake);
// Send this netvc to InactivityCop.
nh->startCop(this);
if (inactivity_timeout_in) {
set_inactivity_timeout(inactivity_timeout_in);
} else {
set_inactivity_timeout(0);
}
if (active_timeout_in) {
set_active_timeout(active_timeout_in);
}
action_.continuation->handleEvent(NET_EVENT_ACCEPT, this);
this->_schedule_packet_write_ready();
this->thread->schedule_in(this, HRTIME_MSECONDS(quiche_conn_timeout_as_millis(this->_quiche_con)));
return EVENT_DONE;
}
int
QUICNetVConnection::connectUp(EThread *t, int fd)
{
return 0;
}
QUICStreamManager *
QUICNetVConnection::stream_manager()
{
return this->_stream_manager;
}
void
QUICNetVConnection::close_quic_connection(QUICConnectionErrorUPtr error)
{
}
void
QUICNetVConnection::reset_quic_connection()
{
}
void
QUICNetVConnection::handle_received_packet(UDPPacket *packet)
{
IOBufferBlock *block = packet->getIOBlockChain();
uint8_t *buf = reinterpret_cast<uint8_t *>(block->buf());
uint64_t buf_len = block->size();
net_activity(this, this_ethread());
quiche_recv_info recv_info = {
&packet->from.sa,
static_cast<socklen_t>(packet->from.isIp4() ? sizeof(packet->from.sin) : sizeof(packet->from.sin6)),
#ifdef HAVE_QUICHE_CONFIG_SET_ACTIVE_CONNECTION_ID_LIMIT
&packet->to.sa,
static_cast<socklen_t>(packet->to.isIp4() ? sizeof(packet->to.sin) : sizeof(packet->to.sin6)),
#endif
};
ssize_t done = quiche_conn_recv(this->_quiche_con, buf, buf_len, &recv_info);
if (done < 0) {
QUICConVDebug("failed to process packet: %zd", done);
return;
}
}
void
QUICNetVConnection::ping()
{
}
QUICConnectionId
QUICNetVConnection::peer_connection_id() const
{
return {};
}
QUICConnectionId
QUICNetVConnection::original_connection_id() const
{
return {};
}
QUICConnectionId
QUICNetVConnection::first_connection_id() const
{
return {};
}
QUICConnectionId
QUICNetVConnection::retry_source_connection_id() const
{
return {};
}
QUICConnectionId
QUICNetVConnection::initial_source_connection_id() const
{
return {};
}
QUICConnectionId
QUICNetVConnection::connection_id() const
{
return {};
}
std::string_view
QUICNetVConnection::cids() const
{
return "";
}
const QUICFiveTuple
QUICNetVConnection::five_tuple() const
{
return {};
}
uint32_t
QUICNetVConnection::pmtu() const
{
return 0;
}
NetVConnectionContext_t
QUICNetVConnection::direction() const
{
return NET_VCONNECTION_IN;
}
QUICVersion
QUICNetVConnection::negotiated_version() const
{
return 0;
}
std::string_view
QUICNetVConnection::negotiated_application_name() const
{
const uint8_t *name;
size_t name_len = 0;
quiche_conn_application_proto(this->_quiche_con, &name, &name_len);
return std::string_view(reinterpret_cast<const char *>(name), name_len);
}
bool
QUICNetVConnection::is_closed() const
{
return quiche_conn_is_closed(this->_quiche_con);
}
bool
QUICNetVConnection::is_at_anti_amplification_limit() const
{
return false;
}
bool
QUICNetVConnection::is_address_validation_completed() const
{
return false;
}
bool
QUICNetVConnection::is_handshake_completed() const
{
return false;
}
bool
QUICNetVConnection::has_keys_for(QUICPacketNumberSpace space) const
{
return false;
}
std::vector<QUICFrameType>
QUICNetVConnection::interests()
{
return {};
}
QUICConnectionErrorUPtr
QUICNetVConnection::handle_frame(QUICEncryptionLevel level, const QUICFrame &frame)
{
return nullptr;
}
void
QUICNetVConnection::net_read_io(NetHandler *nh, EThread *lthread)
{
if (quiche_conn_is_readable(this->_quiche_con)) {
SCOPED_MUTEX_LOCK(lock, this->mutex, this_ethread());
this->handleEvent(QUIC_EVENT_PACKET_READ_READY, nullptr);
}
}
int64_t
QUICNetVConnection::load_buffer_and_write(int64_t towrite, MIOBufferAccessor &buf, int64_t &total_written, int &needs)
{
return 0;
}
void
QUICNetVConnection::_schedule_packet_write_ready(bool delay)
{
if (!this->_packet_write_ready) {
if (delay) {
this->_packet_write_ready = this->thread->schedule_in(this, WRITE_READY_INTERVAL, QUIC_EVENT_PACKET_WRITE_READY, nullptr);
} else {
this->_packet_write_ready = this->thread->schedule_imm(this, QUIC_EVENT_PACKET_WRITE_READY, nullptr);
}
}
}
void
QUICNetVConnection::_unschedule_packet_write_ready()
{
if (this->_packet_write_ready) {
this->_packet_write_ready->cancel();
this->_packet_write_ready = nullptr;
}
}
void
QUICNetVConnection::_close_packet_write_ready(Event *data)
{
ink_assert(this->_packet_write_ready == data);
this->_packet_write_ready = nullptr;
}
void
QUICNetVConnection::_handle_read_ready()
{
quiche_stream_iter *readable = quiche_conn_readable(this->_quiche_con);
uint64_t s = 0;
while (quiche_stream_iter_next(readable, &s)) {
QUICStreamImpl *stream;
QUICConVDebug("stream %" PRIu64 " is readable\n", s);
stream = static_cast<QUICStreamImpl *>(quiche_conn_stream_application_data(this->_quiche_con, s));
if (stream == nullptr) {
this->_stream_manager->create_stream(s);
stream = static_cast<QUICStreamImpl *>(this->_stream_manager->find_stream(s));
quiche_conn_stream_init_application_data(this->_quiche_con, s, stream);
}
stream->receive_data(this->_quiche_con);
}
quiche_stream_iter_free(readable);
}
void
QUICNetVConnection::_handle_write_ready()
{
if (quiche_conn_is_established(this->_quiche_con)) {
quiche_stream_iter *writable = quiche_conn_writable(this->_quiche_con);
uint64_t s = 0;
while (quiche_stream_iter_next(writable, &s)) {
QUICStreamImpl *stream;
stream = static_cast<QUICStreamImpl *>(quiche_conn_stream_application_data(this->_quiche_con, s));
stream->send_data(this->_quiche_con);
}
quiche_stream_iter_free(writable);
}
Ptr<IOBufferBlock> udp_payload;
quiche_send_info send_info;
ssize_t res;
ssize_t written = 0;
size_t quantum = quiche_conn_send_quantum(this->_quiche_con);
size_t max_udp_payload_size = quiche_conn_max_send_udp_payload_size(this->_quiche_con);
// This buffer size must be less than 64KB because it can be used for UDP GSO (UDP_SEGMENT)
udp_payload = new_IOBufferBlock();
udp_payload->alloc(buffer_size_to_index(quantum, BUFFER_SIZE_INDEX_32K));
quantum = std::min(static_cast<int64_t>(quantum), udp_payload->write_avail());
while (written + max_udp_payload_size <= quantum) {
res = quiche_conn_send(this->_quiche_con, reinterpret_cast<uint8_t *>(udp_payload->end()) + written, max_udp_payload_size,
&send_info);
if (res > 0) {
written += res;
}
if (static_cast<size_t>(res) != max_udp_payload_size) {
break;
}
}
if (written > 0) {
udp_payload->fill(written);
int segment_size = 0;
if (static_cast<size_t>(written) > max_udp_payload_size) {
segment_size = max_udp_payload_size;
}
this->_packet_handler->send_packet(this->_udp_con, this->con.addr, udp_payload, segment_size);
net_activity(this, this_ethread());
}
}
void
QUICNetVConnection::_handle_interval()
{
quiche_conn_on_timeout(this->_quiche_con);
if (quiche_conn_is_closed(this->_quiche_con)) {
this->_ctable->erase(this->_quic_connection_id, this);
this->_ctable->erase(this->_original_quic_connection_id, this);
if (quiche_conn_is_timed_out(this->_quiche_con)) {
this->thread->schedule_imm(this, VC_EVENT_INACTIVITY_TIMEOUT);
return;
}
bool is_app;
uint64_t error_code;
const uint8_t *reason;
size_t reason_len;
bool has_error = quiche_conn_peer_error(this->_quiche_con, &is_app, &error_code, &reason, &reason_len) ||
quiche_conn_local_error(this->_quiche_con, &is_app, &error_code, &reason, &reason_len);
if (has_error && error_code != static_cast<uint64_t>(QUICTransErrorCode::NO_ERROR)) {
QUICConDebug("is_app=%d error_code=%" PRId64 " reason=%.*s", is_app, error_code, static_cast<int>(reason_len), reason);
this->thread->schedule_imm(this, VC_EVENT_ERROR);
return;
}
// If it's not timeout nor error, it's probably eos
this->thread->schedule_imm(this, VC_EVENT_EOS);
} else {
// Just schedule timeout event again if the connection is still open
this->thread->schedule_in(this, HRTIME_MSECONDS(quiche_conn_timeout_as_millis(this->_quiche_con)));
}
}
int
QUICNetVConnection::populate_protocol(std::string_view *results, int n) const
{
return 0;
}
const char *
QUICNetVConnection::protocol_contains(std::string_view tag) const
{
return "";
}
SSL *
QUICNetVConnection::_get_ssl_object() const
{
return nullptr;
}
ssl_curve_id
QUICNetVConnection::_get_tls_curve() const
{
return 0;
}