-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Add QUIC protocol implementation #1943
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: testnet
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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) |
| 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 |
| 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
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
| 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(); | ||
| } |
There was a problem hiding this comment.
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).