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: add more quic implementation details (part 3) #47348

Closed
wants to merge 4 commits into from
Closed
Changes from 1 commit
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
Prev Previous commit
Next Next commit
quic: add TransportParams
jasnell committed Apr 10, 2023
commit 7b0fc290b46fb233183b2f01be78553d68ae3b73
2 changes: 2 additions & 0 deletions node.gyp
Original file line number Diff line number Diff line change
@@ -342,13 +342,15 @@
'src/quic/preferredaddress.cc',
'src/quic/sessionticket.cc',
'src/quic/tokens.cc',
'src/quic/transportparams.cc',
'src/quic/bindingdata.h',
'src/quic/cid.h',
'src/quic/data.h',
'src/quic/logstream.h',
'src/quic/preferredaddress.h',
'src/quic/sessionticket.h',
'src/quic/tokens.h',
'src/quic/transportparams.h',
],
'node_mksnapshot_exec': '<(PRODUCT_DIR)/<(EXECUTABLE_PREFIX)node_mksnapshot<(EXECUTABLE_SUFFIX)',
'conditions': [
22 changes: 21 additions & 1 deletion src/quic/bindingdata.h
Original file line number Diff line number Diff line change
@@ -8,6 +8,7 @@
#include <memory_tracker.h>
#include <nghttp3/nghttp3.h>
#include <ngtcp2/ngtcp2.h>
#include <ngtcp2/ngtcp2_crypto.h>
#include <node.h>
#include <node_mem.h>
#include <v8.h>
@@ -17,6 +18,13 @@ namespace quic {

class Endpoint;

enum class Side {
CLIENT = NGTCP2_CRYPTO_SIDE_CLIENT,
SERVER = NGTCP2_CRYPTO_SIDE_SERVER,
};

constexpr size_t kDefaultMaxPacketLength = NGTCP2_MAX_UDP_PAYLOAD_SIZE;

// ============================================================================

// The FunctionTemplates the BindingData will store for us.
@@ -54,10 +62,22 @@ class Endpoint;

// The various JS strings the implementation uses.
#define QUIC_STRINGS(V) \
V(http3_alpn, &NGHTTP3_ALPN_H3[1]) \
V(ack_delay_exponent, "ackDelayExponent") \
V(active_connection_id_limit, "activeConnectionIDLimit") \
V(disable_active_migration, "disableActiveMigration") \
V(endpoint, "Endpoint") \
V(endpoint_udp, "Endpoint::UDP") \
V(http3_alpn, &NGHTTP3_ALPN_H3[1]) \
V(initial_max_data, "initialMaxData") \
V(initial_max_stream_data_bidi_local, "initialMaxStreamDataBidiLocal") \
V(initial_max_stream_data_bidi_remote, "initialMaxStreamDataBidiRemote") \
V(initial_max_stream_data_uni, "initialMaxStreamDataUni") \
V(initial_max_streams_bidi, "initialMaxStreamsBidi") \
V(initial_max_streams_uni, "initialMaxStreamsUni") \
V(logstream, "LogStream") \
V(max_ack_delay, "maxAckDelay") \
V(max_datagram_frame_size, "maxDatagramFrameSize") \
V(max_idle_timeout, "maxIdleTimeout") \
V(packetwrap, "PacketWrap") \
V(session, "Session") \
V(stream, "Stream")
55 changes: 55 additions & 0 deletions src/quic/defs.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
#pragma once

#include <env.h>
#include <node_errors.h>
#include <v8.h>

namespace node {
namespace quic {

template <typename Opt, bool Opt::*member>
bool SetOption(Environment* env,
Opt* options,
const v8::Local<v8::Object>& object,
const v8::Local<v8::String>& name) {
v8::Local<v8::Value> value;
if (!object->Get(env->context(), name).ToLocal(&value)) return false;
if (!value->IsUndefined()) {
CHECK(value->IsBoolean());
anonrig marked this conversation as resolved.
Show resolved Hide resolved
options->*member = value->IsTrue();
}
return true;
}

template <typename Opt, uint64_t Opt::*member>
bool SetOption(Environment* env,
Opt* options,
const v8::Local<v8::Object>& object,
const v8::Local<v8::String>& name) {
v8::Local<v8::Value> value;
if (!object->Get(env->context(), name).ToLocal(&value)) return false;

if (!value->IsUndefined()) {
CHECK_IMPLIES(!value->IsBigInt(), value->IsNumber());

uint64_t val = 0;
if (value->IsBigInt()) {
bool lossless = true;
val = value.As<v8::BigInt>()->Uint64Value(&lossless);
if (!lossless) {
Utf8Value label(env->isolate(), name);
THROW_ERR_OUT_OF_RANGE(
env,
(std::string("options.") + (*label) + " is out of range").c_str());
jasnell marked this conversation as resolved.
Show resolved Hide resolved
jasnell marked this conversation as resolved.
Show resolved Hide resolved
return false;
}
} else {
val = static_cast<int64_t>(value.As<v8::Number>()->Value());
}
options->*member = val;
}
return true;
}

} // namespace quic
} // namespace node
219 changes: 219 additions & 0 deletions src/quic/transportparams.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
#if HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC

#include "transportparams.h"
#include <env-inl.h>
#include <memory_tracker-inl.h>
#include <node_sockaddr-inl.h>
#include <util-inl.h>
#include <v8.h>
#include "bindingdata.h"
#include "defs.h"
#include "tokens.h"

namespace node {

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

namespace quic {
TransportParams::Config::Config(Side side,
const CID& ocid,
const CID& retry_scid)
: side(side), ocid(ocid), retry_scid(retry_scid) {}

Maybe<const TransportParams::Options> TransportParams::Options::From(
Environment* env, Local<Value> value) {
if (value.IsEmpty() || !value->IsObject()) {
return Nothing<const Options>();
}

auto& state = BindingData::Get(env);
auto params = value.As<Object>();
Options options;

#define SET(name) \
SetOption<TransportParams::Options, &TransportParams::Options::name>( \
env, &options, params, state.name##_string())

if (!SET(initial_max_stream_data_bidi_local) ||
!SET(initial_max_stream_data_bidi_remote) ||
!SET(initial_max_stream_data_uni) || !SET(initial_max_data) ||
!SET(initial_max_streams_bidi) || !SET(initial_max_streams_uni) ||
!SET(max_idle_timeout) || !SET(active_connection_id_limit) ||
!SET(ack_delay_exponent) || !SET(max_ack_delay) ||
!SET(max_datagram_frame_size) || !SET(disable_active_migration)) {
return Nothing<const Options>();
}

#undef SET

return Just<const Options>(options);
}

TransportParams::TransportParams(Type type) : type_(type), ptr_(&params_) {}

TransportParams::TransportParams(Type type, const ngtcp2_transport_params* ptr)
: type_(type), ptr_(ptr) {}

TransportParams::TransportParams(const Config& config, const Options& options)
: TransportParams(Type::ENCRYPTED_EXTENSIONS) {
ngtcp2_transport_params_default(&params_);
params_.active_connection_id_limit = options.active_connection_id_limit;
params_.initial_max_stream_data_bidi_local =
options.initial_max_stream_data_bidi_local;
params_.initial_max_stream_data_bidi_remote =
options.initial_max_stream_data_bidi_remote;
params_.initial_max_stream_data_uni = options.initial_max_stream_data_uni;
params_.initial_max_streams_bidi = options.initial_max_streams_bidi;
params_.initial_max_streams_uni = options.initial_max_streams_uni;
params_.initial_max_data = options.initial_max_data;
params_.max_idle_timeout = options.max_idle_timeout * NGTCP2_SECONDS;
params_.max_ack_delay = options.max_ack_delay;
params_.ack_delay_exponent = options.ack_delay_exponent;
params_.max_datagram_frame_size = options.max_datagram_frame_size;
params_.disable_active_migration = options.disable_active_migration ? 1 : 0;
params_.preferred_address_present = 0;
params_.stateless_reset_token_present = 0;
params_.retry_scid_present = 0;

if (config.side == Side::SERVER) {
// For the server side, the original dcid is always set.
CHECK(config.ocid);
params_.original_dcid = config.ocid;

// The retry_scid is only set if the server validated a retry token.
if (config.retry_scid) {
params_.retry_scid = config.retry_scid;
params_.retry_scid_present = 1;
}
}

if (options.preferred_address_ipv4.has_value())
SetPreferredAddress(options.preferred_address_ipv4.value());

if (options.preferred_address_ipv6.has_value())
SetPreferredAddress(options.preferred_address_ipv6.value());
}

TransportParams::TransportParams(Type type, const ngtcp2_vec& vec)
: TransportParams(type) {
int ret = ngtcp2_decode_transport_params(
&params_,
static_cast<ngtcp2_transport_params_type>(type),
vec.base,
vec.len);

if (ret != 0) {
ptr_ = nullptr;
error_ = QuicError::ForNgtcp2Error(ret);
}
}

Store TransportParams::Encode(Environment* env) {
if (ptr_ == nullptr) {
error_ = QuicError::ForNgtcp2Error(NGTCP2_INTERNAL_ERROR);
return Store();
}

// Preflight to see how much storage we'll need.
ssize_t size = ngtcp2_encode_transport_params(
nullptr, 0, static_cast<ngtcp2_transport_params_type>(type_), &params_);

DCHECK_GT(size, 0);

auto result = ArrayBuffer::NewBackingStore(env->isolate(), size);

auto ret = ngtcp2_encode_transport_params(
static_cast<uint8_t*>(result->Data()),
size,
static_cast<ngtcp2_transport_params_type>(type_),
&params_);

if (ret != 0) {
error_ = QuicError::ForNgtcp2Error(ret);
return Store();
}

return Store(std::move(result), static_cast<size_t>(size));
}

void TransportParams::SetPreferredAddress(const SocketAddress& address) {
DCHECK(ptr_ == &params_);
params_.preferred_address_present = 1;
switch (address.family()) {
case AF_INET: {
const sockaddr_in* src =
reinterpret_cast<const sockaddr_in*>(address.data());
memcpy(params_.preferred_address.ipv4_addr,
&src->sin_addr,
sizeof(params_.preferred_address.ipv4_addr));
params_.preferred_address.ipv4_port = address.port();
return;
}
case AF_INET6: {
const sockaddr_in6* src =
reinterpret_cast<const sockaddr_in6*>(address.data());
memcpy(params_.preferred_address.ipv6_addr,
&src->sin6_addr,
sizeof(params_.preferred_address.ipv6_addr));
params_.preferred_address.ipv6_port = address.port();
return;
}
}
UNREACHABLE();
}

void TransportParams::GenerateStatelessResetToken(
const TokenSecret& token_secret, const CID& cid) {
DCHECK(ptr_ == &params_);
DCHECK(cid);
params_.stateless_reset_token_present = 1;

StatelessResetToken token(params_.stateless_reset_token, token_secret, cid);
}

CID TransportParams::GeneratePreferredAddressToken(const Session& session) {
DCHECK_NOT_NULL(session);
DCHECK(ptr_ == &params_);
DCHECK(pscid);
// TODO(@jasnell): To be implemented when Session is implemented
// *pscid = session->cid_factory_.Generate();
// params_.preferred_address.cid = *pscid;
// session->endpoint_->AssociateStatelessResetToken(
// session->endpoint().GenerateNewStatelessResetToken(
// params_.preferred_address.stateless_reset_token, *pscid),
// session);
return CID::kInvalid;
}

TransportParams::Type TransportParams::type() const {
return type_;
}

TransportParams::operator const ngtcp2_transport_params&() const {
DCHECK_NOT_NULL(ptr_);
return *ptr_;
}

TransportParams::operator const ngtcp2_transport_params*() const {
DCHECK_NOT_NULL(ptr_);
return ptr_;
}

TransportParams::operator bool() const {
return ptr_ != nullptr;
}

const QuicError& TransportParams::error() const {
return error_;
}

} // namespace quic
} // namespace node

#endif // HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC
Loading