Skip to content

Commit

Permalink
Opt configuration under the hood
Browse files Browse the repository at this point in the history
- ping binaries added to test timing
- more thoughtful compile time checking
  • Loading branch information
dr7ana committed Sep 13, 2024
1 parent c20c355 commit d837250
Show file tree
Hide file tree
Showing 11 changed files with 425 additions and 87 deletions.
2 changes: 1 addition & 1 deletion include/oxen/quic/connection.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -416,7 +416,7 @@ namespace oxen::quic
std::atomic<bool> _close_quietly{false};
std::atomic<bool> _is_validated{false};

ustring remote_pubkey;
ustring remote_pubkey{};

std::set<int64_t> _early_streams;

Expand Down
91 changes: 44 additions & 47 deletions include/oxen/quic/endpoint.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,6 @@ extern "C"

namespace oxen::quic
{
template <typename... Opt>
static constexpr void check_for_tls_creds()
{
static_assert(
(0 + ... + std::is_convertible_v<std::remove_cvref_t<Opt>, std::shared_ptr<TLSCreds>>) == 1,
"Endpoint listen/connect require exactly one std::shared_ptr<TLSCreds> argument");
}

class Endpoint : public std::enable_shared_from_this<Endpoint>
{
public:
Expand All @@ -61,6 +53,7 @@ namespace oxen::quic
void listen(Opt&&... opts)
{
check_for_tls_creds<Opt...>();
check_verification_scheme<Opt...>(this);

net.call_get([&opts..., this]() mutable {
if (inbound_ctx)
Expand All @@ -73,11 +66,11 @@ namespace oxen::quic
});
}

// creates new outbound connection to remote; emplaces conn/interface pair in outbound map
template <typename... Opt>
std::shared_ptr<connection_interface> connect(RemoteAddress remote, Opt&&... opts)
template <concepts::quic_address_type T, typename... Opt>
std::shared_ptr<connection_interface> connect(T remote, Opt&&... opts)
{
check_for_tls_creds<Opt...>();
check_address_scheme<T, Opt...>();

std::promise<std::shared_ptr<Connection>> p;
auto f = p.get_future();
Expand All @@ -88,43 +81,16 @@ namespace oxen::quic
if (_local.is_ipv6() && !remote.is_ipv6())
remote.map_ipv4_as_ipv6();

Path _path = Path{_local, remote};

net.call([&opts..., &p, path = _path, this, remote_pk = std::move(remote).get_remote_key()]() mutable {
quic_cid qcid;
net.call([this, &opts..., &p, remote = std::move(remote)]() mutable {
quic_cid qcid{};
auto next_rid = next_reference_id();

try
{
// initialize client context and client tls context simultaneously
outbound_ctx = std::make_shared<IOContext>(Direction::OUTBOUND, std::forward<Opt>(opts)...);
_set_context_globals(outbound_ctx);

for (;;)
{
// emplace random CID into lookup keyed to unique reference ID
if (auto [it_a, res_a] = conn_lookup.emplace(quic_cid::random(), next_rid); res_a)
{
qcid = it_a->first;

if (auto [it_b, res_b] = conns.emplace(next_rid, nullptr); res_b)
{
it_b->second = Connection::make_conn(
*this,
next_rid,
it_a->first,
quic_cid::random(),
std::move(path),
outbound_ctx,
outbound_alpns,
handshake_timeout,
remote_pk);

p.set_value(it_b->second);
return;
}
}
}
_connect(std::move(remote), qcid, next_rid, p);
}
catch (...)
{
Expand Down Expand Up @@ -241,9 +207,9 @@ namespace oxen::quic

std::unordered_map<ustring_view, gtls_ticket_ptr> session_tickets;

std::unordered_map<ustring, ustring> encoded_transport_params;
std::unordered_map<Address, ustring> encoded_transport_params;

std::unordered_map<ustring, ustring> path_validation_tokens;
std::unordered_map<Address, ustring> path_validation_tokens;

const std::shared_ptr<event_base>& get_loop() { return net._loop->loop(); }

Expand All @@ -252,6 +218,10 @@ namespace oxen::quic
// Does the non-templated bit of `listen()`
void _listen();

void _connect(RemoteAddress remote, quic_cid qcid, ConnectionID rid, std::promise<std::shared_ptr<Connection>>& p);

void _connect(Address remote, quic_cid qcid, ConnectionID rid, std::promise<std::shared_ptr<Connection>>& p);

void handle_ep_opt(opt::enable_datagrams dc);
void handle_ep_opt(opt::outbound_alpns alpns);
void handle_ep_opt(opt::inbound_alpns alpns);
Expand Down Expand Up @@ -304,13 +274,13 @@ namespace oxen::quic

void connection_established(connection_interface& conn);

void store_0rtt_transport_params(ustring remote_pk, ustring encoded_params);
void store_0rtt_transport_params(Address remote, ustring encoded_params);

std::optional<ustring> get_0rtt_transport_params(const ustring& remote_pk);
std::optional<ustring> get_0rtt_transport_params(const Address& remote);

void store_path_validation_token(ustring remote_pk, ustring token);
void store_path_validation_token(Address remote, ustring token);

std::optional<ustring> get_path_validation_token(const ustring& remote_pk);
std::optional<ustring> get_path_validation_token(const Address& remote);

void initial_association(Connection& conn);

Expand Down Expand Up @@ -413,6 +383,33 @@ namespace oxen::quic
void check_timeouts();

Connection* accept_initial_connection(const Packet& pkt, quic_cid& cid);

template <typename... Opt>
static constexpr void check_for_tls_creds()
{
static_assert(
(0 + ... + std::is_convertible_v<std::remove_cvref_t<Opt>, std::shared_ptr<TLSCreds>>) == 1,
"Endpoint listen/connect require exactly one std::shared_ptr<TLSCreds> argument");
}

template <typename... Opt>
static void check_verification_scheme(Endpoint* e)
{
if constexpr ((std::is_same_v<opt::disable_key_verification, std::remove_cvref_t<Opt>> || ...))
{
if (e->zero_rtt_enabled())
throw std::invalid_argument{"Disabling key verification is incompatible with 0rtt ticketing!"};
}
}

template <concepts::quic_address_type T, typename... Opt>
static constexpr void check_address_scheme()
{
if constexpr ((std::is_same_v<opt::disable_key_verification, std::remove_cvref_t<Opt>> || ...))
{
static_assert(std::is_same_v<T, Address>, "Disabling key verification requires keyless address!");
}
}
};

} // namespace oxen::quic
14 changes: 7 additions & 7 deletions include/oxen/quic/loop.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -206,17 +206,17 @@ namespace oxen::quic
Configurable parameters:
- start_immediately : will call ::event_add() before returning the ticker
- fixed_interval :
- if FALSE (default behavior), will attempt to execute every `interval`, regardless of how long the
event itself takes
- if TRUE, will wait the entire `interval` after finishing execution of the event before attempting
execution again
- wait :
- if FALSE (default behavior), the interval will not wait for the event to complete. will attempt to
execute every `interval`, regardless of how long the event itself takes.
- if TRUE, the interval will wait for the event to complete before beginning. It will wait the entire
`interval` after finishing execution of the event before attempting execution again.
*/
template <typename Callable>
[[nodiscard]] std::shared_ptr<Ticker> call_every(
std::chrono::microseconds interval, Callable&& f, bool start_immediately = true, bool fixed_interval = false)
std::chrono::microseconds interval, Callable&& f, bool start_immediately = true, bool wait = false)
{
return _call_every(interval, std::forward<Callable>(f), Loop::loop_id, start_immediately, fixed_interval);
return _call_every(interval, std::forward<Callable>(f), Loop::loop_id, start_immediately, wait);
}

template <std::invocable Callable>
Expand Down
14 changes: 7 additions & 7 deletions include/oxen/quic/network.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -88,17 +88,17 @@ namespace oxen::quic
Configurable parameters:
- start_immediately : will call ::event_add() before returning the ticker
- fixed_interval :
- if FALSE (default behavior), will attempt to execute every `interval`, regardless of how long the
event itself takes
- if TRUE, will wait the entire `interval` after finishing execution of the event before attempting
execution again
- wait :
- if FALSE (default behavior), the interval will not wait for the event to complete. will attempt to
execute every `interval`, regardless of how long the event itself takes.
- if TRUE, the interval will wait for the event to complete before beginning. It will wait the entire
`interval` after finishing execution of the event before attempting execution again.
*/
template <typename Callable>
[[nodiscard]] std::shared_ptr<Ticker> call_every(
std::chrono::microseconds interval, Callable&& f, bool start_immediately = true, bool fixed_interval = false)
std::chrono::microseconds interval, Callable&& f, bool start_immediately = true, bool wait = false)
{
return _loop->_call_every(interval, std::forward<Callable>(f), net_id, start_immediately, fixed_interval);
return _loop->_call_every(interval, std::forward<Callable>(f), net_id, start_immediately, wait);
}

template <typename Callable>
Expand Down
4 changes: 4 additions & 0 deletions include/oxen/quic/types.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,16 @@
namespace oxen::quic
{
class Stream;
struct Address;

namespace concepts
{
template <typename T>
concept stream_derived_type = std::derived_from<T, Stream>;

template <typename T>
concept quic_address_type = std::derived_from<T, Address>;

template <typename T>
concept raw_sockaddr_type =
std::same_as<T, sockaddr> || std::same_as<T, sockaddr_in> || std::same_as<T, sockaddr_in6>;
Expand Down
25 changes: 12 additions & 13 deletions src/connection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -381,8 +381,7 @@ namespace oxen::quic
int Connection::recv_token(const uint8_t* token, size_t tokenlen)
{
// This should only be called by the client, and therefore this will always have a value
assert(not remote_pubkey.empty());
_endpoint.store_path_validation_token(remote_pubkey, {token, tokenlen});
_endpoint.store_path_validation_token(_path.remote, {token, tokenlen});
return 0;
}

Expand All @@ -406,7 +405,7 @@ namespace oxen::quic

if (auto len = ngtcp2_conn_encode_0rtt_transport_params(conn.get(), data.data(), data.size()); len > 0)
{
_endpoint.store_0rtt_transport_params(remote_pubkey, std::move(data));
_endpoint.store_0rtt_transport_params(_path.remote, std::move(data));
log::info(log_cat, "Client successfully encoded and stored 0rtt transport params");
}
else
Expand Down Expand Up @@ -456,7 +455,7 @@ namespace oxen::quic
{
_is_validated = true;

if (is_inbound())
if (is_inbound() and not context->disable_key_verification)
remote_pubkey = dynamic_cast<GNUTLSSession*>(get_session())->remote_key();
}

Expand Down Expand Up @@ -1543,6 +1542,8 @@ namespace oxen::quic

if (_stateless_reset_enabled)
{
callbacks.recv_stateless_reset = connection_callbacks::recv_stateless_reset;
log::info(log_cat, "Connection configured to watch for stateless reset packets");
callbacks.dcid_status = connection_callbacks::on_connection_id_status;
log::info(log_cat, "Connection configured to monitor activated dcids and stateless reset tokens");
}
Expand Down Expand Up @@ -1676,17 +1677,15 @@ namespace oxen::quic

// Clients should be the ones providing a remote pubkey here. This way we can emplace it into
// the gnutlssession object to be verified. Servers should be verifying via callback
assert(remote_pk.has_value());
remote_pubkey = *remote_pk;
tls_session->set_expected_remote_key(remote_pubkey);

if (_stateless_reset_enabled)
if (not context->disable_key_verification)
{
callbacks.recv_stateless_reset = connection_callbacks::recv_stateless_reset;
log::info(log_cat, "Inbound connection configured to watch for stateless reset packets");
log::debug(log_cat, "Outbound connection configured for key verification");
assert(remote_pk.has_value());
remote_pubkey = *remote_pk;
tls_session->set_expected_remote_key(remote_pubkey);
}

auto maybe_token = _endpoint.get_path_validation_token(remote_pubkey);
auto maybe_token = _endpoint.get_path_validation_token(_path.remote);

if (maybe_token)
{
Expand All @@ -1708,7 +1707,7 @@ namespace oxen::quic

if (_0rtt_enabled)
{
auto maybe_params = _endpoint.get_0rtt_transport_params(remote_pubkey);
auto maybe_params = _endpoint.get_0rtt_transport_params(_path.remote);
if (not maybe_params)
{
log::warning(
Expand Down
Loading

0 comments on commit d837250

Please sign in to comment.