Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,6 @@
[submodule "third-party/tl-parser"]
path = third-party/tl-parser
url = https://github.com/ton-blockchain/tl-parser.git
[submodule "third-party/ngtcp2"]
path = third-party/ngtcp2
url = https://github.com/ngtcp2/ngtcp2.git
12 changes: 12 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,14 @@ if (USE_LIBRAPTORQ)
add_subdirectory(third-party/libraptorq EXCLUDE_FROM_ALL)
endif()

option(USE_QUIC "use ngtcp2 QUIC library" OFF)
if (USE_QUIC)
find_package(OpenSSL 3.2 REQUIRED COMPONENTS SSL Crypto)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We build with OpenSSL 3.1.4 in CI. Not sure how this passed, I think this might just never execute as it sees previously set OpenSSL_FOUND (or something).

message("Add ngtcp2")
add_subdirectory(third-party/ngtcp2 EXCLUDE_FROM_ALL)
set(NGTCP2_ROOT ${CMAKE_SOURCE_DIR}/third-party/ngtcp2)
endif ()

message("Add tl-parser")
add_subdirectory(third-party/tl-parser EXCLUDE_FROM_ALL)

Expand Down Expand Up @@ -464,6 +472,10 @@ add_subdirectory(rldp-http-proxy)
endif()
#END internal

if (USE_QUIC)
add_subdirectory(quic)
endif ()

if (NOT CMAKE_CROSSCOMPILING)
if (TDUTILS_MIME_TYPE)
set(TDMIME_AUTO tdmime_auto)
Expand Down
13 changes: 13 additions & 0 deletions quic/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
add_library(quic STATIC quic-connection.cpp quic-pimpl.cpp)
target_include_directories(quic PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>/..
${NGTCP2_ROOT}/lib/includes
${NGTCP2_ROOT}/crypto/includes
${OPENSSL_INCLUDE_DIR}
)
target_link_libraries(quic PUBLIC tdutils tdactor ngtcp2_static ngtcp2_crypto_ossl_static OpenSSL::SSL OpenSSL::Crypto)

add_executable(quic-example quic-example.cpp)
target_include_directories(quic-example PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/..>)
target_link_libraries(quic-example PUBLIC quic)
111 changes: 111 additions & 0 deletions quic/quic-connection.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
#include "td/actor/actor.h"

#include "quic-connection.h"
#include "quic-pimpl.h"

namespace ton::quic {
td::Result<td::actor::ActorOwn<QuicConnection>> QuicConnection::open(td::Slice host, int port,
std::unique_ptr<Callback> callback,
td::Slice alpn) {
auto p_impl = std::make_unique<QuicConnectionPImpl>();
std::string host_c(host.begin(), host.end());
TRY_STATUS(p_impl->remote_address.init_host_port(td::CSlice(host_c.c_str()), port));
TRY_RESULT_ASSIGN(p_impl->fd, td::UdpSocketFd::open(p_impl->remote_address));
TRY_RESULT_ASSIGN(p_impl->local_address, p_impl->fd.get_local_address());

TRY_STATUS(p_impl->init_tls(host, alpn));
TRY_STATUS(p_impl->init_quic());

auto name = PSTRING() << "QUIC:" << p_impl->local_address << ">[" << host << ':' << port << ']';
return td::actor::create_actor<QuicConnection>(td::actor::ActorOptions().with_name(name).with_poll(true),
std::move(p_impl), std::move(callback));
}

void QuicConnection::start_up() {
LOG(INFO) << "starting up";
self_id_ = actor_id(this);
td::actor::SchedulerContext::get()->get_poll().subscribe(p_impl_->fd.get_poll_info().extract_pollable_fd(this),
td::PollFlags::ReadWrite());
process_operation_status(p_impl_->flush_egress());
LOG(INFO) << "startup completed";
}

void QuicConnection::tear_down() {
// TODO(@avevad): close connection cleanly
}

void QuicConnection::hangup() {
// not used
LOG(ERROR) << "unexpected hangup signal";
}

void QuicConnection::hangup_shared() {
// not used
LOG(ERROR) << "unexpected hangup_shared signal";
}

void QuicConnection::wake_up() {
// not used
LOG(ERROR) << "unexpected wake_up signal";
}
void QuicConnection::alarm() {
// TODO(@avevad): maybe watch for ngtcp2 expiry?
LOG(ERROR) << "unexpected alarm signal";
}

void QuicConnection::loop() {
// not used
LOG(ERROR) << "unexpected loop signal";
}

void QuicConnection::notify() {
td::actor::send_closure(self_id_, &QuicConnection::on_fd_notify);
}

void QuicConnection::on_fd_notify() {
process_operation_status(p_impl_->handle_ingress());
process_operation_status(p_impl_->flush_egress());
}

void QuicConnection::send_data(td::Slice data) {
process_operation_status(p_impl_->write_stream(data, false));
process_operation_status(p_impl_->flush_egress());
}

void QuicConnection::send_disconnect() {
process_operation_status(p_impl_->write_stream("", true));
process_operation_status(p_impl_->flush_egress());
}

void QuicConnection::process_operation_status(td::Status status) {
if (status.is_error()) {
LOG(ERROR) << "connection aborted: " << status;
stop();
}
}

QuicConnection::QuicConnection(std::unique_ptr<QuicConnectionPImpl> p_impl, std::unique_ptr<Callback> callback)
: p_impl_(std::move(p_impl)), callback_(std::move(callback)) {
class PImplCallback : public QuicConnectionPImpl::Callback {
public:
explicit PImplCallback(QuicConnection& connection) : connection_(connection) {
}

void on_handshake_completed(const HandshakeCompletedEvent& event) override {
connection_.callback_->on_connected();
}

void on_stream_data(const StreamDataEvent& event) override {
connection_.callback_->on_data(event.data);
if (event.fin) {
connection_.callback_->on_disconnected();
}
}

private:
QuicConnection& connection_;
};
p_impl_->callback = std::make_unique<PImplCallback>(*this);
}

} // namespace ton::quic
50 changes: 50 additions & 0 deletions quic/quic-connection.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
#pragma once
#include "td/actor/ActorOwn.h"
#include "td/actor/core/Actor.h"
#include "td/utils/buffer.h"
#include "td/utils/port/UdpSocketFd.h"

namespace ton::quic {
struct QuicConnectionPImpl;

class QuicConnection : public td::actor::Actor, public td::ObserverBase {
public:
class Callback {
public:
virtual void on_connected() = 0;
virtual void on_data(td::Slice data) = 0;
virtual void on_disconnected() = 0;
virtual ~Callback() = default;
};

void send_data(td::Slice data);
void send_disconnect();

QuicConnection(std::unique_ptr<QuicConnectionPImpl> p_impl, std::unique_ptr<Callback> callback);
static td::Result<td::actor::ActorOwn<QuicConnection>> open(td::Slice host, int port,
std::unique_ptr<Callback> callback,
td::Slice alpn = "ton");

protected:
void start_up() override;
void tear_down() override;
void hangup() override;
void hangup_shared() override;
void wake_up() override;
void alarm() override;
void loop() override;
Comment on lines +29 to +35
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You don't have to implement these if you don't use them. The default implementation is reasonable enough.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i'm not really familiar with td actor system, so i left them for debug logs in case of some unexpected signal from the scheduler


void notify() override;

private:
friend QuicConnectionPImpl;

void on_fd_notify();

void process_operation_status(td::Status);

std::unique_ptr<QuicConnectionPImpl> p_impl_;
std::unique_ptr<Callback> callback_;
td::actor::ActorId<QuicConnection> self_id_;
};
} // namespace ton::quic
92 changes: 92 additions & 0 deletions quic/quic-example.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
#include <iostream>
#include <optional>

#include "td/actor/actor.h"
#include "td/utils/OptionParser.h"

#include "quic-connection.h"

class QuicTester : public td::actor::Actor {
public:
class Callback : public ton::quic::QuicConnection::Callback {
public:
explicit Callback(QuicTester& tester) : tester_(tester) {
}

void on_connected() override {
LOG(INFO) << "connected to " << tester_.host_ << ':' << tester_.port_;
}

void on_data(td::Slice data) override {
std::cout.flush();
std::cout.write(data.data(), static_cast<std::streamsize>(data.size()));
std::cout.flush();
}

void on_disconnected() override {
LOG(INFO) << "disconnected from " << tester_.host_ << ':' << tester_.port_;
std::exit(0);
}

private:
QuicTester& tester_;
};
friend Callback;

QuicTester(td::Slice host, int port, td::Slice alpn) : alpn_(alpn), host_(host), port_(port) {
}

void start_up() override {
[this] {
TRY_RESULT_ASSIGN(connection_, ton::quic::QuicConnection::open(host_, port_, std::make_unique<Callback>(*this)));
td::actor::send_closure(connection_.get(), &ton::quic::QuicConnection::send_data, td::Slice("GET /\r\n"));
td::actor::send_closure(connection_.get(), &ton::quic::QuicConnection::send_disconnect);
return td::Status::OK();
}()
.ensure();
}

private:
td::Slice alpn_;
td::Slice host_;
int port_;

td::actor::ActorOwn<ton::quic::QuicConnection> connection_ = {};
};

int main(int argc, char** argv) {
SET_VERBOSITY_LEVEL(verbosity_INFO);

std::optional<td::BufferSlice> alpn;
std::optional<td::BufferSlice> host;
std::optional<int> port;

td::OptionParser p;
p.set_description("HTTP/0.9 over QUIC tester");
p.add_option('h', "host", "server hostname", [&](td::Slice arg) { host = td::BufferSlice(arg); });
p.add_checked_option('p', "port", "server port", [&](td::Slice arg) {
TRY_RESULT_ASSIGN(port, td::to_integer_safe<int>(arg));
return td::Status::OK();
});
p.run(argc, argv).ensure();

if (!alpn.has_value()) {
alpn = td::BufferSlice("hq-interop");
}
if (!host.has_value()) {
LOG(ERROR) << "no host specified";
std::exit(1);
}
if (!port.has_value()) {
LOG(ERROR) << "no port specified";
std::exit(1);
}

td::actor::ActorOwn<QuicTester> tester;
td::actor::Scheduler scheduler({1});
scheduler.run_in_context([&] {
tester = td::actor::create_actor<QuicTester>(PSTRING() << "tester", td::CSlice(host.value().as_slice()),
port.value(), alpn.value().as_slice());
});
scheduler.run();
}
Loading
Loading