Skip to content
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

quic: implement various utilities classes to be used by the quic impl #47263

Closed
wants to merge 11 commits into from
Prev Previous commit
Next Next commit
quic: add the PreferredAddress implementation
  • Loading branch information
jasnell committed Mar 27, 2023
commit 9c6c3b0012662ace0728d6a56c87550a6636818b
4 changes: 3 additions & 1 deletion node.gyp
Original file line number Diff line number Diff line change
Expand Up @@ -337,7 +337,9 @@
],
'node_quic_sources': [
'src/quic/cid.cc',
'src/quic/preferredaddress.cc',
'src/quic/cid.h',
'src/quic/preferredaddress.h',
],
'node_mksnapshot_exec': '<(PRODUCT_DIR)/<(EXECUTABLE_PREFIX)node_mksnapshot<(EXECUTABLE_SUFFIX)',
'conditions': [
Expand Down Expand Up @@ -1018,7 +1020,6 @@
'test/cctest/test_traced_value.cc',
'test/cctest/test_util.cc',
'test/cctest/test_dataqueue.cc',
'test/cctest/test_quic_cid.cc',
],

'conditions': [
Expand All @@ -1029,6 +1030,7 @@
'sources': [
'test/cctest/test_crypto_clienthello.cc',
'test/cctest/test_node_crypto.cc',
'test/cctest/test_quic_cid.cc',
]
}],
['v8_enable_inspector==1', {
Expand Down
153 changes: 153 additions & 0 deletions src/quic/preferredaddress.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
#include "preferredaddress.h"
#include <env-inl.h>
#include <node_errors.h>
#include <node_sockaddr-inl.h>
#include <util-inl.h>
#include <ngtcp2/ngtcp2.h>
#include <uv.h>
#include <v8.h>

namespace node {

using v8::Just;
using v8::Local;
using v8::Maybe;
using v8::Nothing;
using v8::Value;

namespace quic {

namespace {
template <int FAMILY>
std::optional<const PreferredAddress::AddressInfo> get_address_info(
const ngtcp2_preferred_addr& paddr) {
if constexpr (FAMILY == AF_INET) {
if (!paddr.ipv4_present) return std::nullopt;
PreferredAddress::AddressInfo address;
address.family = FAMILY;
address.port = paddr.ipv4_port;
if (uv_inet_ntop(FAMILY, paddr.ipv4_addr,
address.host, sizeof(address.host)) == 0) {
address.address = address.host;
}
return address;
} else {
if (!paddr.ipv6_present) return std::nullopt;
PreferredAddress::AddressInfo address;
address.family = FAMILY;
address.port = paddr.ipv6_port;
if (uv_inet_ntop(FAMILY, paddr.ipv6_addr,
address.host, sizeof(address.host)) == 0) {
address.address = address.host;
}
return address;
}
}

template <int FAMILY>
void copy_to_transport_params(
ngtcp2_transport_params* params,
const sockaddr* addr) {
params->preferred_address_present = true;
if constexpr (FAMILY == AF_INET) {
const sockaddr_in* src = reinterpret_cast<const sockaddr_in*>(addr);
params->preferred_address.ipv4_port = SocketAddress::GetPort(addr);
memcpy(params->preferred_address.ipv4_addr,
&src->sin_addr,
sizeof(params->preferred_address.ipv4_addr));
} else {
DCHECK_EQ(FAMILY, AF_INET6);
const sockaddr_in6* src = reinterpret_cast<const sockaddr_in6*>(addr);
params->preferred_address.ipv6_port = SocketAddress::GetPort(addr);
memcpy(params->preferred_address.ipv6_addr,
&src->sin6_addr,
sizeof(params->preferred_address.ipv4_addr));
}
UNREACHABLE();
}

bool resolve(const PreferredAddress::AddressInfo& address,
uv_getaddrinfo_t* req) {
addrinfo hints{};
hints.ai_flags = AI_NUMERICHOST | AI_NUMERICSERV;
hints.ai_family = address.family;
hints.ai_socktype = SOCK_DGRAM;

// ngtcp2 requires the selection of the preferred address
// to be synchronous, which means we have to do a sync resolve
// using uv_getaddrinfo here.
return uv_getaddrinfo(nullptr,
req,
nullptr,
address.host,
// TODO(@jasnell): The to_string here is not really
// the most performant way of converting the uint16_t
// port into a string. Depending on execution count,
// the potential cost here could be mitigated with a
// more efficient conversion. For now, however, this
// works.
std::to_string(address.port).c_str(),
jasnell marked this conversation as resolved.
Show resolved Hide resolved
&hints) == 0 && req->addrinfo != nullptr;
}
} // namespace

Maybe<PreferredAddress::Policy> PreferredAddress::GetPolicy(
Environment* env,
Local<Value> value) {
CHECK(value->IsUint32());
uint32_t val = 0;
if (value->Uint32Value(env->context()).To(&val)) {
switch (val) {
case QUIC_PREFERRED_ADDRESS_USE: return Just(Policy::USE);
case QUIC_PREFERRED_ADDRESS_IGNORE: return Just(Policy::IGNORE);
}
}
THROW_ERR_INVALID_ARG_VALUE(env,
"%d is not a valid preferred address policy", val);
return Nothing<Policy>();
}

PreferredAddress::PreferredAddress(ngtcp2_path* dest,
const ngtcp2_preferred_addr* paddr)
: dest_(dest), paddr_(paddr) {
DCHECK_NOT_NULL(paddr);
DCHECK_NOT_NULL(dest);
}

std::optional<const PreferredAddress::AddressInfo>
PreferredAddress::ipv4() const {
return get_address_info<AF_INET>(*paddr_);
}

std::optional<const PreferredAddress::AddressInfo>
PreferredAddress::ipv6() const {
return get_address_info<AF_INET6>(*paddr_);
}

void PreferredAddress::Use(const AddressInfo& address) {
uv_getaddrinfo_t req;
auto on_exit = OnScopeLeave([&] {
if (req.addrinfo != nullptr) uv_freeaddrinfo(req.addrinfo);
});

if (resolve(address, &req)) {
DCHECK_NOT_NULL(req.addrinfo);
dest_->remote.addrlen = req.addrinfo->ai_addrlen;
memcpy(dest_->remote.addr, req.addrinfo->ai_addr, req.addrinfo->ai_addrlen);
}
}

void PreferredAddress::Set(
ngtcp2_transport_params* params,
const sockaddr* addr) {
DCHECK_NOT_NULL(params);
DCHECK_NOT_NULL(addr);
switch (addr->sa_family) {
case AF_INET: return copy_to_transport_params<AF_INET>(params, addr);
case AF_INET6: return copy_to_transport_params<AF_INET6>(params, addr);
}
// Any other value is just ignored.
jasnell marked this conversation as resolved.
Show resolved Hide resolved
}

} // namespace quic
} // namespace node
70 changes: 70 additions & 0 deletions src/quic/preferredaddress.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
#pragma once

#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS

#include <env.h>
#include <node_internals.h>
#include <ngtcp2/ngtcp2.h>
#include <string>
#include <v8.h>

namespace node {
namespace quic {

// PreferredAddress is a helper class used only when a client Session receives
// an advertised preferred address from a server. The helper provides
// information about the server advertised preferred address and allows
// the preferred address to be selected.
jasnell marked this conversation as resolved.
Show resolved Hide resolved
class PreferredAddress final {
public:
enum class Policy {
// Ignore the server-advertised preferred address.
IGNORE,
// Use the server-advertised preferred address.
USE,
};

// The QUIC_* constants are expected to be exported out to be used on
// the JavaScript side of the API.
static constexpr uint32_t QUIC_PREFERRED_ADDRESS_USE =
static_cast<uint32_t>(Policy::USE);
static constexpr uint32_t QUIC_PREFERRED_ADDRESS_IGNORE =
static_cast<uint32_t>(Policy::IGNORE);

static v8::Maybe<Policy> GetPolicy(Environment* env,
v8::Local<v8::Value> value);

struct AddressInfo final {
char host[NI_MAXHOST];
int family;
uint16_t port;
std::string_view address;
};

explicit PreferredAddress(ngtcp2_path* dest,
const ngtcp2_preferred_addr* paddr);
PreferredAddress(const PreferredAddress&) = delete;
PreferredAddress(PreferredAddress&&) = delete;
PreferredAddress& operator=(const PreferredAddress&) = delete;
PreferredAddress& operator=(PreferredAddress&&) = delete;

void Use(const AddressInfo& address);

std::optional<const AddressInfo> ipv4() const;
std::optional<const AddressInfo> ipv6() const;
jasnell marked this conversation as resolved.
Show resolved Hide resolved

// Set the preferred address in the transport params.
// The address family (ipv4 or ipv6) will be automatically
// detected from the given addr. Any other address family
// will be ignored.
static void Set(ngtcp2_transport_params* params, const sockaddr* addr);

private:
ngtcp2_path* dest_;
const ngtcp2_preferred_addr* paddr_;
};

} // namespace quic
} // namespace node

#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS