You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficserver.apache.org by sc...@apache.org on 2020/05/30 02:30:33 UTC
[trafficserver] 01/01: QUIC: add qlog support
This is an automated email from the ASF dual-hosted git repository.
scw00 pushed a commit to branch qlog
in repository https://gitbox.apache.org/repos/asf/trafficserver.git
commit 1d9cf486ba9b4c960aa605616938a2537b70fe19
Author: scw00 <sc...@apache.org>
AuthorDate: Mon May 25 13:54:49 2020 +0800
QUIC: add qlog support
---
iocore/cache/Makefile.am | 3 +-
iocore/dns/Makefile.am | 3 +-
iocore/hostdb/Makefile.am | 3 +-
iocore/net/P_QUICNetVConnection.h | 4 +
iocore/net/QUICNetVConnection.cc | 22 +-
iocore/net/quic/Makefile.am | 7 +-
iocore/net/quic/QUICCongestionController.h | 2 +
iocore/net/quic/QUICContext.cc | 11 +-
iocore/net/quic/QUICContext.h | 11 +-
iocore/net/quic/QUICFrame.cc | 18 +-
iocore/net/quic/QUICFrame.h | 5 +-
iocore/net/quic/QUICFrameDispatcher.cc | 10 +-
iocore/net/quic/QUICFrameDispatcher.h | 4 +-
iocore/net/quic/QUICLog.cc | 76 +++
iocore/net/quic/QUICLog.h | 113 ++++
iocore/net/quic/QUICLogEvent.cc | 294 +++++++++
iocore/net/quic/QUICLogEvent.h | 984 +++++++++++++++++++++++++++++
iocore/net/quic/QUICLogFrame.cc | 258 ++++++++
iocore/net/quic/QUICLogFrame.h | 286 +++++++++
iocore/net/quic/QUICLogUtils.h | 40 ++
iocore/net/quic/QUICLossDetector.cc | 3 +
iocore/net/quic/QUICTypes.cc | 27 +
iocore/net/quic/QUICTypes.h | 8 +
iocore/net/quic/test/test_QUICType.cc | 32 +
mgmt/Makefile.am | 3 +-
proxy/hdrs/Makefile.am | 3 +-
proxy/http/Makefile.am | 3 +-
proxy/http2/Makefile.am | 3 +-
proxy/http3/Makefile.am | 3 +-
proxy/shared/Makefile.am | 3 +-
src/traffic_quic/Makefile.inc | 3 +-
src/traffic_server/Makefile.inc | 3 +-
32 files changed, 2224 insertions(+), 24 deletions(-)
diff --git a/iocore/cache/Makefile.am b/iocore/cache/Makefile.am
index 307f05b..e7a8803 100644
--- a/iocore/cache/Makefile.am
+++ b/iocore/cache/Makefile.am
@@ -27,7 +27,8 @@ AM_CPPFLAGS += \
-I$(abs_top_srcdir)/proxy/http/remap \
-I$(abs_top_srcdir)/mgmt \
-I$(abs_top_srcdir)/mgmt/utils \
- $(TS_INCLUDES)
+ $(TS_INCLUDES) \
+ @YAMLCPP_INCLUDES@
noinst_LIBRARIES = libinkcache.a
diff --git a/iocore/dns/Makefile.am b/iocore/dns/Makefile.am
index a7287d2..8507b90 100644
--- a/iocore/dns/Makefile.am
+++ b/iocore/dns/Makefile.am
@@ -25,7 +25,8 @@ AM_CPPFLAGS += \
-I$(abs_top_srcdir)/proxy/hdrs \
-I$(abs_top_srcdir)/mgmt \
-I$(abs_top_srcdir)/mgmt/utils \
- $(TS_INCLUDES)
+ $(TS_INCLUDES) \
+ @YAMLCPP_INCLUDES@
noinst_LIBRARIES = libinkdns.a
diff --git a/iocore/hostdb/Makefile.am b/iocore/hostdb/Makefile.am
index 8f49b12..5ecc4dc 100644
--- a/iocore/hostdb/Makefile.am
+++ b/iocore/hostdb/Makefile.am
@@ -25,7 +25,8 @@ AM_CPPFLAGS += \
-I$(abs_top_srcdir)/proxy/http \
-I$(abs_top_srcdir)/mgmt \
-I$(abs_top_srcdir)/mgmt/utils \
- $(TS_INCLUDES)
+ $(TS_INCLUDES) \
+ @YAMLCPP_INCLUDES@
EXTRA_DIST = I_HostDB.h
diff --git a/iocore/net/P_QUICNetVConnection.h b/iocore/net/P_QUICNetVConnection.h
index f603dfe..cc2901c 100644
--- a/iocore/net/P_QUICNetVConnection.h
+++ b/iocore/net/P_QUICNetVConnection.h
@@ -69,6 +69,7 @@
#include "quic/QUICPacketProtectionKeyInfo.h"
#include "quic/QUICContext.h"
#include "quic/QUICTokenCreator.h"
+#include "quic/QUICLog.h"
// Size of connection ids for debug log : e.g. aaaaaaaa-bbbbbbbb\0
static constexpr size_t MAX_CIDS_SIZE = 8 + 1 + 8 + 1;
@@ -372,6 +373,9 @@ private:
QUICAddrVerifyState _verified_state;
std::unique_ptr<QUICContextImpl> _context;
+
+ QLog::QUICLog _qlog;
+ QLog::Trace *_trace;
};
typedef int (QUICNetVConnection::*QUICNetVConnHandler)(int, void *);
diff --git a/iocore/net/QUICNetVConnection.cc b/iocore/net/QUICNetVConnection.cc
index 7940ae4..32cf1ab 100644
--- a/iocore/net/QUICNetVConnection.cc
+++ b/iocore/net/QUICNetVConnection.cc
@@ -42,6 +42,7 @@
#include "QUICHandshake.h"
#include "QUICConfig.h"
#include "QUICIntUtil.h"
+#include "QUICLogUtils.h"
using namespace std::literals;
static constexpr std::string_view QUIC_DEBUG_TAG = "quic_net"sv;
@@ -247,6 +248,8 @@ QUICNetVConnection::init(QUICConnectionId peer_cid, QUICConnectionId original_ci
this->_update_cids();
+ this->_trace = &this->_qlog.new_trace({"ats", QLog::Trace::VantagePointType::client, QLog::Trace::VantagePointType::client},
+ this->_original_quic_connection_id.hex());
if (is_debug_tag_set(QUIC_DEBUG_TAG.data())) {
QUICConDebug("dcid=%s scid=%s", this->_peer_quic_connection_id.hex().c_str(), this->_quic_connection_id.hex().c_str());
}
@@ -275,6 +278,8 @@ QUICNetVConnection::init(QUICConnectionId peer_cid, QUICConnectionId original_ci
this->_update_cids();
+ this->_trace = &this->_qlog.new_trace({"ats", QLog::Trace::VantagePointType::server, QLog::Trace::VantagePointType::server},
+ this->_original_quic_connection_id.hex());
if (is_debug_tag_set(QUIC_DEBUG_TAG.data())) {
QUICConDebug("dcid=%s scid=%s", this->_peer_quic_connection_id.hex().c_str(), this->_quic_connection_id.hex().c_str());
}
@@ -388,7 +393,8 @@ QUICNetVConnection::start()
});
this->_path_manager = new QUICPathManagerImpl(*this, *this->_path_validator);
- this->_context = std::make_unique<QUICContextImpl>(&this->_rtt_measure, this, &this->_pp_key_info, this->_path_manager);
+ this->_context =
+ std::make_unique<QUICContextImpl>(*this->_trace, &this->_rtt_measure, this, &this->_pp_key_info, this->_path_manager);
this->_five_tuple.update(this->local_addr, this->remote_addr, SOCK_DGRAM);
QUICPath trusted_path = {{}, {}};
// Version 0x00000001 uses stream 0 for cryptographic handshake with TLS 1.3, but newer version may not
@@ -422,7 +428,7 @@ QUICNetVConnection::start()
this->_application_map = new QUICApplicationMap();
- this->_frame_dispatcher = new QUICFrameDispatcher(this);
+ this->_frame_dispatcher = new QUICFrameDispatcher(*this->_context, this);
// Create frame handlers
this->_pinger = new QUICPinger();
@@ -487,6 +493,7 @@ QUICNetVConnection::free(EThread *t)
super::clear();
*/
+ this->_qlog.dump();
ALPNSupport::clear();
this->_packet_handler->close_connection(this);
}
@@ -660,6 +667,9 @@ QUICNetVConnection::stream_manager()
void
QUICNetVConnection::handle_received_packet(UDPPacket *packet)
{
+ auto dr = std::make_unique<QLog::Transport::DatagramReceived>();
+ dr->set_byte_length(static_cast<int>(packet->getPktLength()));
+ this->_trace->push_event(std::move(dr));
this->_packet_recv_queue.enqueue(packet);
}
@@ -1576,6 +1586,7 @@ QUICNetVConnection::_packetize_frames(uint8_t *packet_buf, QUICEncryptionLevel l
bool crypto = false;
uint8_t frame_instance_buffer[QUICFrame::MAX_INSTANCE_SIZE]; // This is for a frame instance but not serialized frame data
QUICFrame *frame = nullptr;
+ std::vector<QLog::QLogFrameUPtr> qframes;
for (auto g : this->_frame_generators.generators()) {
// a non-ack_eliciting packet is ready, but we can not send continuous two ack_eliciting packets.
while (g->will_generate_frame(level, len, ack_eliciting, seq_num)) {
@@ -1625,6 +1636,7 @@ QUICNetVConnection::_packetize_frames(uint8_t *packet_buf, QUICEncryptionLevel l
crypto = true;
}
+ qframes.push_back(QLog::QLogFrameFactory::create(*frame));
frame->~QUICFrame();
} else {
// Move to next generator
@@ -1637,6 +1649,12 @@ QUICNetVConnection::_packetize_frames(uint8_t *packet_buf, QUICEncryptionLevel l
if (len != 0) {
// Packet is retransmittable if it's not ack only packet
packet = this->_build_packet(packet_buf, level, first_block, ack_eliciting, probing, crypto);
+ std::unique_ptr<QLog::Transport::PacketSent> ps =
+ std::make_unique<QLog::Transport::PacketSent>(QLog::PacketTypeToName(packet->type()), QLog::QUICPacketToLogPacket(*packet));
+ for (auto &it : qframes) {
+ ps->append_frames(std::move(it));
+ }
+ this->_trace->push_event(std::move(ps));
}
return packet;
diff --git a/iocore/net/quic/Makefile.am b/iocore/net/quic/Makefile.am
index 982c281..d651d2d 100644
--- a/iocore/net/quic/Makefile.am
+++ b/iocore/net/quic/Makefile.am
@@ -29,7 +29,7 @@ AM_CPPFLAGS += \
-I$(abs_top_srcdir)/mgmt \
-I$(abs_top_srcdir)/mgmt/utils \
$(TS_INCLUDES) \
- @OPENSSL_INCLUDES@
+ @OPENSSL_INCLUDES@ @YAMLCPP_INCLUDES@
noinst_LIBRARIES = libquic.a
@@ -103,7 +103,10 @@ libquic_a_SOURCES = \
QUICStreamFactory.cc \
QUICPadder.cc \
QUICContext.cc \
- QUICTokenCreator.cc
+ QUICTokenCreator.cc \
+ QUICLogFrame.cc \
+ QUICLogEvent.cc \
+ QUICLog.cc
#
# Check Programs
diff --git a/iocore/net/quic/QUICCongestionController.h b/iocore/net/quic/QUICCongestionController.h
index 46341dc..9c6bfaf 100644
--- a/iocore/net/quic/QUICCongestionController.h
+++ b/iocore/net/quic/QUICCongestionController.h
@@ -23,6 +23,8 @@
#pragma once
+#include "QUICFrame.h"
+
struct QUICPacketInfo {
// 6.3.1. Sent Packet Fields
QUICPacketNumber packet_number;
diff --git a/iocore/net/quic/QUICContext.cc b/iocore/net/quic/QUICContext.cc
index 640dffd..ec84265 100644
--- a/iocore/net/quic/QUICContext.cc
+++ b/iocore/net/quic/QUICContext.cc
@@ -100,9 +100,10 @@ private:
const QUICConfigParams *_params;
};
-QUICContextImpl::QUICContextImpl(QUICRTTProvider *rtt, QUICConnectionInfoProvider *info,
+QUICContextImpl::QUICContextImpl(QLog::Trace &trace, QUICRTTProvider *rtt, QUICConnectionInfoProvider *info,
QUICPacketProtectionKeyInfoProvider *key_info, QUICPathManager *path_manager)
- : _key_info(key_info),
+ : _qlog_trace(trace),
+ _key_info(key_info),
_connection_info(info),
_rtt_provider(rtt),
_path_manager(path_manager),
@@ -152,3 +153,9 @@ QUICContextImpl::path_manager() const
{
return _path_manager;
}
+
+QLog::Trace &
+QUICContextImpl::qlog_trace()
+{
+ return this->_qlog_trace;
+}
\ No newline at end of file
diff --git a/iocore/net/quic/QUICContext.h b/iocore/net/quic/QUICContext.h
index b5b4023..65044fb 100644
--- a/iocore/net/quic/QUICContext.h
+++ b/iocore/net/quic/QUICContext.h
@@ -25,6 +25,7 @@
#include "QUICConnection.h"
#include "QUICConfig.h"
+#include "QUICLog.h"
class QUICRTTProvider;
class QUICCongestionController;
@@ -39,6 +40,7 @@ public:
virtual ~QUICContext(){};
virtual QUICConnectionInfoProvider *connection_info() const = 0;
virtual QUICConfig::scoped_config config() const = 0;
+ virtual QLog::Trace &qlog_trace() = 0;
};
class QUICLDContext
@@ -48,6 +50,7 @@ public:
virtual QUICConnectionInfoProvider *connection_info() const = 0;
virtual QUICLDConfig &ld_config() const = 0;
virtual QUICPacketProtectionKeyInfoProvider *key_info() const = 0;
+ virtual QLog::Trace &qlog_trace() = 0;
};
class QUICCCContext
@@ -57,6 +60,7 @@ public:
virtual QUICConnectionInfoProvider *connection_info() const = 0;
virtual QUICCCConfig &cc_config() const = 0;
virtual QUICRTTProvider *rtt_provider() const = 0;
+ virtual QLog::Trace &qlog_trace() = 0;
};
class QUICStreamManagerContext
@@ -66,13 +70,14 @@ public:
virtual QUICConnectionInfoProvider *connection_info() const = 0;
virtual QUICRTTProvider *rtt_provider() const = 0;
virtual QUICPathManager *path_manager() const = 0;
+ virtual QLog::Trace &qlog_trace() = 0;
};
class QUICContextImpl : public QUICContext, public QUICCCContext, public QUICLDContext, public QUICStreamManagerContext
{
public:
- QUICContextImpl(QUICRTTProvider *rtt, QUICConnectionInfoProvider *info, QUICPacketProtectionKeyInfoProvider *key_info,
- QUICPathManager *path_manager);
+ QUICContextImpl(QLog::Trace &trace, QUICRTTProvider *rtt, QUICConnectionInfoProvider *info,
+ QUICPacketProtectionKeyInfoProvider *key_info, QUICPathManager *path_manager);
virtual QUICConnectionInfoProvider *connection_info() const override;
virtual QUICConfig::scoped_config config() const override;
@@ -85,8 +90,10 @@ public:
virtual QUICCCConfig &cc_config() const override;
virtual QUICPathManager *path_manager() const override;
+ virtual QLog::Trace &qlog_trace() override;
private:
+ QLog::Trace &_qlog_trace;
QUICConfig::scoped_config _config;
QUICPacketProtectionKeyInfoProvider *_key_info = nullptr;
QUICConnectionInfoProvider *_connection_info = nullptr;
diff --git a/iocore/net/quic/QUICFrame.cc b/iocore/net/quic/QUICFrame.cc
index 2cffbd1..d4c939d 100644
--- a/iocore/net/quic/QUICFrame.cc
+++ b/iocore/net/quic/QUICFrame.cc
@@ -541,6 +541,22 @@ QUICCryptoFrame::data() const
// ACK frame
//
+std::set<QUICAckFrame::PacketNumberRange>
+QUICAckFrame::ranges() const
+{
+ std::set<QUICAckFrame::PacketNumberRange> numbers;
+ QUICPacketNumber x = this->largest_acknowledged();
+ numbers.insert({x, static_cast<uint64_t>(x) - this->ack_block_section()->first_ack_block()});
+ x -= this->ack_block_section()->first_ack_block() + 1;
+ for (auto &&block : *(this->ack_block_section())) {
+ x -= block.gap() + 1;
+ numbers.insert({x, static_cast<uint64_t>(x) - block.length()});
+ x -= block.length() + 1;
+ }
+
+ return numbers;
+}
+
QUICAckFrame::QUICAckFrame(const uint8_t *buf, size_t len, const QUICPacketR *packet) : QUICFrame(0, nullptr, packet)
{
this->parse(buf, len, packet);
@@ -2885,7 +2901,7 @@ QUICFrameFactory::create(uint8_t *buf, const uint8_t *src, size_t len, const QUI
}
}
-const QUICFrame &
+QUICFrame &
QUICFrameFactory::fast_create(const uint8_t *buf, size_t len, const QUICPacketR *packet)
{
if (QUICFrame::type(buf) == QUICFrameType::UNKNOWN) {
diff --git a/iocore/net/quic/QUICFrame.h b/iocore/net/quic/QUICFrame.h
index 0086804..24f8743 100644
--- a/iocore/net/quic/QUICFrame.h
+++ b/iocore/net/quic/QUICFrame.h
@@ -30,6 +30,7 @@
#include "I_IOBuffer.h"
#include <vector>
#include <iterator>
+#include <set>
#include "QUICTypes.h"
@@ -268,6 +269,7 @@ public:
QUICAckFrame(const uint8_t *buf, size_t len, const QUICPacketR *packet = nullptr);
QUICAckFrame(QUICPacketNumber largest_acknowledged, uint64_t ack_delay, uint64_t first_ack_block, QUICFrameId id = 0,
QUICFrameGenerator *owner = nullptr);
+ std::set<PacketNumberRange> ranges() const;
// There's no reasont restrict copy, but we need to write the copy constructor. Otherwise it will crash on destruct.
QUICAckFrame(const QUICAckFrame &) = delete;
@@ -745,6 +747,7 @@ public:
class QUICUnknownFrame : public QUICFrame
{
+public:
QUICFrameType type() const override;
size_t size() const override;
virtual Ptr<IOBufferBlock> to_io_buffer_block(size_t limit) const override;
@@ -767,7 +770,7 @@ public:
* This works almost the same as create() but it reuses created objects for performance.
* If you create a frame object which has the same frame type that you created before, the object will be reset by new data.
*/
- const QUICFrame &fast_create(const uint8_t *buf, size_t len, const QUICPacketR *packet);
+ QUICFrame &fast_create(const uint8_t *buf, size_t len, const QUICPacketR *packet);
/*
* Creates a STREAM frame.
diff --git a/iocore/net/quic/QUICFrameDispatcher.cc b/iocore/net/quic/QUICFrameDispatcher.cc
index 6832c7f..8229054 100644
--- a/iocore/net/quic/QUICFrameDispatcher.cc
+++ b/iocore/net/quic/QUICFrameDispatcher.cc
@@ -23,6 +23,7 @@
#include "QUICFrameDispatcher.h"
#include "QUICDebugNames.h"
+#include "QUICLogUtils.h"
static constexpr char tag[] = "quic_net";
@@ -31,7 +32,7 @@ static constexpr char tag[] = "quic_net";
//
// Frame Dispatcher
//
-QUICFrameDispatcher::QUICFrameDispatcher(QUICConnectionInfoProvider *info) : _info(info) {}
+QUICFrameDispatcher::QUICFrameDispatcher(QUICContext &context, QUICConnectionInfoProvider *info) : _context(context), _info(info) {}
void
QUICFrameDispatcher::add_handler(QUICFrameHandler *handler)
@@ -50,8 +51,10 @@ QUICFrameDispatcher::receive_frames(QUICEncryptionLevel level, const uint8_t *pa
is_flow_controlled = false;
QUICConnectionErrorUPtr error = nullptr;
+ std::unique_ptr<QLog::Transport::PacketReceived> qe =
+ std::make_unique<QLog::Transport::PacketReceived>(QLog::PacketTypeToName(packet->type()), QLog::QUICPacketToLogPacket(*packet));
while (cursor < size) {
- const QUICFrame &frame = this->_frame_factory.fast_create(payload + cursor, size - cursor, packet);
+ QUICFrame &frame = this->_frame_factory.fast_create(payload + cursor, size - cursor, packet);
if (frame.type() == QUICFrameType::UNKNOWN) {
QUICDebug("Failed to create a frame (%u bytes skipped)", size - cursor);
break;
@@ -67,6 +70,8 @@ QUICFrameDispatcher::receive_frames(QUICEncryptionLevel level, const uint8_t *pa
is_flow_controlled = true;
}
+ qe->append_frames(QLog::QLogFrameFactory::create(frame));
+
if (is_debug_tag_set(tag) && type != QUICFrameType::PADDING) {
char msg[1024];
frame.debug_msg(msg, sizeof(msg));
@@ -87,5 +92,6 @@ QUICFrameDispatcher::receive_frames(QUICEncryptionLevel level, const uint8_t *pa
}
}
+ this->_context.qlog_trace().push_event(std::move(qe));
return error;
}
diff --git a/iocore/net/quic/QUICFrameDispatcher.h b/iocore/net/quic/QUICFrameDispatcher.h
index cbbaef6..c7b48f4 100644
--- a/iocore/net/quic/QUICFrameDispatcher.h
+++ b/iocore/net/quic/QUICFrameDispatcher.h
@@ -28,11 +28,12 @@
#include "QUICConnection.h"
#include "QUICFrame.h"
#include "QUICFrameHandler.h"
+#include "QUICContext.h"
class QUICFrameDispatcher
{
public:
- QUICFrameDispatcher(QUICConnectionInfoProvider *info);
+ QUICFrameDispatcher(QUICContext &_context, QUICConnectionInfoProvider *info);
QUICConnectionErrorUPtr receive_frames(QUICEncryptionLevel level, const uint8_t *payload, uint16_t size,
bool &should_send_ackbool, bool &is_flow_controlled, bool *has_non_probing_frame,
@@ -41,6 +42,7 @@ public:
void add_handler(QUICFrameHandler *handler);
private:
+ QUICContext &_context;
QUICConnectionInfoProvider *_info = nullptr;
QUICFrameFactory _frame_factory;
std::vector<QUICFrameHandler *> _handlers[256];
diff --git a/iocore/net/quic/QUICLog.cc b/iocore/net/quic/QUICLog.cc
new file mode 100644
index 0000000..19aaed8
--- /dev/null
+++ b/iocore/net/quic/QUICLog.cc
@@ -0,0 +1,76 @@
+#include "QUICLog.h"
+
+namespace QLog
+{
+void
+Trace::encode(YAML::Node &node)
+{
+ node["title"] = _title;
+ node["description"] = _desc;
+
+ // common fields
+ {
+ YAML::Node cf;
+ cf["ODCID"] = _odcid;
+ cf["reference_time"] = std::to_string(this->_reference_time);
+ node["common_fields"] = cf;
+ }
+
+ {
+ node["event_fields"].push_back("relative_time");
+ node["event_fields"].push_back("category");
+ node["event_fields"].push_back("event");
+ node["event_fields"].push_back("data");
+
+ if (_vp.name != "") {
+ node["vantage_point"]["name"] = _vp.name;
+ }
+
+ if (vantage_point_type_name(_vp.type)) {
+ node["vantage_point"]["type"] = vantage_point_type_name(_vp.type);
+ }
+
+ if (vantage_point_type_name(_vp.flow)) {
+ node["vantage_point"]["flow"] = vantage_point_type_name(_vp.flow);
+ }
+ }
+
+ // events
+ for (auto &&it : _events) {
+ YAML::Node sub(YAML::NodeType::value::Sequence);
+ sub.push_back((it->get_time() - this->_reference_time) / 1000000);
+ sub.push_back(it->category());
+ sub.push_back(it->event());
+ YAML::Node event;
+ it->encode(event);
+ sub.push_back(event);
+ node["events"].push_back(sub);
+ }
+}
+
+void
+QUICLog::dump()
+{
+ YAML::Node root;
+ root["qlog_version"] = this->_ver;
+ root["title"] = this->_title;
+ root["description"] = this->_desc;
+ for (auto &&it : this->_traces) {
+ YAML::Node node;
+ it->encode(node);
+ root["traces"].push_back(node);
+ }
+
+ std::cout << "traces: " << root["traces"].size() << std::endl;
+ std::cout << "events: " << root["traces"][0]["events"].size() << std::endl;
+
+ std::ofstream ofs;
+ ofs.open(this->_file, std::ofstream::in | std::ofstream::trunc);
+
+ YAML::Emitter emitter(ofs);
+ emitter << YAML::DoubleQuoted << YAML::Flow << root;
+ ofs << "\n";
+ ofs.close();
+}
+
+} // namespace QLog
\ No newline at end of file
diff --git a/iocore/net/quic/QUICLog.h b/iocore/net/quic/QUICLog.h
new file mode 100644
index 0000000..5314292
--- /dev/null
+++ b/iocore/net/quic/QUICLog.h
@@ -0,0 +1,113 @@
+
+
+#pragma once
+
+#include <fstream>
+#include <yaml-cpp/yaml.h>
+
+#include "QUICLogEvent.h"
+
+namespace QLog
+{
+class Trace
+{
+public:
+ enum class VantagePointType : uint8_t {
+ client,
+ server,
+ network,
+ unknown,
+ };
+
+ struct VantagePoint {
+ std::string name;
+ VantagePointType type;
+ VantagePointType flow = VantagePointType::unknown;
+ };
+
+ Trace(std::string odcid, std::string title = "", std::string desc = "") : _reference_time(Thread::get_hrtime()) {}
+
+ Trace(const VantagePoint &vp, std::string odcid, std::string title = "", std::string desc = "") : Trace(odcid, title, desc)
+ {
+ set_vantage_point(vp);
+ }
+
+ static const char *
+ vantage_point_type_name(VantagePointType ty)
+ {
+ switch (ty) {
+ case VantagePointType::client:
+ return "client";
+ case VantagePointType::server:
+ return "server";
+ case VantagePointType::network:
+ return "network";
+ case VantagePointType::unknown:
+ return "unknown";
+ default:
+ return nullptr;
+ }
+ }
+
+ void
+ set_vantage_point(const VantagePoint &vp)
+ {
+ this->_vp = vp;
+ }
+
+ Trace &
+ push_event(QLogEventUPtr e)
+ {
+ this->_events.push_back(std::move(e));
+ return *this;
+ }
+
+ void encode(YAML::Node &node);
+
+private:
+ int64_t _reference_time = Thread::get_hrtime();
+ std::string _odcid;
+ std::string _title;
+ std::string _desc;
+
+ VantagePoint _vp;
+
+ std::vector<QLogEventUPtr> _events;
+};
+
+class QUICLog
+{
+public:
+ static constexpr char QLOG_VERSION[] = "draft-01";
+ // FIXME configurable
+ static constexpr char FILENAME[] = "ats.qlog";
+ QUICLog(std::string filename = FILENAME, std::string title = "", std::string desc = "", std::string ver = QLOG_VERSION)
+ : _file(filename), _title(title), _desc(desc), _ver(ver)
+ {
+ }
+
+ Trace &
+ new_trace(Trace::VantagePoint vp, std::string odcid, std::string title = "", std::string desc = "")
+ {
+ this->_traces.push_back(std::make_unique<Trace>(vp, odcid, title, desc));
+ return *this->_traces.back().get();
+ }
+
+ Trace &
+ new_trace(std::string odcid, std::string title = "", std::string desc = "")
+ {
+ this->_traces.push_back(std::make_unique<Trace>(odcid, title, desc));
+ return *this->_traces.back().get();
+ }
+
+ void dump();
+
+private:
+ std::string _file;
+ std::string _title;
+ std::string _desc;
+ std::string _ver;
+ std::vector<std::unique_ptr<Trace>> _traces;
+};
+
+} // namespace QLog
diff --git a/iocore/net/quic/QUICLogEvent.cc b/iocore/net/quic/QUICLogEvent.cc
new file mode 100644
index 0000000..4d69cca
--- /dev/null
+++ b/iocore/net/quic/QUICLogEvent.cc
@@ -0,0 +1,294 @@
+#include "QUICLogEvent.h"
+
+namespace QLog
+{
+void
+check_and_set(YAML::Node &node, std::string key, std::string val)
+{
+ if (val.length() > 0) {
+ node[key] = val;
+ }
+}
+
+void
+check_and_set(YAML::Node &node, std::string key, std::vector<std::string> val)
+{
+ if (val.size() > 0) {
+ node[key] = val;
+ }
+}
+
+template <typename T>
+void
+check_and_set(YAML::Node &node, std::string key, T val)
+{
+ if (val) {
+ node[key] = val;
+ }
+}
+
+namespace Connectivity
+{
+ void
+ ServerListening::encode(YAML::Node &node)
+ {
+ check_and_set(node, "ip_v4", _ip_v4);
+ check_and_set(node, "ip_v6", _ip_v6);
+ check_and_set(node, "port_v4", _port_v4);
+ check_and_set(node, "port_v6", _port_v6);
+ check_and_set(node, "stateless_reset_required", _port_v6);
+ check_and_set(node, "quic_version", _quic_version);
+ check_and_set(node, "alpn_values", _alpn_values);
+ }
+
+ void
+ ConnectionStarted::encode(YAML::Node &node)
+ {
+ check_and_set(node, "quic_version", _quic_version);
+ check_and_set(node, "ip_version", _ip_version);
+ check_and_set(node, "src_ip", _src_ip);
+ check_and_set(node, "dst_ip", _dst_ip);
+ check_and_set(node, "protocol", _protocol);
+ check_and_set(node, "src_port", _src_port);
+ check_and_set(node, "dst_port", _dst_port);
+ check_and_set(node, "src_cid", _src_cid);
+ check_and_set(node, "dst_cid", _dst_cid);
+ check_and_set(node, "alpn_values", _alpn_values);
+ }
+
+ void
+ ConnectionIdUpdated::encode(YAML::Node &node)
+ {
+ check_and_set(node, "src_old", _src_old);
+ check_and_set(node, "src_new", _src_new);
+ check_and_set(node, "dst_old", _dst_old);
+ check_and_set(node, "dst_new", _dst_new);
+ }
+
+ void
+ SpinBitUpdated::encode(YAML::Node &node)
+ {
+ check_and_set(node, "state", _state);
+ }
+
+ void
+ ConnectionStateUpdated::encode(YAML::Node &node)
+ {
+ check_and_set(node, "new", static_cast<int>(_new));
+ check_and_set(node, "old", static_cast<int>(_old));
+ check_and_set(node, "trigger", trigger_name(_trigger));
+ }
+
+} // namespace Connectivity
+
+namespace Security
+{
+ void
+ KeyEvent::encode(YAML::Node &node)
+ {
+ node["key_type"] = static_cast<int>(_key_type);
+ node["new"] = _new;
+ check_and_set(node, "generation", _generation);
+ check_and_set(node, "old", _old);
+ check_and_set(node, "trigger", trigger_name(_trigger));
+ }
+
+} // namespace Security
+
+namespace Transport
+{
+ void
+ ParametersSet::encode(YAML::Node &node)
+ {
+ node["owner"] = _owner ? "local" : "remote";
+ check_and_set(node, "resumption_allowed", _resumption_allowed);
+ check_and_set(node, "early_data_enabled", _early_data_enabled);
+ check_and_set(node, "alpn", _alpn);
+ check_and_set(node, "version", _version);
+ check_and_set(node, "tls_cipher", _tls_cipher);
+ check_and_set(node, "original_connection_id", _original_connection_id);
+ check_and_set(node, "stateless_reset_token", _stateless_reset_token);
+ check_and_set(node, "disable_active_migration", _disable_active_migration);
+ check_and_set(node, "max_idle_timeout", _max_idle_timeout);
+ check_and_set(node, "max_udp_payload_size", _max_udp_payload_size);
+ check_and_set(node, "ack_delay_exponent", _ack_delay_exponent);
+ check_and_set(node, "max_ack_delay", _max_ack_delay);
+ check_and_set(node, "active_connection_id_limit", _active_connection_id_limit);
+ check_and_set(node, "initial_max_data", _initial_max_data);
+ check_and_set(node, "initial_max_stream_data_bidi_local", _initial_max_stream_data_bidi_local);
+ check_and_set(node, "initial_max_stream_data_bidi_remote", _initial_max_stream_data_bidi_remote);
+ check_and_set(node, "initial_max_stream_data_uni", _initial_max_stream_data_uni);
+ check_and_set(node, "initial_max_streams_bidi", _initial_max_streams_bidi);
+ check_and_set(node, "initial_max_streams_uni", _initial_max_streams_uni);
+
+ if (_preferred_address.ip.length() > 0) {
+ YAML::Node sub;
+ check_and_set(sub, _preferred_address.ipv4 ? "ip_v4" : "ip_v6", _preferred_address.ip);
+ check_and_set(sub, _preferred_address.ipv4 ? "port_v4" : "port_v6", _preferred_address.port);
+ check_and_set(sub, "connection_id", _preferred_address.connection_id);
+ check_and_set(sub, "stateless_reset_token", _preferred_address.stateless_reset_token);
+ node["preferred_address"] = sub;
+ }
+ }
+
+ void
+ PacketEvent::encode(YAML::Node &node)
+ {
+ node["packet_type"] = _packet_type;
+ for (auto &&it : this->_frames) {
+ YAML::Node sub;
+ it->encode(sub);
+ node["frames"].push_back(sub);
+ }
+ check_and_set(node, "is_coalesced", _is_coalesced);
+ check_and_set(node, "stateless_reset_token", _stateless_reset_token);
+ check_and_set(node, "supported_version", _supported_version);
+ check_and_set(node, "raw_encrypted", _raw_encrypted);
+ check_and_set(node, "raw_decrypted", _raw_decrypted);
+ check_and_set(node, "supported_version", _supported_version);
+ check_and_set(node, "supported_version", trigger_name(_trigger));
+
+ node["header"]["packet_number"] = _header.packet_number;
+ node["header"]["packet_size"] = _header.packet_size;
+ node["header"]["payload_length"] = _header.payload_length;
+ node["header"]["version"] = _header.version;
+ node["header"]["scil"] = _header.scil;
+ node["header"]["dcil"] = _header.dcil;
+ node["header"]["scid"] = _header.scid;
+ node["header"]["dcid"] = _header.dcid;
+ }
+
+ void
+ PacketDropped::encode(YAML::Node &node)
+ {
+ node["packet_type"] = _packet_type;
+ check_and_set(node, "packet_size", _packet_size);
+ check_and_set(node, "raw", _raw);
+ check_and_set(node, "trigger", trigger_name(_trigger));
+ }
+
+ void
+ PacketBuffered::encode(YAML::Node &node)
+ {
+ node["packet_type"] = _packet_type;
+ check_and_set(node, "trigger", trigger_name(_trigger));
+ check_and_set(node, "packet_number", trigger_name(_trigger));
+ }
+
+ void
+ DatagramsEvent::encode(YAML::Node &node)
+ {
+ check_and_set(node, "count", _count);
+ check_and_set(node, "byte_length", _byte_length);
+ }
+
+ void
+ DatagramsDropped::encode(YAML::Node &node)
+ {
+ check_and_set(node, "byte_length", _byte_length);
+ }
+
+ void
+ StreamStateUpdated::encode(YAML::Node &node)
+ {
+ node["new"] = static_cast<int>(_new);
+ node["stream_id"] = _stream_id;
+ // FXIME
+ // node["stream_type"] = bidi ? "bidirectional" : "unidirectional";
+ // node["stream_side"] = "sending";
+ }
+
+ void
+ FrameProcessed::encode(YAML::Node &node)
+ {
+ for (auto &&it : _frames) {
+ YAML::Node sub;
+ it->encode(sub);
+ node["frames"].push_back(sub);
+ }
+ }
+
+} // namespace Transport
+
+namespace Recovery
+{
+ void
+ ParametersSet::encode(YAML::Node &node)
+ {
+ check_and_set(node, "reordering_threshold", _reordering_threshold);
+ check_and_set(node, "time_threshold", _time_threshold);
+ check_and_set(node, "timer_granularity", _timer_granularity);
+ check_and_set(node, "initial_rtt", _initial_rtt);
+ check_and_set(node, "max_datagram_size", _max_datagram_size);
+ check_and_set(node, "initial_congestion_window", _initial_congestion_window);
+ check_and_set(node, "minimum_congestion_window", _minimum_congestion_window);
+ check_and_set(node, "loss_reduction_factor", _loss_reduction_factor);
+ check_and_set(node, "persistent_congestion_threshold", _persistent_congestion_threshold);
+ }
+
+ void
+ MetricsUpdated::encode(YAML::Node &node)
+ {
+ check_and_set(node, "min_rtt", _min_rtt);
+ check_and_set(node, "smoothed_rtt", _smoothed_rtt);
+ check_and_set(node, "latest_rtt", _latest_rtt);
+ check_and_set(node, "rtt_variance", _rtt_variance);
+ check_and_set(node, "max_ack_delay", _max_ack_delay);
+ check_and_set(node, "pto_count", _pto_count);
+ check_and_set(node, "congestion_window", _congestion_window);
+ check_and_set(node, "bytes_in_flight", _bytes_in_flight);
+ check_and_set(node, "ssthresh", _ssthresh);
+ check_and_set(node, "packets_in_flight", _packets_in_flight);
+ check_and_set(node, "in_recovery", _in_recovery);
+ check_and_set(node, "pacing_rate", _pacing_rate);
+ }
+
+ void
+ CongestionStateUpdated::encode(YAML::Node &node)
+ {
+ node["new"] = state_to_string(_new);
+ check_and_set(node, "old", state_to_string(_old));
+ check_and_set(node, "old", trigger_name(_trigger));
+ }
+
+ void
+ LossTimerUpdated::encode(YAML::Node &node)
+ {
+ node["timer_type"] = _timer_type_ack ? "ack" : "pto";
+ check_and_set(node, "event_type", event_type_name(_event_type));
+ check_and_set(node, "packet_number_space", _packet_number_space);
+ if (_event_type == EventType::set) {
+ check_and_set(node, "delta", _delta);
+ }
+ }
+
+ void
+ PacketLost::encode(YAML::Node &node)
+ {
+ node["packet_number"] = _packet_number;
+ node["packet_type"] = _packet_type;
+ check_and_set(node, "trigger", trigger_name(_trigger));
+ YAML::Node sub;
+ _header.encode(sub);
+ node["header"] = sub;
+
+ for (auto &&it : _frames) {
+ YAML::Node sub;
+ it->encode(sub);
+ node["frames"].push_back(sub);
+ }
+ }
+
+ void
+ MarkedForRetransmit::encode(YAML::Node &node)
+ {
+ for (auto &&it : _frames) {
+ YAML::Node sub;
+ it->encode(sub);
+ node["frames"].push_back(sub);
+ }
+ }
+
+} // namespace Recovery
+
+} // namespace QLog
diff --git a/iocore/net/quic/QUICLogEvent.h b/iocore/net/quic/QUICLogEvent.h
new file mode 100644
index 0000000..f8f2c68
--- /dev/null
+++ b/iocore/net/quic/QUICLogEvent.h
@@ -0,0 +1,984 @@
+#pragma once
+
+#include <string>
+#include <yaml-cpp/yaml.h>
+
+#include "QUICTypes.h"
+#include "QUICLogFrame.h"
+
+namespace QLog
+{
+class QLogEvent
+{
+public:
+ virtual ~QLogEvent() {}
+
+ virtual std::string category() const = 0;
+ virtual std::string event() const = 0;
+ virtual void encode(YAML::Node &) = 0;
+
+ virtual ink_hrtime
+ get_time() const
+ {
+ return this->_time;
+ };
+
+protected:
+ ink_hrtime _time = Thread::get_hrtime();
+};
+
+using QLogEventUPtr = std::unique_ptr<QLogEvent>;
+
+#define SET(field, type) \
+ void set_##field(type v) { this->_node[#field] = v; }
+
+// enum class PacketType : uint8_t { initial, handshake, zerortt, onertt, retry, version_negotiation, unknown };
+using PacketType = std::string;
+
+struct PacketHeader {
+ std::string packet_number;
+ uint64_t packet_size;
+ uint64_t payload_length;
+
+ // only if present in the header
+ // if correctly using NEW_CONNECTION_ID events,
+ // dcid can be skipped for 1RTT packets
+ std::string version;
+ std::string scil;
+ std::string dcil;
+ std::string scid;
+ std::string dcid;
+
+ // Note: short vs long header is implicit through PacketType
+ void
+ encode(YAML::Node &node)
+ {
+ node["packet_number"] = packet_number;
+ node["packet_size"] = packet_size;
+ node["payload_length"] = payload_length;
+ node["version"] = version;
+ node["scil"] = scil;
+ node["dcil"] = dcil;
+ node["scid"] = scid;
+ node["dcid"] = dcid;
+ }
+};
+
+#define SET_FUNC(cla, field, type) \
+public: \
+ cla &set_##field(type v) \
+ { \
+ this->_##field = v; \
+ return *this; \
+ } \
+ \
+private: \
+ type _##field;
+
+#define APPEND_FUNC(cla, field, type) \
+public: \
+ cla &append_##field(type v) \
+ { \
+ this->_##field.push_back(v); \
+ return *this; \
+ } \
+ \
+private: \
+ std::vector<type> _##field;
+
+#define APPEND_FRAME_FUNC(cla) \
+public: \
+ cla &append_frames(QLogFrameUPtr v) \
+ { \
+ this->_frames.push_back(std::move(v)); \
+ return *this; \
+ } \
+ \
+private: \
+ std::vector<QLogFrameUPtr> _frames;
+
+//
+// connectivity
+//
+namespace Connectivity
+{
+ class ConnectivityEvent : public QLogEvent
+ {
+ public:
+ std::string
+ category() const override
+ {
+ return "connectivity";
+ }
+ };
+
+ class ServerListening : public ConnectivityEvent
+ {
+ public:
+ ServerListening(int port, bool v6 = false)
+ {
+ if (v6) {
+ set_port_v6(port);
+ } else {
+ set_port_v4(port);
+ }
+ }
+
+#define _SET(a, b) SET_FUNC(ServerListening, a, b)
+#define _APPEND(a, b) APPEND_FUNC(ServerListening, a, b)
+ _SET(port_v4, int)
+ _SET(port_v6, int)
+ _SET(ip_v4, std::string)
+ _SET(ip_v6, std::string)
+ _SET(stateless_reset_required, bool)
+ _APPEND(quic_version, std::string)
+ _APPEND(alpn_values, std::string)
+
+#undef _SET
+#undef _APPEND
+
+ void encode(YAML::Node &) override;
+
+ std::string
+ event() const override
+ {
+ return "server_listening";
+ }
+ };
+
+ class ConnectionStarted : public ConnectivityEvent
+ {
+ public:
+ ConnectionStarted(std::string version, std::string sip, std::string dip, int sport, int dport, std::string protocol = "QUIC")
+ {
+ set_ip_version(version);
+ set_protocol(protocol);
+ set_src_ip(sip);
+ set_dst_ip(dip);
+ set_src_port(sport);
+ set_dst_port(dport);
+ }
+
+#define _SET(a, b) SET_FUNC(ConnectionStarted, a, b)
+#define _APPEND(a, b) APPEND_FUNC(ConnectionStarted, a, b)
+ _SET(quic_version, std::string);
+ _SET(src_cid, std::string);
+ _SET(dst_cid, std::string);
+ _SET(protocol, std::string);
+ _SET(ip_version, std::string)
+ _SET(src_ip, std::string)
+ _SET(dst_ip, std::string)
+ _SET(src_port, int)
+ _SET(dst_port, int)
+ _APPEND(alpn_values, std::string)
+
+#undef _SET
+#undef _APPEND
+
+ void encode(YAML::Node &) override;
+
+ std::string
+ event() const override
+ {
+ return "connection_started";
+ }
+ };
+
+ class ConnectionIdUpdated : public ConnectivityEvent
+ {
+ public:
+ ConnectionIdUpdated(std::string old, std::string n, bool peer = false)
+ {
+ if (peer) {
+ set_dst_old(old);
+ set_dst_new(n);
+ } else {
+ set_src_old(old);
+ set_src_new(n);
+ }
+ }
+
+#define _SET(a, b) SET_FUNC(ConnectionIdUpdated, a, b)
+#define _APPEND(a, b) APPEND_FUNC(ConnectionIdUpdated, a, b)
+
+ _SET(src_old, std::string);
+ _SET(src_new, std::string);
+ _SET(dst_old, std::string);
+ _SET(dst_new, std::string);
+
+#undef _SET
+#undef _APPEND
+
+ void encode(YAML::Node &) override;
+
+ std::string
+ event() const override
+ {
+ return "connection_id_updated";
+ }
+ };
+
+ class SpinBitUpdated : public ConnectivityEvent
+ {
+ public:
+ SpinBitUpdated(bool state) { set_state(state); }
+
+#define _SET(a, b) SET_FUNC(SpinBitUpdated, a, b)
+ _SET(state, bool);
+#undef _SET
+
+ void encode(YAML::Node &) override;
+
+ std::string
+ event() const override
+ {
+ return "spin_bit_updated";
+ }
+ };
+
+ class ConnectionStateUpdated : public ConnectivityEvent
+ {
+ public:
+ enum class ConnectionState : uint8_t {
+ attempted, // client initial sent
+ reset, // stateless reset sent
+ handshake, // handshake in progress
+ active, // handshake successful, data exchange
+ keepalive, // no data for a longer period
+ draining, // CONNECTION_CLOSE sent
+ closed // connection actually fully closed, memory freed
+ };
+
+ enum class Triggered : uint8_t {
+ unknown,
+ error, // when closing because of an unexpected event
+ clean, // when closing normally
+ application // e.g., HTTP/3's GOAWAY frame
+ };
+
+ ConnectionStateUpdated(ConnectionState n, Triggered tr = Triggered::unknown)
+ {
+ set_new(n);
+ set_trigger(tr);
+ }
+
+#define _SET(a, b) SET_FUNC(ConnectionStateUpdated, a, b)
+ _SET(new, ConnectionState);
+ _SET(old, ConnectionState);
+ _SET(trigger, Triggered)
+
+#undef _SET
+
+ void encode(YAML::Node &) override;
+
+ static const char *
+ trigger_name(Triggered trigger)
+ {
+ switch (trigger) {
+ case Triggered::error:
+ return "error";
+ case Triggered::clean:
+ return "clean";
+ case Triggered::application:
+ return "application";
+ default:
+ return nullptr;
+ }
+ }
+
+ std::string
+ event() const override
+ {
+ return "connection_state_updated";
+ }
+ };
+
+} // namespace Connectivity
+
+namespace Security
+{
+ class KeyEvent : public QLogEvent
+ {
+ public:
+ enum class KeyType : uint8_t {
+ server_initial_secret,
+ client_initial_secret,
+
+ server_handshake_secret,
+ client_handshake_secret,
+
+ server_0rtt_secret,
+ client_0rtt_secret,
+
+ server_1rtt_secret,
+ client_1rtt_secret
+ };
+
+ enum class Triggered : uint8_t {
+ unknown,
+ remote_update,
+ local_update,
+ tls,
+ };
+
+ KeyEvent(KeyType ty, std::string n, int generation, Triggered triggered = Triggered::unknown)
+ {
+ set_key_type(ty);
+ set_new(n);
+ set_generation(generation);
+ set_trigger(triggered);
+ }
+
+#define _SET(a, b) SET_FUNC(KeyEvent, a, b)
+ _SET(key_type, KeyType);
+ _SET(new, std::string)
+ _SET(old, std::string);
+ _SET(generation, int)
+ _SET(trigger, Triggered)
+#undef _SET
+
+ void encode(YAML::Node &) override;
+
+ const char *
+ trigger_name(Triggered triggered)
+ {
+ switch (triggered) {
+ case Triggered::remote_update:
+ return "remote_update";
+ case Triggered::local_update:
+ return "local_update";
+ case Triggered::tls:
+ return "tls";
+ default:
+ return nullptr;
+ }
+ }
+
+ std::string
+ category() const override
+ {
+ return "security";
+ }
+ };
+
+ class KeyUpdated : public KeyEvent
+ {
+ public:
+ KeyUpdated(KeyType ty, std::string n, int generation, Triggered triggered = KeyEvent::Triggered::unknown)
+ : KeyEvent(ty, n, generation, triggered)
+ {
+ }
+
+ std::string
+ event() const override
+ {
+ return "key_updated";
+ }
+ };
+
+ class KeyRetired : public KeyEvent
+ {
+ public:
+ KeyRetired(KeyType ty, std::string n, int generation, Triggered triggered = KeyEvent::Triggered::unknown)
+ : KeyEvent(ty, n, generation, triggered)
+ {
+ }
+
+ std::string
+ event() const override
+ {
+ return "key_retired";
+ }
+ };
+
+} // namespace Security
+
+//
+// transport event
+//
+namespace Transport
+{
+ class TransportEvent : public QLogEvent
+ {
+ public:
+ std::string
+ category() const override
+ {
+ return "transport";
+ }
+ };
+
+ class ParametersSet : public TransportEvent
+ {
+ public:
+ struct PreferredAddress {
+ std::string ip;
+ int port;
+ std::string connection_id;
+ std::string stateless_reset_token;
+ bool ipv4 = true;
+ };
+
+ ParametersSet(bool owner) : _owner(owner) {}
+
+ std::string
+ event() const override
+ {
+ return "parameters_set";
+ }
+
+#define _SET(a, b) SET_FUNC(ParametersSet, a, b)
+ _SET(resumption_allowed, bool); // early data extension was enabled on the TLS layer
+ _SET(early_data_enabled, bool); // early data extension was enabled on the TLS layer
+ _SET(alpn, std::string);
+ _SET(version, std::string); // hex (e.g. 0x);
+ _SET(tls_cipher, std::string); // (e.g. AES_128_GCM_SHA256);
+ _SET(original_connection_id, std::string); // hex
+ _SET(stateless_reset_token, std::string); // hex
+ _SET(disable_active_migration, bool);
+ _SET(idle_timeout, int);
+ _SET(max_packet_size, int);
+ _SET(ack_delay_exponent, int);
+ _SET(max_ack_delay, int);
+ _SET(active_connection_id_limit, int);
+ _SET(initial_max_data, std::string);
+ _SET(initial_max_stream_data_bidi_local, std::string);
+ _SET(initial_max_stream_data_bidi_remote, std::string);
+ _SET(initial_max_stream_data_uni, std::string);
+ _SET(initial_max_streams_bidi, std::string);
+ _SET(initial_max_streams_uni, std::string);
+ _SET(max_idle_timeout, int64_t)
+ _SET(max_udp_payload_size, size_t)
+ _SET(preferred_address, PreferredAddress)
+#undef _SET
+
+ void encode(YAML::Node &) override;
+
+ private:
+ bool _owner = false;
+ };
+
+ class PacketEvent : public TransportEvent
+ {
+ public:
+ enum class Triggered : uint8_t {
+ unknown,
+ keys_available, // if packet was buffered because it couldn't be decrypted before
+ retransmit_reordered, // draft-23 5.1.1
+ retransmit_timeout, // draft-23 5.1.2
+ pto_probe, // draft-23 5.3.1
+ retransmit_crypto, // draft-19 6.2
+ cc_bandwidth_probe, // needed for some CCs to figure out bandwidth allocations when there are no normal sends
+ };
+
+ PacketEvent(PacketType type, PacketHeader h, Triggered tr = Triggered::unknown)
+ {
+ set_packet_type(type).set_header(h).set_trigger(tr);
+ }
+
+#define _SET(a, b) SET_FUNC(PacketEvent, a, b)
+#define _APPEND(a, b) APPEND_FUNC(PacketEvent, a, b)
+ _SET(packet_type, PacketType)
+ _SET(header, PacketHeader)
+ _SET(is_coalesced, bool);
+ _SET(raw_encrypted, std::string);
+ _SET(raw_decrypted, std::string);
+ _SET(stateless_reset_token, std::string);
+ _SET(trigger, Triggered);
+ _APPEND(supported_version, std::string);
+
+#undef _SET
+#undef _APPEND
+ APPEND_FRAME_FUNC(PacketEvent)
+
+ void encode(YAML::Node &) override;
+
+ static const char *
+ trigger_name(Triggered triggered)
+ {
+ switch (triggered) {
+ case Triggered::retransmit_reordered:
+ return "retransmit_reordered";
+ case Triggered::retransmit_timeout:
+ return "retransmit_timeout";
+ case Triggered::pto_probe:
+ return "pto_probe";
+ case Triggered::retransmit_crypto:
+ return "retransmit_crypto";
+ case Triggered::cc_bandwidth_probe:
+ return "cc_bandwidth_probe";
+ break;
+ case Triggered::keys_available:
+ return "keys_available";
+ default:
+ return nullptr;
+ }
+ }
+ };
+
+ class PacketSent : public PacketEvent
+ {
+ public:
+ PacketSent(PacketType type, PacketHeader h, Triggered tr = Triggered::unknown) : PacketEvent(type, h, tr) {}
+ std::string
+ event() const override
+ {
+ return "packet_sent";
+ }
+ };
+
+ class PacketReceived : public PacketEvent
+ {
+ public:
+ PacketReceived(PacketType type, PacketHeader h, Triggered tr = Triggered::unknown) : PacketEvent(type, h, tr) {}
+ std::string
+ event() const override
+ {
+ return "packet_received";
+ }
+ };
+
+ class PacketDropped : public TransportEvent
+ {
+ public:
+ enum class Triggered : uint8_t {
+ unknown,
+ key_unavailable,
+ unknown_connection_id,
+ header_decrypt_error,
+ payload_decrypt_error,
+ protocol_violation,
+ dos_prevention,
+ unsupported_version,
+ unexpected_packet,
+ unexpected_source_connection_id,
+ unexpected_version,
+ };
+
+ PacketDropped(Triggered tr = Triggered::unknown) { set_trigger(tr); }
+
+#define _SET(a, b) SET_FUNC(PacketDropped, a, b)
+ _SET(packet_size, int);
+ _SET(raw, std::string);
+ _SET(trigger, Triggered);
+ _SET(packet_type, PacketType)
+#undef _SET
+
+ void encode(YAML::Node &) override;
+
+ std::string
+ event() const override
+ {
+ return "packet_dropped";
+ }
+
+ static const char *
+ trigger_name(Triggered tr)
+ {
+ switch (tr) {
+ case Triggered::key_unavailable:
+ return "key_unavailable";
+ case Triggered::unknown_connection_id:
+ return "unknown_connection_id";
+ case Triggered::header_decrypt_error:
+ return "header_decrypt_error";
+ case Triggered::payload_decrypt_error:
+ return "payload_decrypt_error";
+ case Triggered::protocol_violation:
+ return "protocol_violation";
+ case Triggered::dos_prevention:
+ return "dos_prevention";
+ case Triggered::unsupported_version:
+ return "unsupported_version";
+ case Triggered::unexpected_packet:
+ return "unexpected_packet";
+ case Triggered::unexpected_source_connection_id:
+ return "unexpected_source_connection_id";
+ case Triggered::unexpected_version:
+ return "unexpected_version";
+ default:
+ return nullptr;
+ }
+ }
+ };
+
+ class PacketBuffered : public TransportEvent
+ {
+ public:
+ enum class Triggered : uint8_t {
+ unknown,
+ backpressure,
+ keys_unavailable,
+ };
+
+ PacketBuffered(Triggered tr = Triggered::unknown) { set_trigger(tr); }
+
+#define _SET(a, b) SET_FUNC(PacketBuffered, a, b)
+ _SET(trigger, Triggered);
+ _SET(packet_type, PacketType)
+ _SET(packet_number, std::string)
+#undef _SET
+
+ void encode(YAML::Node &) override;
+
+ std::string
+ event() const override
+ {
+ return "packet_buffered";
+ }
+
+ static const char *
+ trigger_name(Triggered tr)
+ {
+ switch (tr) {
+ case Triggered::backpressure:
+ return "backpressure";
+ case Triggered::keys_unavailable:
+ return "keys_unavailable";
+ default:
+ return nullptr;
+ }
+ }
+ };
+
+ class DatagramsEvent : public TransportEvent
+ {
+ public:
+#define _SET(a, b) SET_FUNC(DatagramsEvent, a, b)
+ _SET(count, int);
+ _SET(byte_length, int);
+#undef _SET
+ void encode(YAML::Node &) override;
+ };
+
+ class DatagramsSent : public DatagramsEvent
+ {
+ public:
+ std::string
+ event() const override
+ {
+ return "datagrams_sent";
+ }
+ };
+ class DatagramReceived : public DatagramsEvent
+ {
+ public:
+ std::string
+ event() const override
+ {
+ return "datagrams_received";
+ }
+ };
+
+ class DatagramsDropped : public TransportEvent
+ {
+ public:
+#define _SET(a, b) SET_FUNC(DatagramsDropped, a, b)
+ _SET(byte_length, int);
+#undef _SET
+
+ void encode(YAML::Node &) override;
+
+ std::string
+ event() const override
+ {
+ return "datagrams_dropped";
+ }
+ };
+
+ class StreamStateUpdated : public TransportEvent
+ {
+ enum class StreamState {
+ // bidirectional stream states, draft-23 3.4.
+ idle,
+ open,
+ half_closed_local,
+ half_closed_remote,
+ closed,
+
+ // sending-side stream states, draft-23 3.1.
+ ready,
+ send,
+ data_sent,
+ reset_sent,
+ reset_received,
+
+ // receive-side stream states, draft-23 3.2.
+ receive,
+ size_known,
+ data_read,
+ reset_read,
+
+ // both-side states
+ data_received,
+
+ // qlog-defined
+ destroyed // memory actually freed
+ };
+
+ StreamStateUpdated(std::string stream_id, StreamState n) { set_new(n).set_stream_id(stream_id); }
+
+ void encode(YAML::Node &) override;
+
+#define _SET(a, b) SET_FUNC(StreamStateUpdated, a, b)
+ _SET(new, StreamState);
+ _SET(old, StreamState);
+ _SET(stream_id, std::string);
+ _SET(bidi, bool);
+#undef _SET
+
+ std::string
+ event() const override
+ {
+ return "stream_state_updated";
+ }
+ };
+
+ class FrameProcessed : public TransportEvent
+ {
+ public:
+ APPEND_FRAME_FUNC(FrameProcessed)
+
+ void encode(YAML::Node &) override;
+
+ std::string
+ event() const override
+ {
+ return "frame_processed";
+ }
+ };
+
+} // namespace Transport
+
+namespace Recovery
+{
+ class RecoveryEvent : public QLogEvent
+ {
+ public:
+ std::string
+ category() const override
+ {
+ return "recovery";
+ }
+ };
+
+ class ParametersSet : public RecoveryEvent
+ {
+ public:
+#define _SET(a, b) SET_FUNC(ParametersSet, a, b)
+ _SET(reordering_threshold, int);
+ _SET(time_threshold, int);
+ _SET(timer_granularity, int);
+ _SET(initial_rtt, int);
+ _SET(max_datagram_size, int);
+ _SET(initial_congestion_window, int);
+ _SET(minimum_congestion_window, int);
+ _SET(loss_reduction_factor, int);
+ _SET(persistent_congestion_threshold, int);
+#undef _SET
+ void encode(YAML::Node &) override;
+
+ std::string
+ event() const override
+ {
+ return "parameters_set";
+ }
+ };
+
+ class MetricsUpdated : public RecoveryEvent
+ {
+ public:
+#define _SET(a, b) SET_FUNC(MetricsUpdated, a, b)
+ _SET(min_rtt, int);
+ _SET(smoothed_rtt, int);
+ _SET(latest_rtt, int);
+ _SET(rtt_variance, int);
+ _SET(max_ack_delay, int);
+ _SET(pto_count, int);
+ _SET(congestion_window, int);
+ _SET(bytes_in_flight, int);
+ _SET(ssthresh, int);
+ _SET(packets_in_flight, int);
+ _SET(in_recovery, int);
+ _SET(pacing_rate, int);
+#undef _SET
+ void encode(YAML::Node &) override;
+
+ std::string
+ event() const override
+ {
+ return "metrics_updated";
+ }
+ };
+
+ class CongestionStateUpdated : public RecoveryEvent
+ {
+ public:
+ enum class State : uint8_t {
+ slow_start,
+ congestion_avoidance,
+ application_limited,
+ recovery,
+ };
+
+ enum class Triggered : uint8_t {
+ unknown,
+ persistent_congestion,
+ ECN,
+ };
+
+ CongestionStateUpdated(State n, Triggered tr = Triggered::unknown) { set_trigger(tr).set_new(n); }
+
+#define _SET(a, b) SET_FUNC(CongestionStateUpdated, a, b)
+ _SET(trigger, Triggered)
+ _SET(new, State)
+ _SET(old, State)
+#undef _SET
+
+ void encode(YAML::Node &) override;
+
+ std::string
+ event() const override
+ {
+ return "congestion_state_updated";
+ }
+
+ static const char *
+ trigger_name(Triggered tr)
+ {
+ switch (tr) {
+ case Triggered::persistent_congestion:
+ return "persistent_congestion";
+ case Triggered::ECN:
+ return "ECN";
+ default:
+ return nullptr;
+ }
+ }
+
+ static const char *
+ state_to_string(State s)
+ {
+ switch (s) {
+ case State::slow_start:
+ return "slow_start";
+ case State::congestion_avoidance:
+ return "congestion_avoidance";
+ case State::application_limited:
+ return "application_limited";
+ case State::recovery:
+ return "recovery";
+ default:
+ break;
+ }
+ }
+ };
+
+ class LossTimerUpdated : public RecoveryEvent
+ {
+ public:
+ enum class EventType : uint8_t {
+ set,
+ expired,
+ cancelled,
+ };
+
+ void
+ set_timer_type(bool ack)
+ {
+ this->_timer_type_ack = ack;
+ }
+
+ void encode(YAML::Node &) override;
+
+#define _SET(a, b) SET_FUNC(LossTimerUpdated, a, b)
+ _SET(event_type, EventType)
+ _SET(packet_number_space, int);
+ _SET(delta, int);
+#undef _SET
+
+ std::string
+ event() const override
+ {
+ return "loss_timer_updated";
+ }
+
+ static const char *
+ event_type_name(EventType et)
+ {
+ switch (et) {
+ case EventType::set:
+ return "set";
+ case EventType::expired:
+ return "expired";
+ case EventType::cancelled:
+ return "cancelled";
+ }
+ }
+
+ private:
+ bool _timer_type_ack = false;
+ };
+
+ class PacketLost : public RecoveryEvent
+ {
+ public:
+ enum class Triggered : uint8_t {
+ unknown,
+ reordering_threshold,
+ time_threshold,
+ pto_expired,
+ };
+
+ PacketLost(PacketType pt, uint64_t pn, Triggered tr = Triggered::unknown)
+ {
+ set_trigger(tr).set_packet_type(pt).set_packet_number(pn);
+ }
+
+#define _SET(a, b) SET_FUNC(PacketLost, a, b)
+ _SET(header, PacketHeader)
+ _SET(packet_number, uint64_t);
+ _SET(packet_type, PacketType);
+ _SET(trigger, Triggered)
+ APPEND_FRAME_FUNC(PacketLost)
+#undef _SET
+
+ void encode(YAML::Node &) override;
+
+ std::string
+ event() const override
+ {
+ return "packet_lost";
+ }
+
+ static const char *
+ trigger_name(Triggered tr)
+ {
+ switch (tr) {
+ case Triggered::pto_expired:
+ return "pto_expired";
+ case Triggered::reordering_threshold:
+ return "reordering_threshold";
+ case Triggered::time_threshold:
+ return "time_threshold";
+ default:
+ return nullptr;
+ }
+ }
+ };
+
+ class MarkedForRetransmit : public RecoveryEvent
+ {
+ public:
+ APPEND_FRAME_FUNC(MarkedForRetransmit)
+ void encode(YAML::Node &) override;
+ std::string
+ event() const override
+ {
+ return "marked_for_retransmit";
+ }
+ };
+
+} // namespace Recovery
+
+} // namespace QLog
diff --git a/iocore/net/quic/QUICLogFrame.cc b/iocore/net/quic/QUICLogFrame.cc
new file mode 100644
index 0000000..f72dc1e
--- /dev/null
+++ b/iocore/net/quic/QUICLogFrame.cc
@@ -0,0 +1,258 @@
+#include "QUICLogFrame.h"
+
+namespace QLog
+{
+template <typename Real>
+Real &
+Convert(QUICFrame &frame)
+{
+ auto tmp = &frame;
+#if defined(DEBUG)
+ auto ref = dynamic_cast<Real *>(tmp);
+ ink_assert(ref != nullptr);
+ return *ref;
+#endif
+ return *static_cast<Real *>(tmp);
+}
+
+QLogFrameUPtr
+QLogFrameFactory::create(QUICFrame &frame)
+{
+ switch (frame.type()) {
+ case QUICFrameType::ACK:
+ return std::make_unique<Frame::AckFrame>(Convert<QUICAckFrame>(frame));
+ case QUICFrameType::PADDING:
+ return std::make_unique<Frame::PaddingFrame>(Convert<QUICPaddingFrame>(frame));
+ case QUICFrameType::PING:
+ return std::make_unique<Frame::PingFrame>(Convert<QUICPingFrame>(frame));
+ case QUICFrameType::RESET_STREAM:
+ return std::make_unique<Frame::RstStreamFrame>(Convert<QUICRstStreamFrame>(frame));
+ case QUICFrameType::STOP_SENDING:
+ return std::make_unique<Frame::StopSendingFrame>(Convert<QUICStopSendingFrame>(frame));
+ case QUICFrameType::CRYPTO:
+ return std::make_unique<Frame::CryptoFrame>(Convert<QUICCryptoFrame>(frame));
+ case QUICFrameType::NEW_TOKEN:
+ return std::make_unique<Frame::NewTokenFrame>(Convert<QUICNewTokenFrame>(frame));
+ case QUICFrameType::STREAM:
+ return std::make_unique<Frame::StreamFrame>(Convert<QUICStreamFrame>(frame));
+ case QUICFrameType::MAX_DATA:
+ return std::make_unique<Frame::MaxDataFrame>(Convert<QUICMaxDataFrame>(frame));
+ case QUICFrameType::MAX_STREAM_DATA:
+ return std::make_unique<Frame::MaxStreamDataFrame>(Convert<QUICMaxStreamDataFrame>(frame));
+ case QUICFrameType::MAX_STREAMS:
+ return std::make_unique<Frame::MaxStreamsFrame>(Convert<QUICMaxStreamsFrame>(frame));
+ case QUICFrameType::DATA_BLOCKED:
+ return std::make_unique<Frame::DataBlockedFrame>(Convert<QUICDataBlockedFrame>(frame));
+ case QUICFrameType::STREAM_DATA_BLOCKED:
+ return std::make_unique<Frame::StreamDataBlockedFrame>(Convert<QUICStreamDataBlockedFrame>(frame));
+ case QUICFrameType::STREAMS_BLOCKED:
+ return std::make_unique<Frame::StreamsBlockedFrame>(Convert<QUICStreamIdBlockedFrame>(frame));
+ case QUICFrameType::NEW_CONNECTION_ID:
+ return std::make_unique<Frame::NewConnectionIDFrame>(Convert<QUICNewConnectionIdFrame>(frame));
+ case QUICFrameType::RETIRE_CONNECTION_ID:
+ return std::make_unique<Frame::RetireConnectionIDFrame>(Convert<QUICRetireConnectionIdFrame>(frame));
+ case QUICFrameType::PATH_CHALLENGE:
+ return std::make_unique<Frame::PathChallengeFrame>(Convert<QUICPathChallengeFrame>(frame));
+ case QUICFrameType::PATH_RESPONSE:
+ return std::make_unique<Frame::PathResponseFrame>(Convert<QUICPathResponseFrame>(frame));
+ case QUICFrameType::CONNECTION_CLOSE:
+ return std::make_unique<Frame::ConnectionCloseFrame>(Convert<QUICConnectionCloseFrame>(frame));
+ case QUICFrameType::HANDSHAKE_DONE:
+ return std::make_unique<Frame::HandshakeDoneFrame>(Convert<QUICHandshakeDoneFrame>(frame));
+ default:
+ ink_release_assert(0);
+ return nullptr;
+ }
+}
+
+namespace Frame
+{
+ template <typename T>
+ std::string
+ convert_to_string(T a)
+ {
+ return std::to_string(static_cast<uint64_t>(a));
+ }
+
+ void
+ AckFrame::encode(YAML::Node &node)
+ {
+ node["frame_type"] = "ack";
+ node["ack_delay"] = std::to_string(ack_delay);
+ for (auto &it : acked_range) {
+ YAML::Node sub;
+ sub.push_back(convert_to_string(it.first()));
+ sub.push_back(convert_to_string(it.last()));
+ node["acked_ranges"].push_back(sub);
+ }
+
+ if (ect1) {
+ node["ect1"] = ect1;
+ }
+
+ if (ect1) {
+ node["ect0"] = ect0;
+ }
+
+ if (ce) {
+ node["ce"] = ce;
+ }
+ }
+
+ void
+ StreamFrame::encode(YAML::Node &node)
+ {
+ node["frame_type"] = "stream";
+ node["stream_id"] = stream_id;
+ node["offset"] = offset;
+ node["length"] = length;
+ node["fin"] = fin;
+ }
+
+ void
+ PaddingFrame::encode(YAML::Node &node)
+ {
+ node["frame_type"] = "padding";
+ }
+
+ void
+ PingFrame::encode(YAML::Node &node)
+ {
+ node["frame_type"] = "ping";
+ }
+
+ void
+ RstStreamFrame::encode(YAML::Node &node)
+ {
+ node["frame_type"] = "reset_stream";
+ node["stream_id"] = stream_id;
+ node["error_code"] = error_code;
+ node["final_size"] = final_size;
+ }
+
+ void
+ StopSendingFrame::encode(YAML::Node &node)
+ {
+ node["frame_type"] = "stop_sending";
+ node["stream_id"] = stream_id;
+ node["error_code"] = error_code;
+ }
+
+ void
+ CryptoFrame::encode(YAML::Node &node)
+ {
+ node["frame_type"] = "crypto";
+ node["offset"] = offset;
+ node["length"] = length;
+ }
+
+ void
+ NewTokenFrame::encode(YAML::Node &node)
+ {
+ node["frame_type"] = "new_token";
+ node["token"] = token;
+ node["length"] = length;
+ }
+
+ void
+ MaxDataFrame::encode(YAML::Node &node)
+ {
+ node["frame_type"] = "max_data";
+ node["maximum"] = maximum;
+ }
+
+ void
+ MaxStreamDataFrame::encode(YAML::Node &node)
+ {
+ node["frame_type"] = "max_stream_data";
+ node["maximum"] = maximum;
+ node["stream_id"] = stream_id;
+ }
+
+ void
+ MaxStreamsFrame::encode(YAML::Node &node)
+ {
+ node["frame_type"] = "max_streams";
+ node["maximum"] = maximum;
+ node["stream_type"] = stream_type;
+ }
+
+ void
+ DataBlockedFrame::encode(YAML::Node &node)
+ {
+ node["frame_type"] = "data_blocked";
+ node["limit"] = limit;
+ }
+
+ void
+ StreamDataBlockedFrame::encode(YAML::Node &node)
+ {
+ node["frame_type"] = "stream_data_blocked";
+ node["limit"] = limit;
+ node["stream_id"] = stream_id;
+ }
+
+ void
+ StreamsBlockedFrame::encode(YAML::Node &node)
+ {
+ node["frame_type"] = "streams_blocked";
+ node["stream_id"] = stream_id;
+ node["stream_type"] = stream_type;
+ }
+
+ void
+ NewConnectionIDFrame::encode(YAML::Node &node)
+ {
+ node["frame_type"] = "new_connection_id";
+ node["sequence_number"] = sequence_number;
+ node["retire_prior_to"] = retire_prior_to;
+ node["stateless_reset_token"] = stateless_reset_token;
+ node["length"] = length;
+ }
+
+ void
+ RetireConnectionIDFrame::encode(YAML::Node &node)
+ {
+ node["frame_type"] = "retire_connection_id";
+ node["sequence_number"] = sequence_number;
+ }
+
+ void
+ PathChallengeFrame::encode(YAML::Node &node)
+ {
+ node["frame_type"] = "path_challenge";
+ node["data"] = data;
+ }
+
+ void
+ PathResponseFrame::encode(YAML::Node &node)
+ {
+ node["frame_type"] = "path_response";
+ node["data"] = data;
+ }
+
+ void
+ ConnectionCloseFrame::encode(YAML::Node &node)
+ {
+ node["frame_type"] = "connection_close";
+ node["error_space"] = error_space;
+ node["error_code"] = error_code;
+ node["raw_error_code"] = raw_error_code;
+ node["reason"] = reason;
+ }
+
+ void
+ HandshakeDoneFrame::encode(YAML::Node &node)
+ {
+ node["frame_type"] = "handshake_done";
+ }
+
+ void
+ UnknownFrame::encode(YAML::Node &node)
+ {
+ node["frame_type"] = "unknown";
+ node["raw_frame_type"] = raw_frame_type;
+ }
+
+} // namespace Frame
+} // namespace QLog
diff --git a/iocore/net/quic/QUICLogFrame.h b/iocore/net/quic/QUICLogFrame.h
new file mode 100644
index 0000000..dda4c90
--- /dev/null
+++ b/iocore/net/quic/QUICLogFrame.h
@@ -0,0 +1,286 @@
+#pragma once
+
+#include <memory>
+#include <yaml-cpp/yaml.h>
+
+#include "QUICFrame.h"
+
+namespace QLog
+{
+class QLogFrame
+{
+public:
+ QLogFrame(QUICFrameType type) : _type(type) {}
+ virtual ~QLogFrame() {}
+
+ QUICFrameType
+ type() const
+ {
+ return this->_type;
+ }
+
+ // encode frame into YAML stype
+ virtual void encode(YAML::Node &node) = 0;
+
+protected:
+ QUICFrameType _type = QUICFrameType::UNKNOWN;
+};
+
+using QLogFrameUPtr = std::unique_ptr<QLogFrame>;
+
+//
+// convert QUICFrame to QLogFrame
+//
+class QLogFrameFactory
+{
+public:
+ // create QLogFrame
+ static QLogFrameUPtr create(QUICFrame &frame);
+};
+
+namespace Frame
+{
+ struct AckFrame : public QLogFrame {
+ AckFrame(QUICAckFrame &frame) : QLogFrame(frame.type())
+ {
+ acked_range = frame.ranges();
+ ack_delay = frame.ack_delay();
+ if (frame.ecn_section()) {
+ ect0 = frame.ecn_section()->ect0_count();
+ ect1 = frame.ecn_section()->ect1_count();
+ ce = frame.ecn_section()->ecn_ce_count();
+ }
+ }
+
+ void encode(YAML::Node &) override;
+
+ std::set<QUICAckFrame::PacketNumberRange> acked_range;
+ uint64_t ect1 = 0;
+ uint64_t ect0 = 0;
+ uint64_t ce = 0;
+ uint64_t ack_delay = 0;
+ };
+
+ struct StreamFrame : public QLogFrame {
+ StreamFrame(QUICStreamFrame &frame) : QLogFrame(frame.type())
+ {
+ stream_id = std::to_string(static_cast<uint64_t>(frame.stream_id()));
+ offset = std::to_string(static_cast<uint64_t>(frame.offset()));
+ length = frame.data_length();
+ fin = frame.has_fin_flag();
+ }
+
+ void encode(YAML::Node &) override;
+ std::string stream_id;
+
+ // These two MUST always be set
+ // If not present in the Frame type, log their default values
+ std::string offset;
+ uint64_t length = 0;
+
+ // this MAY be set any time, but MUST only be set if the value is "true"
+ // if absent, the value MUST be assumed to be "false"
+ bool fin = false;
+
+ // FIXME raw
+ };
+
+ struct PaddingFrame : public QLogFrame {
+ PaddingFrame(QUICPaddingFrame &frame) : QLogFrame(frame.type()) {}
+ void encode(YAML::Node &) override;
+ };
+
+ struct PingFrame : public QLogFrame {
+ PingFrame(QUICPingFrame &frame) : QLogFrame(frame.type()) {}
+ void encode(YAML::Node &) override;
+ };
+
+ struct RstStreamFrame : public QLogFrame {
+ RstStreamFrame(QUICRstStreamFrame &frame) : QLogFrame(frame.type())
+ {
+ stream_id = std::to_string(static_cast<uint64_t>(frame.stream_id()));
+ error_code = frame.error_code();
+ final_size = std::to_string(frame.final_offset());
+ }
+
+ void encode(YAML::Node &) override;
+ std::string stream_id;
+ // FIXME ApplicationError
+ uint64_t error_code = 0;
+ std::string final_size;
+ };
+
+ struct StopSendingFrame : public QLogFrame {
+ StopSendingFrame(QUICStopSendingFrame &frame) : QLogFrame(frame.type())
+ {
+ stream_id = std::to_string(static_cast<uint64_t>(frame.stream_id()));
+ error_code = frame.error_code();
+ }
+
+ void encode(YAML::Node &) override;
+ std::string stream_id;
+ // FIXME ApplicationError
+ uint64_t error_code = 0;
+ };
+
+ struct CryptoFrame : public QLogFrame {
+ CryptoFrame(QUICCryptoFrame &frame) : QLogFrame(frame.type())
+ {
+ offset = std::to_string(static_cast<uint64_t>(frame.offset()));
+ length = frame.data_length();
+ }
+
+ void encode(YAML::Node &) override;
+ std::string offset;
+ uint64_t length = 0;
+ };
+
+ struct NewTokenFrame : public QLogFrame {
+ NewTokenFrame(QUICNewTokenFrame &frame) : QLogFrame(frame.type())
+ {
+ token = QUICBase::to_hex(frame.token(), frame.token_length());
+ length = frame.token_length();
+ }
+
+ void encode(YAML::Node &) override;
+ std::string token;
+ uint64_t length = 0;
+ };
+
+ struct MaxDataFrame : public QLogFrame {
+ MaxDataFrame(QUICMaxDataFrame &frame) : QLogFrame(frame.type()) { maximum = std::to_string(frame.maximum_data()); }
+
+ void encode(YAML::Node &) override;
+ std::string maximum;
+ };
+
+ struct MaxStreamDataFrame : public QLogFrame {
+ MaxStreamDataFrame(QUICMaxStreamDataFrame &frame) : QLogFrame(frame.type())
+ {
+ stream_id = std::to_string(static_cast<uint64_t>(frame.stream_id()));
+ maximum = std::to_string(frame.maximum_stream_data());
+ }
+
+ void encode(YAML::Node &) override;
+ std::string stream_id;
+ std::string maximum;
+ };
+
+ struct MaxStreamsFrame : public QLogFrame {
+ MaxStreamsFrame(QUICMaxStreamsFrame &frame) : QLogFrame(frame.type())
+ {
+ maximum = std::to_string(frame.maximum_streams());
+ // FIXME
+ stream_type = "bidirectional";
+ }
+
+ void encode(YAML::Node &) override;
+ std::string stream_type;
+ std::string maximum;
+ };
+
+ struct DataBlockedFrame : public QLogFrame {
+ DataBlockedFrame(QUICDataBlockedFrame &frame) : QLogFrame(frame.type())
+ {
+ limit = std::to_string(static_cast<uint64_t>(frame.offset()));
+ }
+ void encode(YAML::Node &) override;
+ std::string limit;
+ };
+
+ struct StreamDataBlockedFrame : public QLogFrame {
+ StreamDataBlockedFrame(QUICStreamDataBlockedFrame &frame) : QLogFrame(frame.type())
+ {
+ limit = std::to_string(static_cast<uint64_t>(frame.offset()));
+ stream_id = std::to_string(static_cast<uint64_t>(frame.stream_id()));
+ }
+
+ void encode(YAML::Node &) override;
+ std::string stream_id, limit;
+ };
+
+ struct StreamsBlockedFrame : public QLogFrame {
+ StreamsBlockedFrame(QUICStreamIdBlockedFrame &frame) : QLogFrame(frame.type())
+ {
+ stream_type = "bidirectional";
+ stream_id = std::to_string(static_cast<uint64_t>(frame.stream_id()));
+ }
+
+ void encode(YAML::Node &) override;
+ std::string stream_id, stream_type;
+ };
+
+ struct NewConnectionIDFrame : public QLogFrame {
+ NewConnectionIDFrame(QUICNewConnectionIdFrame &frame) : QLogFrame(frame.type())
+ {
+ sequence_number = std::to_string(frame.sequence());
+ retire_prior_to = std::to_string(frame.retire_prior_to());
+ connection_id = frame.connection_id().hex();
+ stateless_reset_token = QUICBase::to_hex(frame.stateless_reset_token().buf(), QUICStatelessResetToken::LEN);
+ length = frame.connection_id().length();
+ }
+
+ void encode(YAML::Node &) override;
+ std::string sequence_number, retire_prior_to, connection_id, stateless_reset_token;
+ uint8_t length = 0;
+ };
+
+ struct RetireConnectionIDFrame : public QLogFrame {
+ RetireConnectionIDFrame(QUICRetireConnectionIdFrame &frame) : QLogFrame(frame.type())
+ {
+ sequence_number = std::to_string(frame.seq_num());
+ }
+ void encode(YAML::Node &) override;
+ std::string sequence_number;
+ };
+
+ struct PathChallengeFrame : public QLogFrame {
+ PathChallengeFrame(QUICPathChallengeFrame &frame) : QLogFrame(frame.type())
+ {
+ data = QUICBase::to_hex(frame.data(), QUICPathChallengeFrame::DATA_LEN);
+ }
+ void encode(YAML::Node &) override;
+ std::string data;
+ };
+
+ struct PathResponseFrame : public QLogFrame {
+ PathResponseFrame(QUICPathResponseFrame &frame) : QLogFrame(frame.type())
+ {
+ data = QUICBase::to_hex(frame.data(), QUICPathChallengeFrame::DATA_LEN);
+ }
+ void encode(YAML::Node &) override;
+ std::string data;
+ };
+
+ struct ConnectionCloseFrame : public QLogFrame {
+ ConnectionCloseFrame(QUICConnectionCloseFrame &frame, bool app = false) : QLogFrame(frame.type())
+ {
+ error_space = app ? "application" : "transport";
+ error_code = frame.error_code();
+ // FIXME
+ raw_error_code = error_code;
+ reason = frame.reason_phrase();
+ }
+
+ void encode(YAML::Node &) override;
+ std::string error_space, reason, trigger_frame_type;
+ uint64_t error_code, raw_error_code;
+ };
+
+ struct HandshakeDoneFrame : public QLogFrame {
+ HandshakeDoneFrame(QUICHandshakeDoneFrame &frame) : QLogFrame(frame.type()){};
+ void encode(YAML::Node &) override;
+ };
+
+ struct UnknownFrame : public QLogFrame {
+ UnknownFrame(QUICUnknownFrame &frame) : QLogFrame(frame.type())
+ {
+ // FIXME
+ raw_frame_type = static_cast<uint8_t>(frame.type());
+ }
+
+ void encode(YAML::Node &) override;
+ uint8_t raw_frame_type = 0;
+ };
+} // namespace Frame
+} // namespace QLog
diff --git a/iocore/net/quic/QUICLogUtils.h b/iocore/net/quic/QUICLogUtils.h
new file mode 100644
index 0000000..727b0df
--- /dev/null
+++ b/iocore/net/quic/QUICLogUtils.h
@@ -0,0 +1,40 @@
+#include "QUICLog.h"
+#include "QUICPacket.h"
+
+namespace QLog
+{
+inline static const char *
+PacketTypeToName(QUICPacketType pt)
+{
+ switch (pt) {
+ case QUICPacketType::INITIAL:
+ return "initial";
+ case QUICPacketType::HANDSHAKE:
+ return "handshake";
+ case QUICPacketType::ZERO_RTT_PROTECTED:
+ return "0rtt";
+ case QUICPacketType::PROTECTED:
+ return "1rtt";
+ case QUICPacketType::RETRY:
+ return "retry";
+ case QUICPacketType::VERSION_NEGOTIATION:
+ return "version_negotiation";
+ case QUICPacketType::STATELESS_RESET:
+ return "stateless_reset";
+ default:
+ return "unknown";
+ }
+}
+
+inline static QLog::PacketHeader
+QUICPacketToLogPacket(const QUICPacket &packet)
+{
+ QLog::PacketHeader ph;
+ ph.dcid = packet.destination_cid().hex();
+ ph.packet_number = std::to_string(packet.packet_number());
+ ph.packet_size = packet.size();
+ ph.payload_length = packet.payload_length();
+ return ph;
+}
+
+} // namespace QLog
\ No newline at end of file
diff --git a/iocore/net/quic/QUICLossDetector.cc b/iocore/net/quic/QUICLossDetector.cc
index 9dae182..96e67bb 100644
--- a/iocore/net/quic/QUICLossDetector.cc
+++ b/iocore/net/quic/QUICLossDetector.cc
@@ -32,6 +32,7 @@
#include "QUICPinger.h"
#include "QUICPadder.h"
#include "QUICPacketProtectionKeyInfo.h"
+#include "QUICLogUtils.h"
#define QUICLDDebug(fmt, ...) \
Debug("quic_loss_detector", "[%s] " fmt, this->_context.connection_info()->cids().data(), ##__VA_ARGS__)
@@ -444,6 +445,8 @@ QUICLossDetector::_detect_lost_packets(QUICPacketNumberSpace pn_space)
if (!lost_packets.empty()) {
this->_cc->on_packets_lost(lost_packets);
for (auto lost_packet : lost_packets) {
+ this->_context.qlog_trace().push_event(
+ std::make_unique<QLog::Recovery::PacketLost>(QLog::PacketTypeToName(lost_packet.second->type), lost_packet.first));
// -- ADDITIONAL CODE --
// Not sure how we can get feedback from congestion control and when we should retransmit the lost packets but we need to send
// them somewhere.
diff --git a/iocore/net/quic/QUICTypes.cc b/iocore/net/quic/QUICTypes.cc
index 756af7d..80a8379 100644
--- a/iocore/net/quic/QUICTypes.cc
+++ b/iocore/net/quic/QUICTypes.cc
@@ -297,6 +297,18 @@ QUICStatelessResetToken::_hashcode() const
(static_cast<uint64_t>(this->_token[6]) << 8) + (static_cast<uint64_t>(this->_token[7]));
}
+std::string
+QUICStatelessResetToken::hex() const
+{
+ std::stringstream stream;
+ stream << "0x";
+ for (auto i = 0; i < QUICStatelessResetToken::LEN; i++) {
+ stream << std::setfill('0') << std::setw(2) << std::hex;
+ stream << std::hex << static_cast<int>(this->_token[i]);
+ }
+ return stream.str();
+}
+
QUICResumptionToken::QUICResumptionToken(const IpEndpoint &src, QUICConnectionId cid, ink_hrtime expire_time)
{
// TODO: read cookie secret from file like SSLTicketKeyConfig
@@ -796,3 +808,18 @@ QUICInvariants::scid(QUICConnectionId &dst, const uint8_t *buf, uint64_t buf_len
return true;
}
+
+namespace QUICBase
+{
+std::string
+to_hex(const uint8_t *buf, size_t len)
+{
+ std::stringstream stream;
+ stream << "0x";
+ for (size_t i = 0; i < len; i++) {
+ stream << std::setfill('0') << std::setw(2) << std::hex;
+ stream << std::hex << static_cast<int>(buf[i]);
+ }
+ return stream.str();
+}
+} // namespace QUICBase
diff --git a/iocore/net/quic/QUICTypes.h b/iocore/net/quic/QUICTypes.h
index 016631b..5139b78 100644
--- a/iocore/net/quic/QUICTypes.h
+++ b/iocore/net/quic/QUICTypes.h
@@ -308,6 +308,8 @@ public:
return _token;
}
+ std::string hex() const;
+
private:
uint8_t _token[LEN] = {0};
@@ -610,3 +612,9 @@ public:
};
int to_hex_str(char *dst, size_t dst_len, const uint8_t *src, size_t src_len);
+
+namespace QUICBase
+{
+std::string to_hex(const uint8_t *buf, size_t len);
+
+} // namespace QUICBase
diff --git a/iocore/net/quic/test/test_QUICType.cc b/iocore/net/quic/test/test_QUICType.cc
index fb45c44..79dce74 100644
--- a/iocore/net/quic/test/test_QUICType.cc
+++ b/iocore/net/quic/test/test_QUICType.cc
@@ -26,8 +26,11 @@
#include "quic/QUICTypes.h"
#include "I_EventSystem.h"
#include "tscore/ink_hrtime.h"
+#include "QUICLog.h"
#include <memory>
+#include <yaml-cpp/yaml.h>
+/*
TEST_CASE("QUICType", "[quic]")
{
SECTION("QUICPath")
@@ -144,3 +147,32 @@ TEST_CASE("QUICType", "[quic]")
CHECK(token1.cid() == token2.cid());
}
}
+*/
+
+TEST_CASE("YAML binary", "yaml")
+{
+ QLog::QUICLog log;
+ auto &trace = log.new_trace("0x12345");
+ std::unique_ptr<QLog::Transport::FrameProcessed> de = std::make_unique<QLog::Transport::FrameProcessed>();
+
+ QUICPingFrame ping;
+ de->append_frames(QLog::QLogFrameFactory::create(ping));
+
+ trace.push_event(std::move(de));
+
+ log.dump();
+
+ YAML::Node root;
+ root.push_back("a");
+ root.push_back("b");
+ root.push_back("c");
+ root.push_back("c");
+
+ YAML::Node node;
+ node["hello"] = "world";
+ root.push_back(node);
+
+ YAML::Emitter emitter(std::cout);
+ emitter << YAML::DoubleQuoted << YAML::Flow << root;
+ std::cout << "\n";
+}
diff --git a/mgmt/Makefile.am b/mgmt/Makefile.am
index a52678d..8e5f218 100644
--- a/mgmt/Makefile.am
+++ b/mgmt/Makefile.am
@@ -34,7 +34,8 @@ AM_CPPFLAGS += \
-I$(abs_top_srcdir)/proxy \
-I$(abs_top_srcdir)/proxy/http \
-I$(abs_top_srcdir)/proxy/hdrs \
- $(TS_INCLUDES)
+ $(TS_INCLUDES) \
+ @YAMLCPP_INCLUDES@
libmgmt_c_la_SOURCES = \
RecordsConfig.cc \
diff --git a/proxy/hdrs/Makefile.am b/proxy/hdrs/Makefile.am
index 7fcd248..2ccf231 100644
--- a/proxy/hdrs/Makefile.am
+++ b/proxy/hdrs/Makefile.am
@@ -22,7 +22,8 @@ AM_CPPFLAGS += \
$(iocore_include_dirs) \
-I$(abs_top_srcdir)/include \
-I$(abs_top_srcdir)/lib \
- $(TS_INCLUDES)
+ $(TS_INCLUDES) \
+ @YAMLCPP_INCLUDES@
noinst_LIBRARIES = libhdrs.a
EXTRA_PROGRAMS = load_http_hdr
diff --git a/proxy/http/Makefile.am b/proxy/http/Makefile.am
index 31a06c1..edc10ff 100644
--- a/proxy/http/Makefile.am
+++ b/proxy/http/Makefile.am
@@ -33,7 +33,8 @@ AM_CPPFLAGS += \
-I$(abs_top_srcdir)/proxy/logging \
-I$(abs_top_srcdir)/proxy/http2 \
-I$(abs_top_srcdir)/proxy/http3 \
- $(TS_INCLUDES)
+ $(TS_INCLUDES) \
+ @YAMLCPP_INCLUDES@
noinst_HEADERS = HttpProxyServerMain.h
noinst_LIBRARIES = libhttp.a
diff --git a/proxy/http2/Makefile.am b/proxy/http2/Makefile.am
index d34de70..9cb322b 100644
--- a/proxy/http2/Makefile.am
+++ b/proxy/http2/Makefile.am
@@ -29,7 +29,8 @@ AM_CPPFLAGS += \
-I$(abs_top_srcdir)/proxy/hdrs \
-I$(abs_top_srcdir)/proxy/shared \
-I$(abs_top_srcdir)/proxy/http/remap \
- $(TS_INCLUDES)
+ $(TS_INCLUDES) \
+ @YAMLCPP_INCLUDES@
noinst_LIBRARIES = libhttp2.a
diff --git a/proxy/http3/Makefile.am b/proxy/http3/Makefile.am
index f5d4de5..c066d3f 100644
--- a/proxy/http3/Makefile.am
+++ b/proxy/http3/Makefile.am
@@ -30,7 +30,8 @@ AM_CPPFLAGS += \
-I$(abs_top_srcdir)/proxy/hdrs \
-I$(abs_top_srcdir)/proxy/shared \
-I$(abs_top_srcdir)/proxy/http/remap \
- $(TS_INCLUDES)
+ $(TS_INCLUDES) \
+ @YAMLCPP_INCLUDES@
noinst_LIBRARIES = libhttp3.a
diff --git a/proxy/shared/Makefile.am b/proxy/shared/Makefile.am
index 1d54d40..48a1109 100644
--- a/proxy/shared/Makefile.am
+++ b/proxy/shared/Makefile.am
@@ -35,7 +35,8 @@ AM_CPPFLAGS += \
-I$(abs_top_srcdir)/proxy/http \
-I$(abs_top_srcdir)/proxy/hdrs \
-I$(abs_top_srcdir)/proxy/logging \
- $(TS_INCLUDES)
+ $(TS_INCLUDES) \
+ @YAMLCPP_INCLUDES@
libdiagsconfig_a_SOURCES = \
DiagsConfig.cc
diff --git a/src/traffic_quic/Makefile.inc b/src/traffic_quic/Makefile.inc
index 963576f..c241944 100644
--- a/src/traffic_quic/Makefile.inc
+++ b/src/traffic_quic/Makefile.inc
@@ -32,7 +32,8 @@ traffic_quic_traffic_quic_CPPFLAGS = \
-I$(abs_top_srcdir)/proxy/logging \
-I$(abs_top_srcdir)/proxy/shared \
$(TS_INCLUDES) \
- @OPENSSL_INCLUDES@
+ @OPENSSL_INCLUDES@ \
+ @YAMLCPP_INCLUDES@
traffic_quic_traffic_quic_LDFLAGS = \
$(AM_LDFLAGS) \
diff --git a/src/traffic_server/Makefile.inc b/src/traffic_server/Makefile.inc
index d189bd7..4481969 100644
--- a/src/traffic_server/Makefile.inc
+++ b/src/traffic_server/Makefile.inc
@@ -35,7 +35,8 @@ traffic_server_traffic_server_CPPFLAGS = \
-I$(abs_top_srcdir)/mgmt \
-I$(abs_top_srcdir)/mgmt/utils \
$(TS_INCLUDES) \
- @OPENSSL_INCLUDES@
+ @OPENSSL_INCLUDES@ \
+ @YAMLCPP_INCLUDES@
traffic_server_traffic_server_LDFLAGS = \
$(AM_LDFLAGS) \