#include "guard.h"
#ifndef OPENSSL_NO_QUIC
#include <async_wrap-inl.h>
#include <base_object-inl.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 "endpoint.h"
#include "session.h"
#include "session_manager.h"
#include "tokens.h"
#include "transportparams.h"
namespace node {
using v8::BigInt;
using v8::Boolean;
using v8::DictionaryTemplate;
using v8::Just;
using v8::Local;
using v8::Maybe;
using v8::MaybeLocal;
using v8::Nothing;
using v8::Object;
using v8::Value;
namespace quic {
const TransportParams::Options TransportParams::Options::kDefault = {};
TransportParams::Config::Config(Side side,
const CID& ocid,
const CID& retry_scid)
: side(side), ocid(ocid), retry_scid(retry_scid) {}
Maybe<TransportParams::Options> TransportParams::Options::From(
Environment* env, Local<Value> value) {
if (value.IsEmpty()) {
THROW_ERR_INVALID_ARG_TYPE(env, "options must be an object or undefined");
return Nothing<Options>();
} else if (value->IsUndefined()) {
return Just<Options>(kDefault);
} else if (!value->IsObject()) {
THROW_ERR_INVALID_ARG_TYPE(env, "options must be an object or undefined");
return Nothing<Options>();
}
Options options;
// TODO(@jasnell): We currently only support version 1 of the transport
// parameters, so the options.transportParamsVersion is hardcoded to that.
// In the future, when we support multiple versions, we will need to
// expose this via the options object.
auto& state = BindingData::Get(env);
auto params = value.As<Object>();
#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)) {
return Nothing<Options>();
}
#undef SET
// Parse the preferred address options. These are SocketAddress objects
// (or undefined to skip). Only meaningful for server sessions.
Local<Value> preferred_ipv4;
if (!params->Get(env->context(), state.preferred_address_ipv4_string())
.ToLocal(&preferred_ipv4)) {
return Nothing<Options>();
}
if (!preferred_ipv4->IsUndefined()) {
if (!SocketAddressBase::HasInstance(env, preferred_ipv4)) {
THROW_ERR_INVALID_ARG_TYPE(
env, "transportParams.preferredAddressIpv4 must be a SocketAddress");
return Nothing<Options>();
}
auto* addr = BaseObject::FromJSObject<SocketAddressBase>(
preferred_ipv4.As<Object>());
if (addr->address()->family() != AF_INET) {
THROW_ERR_INVALID_ARG_VALUE(
env, "transportParams.preferredAddressIpv4 must be an IPv4 address");
return Nothing<Options>();
}
options.preferred_address_ipv4 = *addr->address();
}
Local<Value> preferred_ipv6;
if (!params->Get(env->context(), state.preferred_address_ipv6_string())
.ToLocal(&preferred_ipv6)) {
return Nothing<Options>();
}
if (!preferred_ipv6->IsUndefined()) {
if (!SocketAddressBase::HasInstance(env, preferred_ipv6)) {
THROW_ERR_INVALID_ARG_TYPE(
env, "transportParams.preferredAddressIpv6 must be a SocketAddress");
return Nothing<Options>();
}
auto* addr = BaseObject::FromJSObject<SocketAddressBase>(
preferred_ipv6.As<Object>());
if (addr->address()->family() != AF_INET6) {
THROW_ERR_INVALID_ARG_VALUE(
env, "transportParams.preferredAddressIpv6 must be an IPv6 address");
return Nothing<Options>();
}
options.preferred_address_ipv6 = *addr->address();
}
return Just<Options>(options);
}
std::string TransportParams::Options::ToString() const {
DebugIndentScope indent;
auto prefix = indent.Prefix();
std::string res("{");
res += prefix +
"version: " + std::to_string(static_cast<int>(transportParamsVersion));
if (preferred_address_ipv4.has_value()) {
res += prefix + "preferred_address_ipv4: " +
preferred_address_ipv4.value().ToString();
} else {
res += prefix + "preferred_address_ipv4: <none>";
}
if (preferred_address_ipv6.has_value()) {
res += prefix + "preferred_address_ipv6: " +
preferred_address_ipv6.value().ToString();
} else {
res += prefix + "preferred_address_ipv6: <none>";
}
res += prefix + "initial max stream data bidi local: " +
std::to_string(initial_max_stream_data_bidi_local);
res += prefix + "initial max stream data bidi remote: " +
std::to_string(initial_max_stream_data_bidi_remote);
res += prefix + "initial max stream data uni: " +
std::to_string(initial_max_stream_data_uni);
res += prefix + "tinitial max data: " + std::to_string(initial_max_data);
res += prefix + "initial max streams bidi: " +
std::to_string(initial_max_streams_bidi);
res += prefix +
"initial max streams uni: " + std::to_string(initial_max_streams_uni);
res += prefix + "max idle timeout: " + std::to_string(max_idle_timeout);
res += prefix + "active connection id limit: " +
std::to_string(active_connection_id_limit);
res += prefix + "ack delay exponent: " + std::to_string(ack_delay_exponent);
res += prefix + "max ack delay: " + std::to_string(max_ack_delay);
res += prefix +
"max datagram frame size: " + std::to_string(max_datagram_frame_size);
res += indent.Close();
return res;
}
void TransportParams::Options::MemoryInfo(MemoryTracker* tracker) const {
if (preferred_address_ipv4.has_value()) {
tracker->TrackField("preferred_address_ipv4",
preferred_address_ipv4.value());
}
if (preferred_address_ipv6.has_value()) {
tracker->TrackField("preferred_address_ipv6",
preferred_address_ipv6.value());
}
}
TransportParams::TransportParams() : ptr_(¶ms_) {}
TransportParams::TransportParams(const ngtcp2_transport_params* ptr)
: ptr_(ptr) {}
TransportParams::TransportParams(const Config& config, const Options& options)
: TransportParams() {
ngtcp2_transport_params_default(¶ms_);
#define SET_PARAM(name) params_.name = options.name
#define SET_PARAM_V(name, value) params_.name = value
SET_PARAM(active_connection_id_limit);
SET_PARAM(initial_max_stream_data_bidi_local);
SET_PARAM(initial_max_stream_data_bidi_remote);
SET_PARAM(initial_max_stream_data_uni);
SET_PARAM(initial_max_streams_bidi);
SET_PARAM(initial_max_streams_uni);
SET_PARAM(initial_max_data);
SET_PARAM(max_ack_delay);
SET_PARAM(ack_delay_exponent);
SET_PARAM(max_datagram_frame_size);
SET_PARAM_V(max_idle_timeout, options.max_idle_timeout * NGTCP2_SECONDS);
SET_PARAM_V(disable_active_migration, 0);
SET_PARAM_V(grease_quic_bit, 1);
SET_PARAM_V(preferred_addr_present, 0);
SET_PARAM_V(stateless_reset_token_present, 0);
SET_PARAM_V(retry_scid_present, 0);
if (config.side == Side::SERVER) {
// For the server side, the original dcid is always set.
CHECK(config.ocid);
SET_PARAM_V(original_dcid, config.ocid);
SET_PARAM_V(original_dcid_present, 1);
// The retry_scid is only set if the server validated a retry token.
if (config.retry_scid) {
SET_PARAM_V(retry_scid, config.retry_scid);
SET_PARAM_V(retry_scid_present, 1);
}
}
#undef SET_PARAM
#undef SET_PARAM_V
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(const ngtcp2_vec& vec, Version version)
: TransportParams() {
int ret = ngtcp2_transport_params_decode_versioned(
static_cast<int>(version), ¶ms_, vec.base, vec.len);
// The only error we should see here is NGTCP2_ERR_MALFORMED_TRANSPORT_PARAM,
// which indicates that the provided data was not valid transport parameters.
// In that case, we set ptr_ to nullptr to indicate that the parameters
// could not be decoded.
if (ret != 0) {
ptr_ = nullptr;
}
}
Store TransportParams::Encode(Environment* env, Version version) const {
if (ptr_ == nullptr) {
return {};
}
// Preflight to see how much storage we'll need.
ssize_t size = ngtcp2_transport_params_encode_versioned(
nullptr, 0, static_cast<int>(version), ¶ms_);
if (size == 0) {
return {};
}
JS_TRY_ALLOCATE_BACKING_OR_RETURN(env, result, size, {});
auto ret = ngtcp2_transport_params_encode_versioned(
static_cast<uint8_t*>(result->Data()),
size,
static_cast<int>(version),
¶ms_);
// The ret is the number of bytes written, or a negative error code.
if (ret < 0) return {};
CHECK_EQ(ret, size);
return Store(std::move(result), static_cast<size_t>(size));
}
ssize_t TransportParams::EncodedSize(Version version) const {
if (ptr_ == nullptr) return 0;
return ngtcp2_transport_params_encode_versioned(
nullptr, 0, static_cast<int>(version), ¶ms_);
}
ssize_t TransportParams::EncodeInto(uint8_t* buf,
size_t len,
Version version) const {
if (ptr_ == nullptr) return -1;
return ngtcp2_transport_params_encode_versioned(
buf, len, static_cast<int>(version), ¶ms_);
}
void TransportParams::SetPreferredAddress(const SocketAddress& address) {
DCHECK(ptr_ == ¶ms_);
params_.preferred_addr_present = 1;
switch (address.family()) {
case AF_INET: {
const sockaddr_in* src =
reinterpret_cast<const sockaddr_in*>(address.data());
memcpy(¶ms_.preferred_addr.ipv4.sin_addr,
&src->sin_addr,
sizeof(params_.preferred_addr.ipv4.sin_addr));
params_.preferred_addr.ipv4.sin_port = address.port();
params_.preferred_addr.ipv4_present = 1;
return;
}
case AF_INET6: {
const sockaddr_in6* src =
reinterpret_cast<const sockaddr_in6*>(address.data());
memcpy(¶ms_.preferred_addr.ipv6.sin6_addr,
&src->sin6_addr,
sizeof(params_.preferred_addr.ipv6.sin6_addr));
params_.preferred_addr.ipv6.sin6_port = address.port();
params_.preferred_addr.ipv6_present = 1;
return;
}
}
UNREACHABLE();
}
void TransportParams::GenerateSessionTokens(Session* session) {
if (session->is_server()) {
GenerateStatelessResetToken(session->endpoint(), session->config().scid);
GeneratePreferredAddressToken(session);
}
}
void TransportParams::GenerateStatelessResetToken(const Endpoint& endpoint,
const CID& cid) {
DCHECK(ptr_ == ¶ms_);
DCHECK(cid);
params_.stateless_reset_token_present = 1;
endpoint.GenerateNewStatelessResetToken(params_.stateless_reset_token, cid);
}
void TransportParams::GeneratePreferredAddressToken(Session* session) {
DCHECK(ptr_ == ¶ms_);
Session::Config& config = session->config();
if (params_.preferred_addr_present) {
config.preferred_address_cid = session->new_cid();
params_.preferred_addr.cid = config.preferred_address_cid;
auto& endpoint = session->endpoint();
endpoint.AssociateStatelessResetToken(
endpoint.GenerateNewStatelessResetToken(
params_.preferred_addr.stateless_reset_token,
config.preferred_address_cid),
session);
// Register the preferred address CID with SessionManager for
// cross-endpoint routing. This is a locally-generated CID that needs
// to be routable from the preferred address endpoint (which may be
// different from the primary endpoint).
auto& mgr = BindingData::Get(session->env()).session_manager();
mgr.AssociateCID(config.preferred_address_cid, config.scid);
}
}
v8::MaybeLocal<v8::Object> TransportParams::ToObject(Environment* env) const {
auto& binding_data = BindingData::Get(env);
auto tmpl = binding_data.transport_params_template();
static constexpr std::string_view names[] = {
"preferredAddressIpv4",
"preferredAddressIpv6",
"originalDCID",
"initialSCID",
"retrySCID",
"initialMaxStreamDataBidiLocal",
"initialMaxStreamDataBidiRemote",
"initialMaxStreamDataUni",
"initialMaxData",
"initialMaxStreamsBidi",
"initialMaxStreamsUni",
"maxIdleTimeout",
"activeConnectionIDLimit",
"ackDelayExponent",
"maxAckDelay",
"maxDatagramFrameSize",
"disableActiveMigration",
};
if (tmpl.IsEmpty()) {
tmpl = DictionaryTemplate::New(env->isolate(), names);
binding_data.set_transport_params_template(tmpl);
}
MaybeLocal<Value> values[] = {
Undefined(env->isolate()), // preferredAddressIpv4
Undefined(env->isolate()), // preferredAddressIpv6
Undefined(env->isolate()), // originalDCID
Undefined(env->isolate()), // initialSCID
Undefined(env->isolate()), // retrySCID
Undefined(env->isolate()), // initialMaxStreamDataBidiLocal
Undefined(env->isolate()), // initialMaxStreamDataBidiRemote
Undefined(env->isolate()), // initialMaxStreamDataUni
Undefined(env->isolate()), // initialMaxData
Undefined(env->isolate()), // initialMaxStreamsBidi
Undefined(env->isolate()), // initialMaxStreamsUni
Undefined(env->isolate()), // maxIdleTimeout
Undefined(env->isolate()), // activeConnectionIDLimit
Undefined(env->isolate()), // ackDelayExponent
Undefined(env->isolate()), // maxAckDelay
Undefined(env->isolate()), // maxDatagramFrameSize
Undefined(env->isolate()), // disableActiveMigration
};
static_assert(std::size(values) == std::size(names));
static constexpr size_t kPreferredAddressIpv4Index = 0;
static constexpr size_t kPreferredAddressIpv6Index = 1;
static constexpr size_t kOriginalDCIDIndex = 2;
static constexpr size_t kInitialSCIDIndex = 3;
static constexpr size_t kRetrySCIDIndex = 4;
static constexpr size_t kInitialMaxStreamDataBidiLocalIndex = 5;
static constexpr size_t kInitialMaxStreamDataBidiRemoteIndex = 6;
static constexpr size_t kInitialMaxStreamDataUniIndex = 7;
static constexpr size_t kInitialMaxDataIndex = 8;
static constexpr size_t kInitialMaxStreamsBidiIndex = 9;
static constexpr size_t kInitialMaxStreamsUniIndex = 10;
static constexpr size_t kMaxIdleTimeoutIndex = 11;
static constexpr size_t kActiveConnectionIDLimitIndex = 12;
static constexpr size_t kAckDelayExponentIndex = 13;
static constexpr size_t kMaxAckDelayIndex = 14;
static constexpr size_t kMaxDatagramFrameSizeIndex = 15;
static constexpr size_t kDisableActiveMigrationIndex = 16;
if (ptr_ != nullptr) {
if (ptr_->preferred_addr_present) {
if (ptr_->preferred_addr.ipv4_present) {
auto address = std::make_shared<SocketAddress>(
reinterpret_cast<const sockaddr*>(&ptr_->preferred_addr.ipv4));
auto addr = SocketAddressBase::Create(env, std::move(address));
if (!addr) return {};
values[kPreferredAddressIpv4Index] = addr->object();
}
if (ptr_->preferred_addr.ipv6_present) {
auto address = std::make_shared<SocketAddress>(
reinterpret_cast<const sockaddr*>(&ptr_->preferred_addr.ipv6));
auto addr = SocketAddressBase::Create(env, std::move(address));
if (!addr) return {};
values[kPreferredAddressIpv6Index] = addr->object();
}
// ngtcp2_preferred_addr preferred_addr;
}
if (ptr_->original_dcid_present) {
CID cid(ptr_->original_dcid);
Local<Value> value;
if (!ToV8Value(env->context(), cid.ToString()).ToLocal(&value)) {
return {};
}
values[kOriginalDCIDIndex] = value;
}
if (ptr_->initial_scid_present) {
CID cid(ptr_->initial_scid);
Local<Value> value;
if (!ToV8Value(env->context(), cid.ToString()).ToLocal(&value)) {
return {};
}
values[kInitialSCIDIndex] = value;
}
if (ptr_->retry_scid_present) {
CID cid(ptr_->retry_scid);
Local<Value> value;
if (!ToV8Value(env->context(), cid.ToString()).ToLocal(&value)) {
return {};
}
values[kRetrySCIDIndex] = value;
}
values[kInitialMaxStreamDataBidiLocalIndex] = BigInt::NewFromUnsigned(
env->isolate(), ptr_->initial_max_stream_data_bidi_local);
values[kInitialMaxStreamDataBidiRemoteIndex] = BigInt::NewFromUnsigned(
env->isolate(), ptr_->initial_max_stream_data_bidi_remote);
values[kInitialMaxStreamDataUniIndex] = BigInt::NewFromUnsigned(
env->isolate(), ptr_->initial_max_stream_data_uni);
values[kInitialMaxDataIndex] =
BigInt::NewFromUnsigned(env->isolate(), ptr_->initial_max_data);
values[kInitialMaxStreamsBidiIndex] =
BigInt::NewFromUnsigned(env->isolate(), ptr_->initial_max_streams_bidi);
values[kInitialMaxStreamsUniIndex] =
BigInt::NewFromUnsigned(env->isolate(), ptr_->initial_max_streams_uni);
values[kMaxIdleTimeoutIndex] = BigInt::NewFromUnsigned(
env->isolate(), ptr_->max_idle_timeout / NGTCP2_SECONDS);
values[kActiveConnectionIDLimitIndex] = BigInt::NewFromUnsigned(
env->isolate(), ptr_->active_connection_id_limit);
values[kAckDelayExponentIndex] =
BigInt::NewFromUnsigned(env->isolate(), ptr_->ack_delay_exponent);
values[kMaxAckDelayIndex] =
BigInt::NewFromUnsigned(env->isolate(), ptr_->ack_delay_exponent);
values[kMaxAckDelayIndex] =
BigInt::NewFromUnsigned(env->isolate(), ptr_->max_ack_delay);
values[kMaxDatagramFrameSizeIndex] =
BigInt::NewFromUnsigned(env->isolate(), ptr_->max_datagram_frame_size);
values[kDisableActiveMigrationIndex] =
Boolean::New(env->isolate(), ptr_->disable_active_migration);
}
auto obj = tmpl->NewInstance(env->context(), values);
if (obj->SetPrototype(env->context(), Null(env->isolate())).IsNothing()) {
return {};
}
return obj;
}
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;
}
uint64_t TransportParams::initial_max_streams_bidi() const {
DCHECK_NOT_NULL(ptr_);
return ptr_->initial_max_streams_bidi;
}
uint64_t TransportParams::initial_max_streams_uni() const {
DCHECK_NOT_NULL(ptr_);
return ptr_->initial_max_streams_uni;
}
void TransportParams::Initialize(Environment* env, Local<Object> target) {
NODE_DEFINE_CONSTANT(target, DEFAULT_MAX_STREAM_DATA);
NODE_DEFINE_CONSTANT(target, DEFAULT_MAX_DATA);
NODE_DEFINE_CONSTANT(target, DEFAULT_MAX_IDLE_TIMEOUT);
NODE_DEFINE_CONSTANT(target, DEFAULT_MAX_STREAMS_BIDI);
NODE_DEFINE_CONSTANT(target, DEFAULT_MAX_STREAMS_UNI);
NODE_DEFINE_CONSTANT(target, DEFAULT_ACTIVE_CONNECTION_ID_LIMIT);
}
} // namespace quic
} // namespace node
#endif // OPENSSL_NO_QUIC