diff options
author | James M Snell <jasnell@gmail.com> | 2021-01-25 21:49:03 +0300 |
---|---|---|
committer | Michaël Zasso <targos@protonmail.com> | 2021-02-02 12:50:12 +0300 |
commit | 11dd2672cdc8fe7ba2968401de3c1c212596c4cc (patch) | |
tree | 858ed45bb0a6a1db81d20e3772f593e77643adf2 /src | |
parent | f74b3765965614124c4df860adc008e0d9a92479 (diff) |
quic: remove quic
PR-URL: https://github.com/nodejs/node/pull/37067
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Rich Trott <rtrott@gmail.com>
Reviewed-By: Tobias Nießen <tniessen@tnie.de>
Reviewed-By: Michael Dawson <midawson@redhat.com>
Reviewed-By: Trivikram Kamat <trivikr.dev@gmail.com>
Diffstat (limited to 'src')
31 files changed, 5 insertions, 13249 deletions
diff --git a/src/async_wrap.h b/src/async_wrap.h index 76de08053b5..522779d57a0 100644 --- a/src/async_wrap.h +++ b/src/async_wrap.h @@ -60,12 +60,6 @@ namespace node { V(PROCESSWRAP) \ V(PROMISE) \ V(QUERYWRAP) \ - V(QLOGSTREAM) \ - V(QUICCLIENTSESSION) \ - V(QUICSERVERSESSION) \ - V(QUICSENDWRAP) \ - V(QUICSOCKET) \ - V(QUICSTREAM) \ V(SHUTDOWNWRAP) \ V(SIGNALWRAP) \ V(STATWATCHER) \ diff --git a/src/env.h b/src/env.h index cf93622ea89..38c087b650e 100644 --- a/src/env.h +++ b/src/env.h @@ -366,7 +366,6 @@ constexpr size_t kFsStatsBufferLength = V(pubkey_string, "pubkey") \ V(public_exponent_string, "publicExponent") \ V(query_string, "query") \ - V(http3_alpn_string, "h3-29") \ V(rate_string, "rate") \ V(raw_string, "raw") \ V(read_host_object_string, "_readHostObject") \ @@ -435,16 +434,6 @@ constexpr size_t kFsStatsBufferLength = V(x_forwarded_string, "x-forwarded-for") \ V(zero_return_string, "ZERO_RETURN") -#if defined(NODE_EXPERIMENTAL_QUIC) && NODE_EXPERIMENTAL_QUIC -# define QUIC_ENVIRONMENT_STRONG_PERSISTENT_TEMPLATES(V) \ - V(quicclientsession_instance_template, v8::ObjectTemplate) \ - V(quicserversession_instance_template, v8::ObjectTemplate) \ - V(quicserverstream_instance_template, v8::ObjectTemplate) \ - V(quicsocketsendwrap_instance_template, v8::ObjectTemplate) -#else -# define QUIC_ENVIRONMENT_STRONG_PERSISTENT_TEMPLATES(V) -#endif - #define ENVIRONMENT_STRONG_PERSISTENT_TEMPLATES(V) \ V(async_wrap_ctor_template, v8::FunctionTemplate) \ V(async_wrap_object_ctor_template, v8::FunctionTemplate) \ @@ -479,34 +468,7 @@ constexpr size_t kFsStatsBufferLength = V(tty_constructor_template, v8::FunctionTemplate) \ V(write_wrap_template, v8::ObjectTemplate) \ V(worker_heap_snapshot_taker_template, v8::ObjectTemplate) \ - V(x509_constructor_template, v8::FunctionTemplate) \ - QUIC_ENVIRONMENT_STRONG_PERSISTENT_TEMPLATES(V) - -#if defined(NODE_EXPERIMENTAL_QUIC) && NODE_EXPERIMENTAL_QUIC -# define QUIC_ENVIRONMENT_STRONG_PERSISTENT_VALUES(V) \ - V(quic_on_socket_close_function, v8::Function) \ - V(quic_on_socket_server_busy_function, v8::Function) \ - V(quic_on_session_cert_function, v8::Function) \ - V(quic_on_session_client_hello_function, v8::Function) \ - V(quic_on_session_close_function, v8::Function) \ - V(quic_on_session_handshake_function, v8::Function) \ - V(quic_on_session_keylog_function, v8::Function) \ - V(quic_on_session_path_validation_function, v8::Function) \ - V(quic_on_session_use_preferred_address_function, v8::Function) \ - V(quic_on_session_qlog_function, v8::Function) \ - V(quic_on_session_ready_function, v8::Function) \ - V(quic_on_session_status_function, v8::Function) \ - V(quic_on_session_ticket_function, v8::Function) \ - V(quic_on_session_version_negotiation_function, v8::Function) \ - V(quic_on_stream_close_function, v8::Function) \ - V(quic_on_stream_error_function, v8::Function) \ - V(quic_on_stream_ready_function, v8::Function) \ - V(quic_on_stream_reset_function, v8::Function) \ - V(quic_on_stream_headers_function, v8::Function) \ - V(quic_on_stream_blocked_function, v8::Function) -#else -# define QUIC_ENVIRONMENT_STRONG_PERSISTENT_VALUES(V) -#endif + V(x509_constructor_template, v8::FunctionTemplate) #define ENVIRONMENT_STRONG_PERSISTENT_VALUES(V) \ V(async_hooks_after_function, v8::Function) \ @@ -563,8 +525,7 @@ constexpr size_t kFsStatsBufferLength = V(tls_wrap_constructor_function, v8::Function) \ V(trace_category_state_function, v8::Function) \ V(udp_constructor_function, v8::Function) \ - V(url_constructor_function, v8::Function) \ - QUIC_ENVIRONMENT_STRONG_PERSISTENT_VALUES(V) + V(url_constructor_function, v8::Function) class Environment; struct AllocatedBuffer; diff --git a/src/node_binding.cc b/src/node_binding.cc index 0db930adff1..6c7ab4b21ed 100644 --- a/src/node_binding.cc +++ b/src/node_binding.cc @@ -14,12 +14,6 @@ #define NODE_BUILTIN_OPENSSL_MODULES(V) #endif -#if defined(NODE_EXPERIMENTAL_QUIC) && NODE_EXPERIMENTAL_QUIC -#define NODE_BUILTIN_QUIC_MODULES(V) V(quic) -#else -#define NODE_BUILTIN_QUIC_MODULES(V) -#endif - #if NODE_HAVE_I18N_SUPPORT #define NODE_BUILTIN_ICU_MODULES(V) V(icu) #else @@ -98,7 +92,6 @@ #define NODE_BUILTIN_MODULES(V) \ NODE_BUILTIN_STANDARD_MODULES(V) \ NODE_BUILTIN_OPENSSL_MODULES(V) \ - NODE_BUILTIN_QUIC_MODULES(V) \ NODE_BUILTIN_ICU_MODULES(V) \ NODE_BUILTIN_PROFILER_MODULES(V) \ NODE_BUILTIN_DTRACE_MODULES(V) diff --git a/src/node_errors.h b/src/node_errors.h index 6158a968d27..984603c42e2 100644 --- a/src/node_errors.h +++ b/src/node_errors.h @@ -78,8 +78,6 @@ void OnFatalError(const char* location, const char* message); V(ERR_WASI_NOT_STARTED, Error) \ V(ERR_WORKER_INIT_FAILED, Error) \ V(ERR_PROTO_ACCESS, Error) \ - V(ERR_QUIC_CANNOT_SET_GROUPS, Error) \ - V(ERR_QUIC_FAILURE_SETTING_SNI_CONTEXT, Error) #define V(code, type) \ inline v8::Local<v8::Value> code(v8::Isolate* isolate, \ @@ -156,9 +154,7 @@ void OnFatalError(const char* location, const char* message); V(ERR_WORKER_INIT_FAILED, "Worker initialization failure") \ V(ERR_PROTO_ACCESS, \ "Accessing Object.prototype.__proto__ has been " \ - "disallowed with --disable-proto=throw") \ - V(ERR_QUIC_CANNOT_SET_GROUPS, "Cannot set groups") \ - V(ERR_QUIC_FAILURE_SETTING_SNI_CONTEXT, "Failure setting SNI context") + "disallowed with --disable-proto=throw") #define V(code, message) \ inline v8::Local<v8::Value> code(v8::Isolate* isolate) { \ diff --git a/src/node_http_common.h b/src/node_http_common.h index 8017c0d7aad..ad9f2a864e0 100644 --- a/src/node_http_common.h +++ b/src/node_http_common.h @@ -270,7 +270,7 @@ class NgHeaders { MaybeStackBuffer<char, 3000> buf_; }; -// The ng libraries (nghttp2 and nghttp3) each use nearly identical +// The ng libraries use nearly identical // reference counted structures for retaining header name and value // information in memory until the application is done with it. // The NgRcBufPointer is an intelligent pointer capable of working diff --git a/src/node_mem.h b/src/node_mem.h index f8cdc20848f..332d1cd1eff 100644 --- a/src/node_mem.h +++ b/src/node_mem.h @@ -8,7 +8,7 @@ namespace node { namespace mem { -// Both ngtcp2 and nghttp2 allow custom allocators that +// nghttp2 allows custom allocators that // follow exactly the same structure and behavior, but // use different struct names. To allow for code re-use, // the NgLibMemoryManager template class can be used for both. diff --git a/src/node_metadata.cc b/src/node_metadata.cc index e8864d35527..8d0a725de45 100644 --- a/src/node_metadata.cc +++ b/src/node_metadata.cc @@ -13,11 +13,6 @@ #include <openssl/opensslv.h> #endif // HAVE_OPENSSL -#if defined(NODE_EXPERIMENTAL_QUIC) && NODE_EXPERIMENTAL_QUIC -#include <ngtcp2/version.h> -#include <nghttp3/version.h> -#endif - #ifdef NODE_HAVE_I18N_SUPPORT #include <unicode/timezone.h> #include <unicode/ulocdata.h> @@ -96,11 +91,6 @@ Metadata::Versions::Versions() { openssl = GetOpenSSLVersion(); #endif -#if defined(NODE_EXPERIMENTAL_QUIC) && NODE_EXPERIMENTAL_QUIC - ngtcp2 = NGTCP2_VERSION; - nghttp3 = NGHTTP3_VERSION; -#endif - #ifdef NODE_HAVE_I18N_SUPPORT icu = U_ICU_VERSION; unicode = U_UNICODE_VERSION; diff --git a/src/node_metadata.h b/src/node_metadata.h index 2a4571883d8..bf7e5d3ff4e 100644 --- a/src/node_metadata.h +++ b/src/node_metadata.h @@ -38,12 +38,6 @@ namespace node { #define NODE_VERSIONS_KEY_CRYPTO(V) #endif -#if defined(NODE_EXPERIMENTAL_QUIC) && NODE_EXPERIMENTAL_QUIC -#define NODE_VERSIONS_KEY_QUIC(V) V(ngtcp2) V(nghttp3) -#else -#define NODE_VERSIONS_KEY_QUIC(V) -#endif - #ifdef NODE_HAVE_I18N_SUPPORT #define NODE_VERSIONS_KEY_INTL(V) \ V(cldr) \ @@ -57,7 +51,6 @@ namespace node { #define NODE_VERSIONS_KEYS(V) \ NODE_VERSIONS_KEYS_BASE(V) \ NODE_VERSIONS_KEY_CRYPTO(V) \ - NODE_VERSIONS_KEY_QUIC(V) \ NODE_VERSIONS_KEY_INTL(V) class Metadata { diff --git a/src/node_native_module.cc b/src/node_native_module.cc index f7d73544d2d..5a8cf12347d 100644 --- a/src/node_native_module.cc +++ b/src/node_native_module.cc @@ -105,10 +105,6 @@ void NativeModuleLoader::InitializeModuleCategories() { "internal/process/policy", "internal/streams/lazy_transform", #endif // !HAVE_OPENSSL -#if !NODE_EXPERIMENTAL_QUIC - "internal/quic/core", - "internal/quic/util", -#endif "sys", // Deprecated. "wasi", // Experimental. "internal/test/binding", diff --git a/src/quic/node_quic.cc b/src/quic/node_quic.cc deleted file mode 100644 index f34c8669f94..00000000000 --- a/src/quic/node_quic.cc +++ /dev/null @@ -1,271 +0,0 @@ -#include "debug_utils-inl.h" -#include "node.h" -#include "env-inl.h" -#include "crypto/crypto_context.h" -#include "crypto/crypto_common.h" -#include "node_errors.h" -#include "node_process.h" -#include "node_quic_crypto.h" -#include "node_quic_session-inl.h" -#include "node_quic_socket-inl.h" -#include "node_quic_stream-inl.h" -#include "node_quic_state.h" -#include "node_quic_util-inl.h" -#include "node_sockaddr-inl.h" -#include "nghttp3/nghttp3.h" - -#include <memory> -#include <utility> - -namespace node { - -using crypto::SecureContext; -using v8::Context; -using v8::Function; -using v8::FunctionCallbackInfo; -using v8::HandleScope; -using v8::Isolate; -using v8::Local; -using v8::Object; -using v8::Value; - -namespace quic { - -constexpr FastStringKey QuicState::binding_data_name; - -void QuicState::MemoryInfo(MemoryTracker* tracker) const { - tracker->TrackField("root_buffer", root_buffer); -} - -namespace { -// Register the JavaScript callbacks the internal binding will use to report -// status and updates. This is called only once when the quic module is loaded. -void QuicSetCallbacks(const FunctionCallbackInfo<Value>& args) { - Environment* env = Environment::GetCurrent(args); - CHECK(args[0]->IsObject()); - Local<Object> obj = args[0].As<Object>(); - -#define SETFUNCTION(name, callback) \ - do { \ - Local<Value> fn; \ - CHECK(obj->Get(env->context(), \ - FIXED_ONE_BYTE_STRING(env->isolate(), name)).ToLocal(&fn));\ - CHECK(fn->IsFunction()); \ - env->set_quic_on_##callback##_function(fn.As<Function>()); \ - } while (0) - - SETFUNCTION("onSocketClose", socket_close); - SETFUNCTION("onSessionReady", session_ready); - SETFUNCTION("onSessionCert", session_cert); - SETFUNCTION("onSessionClientHello", session_client_hello); - SETFUNCTION("onSessionClose", session_close); - SETFUNCTION("onSessionHandshake", session_handshake); - SETFUNCTION("onSessionKeylog", session_keylog); - SETFUNCTION("onSessionUsePreferredAddress", session_use_preferred_address); - SETFUNCTION("onSessionPathValidation", session_path_validation); - SETFUNCTION("onSessionQlog", session_qlog); - SETFUNCTION("onSessionStatus", session_status); - SETFUNCTION("onSessionTicket", session_ticket); - SETFUNCTION("onSessionVersionNegotiation", session_version_negotiation); - SETFUNCTION("onStreamReady", stream_ready); - SETFUNCTION("onStreamClose", stream_close); - SETFUNCTION("onStreamError", stream_error); - SETFUNCTION("onStreamReset", stream_reset); - SETFUNCTION("onSocketServerBusy", socket_server_busy); - SETFUNCTION("onStreamHeaders", stream_headers); - SETFUNCTION("onStreamBlocked", stream_blocked); - -#undef SETFUNCTION -} - -// Sets QUIC specific configuration options for the SecureContext. -// It's entirely likely that there's a better way to do this, but -// for now this works. -template <ngtcp2_crypto_side side> -void QuicInitSecureContext(const FunctionCallbackInfo<Value>& args) { - Environment* env = Environment::GetCurrent(args); - CHECK(args[0]->IsObject()); // Secure Context - CHECK(args[1]->IsString()); // groups - CHECK(args[2]->IsBoolean()); // early data - - SecureContext* sc; - ASSIGN_OR_RETURN_UNWRAP(&sc, args[0].As<Object>(), - args.GetReturnValue().Set(UV_EBADF)); - const node::Utf8Value groups(env->isolate(), args[1]); - - bool early_data = args[2]->BooleanValue(env->isolate()); - - InitializeSecureContext( - BaseObjectPtr<SecureContext>(sc), - early_data, - side); - - if (!crypto::SetGroups(sc, *groups)) - THROW_ERR_QUIC_CANNOT_SET_GROUPS(env); -} -} // namespace - -void Initialize(Local<Object> target, - Local<Value> unused, - Local<Context> context, - void* priv) { - Environment* env = Environment::GetCurrent(context); - Isolate* isolate = env->isolate(); - HandleScope handle_scope(isolate); - - HistogramBase::Initialize(env); - - QuicState* const state = - env->AddBindingData<QuicState>(context, target); - if (state == nullptr) return; - -#define SET_STATE_TYPEDARRAY(name, field) \ - target->Set(context, \ - FIXED_ONE_BYTE_STRING(isolate, (name)), \ - (field.GetJSArray())).Check() - SET_STATE_TYPEDARRAY("sessionConfig", state->quicsessionconfig_buffer); - SET_STATE_TYPEDARRAY("http3Config", state->http3config_buffer); -#undef SET_STATE_TYPEDARRAY - - QuicSocket::Initialize(env, target, context); - QuicEndpoint::Initialize(env, target, context); - QuicSession::Initialize(env, target, context); - QuicStream::Initialize(env, target, context); - - env->SetMethod(target, - "setCallbacks", - QuicSetCallbacks); - env->SetMethod(target, - "initSecureContext", - QuicInitSecureContext<NGTCP2_CRYPTO_SIDE_SERVER>); - env->SetMethod(target, - "initSecureContextClient", - QuicInitSecureContext<NGTCP2_CRYPTO_SIDE_CLIENT>); - - Local<Object> constants = Object::New(env->isolate()); - -// TODO(@jasnell): Audit which constants are actually being used in JS -#define QUIC_CONSTANTS(V) \ - V(DEFAULT_MAX_STREAM_DATA_BIDI_LOCAL) \ - V(DEFAULT_RETRYTOKEN_EXPIRATION) \ - V(DEFAULT_MAX_CONNECTIONS) \ - V(DEFAULT_MAX_CONNECTIONS_PER_HOST) \ - V(DEFAULT_MAX_STATELESS_RESETS_PER_HOST) \ - V(IDX_HTTP3_QPACK_MAX_TABLE_CAPACITY) \ - V(IDX_HTTP3_QPACK_BLOCKED_STREAMS) \ - V(IDX_HTTP3_MAX_HEADER_LIST_SIZE) \ - V(IDX_HTTP3_MAX_PUSHES) \ - V(IDX_HTTP3_MAX_HEADER_PAIRS) \ - V(IDX_HTTP3_MAX_HEADER_LENGTH) \ - V(IDX_HTTP3_CONFIG_COUNT) \ - V(IDX_QUIC_SESSION_ACTIVE_CONNECTION_ID_LIMIT) \ - V(IDX_QUIC_SESSION_MAX_IDLE_TIMEOUT) \ - V(IDX_QUIC_SESSION_MAX_DATA) \ - V(IDX_QUIC_SESSION_MAX_STREAM_DATA_BIDI_LOCAL) \ - V(IDX_QUIC_SESSION_MAX_STREAM_DATA_BIDI_REMOTE) \ - V(IDX_QUIC_SESSION_MAX_STREAM_DATA_UNI) \ - V(IDX_QUIC_SESSION_MAX_STREAMS_BIDI) \ - V(IDX_QUIC_SESSION_MAX_STREAMS_UNI) \ - V(IDX_QUIC_SESSION_MAX_UDP_PAYLOAD_SIZE) \ - V(IDX_QUIC_SESSION_ACK_DELAY_EXPONENT) \ - V(IDX_QUIC_SESSION_DISABLE_MIGRATION) \ - V(IDX_QUIC_SESSION_MAX_ACK_DELAY) \ - V(IDX_QUIC_SESSION_CC_ALGO) \ - V(IDX_QUIC_SESSION_CONFIG_COUNT) \ - V(MAX_RETRYTOKEN_EXPIRATION) \ - V(MIN_RETRYTOKEN_EXPIRATION) \ - V(NGTCP2_APP_NOERROR) \ - V(NGTCP2_PATH_VALIDATION_RESULT_FAILURE) \ - V(NGTCP2_PATH_VALIDATION_RESULT_SUCCESS) \ - V(NGTCP2_CC_ALGO_CUBIC) \ - V(NGTCP2_CC_ALGO_RENO) \ - V(QUIC_ERROR_APPLICATION) \ - V(QUIC_ERROR_CRYPTO) \ - V(QUIC_ERROR_SESSION) \ - V(QUIC_PREFERRED_ADDRESS_USE) \ - V(QUIC_PREFERRED_ADDRESS_IGNORE) \ - V(QUICCLIENTSESSION_OPTION_REQUEST_OCSP) \ - V(QUICCLIENTSESSION_OPTION_VERIFY_HOSTNAME_IDENTITY) \ - V(QUICSERVERSESSION_OPTION_REJECT_UNAUTHORIZED) \ - V(QUICSERVERSESSION_OPTION_REQUEST_CERT) \ - V(QUICSOCKET_OPTIONS_VALIDATE_ADDRESS) \ - V(QUICSTREAM_HEADER_FLAGS_NONE) \ - V(QUICSTREAM_HEADER_FLAGS_TERMINAL) \ - V(QUICSTREAM_HEADERS_KIND_NONE) \ - V(QUICSTREAM_HEADERS_KIND_INFORMATIONAL) \ - V(QUICSTREAM_HEADERS_KIND_PUSH) \ - V(QUICSTREAM_HEADERS_KIND_INITIAL) \ - V(QUICSTREAM_HEADERS_KIND_TRAILING) \ - V(ERR_FAILED_TO_CREATE_SESSION) \ - V(UV_EBADF) - -#define V(id, _, __) \ - NODE_DEFINE_CONSTANT(constants, IDX_QUICSESSION_STATE_##id); - QUICSESSION_SHARED_STATE(V) -#undef V - -#define V(id, _, __) \ - NODE_DEFINE_CONSTANT(constants, IDX_QUICSOCKET_STATE_##id); - QUICSOCKET_SHARED_STATE(V) -#undef V - -#define V(id, _, __) \ - NODE_DEFINE_CONSTANT(constants, IDX_QUICSTREAM_STATE_##id); - QUICSTREAM_SHARED_STATE(V) -#undef V - -#define V(name, _, __) \ - NODE_DEFINE_CONSTANT(constants, IDX_QUIC_SESSION_STATS_##name); - SESSION_STATS(V) -#undef V - -#define V(name, _, __) \ - NODE_DEFINE_CONSTANT(constants, IDX_QUIC_SOCKET_STATS_##name); - SOCKET_STATS(V) -#undef V - -#define V(name, _, __) \ - NODE_DEFINE_CONSTANT(constants, IDX_QUIC_STREAM_STATS_##name); - STREAM_STATS(V) -#undef V - -#define V(name) NODE_DEFINE_CONSTANT(constants, name); - QUIC_CONSTANTS(V) -#undef V - - NODE_DEFINE_CONSTANT(constants, NGTCP2_DEFAULT_MAX_PKTLEN); - NODE_DEFINE_CONSTANT(constants, NGTCP2_PROTO_VER); - NODE_DEFINE_CONSTANT(constants, NGTCP2_DEFAULT_MAX_ACK_DELAY); - NODE_DEFINE_CONSTANT(constants, NGTCP2_MAX_CIDLEN); - NODE_DEFINE_CONSTANT(constants, NGTCP2_MIN_CIDLEN); - - NODE_DEFINE_CONSTANT(constants, NGTCP2_NO_ERROR); - NODE_DEFINE_CONSTANT(constants, NGTCP2_INTERNAL_ERROR); - NODE_DEFINE_CONSTANT(constants, NGTCP2_CONNECTION_REFUSED); - NODE_DEFINE_CONSTANT(constants, NGTCP2_FLOW_CONTROL_ERROR); - NODE_DEFINE_CONSTANT(constants, NGTCP2_STREAM_LIMIT_ERROR); - NODE_DEFINE_CONSTANT(constants, NGTCP2_STREAM_STATE_ERROR); - NODE_DEFINE_CONSTANT(constants, NGTCP2_FINAL_SIZE_ERROR); - NODE_DEFINE_CONSTANT(constants, NGTCP2_FRAME_ENCODING_ERROR); - NODE_DEFINE_CONSTANT(constants, NGTCP2_TRANSPORT_PARAMETER_ERROR); - NODE_DEFINE_CONSTANT(constants, NGTCP2_CONNECTION_ID_LIMIT_ERROR); - NODE_DEFINE_CONSTANT(constants, NGTCP2_PROTOCOL_VIOLATION); - NODE_DEFINE_CONSTANT(constants, NGTCP2_INVALID_TOKEN); - NODE_DEFINE_CONSTANT(constants, NGTCP2_APPLICATION_ERROR); - NODE_DEFINE_CONSTANT(constants, NGTCP2_CRYPTO_BUFFER_EXCEEDED); - NODE_DEFINE_CONSTANT(constants, NGTCP2_KEY_UPDATE_ERROR); - NODE_DEFINE_CONSTANT(constants, NGTCP2_CRYPTO_ERROR); - - NODE_DEFINE_CONSTANT(constants, AF_INET); - NODE_DEFINE_CONSTANT(constants, AF_INET6); - NODE_DEFINE_STRING_CONSTANT(constants, - NODE_STRINGIFY_HELPER(NGHTTP3_ALPN_H3), - NGHTTP3_ALPN_H3); - - target->Set(context, env->constants_string(), constants).FromJust(); -} - -} // namespace quic -} // namespace node - -NODE_MODULE_CONTEXT_AWARE_INTERNAL(quic, node::quic::Initialize) diff --git a/src/quic/node_quic_buffer-inl.h b/src/quic/node_quic_buffer-inl.h deleted file mode 100644 index 25f130ac9fe..00000000000 --- a/src/quic/node_quic_buffer-inl.h +++ /dev/null @@ -1,98 +0,0 @@ -#ifndef SRC_QUIC_NODE_QUIC_BUFFER_INL_H_ -#define SRC_QUIC_NODE_QUIC_BUFFER_INL_H_ - -#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS - -#include "node_quic_buffer.h" -#include "node_bob-inl.h" -#include "util-inl.h" -#include "uv.h" - -#include <algorithm> - -namespace node { - -namespace quic { - -QuicBufferChunk::QuicBufferChunk(size_t len) - : data_buf_(len, 0), - buf_(uv_buf_init(reinterpret_cast<char*>(data_buf_.data()), len)), - length_(len), - done_called_(true) {} - -QuicBufferChunk::QuicBufferChunk(uv_buf_t buf, DoneCB done) - : buf_(buf), - length_(buf.len) { - if (done != nullptr) - done_ = std::move(done); -} - -QuicBufferChunk::~QuicBufferChunk() { - CHECK(done_called_); -} - -size_t QuicBufferChunk::Seek(size_t amount) { - amount = std::min(amount, remaining()); - buf_.base += amount; - buf_.len -= amount; - return amount; -} - -size_t QuicBufferChunk::Consume(size_t amount) { - amount = std::min(amount, length_); - length_ -= amount; - return amount; -} - -void QuicBufferChunk::Done(int status) { - if (done_called_) return; - done_called_ = true; - if (done_ != nullptr) - std::move(done_)(status); -} - -QuicBuffer::QuicBuffer(QuicBuffer&& src) noexcept - : head_(src.head_), - tail_(src.tail_), - ended_(src.ended_), - length_(src.length_) { - root_ = std::move(src.root_); - src.head_ = nullptr; - src.tail_ = nullptr; - src.length_ = 0; - src.remaining_ = 0; - src.ended_ = false; -} - -QuicBuffer& QuicBuffer::operator=(QuicBuffer&& src) noexcept { - if (this == &src) return *this; - this->~QuicBuffer(); - return *new(this) QuicBuffer(std::move(src)); -} - -bool QuicBuffer::is_empty(uv_buf_t buf) { - DCHECK_IMPLIES(buf.base == nullptr, buf.len == 0); - return buf.len == 0; -} - -size_t QuicBuffer::Consume(size_t amount) { - return Consume(0, amount); -} - -size_t QuicBuffer::Cancel(int status) { - if (canceled_) return 0; - canceled_ = true; - size_t t = Consume(status, length()); - return t; -} - -void QuicBuffer::Push(uv_buf_t buf, DoneCB done) { - std::unique_ptr<QuicBufferChunk> chunk = - std::make_unique<QuicBufferChunk>(buf, done); - Push(std::move(chunk)); -} -} // namespace quic -} // namespace node - -#endif // NODE_WANT_INTERNALS -#endif // SRC_QUIC_NODE_QUIC_BUFFER_INL_H_ diff --git a/src/quic/node_quic_buffer.cc b/src/quic/node_quic_buffer.cc deleted file mode 100644 index ebe230271ac..00000000000 --- a/src/quic/node_quic_buffer.cc +++ /dev/null @@ -1,166 +0,0 @@ -#include "node_quic_buffer-inl.h" // NOLINT(build/include) -#include "node_bob-inl.h" -#include "util.h" -#include "uv.h" - -#include <algorithm> -#include <memory> -#include <utility> - -namespace node { -namespace quic { - -void QuicBufferChunk::MemoryInfo(MemoryTracker* tracker) const { - tracker->TrackField("buf", data_buf_); - tracker->TrackField("next", next_); -} - -size_t QuicBuffer::Push(uv_buf_t* bufs, size_t nbufs, DoneCB done) { - size_t len = 0; - if (UNLIKELY(nbufs == 0)) { - done(0); - return 0; - } - DCHECK_NOT_NULL(bufs); - size_t n = 0; - while (nbufs > 1) { - if (!is_empty(bufs[n])) { - Push(bufs[n]); - len += bufs[n].len; - } - n++; - nbufs--; - } - if (!is_empty(bufs[n])) { - Push(bufs[n], done); - len += bufs[n].len; - } - // Special case if all the bufs were empty. - if (UNLIKELY(len == 0)) - done(0); - - return len; -} - -void QuicBuffer::Push(std::unique_ptr<QuicBufferChunk> chunk) { - CHECK(!ended_); - length_ += chunk->remaining(); - remaining_ += chunk->remaining(); - if (!tail_) { - root_ = std::move(chunk); - head_ = tail_ = root_.get(); - } else { - tail_->next_ = std::move(chunk); - tail_ = tail_->next_.get(); - if (!head_) - head_ = tail_; - } -} - -size_t QuicBuffer::Seek(size_t amount) { - size_t len = 0; - while (head_ && amount > 0) { - size_t amt = head_->Seek(amount); - amount -= amt; - len += amt; - remaining_ -= amt; - if (head_->remaining()) - break; - head_ = head_->next_.get(); - } - return len; -} - -bool QuicBuffer::Pop(int status) { - if (!root_) - return false; - std::unique_ptr<QuicBufferChunk> root(std::move(root_)); - root_ = std::move(root.get()->next_); - - if (head_ == root.get()) - head_ = root_.get(); - if (tail_ == root.get()) - tail_ = root_.get(); - - root->Done(status); - return true; -} - -size_t QuicBuffer::Consume(int status, size_t amount) { - size_t amt = std::min(amount, length_); - size_t len = 0; - while (root_ && amt > 0) { - auto root = root_.get(); - size_t consumed = root->Consume(amt); - len += consumed; - length_ -= consumed; - amt -= consumed; - if (root->length() > 0) - break; - Pop(status); - } - return len; -} - -void QuicBuffer::MemoryInfo(MemoryTracker* tracker) const { - tracker->TrackField("root", root_); -} - -int QuicBuffer::DoPull( - bob::Next<ngtcp2_vec> next, - int options, - ngtcp2_vec* data, - size_t count, - size_t max_count_hint) { - size_t len = 0; - size_t numbytes = 0; - int status = bob::Status::STATUS_CONTINUE; - - // There's no data to read. - if (!remaining() || head_ == nullptr) { - status = is_ended() ? - bob::Status::STATUS_END : - bob::Status::STATUS_BLOCK; - std::move(next)(status, nullptr, 0, [](size_t len) {}); - return status; - } - - // Ensure that there's storage space. - MaybeStackBuffer<ngtcp2_vec, kMaxVectorCount> vec; - if (data == nullptr || count == 0) { - vec.AllocateSufficientStorage(max_count_hint); - data = vec.out(); - } else { - max_count_hint = std::min(count, max_count_hint); - } - - // Build the list of buffers. - QuicBufferChunk* pos = head_; - while (pos != nullptr && len < max_count_hint) { - data[len].base = reinterpret_cast<uint8_t*>(pos->buf().base); - data[len].len = pos->buf().len; - numbytes += data[len].len; - len++; - pos = pos->next_.get(); - } - - // If the buffer is ended, and the number of bytes - // matches the total remaining and OPTIONS_END is - // used, set the status to STATUS_END. - if (is_ended() && - numbytes == remaining() && - options & bob::OPTIONS_END) - status = bob::Status::STATUS_END; - - // Pass the data back out to the caller. - std::move(next)( - status, - data, - len, - [this](size_t len) { Seek(len); }); - - return status; -} - -} // namespace quic -} // namespace node diff --git a/src/quic/node_quic_buffer.h b/src/quic/node_quic_buffer.h deleted file mode 100644 index 3d81a28176d..00000000000 --- a/src/quic/node_quic_buffer.h +++ /dev/null @@ -1,239 +0,0 @@ -#ifndef SRC_QUIC_NODE_QUIC_BUFFER_H_ -#define SRC_QUIC_NODE_QUIC_BUFFER_H_ - -#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS - -#include "memory_tracker-inl.h" -#include "ngtcp2/ngtcp2.h" -#include "node.h" -#include "node_bob.h" -#include "node_internals.h" -#include "util.h" -#include "uv.h" - -#include <vector> - -namespace node { -namespace quic { - -class QuicBuffer; - -constexpr size_t kMaxVectorCount = 16; - -using DoneCB = std::function<void(int)>; - -// When data is sent over QUIC, we are required to retain it in memory -// until we receive an acknowledgement that it has been successfully -// received. The QuicBuffer object is what we use to handle that -// and track until it is acknowledged. To understand the QuicBuffer -// object itself, it is important to understand how ngtcp2 and nghttp3 -// handle data that is given to it to serialize into QUIC packets. -// -// An individual QUIC packet may contain multiple QUIC frames. Whenever -// we create a QUIC packet, we really have no idea what frames are going -// to be encoded or how much buffered handshake or stream data is going -// to be included within that QuicPacket. If there is buffered data -// available for a stream, we provide an array of pointers to that data -// and an indication about how much data is available, then we leave it -// entirely up to ngtcp2 and nghttp3 to determine how much of the data -// to encode into the QUIC packet. It is only *after* the QUIC packet -// is encoded that we can know how much was actually written. -// -// Once written to a QUIC Packet, we have to keep the data in memory -// until an acknowledgement is received. In QUIC, acknowledgements are -// received per range of packets. -// -// QuicBuffer is complicated because it needs to be able to accomplish -// three things: (a) buffering uv_buf_t instances passed down from -// JavaScript without memcpy and keeping track of the Write callback -// associated with each, (b) tracking what data has already been -// encoded in a QUIC packet and what data is remaining to be read, and -// (c) tracking which data has been acknowledged and which hasn't. -// QuicBuffer is further complicated by design quirks and limitations -// of the StreamBase API and how it interacts with the JavaScript side. -// -// QuicBuffer is a linked list of QuicBufferChunk instances. -// A single QuicBufferChunk wraps a single non-zero-length uv_buf_t. -// When the QuicBufferChunk is created, we capture the total length -// of the buffer and the total number of bytes remaining to be sent. -// Initially, these numbers are identical. -// -// When data is encoded into a QuicPacket, we advance the QuicBufferChunk's -// remaining-to-be-read by the number of bytes actually encoded. If there -// are no more bytes remaining to be encoded, we move to the next chunk -// in the linked list. -// -// When an acknowledgement is received, we decrement the QuicBufferChunk's -// length by the number of acknowledged bytes. Once the unacknowledged -// length reaches 0, we invoke the callback function associated with the -// QuicBufferChunk (if any). -// -// QuicStream is a StreamBase implementation. For every DoWrite call, -// it receives one or more uv_buf_t instances in a single batch associated -// with a single write callback. For each uv_buf_t DoWrite gets, a -// corresponding QuicBufferChunk is added to the QuicBuffer, with the -// callback associated with the final chunk added to the list. - - -// A QuicBufferChunk contains the actual buffered data -// along with a callback to be called when the data has -// been consumed. -// -// Any given chunk has a remaining-to-be-acknowledged length (length()) and a -// remaining-to-be-read-length (remaining()). The former tracks the number -// of bytes that have yet to be acknowledged by the QUIC peer. Once the -// remaining-to-be-acknowledged length reaches zero, the done callback -// associated with the QuicBufferChunk can be called and the QuicBufferChunk -// can be discarded. The remaining-to-be-read length is adjusted as data is -// serialized into QUIC packets and sent. -// The remaining-to-be-acknowledged length is adjusted using consume(), -// while the remaining-to-be-ead length is adjusted using seek(). -class QuicBufferChunk final : public MemoryRetainer { - public: - // Default non-op done handler. - static void default_done(int status) {} - - // In this variant, the QuicBufferChunk owns the underlying - // data storage within a vector. The data will be - // freed when the QuicBufferChunk is destroyed. - inline explicit QuicBufferChunk(size_t len); - - // In this variant, the QuicBufferChunk only maintains a - // pointer to the underlying data buffer. The QuicBufferChunk - // does not take ownership of the buffer. The done callback - // is invoked to let the caller know when the chunk is no - // longer being used. - inline QuicBufferChunk(uv_buf_t buf_, DoneCB done_); - - inline ~QuicBufferChunk() override; - - // Invokes the done callback associated with the QuicBufferChunk. - inline void Done(int status); - - // length() provides the remaining-to-be-acknowledged length. - // The QuicBufferChunk must be retained in memory while this - // count is greater than zero. The length is adjusted by - // calling Consume(); - inline size_t length() const { return length_; } - - // remaining() provides the remaining-to-be-read length number of bytes. - // The length is adjusted by calling Seek() - inline size_t remaining() const { return buf_.len; } - - // Consumes (acknowledges) the given number of bytes. If amount - // is greater than length(), only length() bytes are consumed. - // Returns the actual number of bytes consumed. - inline size_t Consume(size_t amount); - - // Seeks (reads) the given number of bytes. If amount is greater - // than remaining(), only remaining() bytes are read. Returns - // the actual number of bytes read. - inline size_t Seek(size_t amount); - - uint8_t* out() { return reinterpret_cast<uint8_t*>(buf_.base); } - uv_buf_t buf() { return buf_; } - const uv_buf_t buf() const { return buf_; } - - void MemoryInfo(MemoryTracker* tracker) const override; - SET_MEMORY_INFO_NAME(QuicBufferChunk) - SET_SELF_SIZE(QuicBufferChunk) - - private: - std::vector<uint8_t> data_buf_; - uv_buf_t buf_; - DoneCB done_ = default_done; - size_t length_ = 0; - bool done_called_ = false; - std::unique_ptr<QuicBufferChunk> next_; - - friend class QuicBuffer; -}; - -class QuicBuffer final : public bob::SourceImpl<ngtcp2_vec>, - public MemoryRetainer { - public: - QuicBuffer() = default; - - inline QuicBuffer(QuicBuffer&& src) noexcept; - inline QuicBuffer& operator=(QuicBuffer&& src) noexcept; - - ~QuicBuffer() override { - Cancel(); // Cancel the remaining data - CHECK_EQ(length_, 0); - } - - // Marks the QuicBuffer as having ended, preventing new QuicBufferChunk - // instances from being appended to the linked list and allowing the - // Pull operation to know when to signal that the flow of data is - // completed. - void End() { ended_ = true; } - bool is_ended() const { return ended_; } - - // Push one or more uv_buf_t instances into the buffer. - // the DoneCB callback will be invoked when the last - // uv_buf_t in the bufs array is consumed and popped out - // of the internal linked list. Ownership of the uv_buf_t - // remains with the caller. - size_t Push( - uv_buf_t* bufs, - size_t nbufs, - DoneCB done = QuicBufferChunk::default_done); - - // Pushes a single QuicBufferChunk into the linked list - void Push(std::unique_ptr<QuicBufferChunk> chunk); - - // Consume the given number of bytes within the buffer. If amount - // is greater than length(), length() bytes are consumed. Returns - // the actual number of bytes consumed. - inline size_t Consume(size_t amount); - - // Cancels the remaining bytes within the buffer. - inline size_t Cancel(int status = UV_ECANCELED); - - // Seeks (reads) the given number of bytes. If amount is greater - // than remaining(), seeks remaining() bytes. Returns the actual - // number of bytes read. - size_t Seek(size_t amount); - - // The total number of unacknowledged bytes remaining. - size_t length() const { return length_; } - - // The total number of unread bytes remaining. - size_t remaining() const { return remaining_; } - - void MemoryInfo(MemoryTracker* tracker) const override; - SET_MEMORY_INFO_NAME(QuicBuffer); - SET_SELF_SIZE(QuicBuffer); - - protected: - int DoPull( - bob::Next<ngtcp2_vec> next, - int options, - ngtcp2_vec* data, - size_t count, - size_t max_count_hint) override; - - private: - inline static bool is_empty(uv_buf_t buf); - size_t Consume(int status, size_t amount); - bool Pop(int status = 0); - inline void Push(uv_buf_t buf, DoneCB done = nullptr); - - std::unique_ptr<QuicBufferChunk> root_; - QuicBufferChunk* head_ = nullptr; // Current Read Position - QuicBufferChunk* tail_ = nullptr; // Current Write Position - - bool canceled_ = false; - bool ended_ = false; - size_t length_ = 0; - size_t remaining_ = 0; - - friend class QuicBufferChunk; -}; - -} // namespace quic -} // namespace node - -#endif // NODE_WANT_INTERNALS - -#endif // SRC_QUIC_NODE_QUIC_BUFFER_H_ diff --git a/src/quic/node_quic_crypto.cc b/src/quic/node_quic_crypto.cc deleted file mode 100644 index 74bb4f18783..00000000000 --- a/src/quic/node_quic_crypto.cc +++ /dev/null @@ -1,748 +0,0 @@ -#include "node_quic_crypto.h" -#include "env-inl.h" -#include "node_crypto.h" -#include "crypto/crypto_util.h" -#include "crypto/crypto_context.h" -#include "crypto/crypto_common.h" -#include "node_process.h" -#include "node_quic_session-inl.h" -#include "node_quic_util-inl.h" -#include "node_sockaddr-inl.h" -#include "node_url.h" -#include "string_bytes.h" -#include "v8.h" -#include "util-inl.h" - -#include <ngtcp2/ngtcp2.h> -#include <ngtcp2/ngtcp2_crypto.h> -#include <nghttp3/nghttp3.h> // NGHTTP3_ALPN_H3 -#include <openssl/bio.h> -#include <openssl/err.h> -#include <openssl/evp.h> -#include <openssl/kdf.h> -#include <openssl/ssl.h> -#include <openssl/x509v3.h> - -#include <iterator> -#include <numeric> -#include <unordered_map> -#include <string> -#include <sstream> -#include <vector> - -namespace node { - -using crypto::EntropySource; -using v8::Local; -using v8::Value; - -namespace quic { - -bool SessionTicketAppData::Set(const uint8_t* data, size_t len) { - if (set_) - return false; - set_ = true; - SSL_SESSION_set1_ticket_appdata(session_, data, len); - return set_; -} - -bool SessionTicketAppData::Get(uint8_t** data, size_t* len) const { - return SSL_SESSION_get0_ticket_appdata( - session_, - reinterpret_cast<void**>(data), - len) == 1; -} - -namespace { -constexpr int kCryptoTokenKeylen = 32; -constexpr int kCryptoTokenIvlen = 32; - -// Used solely to derive the keys used to generate and -// validate retry tokens. The implementation of this is -// Node.js specific. We use the current implementation -// because it is simple. -bool DeriveTokenKey( - uint8_t* token_key, - uint8_t* token_iv, - const uint8_t* rand_data, - size_t rand_datalen, - const ngtcp2_crypto_ctx& ctx, - const uint8_t* token_secret) { - static constexpr int kCryptoTokenSecretlen = 32; - uint8_t secret[kCryptoTokenSecretlen]; - - return - NGTCP2_OK(ngtcp2_crypto_hkdf_extract( - secret, - &ctx.md, - token_secret, - kTokenSecretLen, - rand_data, - rand_datalen)) && - NGTCP2_OK(ngtcp2_crypto_derive_packet_protection_key( - token_key, - token_iv, - nullptr, - &ctx.aead, - &ctx.md, - secret, - kCryptoTokenSecretlen)); -} - -// Retry tokens are generated only by QUIC servers. They -// are opaque to QUIC clients and must not be guessable by -// on- or off-path attackers. A QUIC server sends a RETRY -// token as a way of initiating explicit path validation -// with a client in response to an initial QUIC packet. -// The client, upon receiving a RETRY, must abandon the -// initial connection attempt and try again, including the -// received retry token in the new initial packet sent to -// the server. If the server is performing explicit -// valiation, it will look for the presence of the retry -// token and validate it if found. The internal structure -// of the retry token must be meaningful to the server, -// and the server must be able to validate the token without -// relying on any state left over from the previous connection -// attempt. The implementation here is entirely Node.js -// specific. -// -// The token is generated by: -// 1. Appending the raw bytes of given socket address, the current -// timestamp, and the original CID together into a single byte -// array. -// 2. Generating a block of random data that is used together with -// the token secret to cryptographically derive an encryption key. -// 3. Encrypting the byte array from step 1 using the encryption key -// from step 2. -// 4. Appending random data generated in step 2 to the token. -// -// The token secret must be kept secret on the QUIC server that -// generated the retry. When multiple QUIC servers are used in a -// cluster, it cannot be guaranteed that the same QUIC server -// instance will receive the subsequent new Initial packet. Therefore, -// all QUIC servers in the cluster should either share or be aware -// of the same token secret or a mechanism needs to be implemented -// to ensure that subsequent packets are routed to the same QUIC -// server instance. -// -// A malicious peer could attempt to guess the token secret by -// sending a large number specially crafted RETRY-eliciting packets -// to a server then analyzing the resulting retry tokens. To reduce -// the possibility of such attacks, the current implementation of -// QuicSocket generates the token secret randomly for each instance, -// and the number of RETRY responses sent to a given remote address -// should be limited. Such attacks should be of little actual value -// in most cases. -bool GenerateRetryToken( - uint8_t* token, - size_t* tokenlen, - const SocketAddress& addr, - const QuicCID& ocid, - const uint8_t* token_secret) { - std::array<uint8_t, 4096> plaintext; - uint8_t rand_data[kTokenRandLen]; - uint8_t token_key[kCryptoTokenKeylen]; - uint8_t token_iv[kCryptoTokenIvlen]; - - ngtcp2_crypto_ctx ctx; - ngtcp2_crypto_ctx_initial(&ctx); - size_t ivlen = ngtcp2_crypto_packet_protection_ivlen(&ctx.aead); - uint64_t now = uv_hrtime(); - - auto p = std::begin(plaintext); - p = std::copy_n(addr.raw(), addr.length(), p); - p = std::copy_n(reinterpret_cast<uint8_t*>(&now), sizeof(uint64_t), p); - p = std::copy_n(ocid->data, ocid->datalen, p); - - EntropySource(rand_data, kTokenRandLen); - - if (!DeriveTokenKey( - token_key, - token_iv, - rand_data, - kTokenRandLen, - ctx, - token_secret)) { - return false; - } - - ngtcp2_crypto_aead_ctx aead_ctx; - if (NGTCP2_ERR(ngtcp2_crypto_aead_ctx_encrypt_init( - &aead_ctx, - &ctx.aead, - token_key, - ivlen))) { - return false; - } - - size_t plaintextlen = std::distance(std::begin(plaintext), p); - if (NGTCP2_ERR(ngtcp2_crypto_encrypt( - token, - &ctx.aead, - &aead_ctx, - plaintext.data(), - plaintextlen, - token_iv, - ivlen, - addr.raw(), - addr.length()))) { - return false; - } - - *tokenlen = plaintextlen + ngtcp2_crypto_aead_taglen(&ctx.aead); - memcpy(token + (*tokenlen), rand_data, kTokenRandLen); - *tokenlen += kTokenRandLen; - return true; -} -} // namespace - -// A stateless reset token is used when a QUIC endpoint receives a -// QUIC packet with a short header but the associated connection ID -// cannot be matched to any known QuicSession. In such cases, the -// receiver may choose to send a subtle opaque indication to the -// sending peer that state for the QuicSession has apparently been -// lost. For any on- or off- path attacker, a stateless reset packet -// resembles any other QUIC packet with a short header. In order to -// be successfully handled as a stateless reset, the peer must have -// already seen a reset token issued to it associated with the given -// CID. The token itself is opaque to the peer that receives is but -// must be possible to statelessly recreate by the peer that -// originally created it. The actual implementation is Node.js -// specific but we currently defer to a utility function provided -// by ngtcp2. -bool GenerateResetToken( - uint8_t* token, - const uint8_t* secret, - const QuicCID& cid) { - ngtcp2_crypto_ctx ctx; - ngtcp2_crypto_ctx_initial(&ctx); - return NGTCP2_OK(ngtcp2_crypto_generate_stateless_reset_token( - token, - &ctx.md, - secret, - NGTCP2_STATELESS_RESET_TOKENLEN, - cid.cid())); -} - -// Generates a RETRY packet. See the notes for GenerateRetryToken for details. -std::unique_ptr<QuicPacket> GenerateRetryPacket( - const uint8_t* token_secret, - const QuicCID& dcid, - const QuicCID& scid, - const SocketAddress& local_addr, - const SocketAddress& remote_addr) { - - uint8_t token[256]; - size_t tokenlen = sizeof(token); - - if (!GenerateRetryToken(token, &tokenlen, remote_addr, dcid, token_secret)) - return {}; - - QuicCID cid; - EntropySource(cid.data(), kScidLen); - cid.set_length(kScidLen); - - size_t pktlen = tokenlen + (2 * NGTCP2_MAX_CIDLEN) + scid.length() + 8; - - auto packet = QuicPacket::Create("retry", pktlen); - ssize_t nwrite = - ngtcp2_crypto_write_retry( - packet->data(), - NGTCP2_MAX_PKTLEN_IPV4, - scid.cid(), - cid.cid(), - dcid.cid(), - token, - tokenlen); - if (nwrite <= 0) - return {}; - packet->set_length(nwrite); - return packet; -} - -// Validates a retry token included in the given header. This will return -// true if the token cannot be validated, false otherwise. A token is -// valid if it can be successfully decrypted using the key derived from -// random data embedded in the token, the structure of the token matches -// that generated by the GenerateRetryToken function, and the token was -// not generated earlier than now - verification_expiration. If validation -// is successful, ocid will be updated to the original connection ID encoded -// in the encrypted token. -bool InvalidRetryToken( - const ngtcp2_vec& token, - const SocketAddress& addr, - QuicCID* ocid, - const uint8_t* token_secret, - uint64_t verification_expiration) { - - if (token.len < kTokenRandLen) - return true; - - ngtcp2_crypto_ctx ctx; - ngtcp2_crypto_ctx_initial(&ctx); - - size_t ivlen = ngtcp2_crypto_packet_protection_ivlen(&ctx.aead); - - size_t ciphertextlen = token.len - kTokenRandLen; - const uint8_t* ciphertext = token.base; - const uint8_t* rand_data = token.base + ciphertextlen; - - uint8_t token_key[kCryptoTokenKeylen]; - uint8_t token_iv[kCryptoTokenIvlen]; - - if (!DeriveTokenKey( - token_key, - token_iv, - rand_data, - kTokenRandLen, - ctx, - token_secret)) { - return true; - } - - uint8_t plaintext[4096]; - - ngtcp2_crypto_aead_ctx aead_ctx; - if (NGTCP2_ERR(ngtcp2_crypto_aead_ctx_decrypt_init( - &aead_ctx, - &ctx.aead, - token_key, - ivlen))) { - return true; - } - - if (NGTCP2_ERR(ngtcp2_crypto_decrypt( - plaintext, - &ctx.aead, - &aead_ctx, - ciphertext, - ciphertextlen, - token_iv, - ivlen, - addr.raw(), - addr.length()))) { - return true; - } - - size_t plaintextlen = ciphertextlen - ngtcp2_crypto_aead_taglen(&ctx.aead); - if (plaintextlen < addr.length() + sizeof(uint64_t)) - return true; - - ssize_t cil = plaintextlen - addr.length() - sizeof(uint64_t); - if ((cil != 0 && (cil < NGTCP2_MIN_CIDLEN || cil > NGTCP2_MAX_CIDLEN)) || - memcmp(plaintext, addr.raw(), addr.length()) != 0) { - return true; - } - - uint64_t t; - memcpy(&t, plaintext + addr.length(), sizeof(uint64_t)); - - // 10-second window by default, but configurable for each - // QuicSocket instance with a MIN_RETRYTOKEN_EXPIRATION second - // minimum and a MAX_RETRYTOKEN_EXPIRATION second maximum. - if (t + verification_expiration * NGTCP2_SECONDS < uv_hrtime()) - return true; - - ngtcp2_cid_init( - ocid->cid(), - plaintext + addr.length() + sizeof(uint64_t), - cil); - - return false; -} - -// Get the ALPN protocol identifier that was negotiated for the session -Local<Value> GetALPNProtocol(const QuicSession& session) { - QuicCryptoContext* ctx = session.crypto_context(); - Environment* env = session.env(); - std::string alpn = ctx->selected_alpn(); - // This supposed to be `NGHTTP3_ALPN_H3 + 1` - // Details see https://github.com/nodejs/node/issues/33959 - if (alpn == &NGHTTP3_ALPN_H3[1]) { - return env->http3_alpn_string(); - } else { - return ToV8Value( - env->context(), - alpn, - env->isolate()).FromMaybe(Local<Value>()); - } -} - -namespace { -int CertCB(SSL* ssl, void* arg) { - QuicSession* session = static_cast<QuicSession*>(arg); - int ret; - switch (SSL_get_tlsext_status_type(ssl)) { - case TLSEXT_STATUSTYPE_ocsp: - ret = session->crypto_context()->OnOCSP(); - return UNLIKELY(session->is_destroyed()) ? 0 : ret; - default: - return 1; - } -} - -void Keylog_CB(const SSL* ssl, const char* line) { - QuicSession* session = static_cast<QuicSession*>(SSL_get_app_data(ssl)); - session->crypto_context()->Keylog(line); -} - -int Client_Hello_CB( - SSL* ssl, - int* tls_alert, - void* arg) { - QuicSession* session = static_cast<QuicSession*>(SSL_get_app_data(ssl)); - int ret = session->crypto_context()->OnClientHello(); - if (UNLIKELY(session->is_destroyed())) { - *tls_alert = SSL_R_SSL_HANDSHAKE_FAILURE; - return 0; - } - switch (ret) { - case 0: - return 1; - case -1: - return -1; - default: - *tls_alert = ret; - return 0; - } -} - -int AlpnSelection( - SSL* ssl, - const unsigned char** out, - unsigned char* outlen, - const unsigned char* in, - unsigned int inlen, - void* arg) { - QuicSession* session = static_cast<QuicSession*>(SSL_get_app_data(ssl)); - - unsigned char* tmp; - - // The QuicServerSession supports exactly one ALPN identifier. If that does - // not match any of the ALPN identifiers provided in the client request, - // then we fail here. Note that this will not fail the TLS handshake, so - // we have to check later if the ALPN matches the expected identifier or not. - if (SSL_select_next_proto( - &tmp, - outlen, - reinterpret_cast<const unsigned char*>(session->alpn().c_str()), - session->alpn().length(), - in, - inlen) == OPENSSL_NPN_NO_OVERLAP) { - return SSL_TLSEXT_ERR_NOACK; - } - *out = tmp; - return SSL_TLSEXT_ERR_OK; -} - -int AllowEarlyDataCB(SSL* ssl, void* arg) { - QuicSession* session = static_cast<QuicSession*>(SSL_get_app_data(ssl)); - return session->allow_early_data() ? 1 : 0; -} - -int TLS_Status_Callback(SSL* ssl, void* arg) { - QuicSession* session = static_cast<QuicSession*>(SSL_get_app_data(ssl)); - return session->crypto_context()->OnTLSStatus(); -} - -int New_Session_Callback(SSL* ssl, SSL_SESSION* session) { - QuicSession* s = static_cast<QuicSession*>(SSL_get_app_data(ssl)); - return s->set_session(session); -} - -int GenerateSessionTicket(SSL* ssl, void* arg) { - QuicSession* s = static_cast<QuicSession*>(SSL_get_app_data(ssl)); - SessionTicketAppData app_data(SSL_get_session(ssl)); - s->SetSessionTicketAppData(app_data); - return 1; -} - -SSL_TICKET_RETURN DecryptSessionTicket( - SSL* ssl, - SSL_SESSION* session, - const unsigned char* keyname, - size_t keyname_len, - SSL_TICKET_STATUS status, - void* arg) { - QuicSession* s = static_cast<QuicSession*>(SSL_get_app_data(ssl)); - SessionTicketAppData::Flag flag = SessionTicketAppData::Flag::STATUS_NONE; - switch (status) { - default: - return SSL_TICKET_RETURN_IGNORE; - case SSL_TICKET_EMPTY: - // Fall through - case SSL_TICKET_NO_DECRYPT: - return SSL_TICKET_RETURN_IGNORE_RENEW; - case SSL_TICKET_SUCCESS_RENEW: - flag = SessionTicketAppData::Flag::STATUS_RENEW; - // Fall through - case SSL_TICKET_SUCCESS: - SessionTicketAppData app_data(session); - switch (s->GetSessionTicketAppData(app_data, flag)) { - default: - return SSL_TICKET_RETURN_IGNORE; - case SessionTicketAppData::Status::TICKET_IGNORE: - return SSL_TICKET_RETURN_IGNORE; - case SessionTicketAppData::Status::TICKET_IGNORE_RENEW: - return SSL_TICKET_RETURN_IGNORE_RENEW; - case SessionTicketAppData::Status::TICKET_USE: - return SSL_TICKET_RETURN_USE; - case SessionTicketAppData::Status::TICKET_USE_RENEW: - return SSL_TICKET_RETURN_USE_RENEW; - } - } -} - -int SetEncryptionSecrets( - SSL* ssl, - OSSL_ENCRYPTION_LEVEL ossl_level, - const uint8_t* read_secret, - const uint8_t* write_secret, - size_t secret_len) { - QuicSession* session = static_cast<QuicSession*>(SSL_get_app_data(ssl)); - return session->crypto_context()->OnSecrets( - from_ossl_level(ossl_level), - read_secret, - write_secret, - secret_len) ? 1 : 0; -} - -int AddHandshakeData( - SSL* ssl, - OSSL_ENCRYPTION_LEVEL ossl_level, - const uint8_t* data, - size_t len) { - QuicSession* session = static_cast<QuicSession*>(SSL_get_app_data(ssl)); - session->crypto_context()->WriteHandshake( - from_ossl_level(ossl_level), - data, - len); - return 1; -} - -int FlushFlight(SSL* ssl) { return 1; } - -int SendAlert( - SSL* ssl, - enum ssl_encryption_level_t level, - uint8_t alert) { - QuicSession* session = static_cast<QuicSession*>(SSL_get_app_data(ssl)); - session->crypto_context()->set_tls_alert(alert); - return 1; -} - -bool SetTransportParams(QuicSession* session, const crypto::SSLPointer& ssl) { - ngtcp2_transport_params params; - ngtcp2_conn_get_local_transport_params(session->connection(), ¶ms); - uint8_t buf[512]; - ssize_t nwrite = ngtcp2_encode_transport_params( - buf, - arraysize(buf), - NGTCP2_TRANSPORT_PARAMS_TYPE_ENCRYPTED_EXTENSIONS, - ¶ms); - return nwrite >= 0 && - SSL_set_quic_transport_params(ssl.get(), buf, nwrite) == 1; -} - -SSL_QUIC_METHOD quic_method = SSL_QUIC_METHOD{ - SetEncryptionSecrets, - AddHandshakeData, - FlushFlight, - SendAlert -}; - -void SetHostname(const crypto::SSLPointer& ssl, const std::string& hostname) { - // If the hostname is an IP address, use an empty string - // as the hostname instead. - X509_VERIFY_PARAM* param = SSL_get0_param(ssl.get()); - X509_VERIFY_PARAM_set_hostflags(param, 0); - - if (UNLIKELY(SocketAddress::is_numeric_host(hostname.c_str()))) { - SSL_set_tlsext_host_name(ssl.get(), ""); - CHECK_EQ(X509_VERIFY_PARAM_set1_host(param, "", 0), 1); - } else { - SSL_set_tlsext_host_name(ssl.get(), hostname.c_str()); - CHECK_EQ( - X509_VERIFY_PARAM_set1_host(param, hostname.c_str(), hostname.length()), - 1); - } -} - -} // namespace - -void InitializeTLS(QuicSession* session, const crypto::SSLPointer& ssl) { - QuicCryptoContext* ctx = session->crypto_context(); - Environment* env = session->env(); - QuicState* quic_state = session->quic_state(); - - SSL_set_app_data(ssl.get(), session); - SSL_set_cert_cb(ssl.get(), CertCB, - const_cast<void*>(reinterpret_cast<const void*>(session))); - SSL_set_verify(ssl.get(), SSL_VERIFY_NONE, crypto::VerifyCallback); - - // Enable tracing if the `--trace-tls` command line flag is used. - if (env->options()->trace_tls) { - ctx->EnableTrace(); - if (quic_state->warn_trace_tls) { - quic_state->warn_trace_tls = false; - ProcessEmitWarning(env, - "Enabling --trace-tls can expose sensitive data " - "in the resulting log"); - } - } - - switch (ctx->side()) { - case NGTCP2_CRYPTO_SIDE_CLIENT: { - SSL_set_connect_state(ssl.get()); - crypto::SetALPN(ssl, session->alpn()); - SetHostname(ssl, session->hostname()); - if (ctx->is_option_set(QUICCLIENTSESSION_OPTION_REQUEST_OCSP)) - SSL_set_tlsext_status_type(ssl.get(), TLSEXT_STATUSTYPE_ocsp); - break; - } - case NGTCP2_CRYPTO_SIDE_SERVER: { - SSL_set_accept_state(ssl.get()); - if (ctx->is_option_set(QUICSERVERSESSION_OPTION_REQUEST_CERT)) { - int verify_mode = SSL_VERIFY_PEER; - if (ctx->is_option_set(QUICSERVERSESSION_OPTION_REJECT_UNAUTHORIZED)) - verify_mode |= SSL_VERIFY_FAIL_IF_NO_PEER_CERT; - SSL_set_verify(ssl.get(), verify_mode, crypto::VerifyCallback); - } - break; - } - default: - UNREACHABLE(); - } - - ngtcp2_conn_set_tls_native_handle(session->connection(), ssl.get()); - SetTransportParams(session, ssl); -} - -void InitializeSecureContext( - BaseObjectPtr<crypto::SecureContext> sc, - bool early_data, - ngtcp2_crypto_side side) { - constexpr static unsigned char session_id_ctx[] = "node.js quic server"; - switch (side) { - case NGTCP2_CRYPTO_SIDE_SERVER: - SSL_CTX_set_options( - **sc, - (SSL_OP_ALL & ~SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS) | - SSL_OP_SINGLE_ECDH_USE | - SSL_OP_CIPHER_SERVER_PREFERENCE | - SSL_OP_NO_ANTI_REPLAY); - - SSL_CTX_set_mode(**sc, SSL_MODE_RELEASE_BUFFERS); - - SSL_CTX_set_alpn_select_cb(**sc, AlpnSelection, nullptr); - SSL_CTX_set_client_hello_cb(**sc, Client_Hello_CB, nullptr); - - SSL_CTX_set_session_ticket_cb( - **sc, - GenerateSessionTicket, - DecryptSessionTicket, - nullptr); - - if (early_data) { - SSL_CTX_set_max_early_data(**sc, 0xffffffff); - SSL_CTX_set_allow_early_data_cb(**sc, AllowEarlyDataCB, nullptr); - } - - SSL_CTX_set_session_id_context( - **sc, - session_id_ctx, - sizeof(session_id_ctx) - 1); - break; - case NGTCP2_CRYPTO_SIDE_CLIENT: - SSL_CTX_set_session_cache_mode( - **sc, - SSL_SESS_CACHE_CLIENT | - SSL_SESS_CACHE_NO_INTERNAL_STORE); - SSL_CTX_sess_set_new_cb(**sc, New_Session_Callback); - break; - default: - UNREACHABLE(); - } - SSL_CTX_set_min_proto_version(**sc, TLS1_3_VERSION); - SSL_CTX_set_max_proto_version(**sc, TLS1_3_VERSION); - SSL_CTX_set_default_verify_paths(**sc); - SSL_CTX_set_tlsext_status_cb(**sc, TLS_Status_Callback); - SSL_CTX_set_keylog_callback(**sc, Keylog_CB); - SSL_CTX_set_tlsext_status_arg(**sc, nullptr); - SSL_CTX_set_quic_method(**sc, &quic_method); -} - -ngtcp2_crypto_level from_ossl_level(OSSL_ENCRYPTION_LEVEL ossl_level) { - switch (ossl_level) { - case ssl_encryption_initial: - return NGTCP2_CRYPTO_LEVEL_INITIAL; - case ssl_encryption_early_data: - return NGTCP2_CRYPTO_LEVEL_EARLY; - case ssl_encryption_handshake: - return NGTCP2_CRYPTO_LEVEL_HANDSHAKE; - case ssl_encryption_application: - return NGTCP2_CRYPTO_LEVEL_APP; - default: - UNREACHABLE(); - } -} - -const char* crypto_level_name(ngtcp2_crypto_level level) { - switch (level) { - case NGTCP2_CRYPTO_LEVEL_INITIAL: - return "initial"; - case NGTCP2_CRYPTO_LEVEL_EARLY: - return "early"; - case NGTCP2_CRYPTO_LEVEL_HANDSHAKE: - return "handshake"; - case NGTCP2_CRYPTO_LEVEL_APP: - return "app"; - default: - UNREACHABLE(); - } -} - -// When using IPv6, QUIC recommends the use of IPv6 Flow Labels -// as specified in https://tools.ietf.org/html/rfc6437. These -// are used as a means of reliably associating packets exchanged -// as part of a single flow and protecting against certain kinds -// of attacks. -uint32_t GenerateFlowLabel( - const SocketAddress& local, - const SocketAddress& remote, - const QuicCID& cid, - const uint8_t* secret, - size_t secretlen) { - static constexpr size_t kInfoLen = - (sizeof(sockaddr_in6) * 2) + NGTCP2_MAX_CIDLEN; - - uint32_t label = 0; - - std::array<uint8_t, kInfoLen> plaintext; - size_t infolen = local.length() + remote.length() + cid.length(); - CHECK_LE(infolen, kInfoLen); - - ngtcp2_crypto_ctx ctx; - ngtcp2_crypto_ctx_initial(&ctx); - - auto p = std::begin(plaintext); - p = std::copy_n(local.raw(), local.length(), p); - p = std::copy_n(remote.raw(), remote.length(), p); - p = std::copy_n(cid->data, cid->datalen, p); - - ngtcp2_crypto_hkdf_expand( - reinterpret_cast<uint8_t*>(&label), - sizeof(label), - &ctx.md, - secret, - secretlen, - plaintext.data(), - infolen); - - label &= kLabelMask; - DCHECK_LE(label, kLabelMask); - return label; -} - -} // namespace quic -} // namespace node diff --git a/src/quic/node_quic_crypto.h b/src/quic/node_quic_crypto.h deleted file mode 100644 index e37c5e059a5..00000000000 --- a/src/quic/node_quic_crypto.h +++ /dev/null @@ -1,144 +0,0 @@ -#ifndef SRC_QUIC_NODE_QUIC_CRYPTO_H_ -#define SRC_QUIC_NODE_QUIC_CRYPTO_H_ - -#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS - -#include "node_crypto.h" -#include "node_quic_util.h" -#include "v8.h" - -#include <ngtcp2/ngtcp2.h> -#include <ngtcp2/ngtcp2_crypto.h> -#include <openssl/ssl.h> - -namespace node { - -namespace quic { - -// Crypto and OpenSSL related utility functions used in -// various places throughout the QUIC implementation. - -// Forward declaration -class QuicSession; -class QuicPacket; - -// many ngtcp2 functions return 0 to indicate success -// and non-zero to indicate failure. Most of the time, -// for such functions we don't care about the specific -// return value so we simplify using a macro. - -#define NGTCP2_ERR(V) (V != 0) -#define NGTCP2_OK(V) (V == 0) - -// Called by QuicInitSecureContext to initialize the -// given SecureContext with the defaults for the given -// QUIC side (client or server). -void InitializeSecureContext( - BaseObjectPtr<crypto::SecureContext> sc, - bool early_data, - ngtcp2_crypto_side side); - -// Called in the QuicSession::InitServer and -// QuicSession::InitClient to configure the -// appropriate settings for the SSL* associated -// with the session. -void InitializeTLS(QuicSession* session, const crypto::SSLPointer& ssl); - -// Generates a stateless reset token using HKDF with the -// cid and token secret as input. The token secret is -// either provided by user code when a QuicSocket is -// created or is generated randomly. -// -// QUIC leaves the generation of stateless session tokens -// up to the implementation to figure out. The idea, however, -// is that it ought to be possible to generate a stateless -// reset token reliably even when all state for a connection -// has been lost. We use the cid as it's the only reliably -// consistent bit of data we have when a session is destroyed. -bool GenerateResetToken( - uint8_t* token, - const uint8_t* secret, - const QuicCID& cid); - -// The Retry Token is an encrypted token that is sent to the client -// by the server as part of the path validation flow. The plaintext -// format within the token is opaque and only meaningful the server. -// We can structure it any way we want. It needs to: -// * be hard to guess -// * be time limited -// * be specific to the client address -// * be specific to the original cid -// * contain random data. -std::unique_ptr<QuicPacket> GenerateRetryPacket( - const uint8_t* token_secret, - const QuicCID& dcid, - const QuicCID& scid, - const SocketAddress& local_addr, - const SocketAddress& remote_addr); - -// The IPv6 Flow Label is generated and set whenever IPv6 is used. -// The label is derived as a cryptographic function of the CID, -// local and remote addresses, and the given secret, that is then -// truncated to a 20-bit value (per IPv6 requirements). In QUIC, -// the flow label *may* be used as a way of disambiguating IP -// packets that belong to the same flow from a remote peer. -uint32_t GenerateFlowLabel( - const SocketAddress& local, - const SocketAddress& remote, - const QuicCID& cid, - const uint8_t* secret, - size_t secretlen); - -// Verifies the validity of a retry token. Returns true if the -// token is *not valid*, false otherwise. If the token is valid, -// the ocid will be updated to the original CID value encoded -// within the successfully validated, encrypted token. -bool InvalidRetryToken( - const ngtcp2_vec& token, - const SocketAddress& addr, - QuicCID* ocid, - const uint8_t* token_secret, - uint64_t verification_expiration); - -// Get the ALPN protocol identifier that was negotiated for the session -v8::Local<v8::Value> GetALPNProtocol(const QuicSession& session); - -ngtcp2_crypto_level from_ossl_level(OSSL_ENCRYPTION_LEVEL ossl_level); -const char* crypto_level_name(ngtcp2_crypto_level level); - -// SessionTicketAppData is a utility class that is used only during -// the generation or access of TLS stateless sesson tickets. It -// exists solely to provide a easier way for QuicApplication instances -// to set relevant metadata in the session ticket when it is created, -// and the exract and subsequently verify that data when a ticket is -// received and is being validated. The app data is completely opaque -// to anything other than the server-side of the QuicApplication that -// sets it. -class SessionTicketAppData { - public: - enum class Status { - TICKET_USE, - TICKET_USE_RENEW, - TICKET_IGNORE, - TICKET_IGNORE_RENEW - }; - - enum class Flag { - STATUS_NONE, - STATUS_RENEW - }; - - explicit SessionTicketAppData(SSL_SESSION* session) : session_(session) {} - bool Set(const uint8_t* data, size_t len); - bool Get(uint8_t** data, size_t* len) const; - - private: - bool set_ = false; - SSL_SESSION* session_; -}; - -} // namespace quic -} // namespace node - -#endif // NODE_WANT_INTERNALS -#endif // SRC_QUIC_NODE_QUIC_CRYPTO_H_ diff --git a/src/quic/node_quic_default_application.cc b/src/quic/node_quic_default_application.cc deleted file mode 100644 index 5005d3d6919..00000000000 --- a/src/quic/node_quic_default_application.cc +++ /dev/null @@ -1,177 +0,0 @@ -#include "debug_utils-inl.h" -#include "node_quic_buffer-inl.h" -#include "node_quic_default_application.h" -#include "node_quic_session-inl.h" -#include "node_quic_socket-inl.h" -#include "node_quic_stream-inl.h" -#include "node_quic_util-inl.h" -#include "node_sockaddr-inl.h" -#include <ngtcp2/ngtcp2.h> - -#include <vector> - -namespace node { -namespace quic { - -namespace { -void Consume(ngtcp2_vec** pvec, size_t* pcnt, size_t len) { - ngtcp2_vec* v = *pvec; - size_t cnt = *pcnt; - - for (; cnt > 0; --cnt, ++v) { - if (v->len > len) { - v->len -= len; - v->base += len; - break; - } - len -= v->len; - } - - *pvec = v; - *pcnt = cnt; -} - -int IsEmpty(const ngtcp2_vec* vec, size_t cnt) { - size_t i; - for (i = 0; i < cnt && vec[i].len == 0; ++i) {} - return i == cnt; -} -} // anonymous namespace - -DefaultApplication::DefaultApplication( - QuicSession* session) - : QuicApplication(session) {} - -bool DefaultApplication::Initialize() { - if (needs_init()) { - Debug(session(), "Default QUIC Application Initialized"); - set_init_done(); - } - return true; -} - -void DefaultApplication::ScheduleStream(int64_t stream_id) { - BaseObjectPtr<QuicStream> stream = session()->FindStream(stream_id); - if (LIKELY(stream && !stream->is_destroyed())) { - Debug(session(), "Scheduling stream %" PRIu64, stream_id); - stream->Schedule(&stream_queue_); - } -} - -void DefaultApplication::UnscheduleStream(int64_t stream_id) { - BaseObjectPtr<QuicStream> stream = session()->FindStream(stream_id); - if (LIKELY(stream)) { - Debug(session(), "Unscheduling stream %" PRIu64, stream_id); - stream->Unschedule(); - } -} - -void DefaultApplication::ResumeStream(int64_t stream_id) { - ScheduleStream(stream_id); -} - -bool DefaultApplication::ReceiveStreamData( - uint32_t flags, - int64_t stream_id, - const uint8_t* data, - size_t datalen, - uint64_t offset) { - // Ensure that the QuicStream exists before deferring to - // QuicApplication specific processing logic. - Debug(session(), "Default QUIC Application receiving stream data"); - BaseObjectPtr<QuicStream> stream = session()->FindStream(stream_id); - if (!stream) { - // Shutdown the stream explicitly if the session is being closed. - if (session()->is_graceful_closing()) { - session()->ShutdownStream(stream_id, NGTCP2_ERR_CLOSING); - return true; - } - - // One potential DOS attack vector is to send a bunch of - // empty stream frames to commit resources. Check that - // here. Essentially, we only want to create a new stream - // if the datalen is greater than 0, otherwise, we ignore - // the packet. ngtcp2 should be handling this for us, - // but we handle it just to be safe. - if (UNLIKELY(datalen == 0)) - return true; - - stream = session()->CreateStream(stream_id); - } - CHECK(stream); - - // If the stream ended up being destroyed immediately after - // creation, just skip the data processing and return. - if (UNLIKELY(stream->is_destroyed())) - return true; - - stream->ReceiveData(flags, data, datalen, offset); - return true; -} - -int DefaultApplication::GetStreamData(StreamData* stream_data) { - QuicStream* stream = stream_queue_.PopFront(); - // If stream is nullptr, there are no streams with data pending. - if (stream == nullptr) - return 0; - - stream_data->stream.reset(stream); - stream_data->id = stream->id(); - - auto next = [&]( - int status, - const ngtcp2_vec* data, - size_t count, - bob::Done done) { - switch (status) { - case bob::Status::STATUS_BLOCK: - // Fall through - case bob::Status::STATUS_WAIT: - // Fall through - case bob::Status::STATUS_EOS: - return; - case bob::Status::STATUS_END: - stream_data->fin = 1; - } - - stream_data->count = count; - - if (count > 0) { - stream->Schedule(&stream_queue_); - stream_data->remaining = get_length(data, count); - } else { - stream_data->remaining = 0; - } - }; - - if (LIKELY(!stream->is_eos())) { - CHECK_GE(stream->Pull( - std::move(next), - bob::Options::OPTIONS_SYNC, - stream_data->data, - arraysize(stream_data->data), - kMaxVectorCount), 0); - } - - return 0; -} - -bool DefaultApplication::StreamCommit( - StreamData* stream_data, - size_t datalen) { - CHECK(stream_data->stream); - stream_data->remaining -= datalen; - Consume(&stream_data->buf, &stream_data->count, datalen); - stream_data->stream->Commit(datalen); - return true; -} - -bool DefaultApplication::ShouldSetFin(const StreamData& stream_data) { - if (!stream_data.stream || - !IsEmpty(stream_data.buf, stream_data.count)) - return false; - return !stream_data.stream->is_writable(); -} - -} // namespace quic -} // namespace node diff --git a/src/quic/node_quic_default_application.h b/src/quic/node_quic_default_application.h deleted file mode 100644 index 52c9044c0e3..00000000000 --- a/src/quic/node_quic_default_application.h +++ /dev/null @@ -1,60 +0,0 @@ -#ifndef SRC_QUIC_NODE_QUIC_DEFAULT_APPLICATION_H_ -#define SRC_QUIC_NODE_QUIC_DEFAULT_APPLICATION_H_ - -#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS - -#include "node_quic_stream.h" -#include "node_quic_session.h" -#include "node_quic_util.h" -#include "util.h" -#include "v8.h" - -namespace node { - -namespace quic { - -// The DefaultApplication is used whenever an unknown/unrecognized -// alpn identifier is used. It switches the QUIC implementation into -// a minimal/generic mode that defers all application level processing -// to the user-code level. Headers are not supported by QuicStream -// instances created under the default application. -class DefaultApplication final : public QuicApplication { - public: - explicit DefaultApplication(QuicSession* session); - - bool Initialize() override; - - void StopTrackingMemory(void* ptr) override { - // Do nothing. Not used. - } - - bool ReceiveStreamData( - uint32_t flags, - int64_t stream_id, - const uint8_t* data, - size_t datalen, - uint64_t offset) override; - - int GetStreamData(StreamData* stream_data) override; - - void ResumeStream(int64_t stream_id) override; - bool ShouldSetFin(const StreamData& stream_data) override; - bool StreamCommit(StreamData* stream_data, size_t datalen) override; - - SET_SELF_SIZE(DefaultApplication) - SET_MEMORY_INFO_NAME(DefaultApplication) - SET_NO_MEMORY_INFO() - - private: - void ScheduleStream(int64_t stream_id); - void UnscheduleStream(int64_t stream_id); - - QuicStream::Queue stream_queue_; -}; - -} // namespace quic - -} // namespace node - -#endif // NODE_WANT_INTERNALS -#endif // SRC_QUIC_NODE_QUIC_DEFAULT_APPLICATION_H_ diff --git a/src/quic/node_quic_http3_application.cc b/src/quic/node_quic_http3_application.cc deleted file mode 100644 index 82cd677d1ce..00000000000 --- a/src/quic/node_quic_http3_application.cc +++ /dev/null @@ -1,941 +0,0 @@ -#include "node.h" -#include "debug_utils-inl.h" -#include "node_mem-inl.h" -#include "node_quic_buffer-inl.h" -#include "node_quic_http3_application.h" -#include "node_quic_session-inl.h" -#include "node_quic_socket-inl.h" -#include "node_quic_stream-inl.h" -#include "node_quic_util-inl.h" -#include "node_sockaddr-inl.h" -#include "node_http_common-inl.h" - -#include <ngtcp2/ngtcp2.h> -#include <nghttp3/nghttp3.h> -#include <algorithm> - -namespace node { - -using v8::Array; -using v8::Local; - -namespace quic { - -// nghttp3 uses a numeric identifier for a large number -// of known HTTP header names. These allow us to use -// static strings for those rather than allocating new -// strings all of the time. The list of strings supported -// is included in node_http_common.h -#define V1(name, value) case NGHTTP3_QPACK_TOKEN__##name: return value; -#define V2(name, value) case NGHTTP3_QPACK_TOKEN_##name: return value; -const char* Http3HeaderTraits::ToHttpHeaderName(int32_t token) { - switch (token) { - default: - // Fall through - case -1: return nullptr; - HTTP_SPECIAL_HEADERS(V1) - HTTP_REGULAR_HEADERS(V2) - } -} -#undef V1 -#undef V2 - -template <typename M, typename T> -void Http3Application::SetConfig( - int idx, - M T::*member) { - AliasedFloat64Array& buffer = session()->quic_state()->http3config_buffer; - uint64_t flags = static_cast<uint64_t>(buffer[IDX_HTTP3_CONFIG_COUNT]); - if (flags & (1ULL << idx)) - config_.*member = static_cast<uint64_t>(buffer[idx]); -} - -Http3Application::Http3Application( - QuicSession* session) - : QuicApplication(session), - alloc_info_(MakeAllocator()) { - // Collect Configuration Details. - SetConfig<size_t>(IDX_HTTP3_QPACK_MAX_TABLE_CAPACITY, - &Http3ApplicationConfig::qpack_max_table_capacity); - SetConfig<size_t>(IDX_HTTP3_QPACK_BLOCKED_STREAMS, - &Http3ApplicationConfig::qpack_blocked_streams); - SetConfig(IDX_HTTP3_MAX_HEADER_LIST_SIZE, - &Http3ApplicationConfig::max_field_section_size); - SetConfig(IDX_HTTP3_MAX_PUSHES, - &Http3ApplicationConfig::max_pushes); - SetConfig(IDX_HTTP3_MAX_HEADER_PAIRS, - &Http3ApplicationConfig::max_header_pairs); - SetConfig(IDX_HTTP3_MAX_HEADER_LENGTH, - &Http3ApplicationConfig::max_header_length); - set_max_header_pairs( - session->is_server() - ? GetServerMaxHeaderPairs(config_.max_header_pairs) - : GetClientMaxHeaderPairs(config_.max_header_pairs)); - set_max_header_length(config_.max_header_length); - - session->quic_state()->http3config_buffer[IDX_HTTP3_CONFIG_COUNT] = 0; -} - -// Push streams in HTTP/3 are a bit complicated. -// First, it's important to know that only an HTTP/3 server can -// create a push stream. -// Second, a push stream is essentially an *assumed* request. For -// instance, if a client requests a webpage that has links to css -// and js files, and the server expects the client to send subsequent -// requests for those css and js files, the server can shortcut the -// process by opening a push stream for each additional resource -// it assumes the client to make. -// Third, a push stream can only be opened within the context -// of an HTTP/3 request/response. Essentially, a server receives -// a request and while processing the response, the server can -// open one or more push streams. -// -// Now... a push stream consists of two components: a push promise -// and a push fulfillment. The push promise is sent *as part of -// the response on the original stream* and is assigned a push id -// and a block of headers containing the *assumed request headers*. -// The push promise is sent on the request/response bidirectional -// stream. -// The push fulfillment is a unidirectional stream opened by the -// server that contains the push id, the response header block, and -// the response payload. -// Here's where it can get a bit complicated: the server sends the -// push promise and the push fulfillment on two different, and -// independent QUIC streams. The push id is used to correlate -// those on the client side, but, it's entirely possible for the -// client to receive the push fulfillment before it actually receives -// the push promise. It's *unlikely*, but it's possible. Fortunately, -// nghttp3 handles the complexity of that for us internally but -// makes for some weird timing and could lead to some amount of -// buffering to occur. -// -// The *logical* order of events from the client side *should* -// be: (a) receive the push promise containing assumed request -// headers, (b) receive the push fulfillment containing the -// response headers followed immediately by the response payload. -// -// On the server side, the steps are: (a) first create the push -// promise creating the push_id then (b) open the unidirectional -// stream that will be used to fullfil the push promise. Once that -// unidirectional stream is created, the push id and unidirectional -// stream ID must be bound. The CreateAndBindPushStream handles (b) -int64_t Http3Application::CreateAndBindPushStream(int64_t push_id) { - CHECK(session()->is_server()); - int64_t stream_id; - if (!session()->OpenUnidirectionalStream(&stream_id)) - return 0; - return nghttp3_conn_bind_push_stream( - connection(), - push_id, - stream_id) == 0 ? stream_id : 0; -} - -bool Http3Application::SubmitPushPromise( - int64_t id, - int64_t* push_id, - int64_t* stream_id, - const Http3Headers& headers) { - // Successfully creating the push promise and opening the - // fulfillment stream will queue nghttp3 up to send data. - // Creating the SendSessionScope here ensures that when - // SubmitPush exits, SendPendingData will be called if - // we are not within the context of an ngtcp2 callback. - QuicSession::SendSessionScope send_scope(session()); - - Debug( - session(), - "Submitting %d push promise headers", - headers.length()); - if (nghttp3_conn_submit_push_promise( - connection(), - push_id, - id, - headers.data(), - headers.length()) != 0) { - return false; - } - // Once we've successfully submitting the push promise and have - // a push id assigned, we create the push fulfillment stream. - *stream_id = CreateAndBindPushStream(*push_id); - return *stream_id != 0; // push stream can never use stream id 0 -} - -bool Http3Application::SubmitInformation( - int64_t id, - const Http3Headers& headers) { - QuicSession::SendSessionScope send_scope(session()); - Debug( - session(), - "Submitting %d informational headers for stream %" PRId64, - headers.length(), - id); - return nghttp3_conn_submit_info( - connection(), - id, - headers.data(), - headers.length()) == 0; -} - -bool Http3Application::SubmitTrailers( - int64_t id, - const Http3Headers& headers) { - QuicSession::SendSessionScope send_scope(session()); - Debug( - session(), - "Submitting %d trailing headers for stream %" PRId64, - headers.length(), - id); - return nghttp3_conn_submit_trailers( - connection(), - id, - headers.data(), - headers.length()) == 0; -} - -bool Http3Application::SubmitHeaders( - int64_t id, - const Http3Headers& headers, - int32_t flags) { - QuicSession::SendSessionScope send_scope(session()); - static constexpr nghttp3_data_reader reader = { - Http3Application::OnReadData }; - const nghttp3_data_reader* reader_ptr = nullptr; - if (!(flags & QUICSTREAM_HEADER_FLAGS_TERMINAL)) - reader_ptr = &reader; - - switch (session()->crypto_context()->side()) { - case NGTCP2_CRYPTO_SIDE_CLIENT: - return nghttp3_conn_submit_request( - connection(), - id, - headers.data(), - headers.length(), - reader_ptr, - nullptr) == 0; - case NGTCP2_CRYPTO_SIDE_SERVER: - return nghttp3_conn_submit_response( - connection(), - id, - headers.data(), - headers.length(), - reader_ptr) == 0; - default: - UNREACHABLE(); - } -} - -// SubmitPush initiates a push stream by first creating a push promise -// with an associated push id, then opening the unidirectional stream -// that is used to fullfill it. Assuming both operations are successful, -// the QuicStream instance is created and added to the server QuicSession. -// -// The headers block passed to the submit push contains the assumed -// *request* headers. The response headers are provided using the -// SubmitHeaders() function on the created QuicStream. -BaseObjectPtr<QuicStream> Http3Application::SubmitPush( - int64_t id, - Local<Array> headers) { - // If the QuicSession is not a server session, return false - // immediately. Push streams cannot be sent by an HTTP/3 client. - if (!session()->is_server()) - return {}; - - Http3Headers nva(env(), headers); - int64_t push_id; - int64_t stream_id; - - // There are several reasons why push may fail. We currently handle - // them all the same. Later we might want to differentiate when the - // return value is NGHTTP3_ERR_PUSH_ID_BLOCKED. - return SubmitPushPromise(id, &push_id, &stream_id, nva) ? - QuicStream::New(session(), stream_id, push_id) : - BaseObjectPtr<QuicStream>(); -} - -// Submit informational headers (response headers that use a 1xx -// status code). If the QuicSession is not a server session, return -// false immediately because info headers cannot be sent by a -// client -bool Http3Application::SubmitInformation( - int64_t stream_id, - Local<Array> headers) { - if (!session()->is_server()) - return false; - Http3Headers nva(session()->env(), headers); - return SubmitInformation(stream_id, nva); -} - -// For client sessions, submits request headers. For server sessions, -// submits response headers. -bool Http3Application::SubmitHeaders( - int64_t stream_id, - Local<Array> headers, - uint32_t flags) { - Http3Headers nva(session()->env(), headers); - return SubmitHeaders(stream_id, nva, flags); -} - -// Submits trailing headers for the HTTP/3 request or response. -bool Http3Application::SubmitTrailers( - int64_t stream_id, - Local<Array> headers) { - Http3Headers nva(session()->env(), headers); - return SubmitTrailers(stream_id, nva); -} - -void Http3Application::CheckAllocatedSize(size_t previous_size) const { - CHECK_GE(current_nghttp3_memory_, previous_size); -} - -void Http3Application::IncreaseAllocatedSize(size_t size) { - current_nghttp3_memory_ += size; -} - -void Http3Application::DecreaseAllocatedSize(size_t size) { - current_nghttp3_memory_ -= size; -} - -void Http3Application::MemoryInfo(MemoryTracker* tracker) const { - tracker->TrackFieldWithSize("current_nghttp3_memory", - current_nghttp3_memory_); -} - -// Creates the underlying nghttp3 connection state for the session. -void Http3Application::CreateConnection() { - // nghttp3_conn_server_new and nghttp3_conn_client_new share - // identical definitions, so new_fn will work for both. - using new_fn = decltype(&nghttp3_conn_server_new); - static new_fn fns[] = { - nghttp3_conn_client_new, // NGTCP2_CRYPTO_SIDE_CLIENT - nghttp3_conn_server_new, // NGTCP2_CRYPTO_SIDE_SERVER - }; - - ngtcp2_crypto_side side = session()->crypto_context()->side(); - nghttp3_conn* conn; - - CHECK_EQ(fns[side]( - &conn, - &callbacks_[side], - &config_, - &alloc_info_, - this), 0); - CHECK_NOT_NULL(conn); - connection_.reset(conn); -} - -// The HTTP/3 QUIC binding uses a single unidirectional control -// stream in each direction to exchange frames impacting the entire -// connection. -bool Http3Application::CreateAndBindControlStream() { - if (!session()->OpenUnidirectionalStream(&control_stream_id_)) - return false; - Debug( - session(), - "Open stream %" PRId64 " and bind as control stream", - control_stream_id_); - return nghttp3_conn_bind_control_stream( - connection(), - control_stream_id_) == 0; -} - -// The HTTP/3 QUIC binding creates two unidirectional streams in -// each direction to exchange header compression details. -bool Http3Application::CreateAndBindQPackStreams() { - if (!session()->OpenUnidirectionalStream(&qpack_enc_stream_id_) || - !session()->OpenUnidirectionalStream(&qpack_dec_stream_id_)) { - return false; - } - Debug( - session(), - "Open streams %" PRId64 " and %" PRId64 " and bind as qpack streams", - qpack_enc_stream_id_, - qpack_dec_stream_id_); - return nghttp3_conn_bind_qpack_streams( - connection(), - qpack_enc_stream_id_, - qpack_dec_stream_id_) == 0; -} - -bool Http3Application::Initialize() { - if (!needs_init()) - return false; - - // The QuicSession must allow for at least three local unidirectional streams. - // This number is fixed by the http3 specification and represent the - // control stream and two qpack management streams. - if (session()->max_local_streams_uni() < 3) - return false; - - Debug(session(), "QPack Max Table Capacity: %" PRIu64, - config_.qpack_max_table_capacity); - Debug(session(), "QPack Blocked Streams: %" PRIu64, - config_.qpack_blocked_streams); - Debug(session(), "Max Header List Size: %" PRIu64, - config_.max_field_section_size); - Debug(session(), "Max Pushes: %" PRIu64, - config_.max_pushes); - - CreateConnection(); - Debug(session(), "HTTP/3 connection created"); - - ngtcp2_transport_params params; - session()->GetLocalTransportParams(¶ms); - - if (session()->is_server()) { - nghttp3_conn_set_max_client_streams_bidi( - connection(), - params.initial_max_streams_bidi); - } - - if (!CreateAndBindControlStream() || - !CreateAndBindQPackStreams()) { - return false; - } - - set_init_done(); - return true; -} - -// All HTTP/3 control, header, and stream data arrives as QUIC stream data. -// Here we pass the received data off to nghttp3 for processing. This will -// trigger the invocation of the various nghttp3 callbacks. -bool Http3Application::ReceiveStreamData( - uint32_t flags, - int64_t stream_id, - const uint8_t* data, - size_t datalen, - uint64_t offset) { - Debug(session(), "Receiving %" PRIu64 " bytes for stream %" PRIu64 "%s", - datalen, - stream_id, - flags & NGTCP2_STREAM_DATA_FLAG_FIN ? " (fin)" : ""); - ssize_t nread = - nghttp3_conn_read_stream( - connection(), - stream_id, - data, - datalen, - flags & NGTCP2_STREAM_DATA_FLAG_FIN); - if (nread < 0) { - Debug(session(), "Failure to read HTTP/3 Stream Data [%" PRId64 "]", nread); - return false; - } - - return true; -} - -// This is the QUIC-level stream data acknowledgement. It is called for -// all streams, including unidirectional streams. This has to forward on -// to nghttp3 for processing. The Http3Application::AckedStreamData might -// be called as a result to acknowledge (and free) QuicStream data. -void Http3Application::AcknowledgeStreamData( - int64_t stream_id, - uint64_t offset, - size_t datalen) { - if (nghttp3_conn_add_ack_offset(connection(), stream_id, datalen) != 0) - Debug(session(), "Failure to acknowledge HTTP/3 Stream Data"); -} - -void Http3Application::StreamClose( - int64_t stream_id, - uint64_t app_error_code) { - if (app_error_code == 0) - app_error_code = NGTCP2_APP_NOERROR; - nghttp3_conn_close_stream(connection(), stream_id, app_error_code); - QuicApplication::StreamClose(stream_id, app_error_code); -} - -void Http3Application::StreamReset( - int64_t stream_id, - uint64_t app_error_code) { - nghttp3_conn_reset_stream(connection(), stream_id); - QuicApplication::StreamReset(stream_id, app_error_code); -} - -// When SendPendingData tries to send data for a given stream and there -// is no data to send but the QuicStream is still writable, it will -// be paused. When there's data available, the stream is resumed. -void Http3Application::ResumeStream(int64_t stream_id) { - nghttp3_conn_resume_stream(connection(), stream_id); -} - -// When stream data cannot be sent because of flow control, it is marked -// as being blocked. When the flow control windows expands, nghttp3 has -// to be told to unblock the stream so it knows to try sending data again. -void Http3Application::ExtendMaxStreamData( - int64_t stream_id, - uint64_t max_data) { - nghttp3_conn_unblock_stream(connection(), stream_id); -} - -// When stream data cannot be sent because of flow control, it is marked -// as being blocked. -bool Http3Application::BlockStream(int64_t stream_id) { - int err = nghttp3_conn_block_stream(connection(), stream_id); - if (err != 0) { - session()->set_last_error(QUIC_ERROR_APPLICATION, err); - return false; - } - return true; -} - -// nghttp3 keeps track of how much QuicStream data it has available and -// has sent. StreamCommit is called when a QuicPacket is serialized -// and updates nghttp3's internal state. -bool Http3Application::StreamCommit(StreamData* stream_data, size_t datalen) { - int err = nghttp3_conn_add_write_offset( - connection(), - stream_data->id, - datalen); - if (err != 0) { - session()->set_last_error(QUIC_ERROR_APPLICATION, err); - return false; - } - return true; -} - -// GetStreamData is called by SendPendingData to collect the QuicStream data -// that is to be packaged into a serialized QuicPacket. There may or may not -// be any stream data to send. The call to nghttp3_conn_writev_stream will -// provide any available stream data (if any). If nghttp3 is not sure if -// there is data to send, it will subsequently call Http3Application::ReadData -// to collect available data from the QuicStream. -int Http3Application::GetStreamData(StreamData* stream_data) { - ssize_t ret = 0; - if (connection() && session()->max_data_left()) { - ret = nghttp3_conn_writev_stream( - connection(), - &stream_data->id, - &stream_data->fin, - reinterpret_cast<nghttp3_vec*>(stream_data->data), - sizeof(stream_data->data)); - if (ret < 0) - return static_cast<int>(ret); - else - stream_data->remaining = stream_data->count = static_cast<size_t>(ret); - } - if (stream_data->id > -1) { - Debug(session(), "Selected %" PRId64 " buffers for stream %" PRId64 "%s", - stream_data->count, - stream_data->id, - stream_data->fin == 1 ? " (fin)" : ""); - } - return 0; -} - -// Determines whether SendPendingData should set fin on the QuicStream -bool Http3Application::ShouldSetFin(const StreamData& stream_data) { - return stream_data.id > -1 && - !is_control_stream(stream_data.id) && - stream_data.fin == 1; -} - -// This is where nghttp3 pulls the data from the outgoing -// buffer to prepare it to be sent on the QUIC stream. -ssize_t Http3Application::ReadData( - int64_t stream_id, - nghttp3_vec* vec, - size_t veccnt, - uint32_t* pflags) { - BaseObjectPtr<QuicStream> stream = session()->FindStream(stream_id); - CHECK(stream); - - ssize_t ret = NGHTTP3_ERR_WOULDBLOCK; - - auto next = [&]( - int status, - const ngtcp2_vec* data, - size_t count, - bob::Done done) { - CHECK_LE(count, veccnt); - - switch (status) { - case bob::Status::STATUS_BLOCK: - // Fall through - case bob::Status::STATUS_WAIT: - // Fall through - case bob::Status::STATUS_EOS: - return; - case bob::Status::STATUS_END: - *pflags |= NGHTTP3_DATA_FLAG_EOF; - break; - } - - ret = count; - size_t numbytes = - nghttp3_vec_len( - reinterpret_cast<const nghttp3_vec*>(data), - count); - std::move(done)(numbytes); - - Debug(session(), "Sending %" PRIu64 " bytes for stream %" PRId64, - numbytes, stream_id); - }; - - CHECK_GE(stream->Pull( - std::move(next), - // Set OPTIONS_END here because nghttp3 takes over responsibility - // for ensuring the data all gets written out. - bob::Options::OPTIONS_END | bob::Options::OPTIONS_SYNC, - reinterpret_cast<ngtcp2_vec*>(vec), - veccnt, - kMaxVectorCount), 0); - - return ret; -} - -// Outgoing data is retained in memory until it is acknowledged. -void Http3Application::AckedStreamData(int64_t stream_id, size_t datalen) { - Acknowledge(stream_id, 0, datalen); -} - -void Http3Application::StreamClosed( - int64_t stream_id, - uint64_t app_error_code) { - BaseObjectPtr<QuicStream> stream = session()->FindStream(stream_id); - if (stream) - stream->ReceiveData(1, nullptr, 0, 0); -} - -BaseObjectPtr<QuicStream> Http3Application::FindOrCreateStream( - int64_t stream_id) { - BaseObjectPtr<QuicStream> stream = session()->FindStream(stream_id); - if (!stream) { - if (session()->is_graceful_closing()) { - nghttp3_conn_close_stream(connection(), stream_id, NGTCP2_ERR_CLOSING); - return {}; - } - stream = session()->CreateStream(stream_id); - nghttp3_conn_set_stream_user_data(connection(), stream_id, stream.get()); - } - CHECK(stream); - return stream; -} - -void Http3Application::ReceiveData( - int64_t stream_id, - const uint8_t* data, - size_t datalen) { - FindOrCreateStream(stream_id)->ReceiveData(0, data, datalen, 0); -} - -void Http3Application::DeferredConsume( - int64_t stream_id, - size_t consumed) { - // Do nothing here for now. nghttp3 uses the on_deferred_consume - // callback to notify when stream data that had previously been - // deferred has been delivered to the application so that the - // stream data offset can be extended. However, we extend the - // data offset from within QuicStream when the data is delivered - // so we don't have to do it here. -} - -// Called when a nghttp3 detects that a new block of headers -// has been received. Http3Application::ReceiveHeader will -// be called for each name+value pair received, then -// Http3Application::EndHeaders will be called to finalize -// the header block. -void Http3Application::BeginHeaders( - int64_t stream_id, - QuicStreamHeadersKind kind) { - Debug(session(), "Starting header block for stream %" PRId64, stream_id); - FindOrCreateStream(stream_id)->BeginHeaders(kind); -} - -// As each header name+value pair is received, it is stored internally -// by the QuicStream until stream->EndHeaders() is called, during which -// the collected headers are converted to an array and passed off to -// the javascript side. -bool Http3Application::ReceiveHeader( - int64_t stream_id, - int32_t token, - nghttp3_rcbuf* name, - nghttp3_rcbuf* value, - uint8_t flags) { - // Protect against zero-length headers (zero-length if either the - // name or value are zero-length). Such headers are simply ignored. - if (!Http3Header::IsZeroLength(name, value)) { - Debug(session(), "Receiving header for stream %" PRId64, stream_id); - BaseObjectPtr<QuicStream> stream = session()->FindStream(stream_id); - CHECK(stream); - if (token == NGHTTP3_QPACK_TOKEN__STATUS) { - nghttp3_vec vec = nghttp3_rcbuf_get_buf(value); - if (vec.base[0] == '1') - stream->set_headers_kind(QUICSTREAM_HEADERS_KIND_INFORMATIONAL); - else - stream->set_headers_kind(QUICSTREAM_HEADERS_KIND_INITIAL); - } - auto header = std::make_unique<Http3Header>( - session()->env(), - token, - name, - value, - flags); - return stream->AddHeader(std::move(header)); - } - return true; -} - -// Marks the completion of a headers block. -void Http3Application::EndHeaders(int64_t stream_id, int64_t push_id) { - Debug(session(), "Ending header block for stream %" PRId64, stream_id); - BaseObjectPtr<QuicStream> stream = session()->FindStream(stream_id); - CHECK(stream); - stream->EndHeaders(); -} - -void Http3Application::CancelPush( - int64_t push_id, - int64_t stream_id) { - Debug(session(), "push stream canceled"); -} - -void Http3Application::PushStream( - int64_t push_id, - int64_t stream_id) { - Debug(session(), "Received push stream %" PRIu64 " (%" PRIu64 ")", - stream_id, push_id); -} - -void Http3Application::SendStopSending( - int64_t stream_id, - uint64_t app_error_code) { - ngtcp2_conn_shutdown_stream_read( - session()->connection(), - stream_id, - app_error_code); -} - -void Http3Application::EndStream(int64_t stream_id) { - BaseObjectPtr<QuicStream> stream = session()->FindStream(stream_id); - CHECK(stream); - stream->ReceiveData(1, nullptr, 0, 0); -} - -const nghttp3_conn_callbacks Http3Application::callbacks_[2] = { - // NGTCP2_CRYPTO_SIDE_CLIENT - { - OnAckedStreamData, - OnStreamClose, - OnReceiveData, - OnDeferredConsume, - OnBeginHeaders, - OnReceiveHeader, - OnEndHeaders, - OnBeginTrailers, // Begin Trailers - OnReceiveHeader, // Receive Trailer - OnEndHeaders, // End Trailers - OnBeginPushPromise, - OnReceivePushPromise, - OnEndPushPromise, - OnCancelPush, - OnSendStopSending, - OnPushStream, - OnEndStream - }, - // NGTCP2_CRYPTO_SIDE_SERVER - { - OnAckedStreamData, - OnStreamClose, - OnReceiveData, - OnDeferredConsume, - OnBeginHeaders, - OnReceiveHeader, - OnEndHeaders, - OnBeginTrailers, // Begin Trailers - OnReceiveHeader, // Receive Trailer - OnEndHeaders, // End Trailers - OnBeginPushPromise, - OnReceivePushPromise, - OnEndPushPromise, - OnCancelPush, - OnSendStopSending, - OnPushStream, - OnEndStream - } -}; - -int Http3Application::OnAckedStreamData( - nghttp3_conn* conn, - int64_t stream_id, - size_t datalen, - void* conn_user_data, - void* stream_user_data) { - Http3Application* app = static_cast<Http3Application*>(conn_user_data); - app->AckedStreamData(stream_id, datalen); - return 0; -} - -int Http3Application::OnStreamClose( - nghttp3_conn* conn, - int64_t stream_id, - uint64_t app_error_code, - void* conn_user_data, - void* stream_user_data) { - Http3Application* app = static_cast<Http3Application*>(conn_user_data); - app->StreamClosed(stream_id, app_error_code); - return 0; -} - -int Http3Application::OnReceiveData( - nghttp3_conn* conn, - int64_t stream_id, - const uint8_t* data, - size_t datalen, - void* conn_user_data, - void* stream_user_data) { - Http3Application* app = static_cast<Http3Application*>(conn_user_data); - app->ReceiveData(stream_id, data, datalen); - return 0; -} - -int Http3Application::OnDeferredConsume( - nghttp3_conn* conn, - int64_t stream_id, - size_t consumed, - void* conn_user_data, - void* stream_user_data) { - Http3Application* app = static_cast<Http3Application*>(conn_user_data); - app->DeferredConsume(stream_id, consumed); - return 0; -} - -int Http3Application::OnBeginHeaders( - nghttp3_conn* conn, - int64_t stream_id, - void* conn_user_data, - void* stream_user_data) { - Http3Application* app = static_cast<Http3Application*>(conn_user_data); - app->BeginHeaders(stream_id); - return 0; -} - -int Http3Application::OnBeginTrailers( - nghttp3_conn* conn, - int64_t stream_id, - void* conn_user_data, - void* stream_user_data) { - Http3Application* app = static_cast<Http3Application*>(conn_user_data); - app->BeginHeaders(stream_id, QUICSTREAM_HEADERS_KIND_TRAILING); - return 0; -} - -int Http3Application::OnReceiveHeader( - nghttp3_conn* conn, - int64_t stream_id, - int32_t token, - nghttp3_rcbuf* name, - nghttp3_rcbuf* value, - uint8_t flags, - void* conn_user_data, - void* stream_user_data) { - Http3Application* app = static_cast<Http3Application*>(conn_user_data); - // TODO(@jasnell): Need to determine the appropriate response code here - // for when the header is not going to be accepted. - return app->ReceiveHeader(stream_id, token, name, value, flags) ? - 0 : NGHTTP3_ERR_CALLBACK_FAILURE; -} - -int Http3Application::OnEndHeaders( - nghttp3_conn* conn, - int64_t stream_id, - void* conn_user_data, - void* stream_user_data) { - Http3Application* app = static_cast<Http3Application*>(conn_user_data); - app->EndHeaders(stream_id); - return 0; -} - -int Http3Application::OnBeginPushPromise( - nghttp3_conn* conn, - int64_t stream_id, - int64_t push_id, - void* conn_user_data, - void* stream_user_data) { - Http3Application* app = static_cast<Http3Application*>(conn_user_data); - app->BeginHeaders(stream_id, QUICSTREAM_HEADERS_KIND_PUSH); - return 0; -} - -int Http3Application::OnReceivePushPromise( - nghttp3_conn* conn, - int64_t stream_id, - int64_t push_id, - int32_t token, - nghttp3_rcbuf* name, - nghttp3_rcbuf* value, - uint8_t flags, - void* conn_user_data, - void* stream_user_data) { - Http3Application* app = static_cast<Http3Application*>(conn_user_data); - if (!app->ReceiveHeader(stream_id, token, name, value, flags)) - return NGHTTP3_ERR_CALLBACK_FAILURE; - return 0; -} - -int Http3Application::OnEndPushPromise( - nghttp3_conn* conn, - int64_t stream_id, - int64_t push_id, - void* conn_user_data, - void* stream_user_data) { - Http3Application* app = static_cast<Http3Application*>(conn_user_data); - app->EndHeaders(stream_id, push_id); - return 0; -} - -int Http3Application::OnCancelPush( - nghttp3_conn* conn, - int64_t push_id, - int64_t stream_id, - void* conn_user_data, - void* stream_user_data) { - Http3Application* app = static_cast<Http3Application*>(conn_user_data); - app->CancelPush(push_id, stream_id); - return 0; -} - -int Http3Application::OnSendStopSending( - nghttp3_conn* conn, - int64_t stream_id, - uint64_t app_error_code, - void* conn_user_data, - void* stream_user_data) { - Http3Application* app = static_cast<Http3Application*>(conn_user_data); - app->SendStopSending(stream_id, app_error_code); - return 0; -} - -int Http3Application::OnPushStream( - nghttp3_conn* conn, - int64_t push_id, - int64_t stream_id, - void* conn_user_data) { - Http3Application* app = static_cast<Http3Application*>(conn_user_data); - app->PushStream(push_id, stream_id); - return 0; -} - -int Http3Application::OnEndStream( - nghttp3_conn* conn, - int64_t stream_id, - void* conn_user_data, - void* stream_user_data) { - Http3Application* app = static_cast<Http3Application*>(conn_user_data); - app->EndStream(stream_id); - return 0; -} - -ssize_t Http3Application::OnReadData( - nghttp3_conn* conn, - int64_t stream_id, - nghttp3_vec* vec, - size_t veccnt, - uint32_t* pflags, - void* conn_user_data, - void* stream_user_data) { - Http3Application* app = static_cast<Http3Application*>(conn_user_data); - return app->ReadData(stream_id, vec, veccnt, pflags); -} -} // namespace quic -} // namespace node diff --git a/src/quic/node_quic_http3_application.h b/src/quic/node_quic_http3_application.h deleted file mode 100644 index 35387414625..00000000000 --- a/src/quic/node_quic_http3_application.h +++ /dev/null @@ -1,331 +0,0 @@ -#ifndef SRC_QUIC_NODE_QUIC_HTTP3_APPLICATION_H_ -#define SRC_QUIC_NODE_QUIC_HTTP3_APPLICATION_H_ - -#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS - -#include "node.h" -#include "node_http_common.h" -#include "node_mem.h" -#include "node_quic_session.h" -#include "node_quic_stream-inl.h" -#include "node_quic_util.h" -#include "v8.h" -#include <ngtcp2/ngtcp2.h> -#include <nghttp3/nghttp3.h> - -namespace node { - -namespace quic { - -constexpr uint64_t DEFAULT_QPACK_MAX_TABLE_CAPACITY = 4096; -constexpr uint64_t DEFAULT_QPACK_BLOCKED_STREAMS = 100; -constexpr size_t DEFAULT_MAX_HEADER_LIST_SIZE = 65535; -constexpr size_t DEFAULT_MAX_PUSHES = 65535; - -struct Http3RcBufferPointerTraits { - typedef nghttp3_rcbuf rcbuf_t; - typedef nghttp3_vec vector_t; - - static void inc(rcbuf_t* buf) { - nghttp3_rcbuf_incref(buf); - } - static void dec(rcbuf_t* buf) { - nghttp3_rcbuf_decref(buf); - } - static vector_t get_vec(const rcbuf_t* buf) { - return nghttp3_rcbuf_get_buf(buf); - } - static bool is_static(const rcbuf_t* buf) { - return nghttp3_rcbuf_is_static(buf); - } -}; - -struct Http3HeadersTraits { - typedef nghttp3_nv nv_t; -}; - -using Http3ConnectionPointer = DeleteFnPtr<nghttp3_conn, nghttp3_conn_del>; -using Http3RcBufferPointer = NgRcBufPointer<Http3RcBufferPointerTraits>; -using Http3Headers = NgHeaders<Http3HeadersTraits>; - -struct Http3HeaderTraits { - typedef Http3RcBufferPointer rcbufferpointer_t; - typedef QuicApplication allocator_t; - - static const char* ToHttpHeaderName(int32_t token); -}; - -using Http3Header = NgHeader<Http3HeaderTraits>; - -struct Http3ApplicationConfig : public nghttp3_conn_settings { - Http3ApplicationConfig() { - nghttp3_conn_settings_default(this); - qpack_max_table_capacity = DEFAULT_QPACK_MAX_TABLE_CAPACITY; - qpack_blocked_streams = DEFAULT_QPACK_BLOCKED_STREAMS; - max_field_section_size = DEFAULT_MAX_HEADER_LIST_SIZE; - max_pushes = DEFAULT_MAX_PUSHES; - } - uint64_t max_header_pairs = DEFAULT_MAX_HEADER_LIST_PAIRS; - uint64_t max_header_length = DEFAULT_MAX_HEADER_LENGTH; -}; - -class Http3Application; -using Http3MemoryManager = - mem::NgLibMemoryManager<Http3Application, nghttp3_mem>; - -// Http3Application is used whenever the h3 alpn identifier is used. -// It causes the QuicSession to apply HTTP/3 semantics to the connection, -// including handling of headers and other HTTP/3 specific processing. -class Http3Application final : - public QuicApplication, - public Http3MemoryManager { - public: - explicit Http3Application(QuicSession* session); - - bool Initialize() override; - - void StopTrackingMemory(void* ptr) override { - Http3MemoryManager::StopTrackingMemory(ptr); - } - - bool ReceiveStreamData( - uint32_t flags, - int64_t stream_id, - const uint8_t* data, - size_t datalen, - uint64_t offset) override; - - void AcknowledgeStreamData( - int64_t stream_id, - uint64_t offset, - size_t datalen) override; - - void StreamClose(int64_t stream_id, uint64_t app_error_code) override; - - void StreamReset( - int64_t stream_id, - uint64_t app_error_code) override; - - void ResumeStream(int64_t stream_id) override; - - void ExtendMaxStreamData(int64_t stream_id, uint64_t max_data) override; - - bool SubmitInformation( - int64_t stream_id, - v8::Local<v8::Array> headers) override; - - bool SubmitHeaders( - int64_t stream_id, - v8::Local<v8::Array> headers, - uint32_t flags) override; - - bool SubmitTrailers( - int64_t stream_id, - v8::Local<v8::Array> headers) override; - - BaseObjectPtr<QuicStream> SubmitPush( - int64_t id, - v8::Local<v8::Array> headers) override; - - // Implementation for mem::NgLibMemoryManager - void CheckAllocatedSize(size_t previous_size) const; - void IncreaseAllocatedSize(size_t size); - void DecreaseAllocatedSize(size_t size); - - SET_SELF_SIZE(Http3Application) - SET_MEMORY_INFO_NAME(Http3Application) - void MemoryInfo(MemoryTracker* tracker) const override; - - private: - template <typename M = uint64_t, typename T> - void SetConfig(int idx, M T::*member); - - nghttp3_conn* connection() const { return connection_.get(); } - BaseObjectPtr<QuicStream> FindOrCreateStream(int64_t stream_id); - - bool CreateAndBindControlStream(); - bool CreateAndBindQPackStreams(); - int64_t CreateAndBindPushStream(int64_t push_id); - - int GetStreamData(StreamData* stream_data) override; - - bool BlockStream(int64_t stream_id) override; - bool StreamCommit(StreamData* stream_data, size_t datalen) override; - bool ShouldSetFin(const StreamData& data) override; - bool SubmitPushPromise( - int64_t id, - int64_t* push_id, - int64_t* stream_id, - const Http3Headers& headers); - bool SubmitInformation(int64_t id, const Http3Headers& headers); - bool SubmitTrailers(int64_t id, const Http3Headers& headers); - bool SubmitHeaders(int64_t id, const Http3Headers& headers, int32_t flags); - - ssize_t ReadData( - int64_t stream_id, - nghttp3_vec* vec, - size_t veccnt, - uint32_t* pflags); - - void AckedStreamData(int64_t stream_id, size_t datalen); - void StreamClosed(int64_t stream_id, uint64_t app_error_code); - void ReceiveData(int64_t stream_id, const uint8_t* data, size_t datalen); - void DeferredConsume(int64_t stream_id, size_t consumed); - void BeginHeaders( - int64_t stream_id, - QuicStreamHeadersKind kind = QUICSTREAM_HEADERS_KIND_NONE); - bool ReceiveHeader( - int64_t stream_id, - int32_t token, - nghttp3_rcbuf* name, - nghttp3_rcbuf* value, - uint8_t flags); - void EndHeaders(int64_t stream_id, int64_t push_id = 0); - void CancelPush(int64_t push_id, int64_t stream_id); - void SendStopSending(int64_t stream_id, uint64_t app_error_code); - void PushStream(int64_t push_id, int64_t stream_id); - void EndStream(int64_t stream_id); - - bool is_control_stream(int64_t stream_id) const { - return stream_id == control_stream_id_ || - stream_id == qpack_dec_stream_id_ || - stream_id == qpack_enc_stream_id_; - } - - nghttp3_mem alloc_info_; - Http3ConnectionPointer connection_; - int64_t control_stream_id_; - int64_t qpack_enc_stream_id_; - int64_t qpack_dec_stream_id_; - size_t current_nghttp3_memory_ = 0; - - Http3ApplicationConfig config_; - - void CreateConnection(); - - static const nghttp3_conn_callbacks callbacks_[2]; - - static int OnAckedStreamData( - nghttp3_conn* conn, - int64_t stream_id, - size_t datalen, - void* conn_user_data, - void* stream_user_data); - - static int OnStreamClose( - nghttp3_conn* conn, - int64_t stream_id, - uint64_t app_error_code, - void* conn_user_data, - void* stream_user_data); - - static int OnReceiveData( - nghttp3_conn* conn, - int64_t stream_id, - const uint8_t* data, - size_t datalen, - void* conn_user_data, - void* stream_user_data); - - static int OnDeferredConsume( - nghttp3_conn* conn, - int64_t stream_id, - size_t consumed, - void* conn_user_data, - void* stream_user_data); - - static int OnBeginHeaders( - nghttp3_conn* conn, - int64_t stream_id, - void* conn_user_data, - void* stream_user_data); - - static int OnBeginTrailers( - nghttp3_conn* conn, - int64_t stream_id, - void* conn_user_data, - void* stream_user_data); - - static int OnReceiveHeader( - nghttp3_conn* conn, - int64_t stream_id, - int32_t token, - nghttp3_rcbuf* name, - nghttp3_rcbuf* value, - uint8_t flags, - void* conn_user_data, - void* stream_user_data); - - static int OnEndHeaders( - nghttp3_conn* conn, - int64_t stream_id, - void* conn_user_data, - void* stream_user_data); - - static int OnBeginPushPromise( - nghttp3_conn* conn, - int64_t stream_id, - int64_t push_id, - void* conn_user_data, - void* stream_user_data); - - static int OnReceivePushPromise( - nghttp3_conn* conn, - int64_t stream_id, - int64_t push_id, - int32_t token, - nghttp3_rcbuf* name, - nghttp3_rcbuf* value, - uint8_t flags, - void* conn_user_data, - void* stream_user_data); - - static int OnEndPushPromise( - nghttp3_conn* conn, - int64_t stream_id, - int64_t push_id, - void* conn_user_data, - void* stream_user_data); - - static int OnCancelPush( - nghttp3_conn* conn, - int64_t push_id, - int64_t stream_id, - void* conn_user_data, - void* stream_user_data); - - static int OnSendStopSending( - nghttp3_conn* conn, - int64_t stream_id, - uint64_t app_error_code, - void* conn_user_data, - void* stream_user_data); - - static int OnPushStream( - nghttp3_conn* conn, - int64_t push_id, - int64_t stream_id, - void* conn_user_data); - - static int OnEndStream( - nghttp3_conn* conn, - int64_t stream_id, - void* conn_user_data, - void* stream_user_data); - - static ssize_t OnReadData( - nghttp3_conn* conn, - int64_t stream_id, - nghttp3_vec* vec, - size_t veccnt, - uint32_t* pflags, - void* conn_user_data, - void* stream_user_data); -}; - -} // namespace quic - -} // namespace node - -#endif // NODE_WANT_INTERNALS -#endif // SRC_QUIC_NODE_QUIC_HTTP3_APPLICATION_H_ diff --git a/src/quic/node_quic_session-inl.h b/src/quic/node_quic_session-inl.h deleted file mode 100644 index c506812b044..00000000000 --- a/src/quic/node_quic_session-inl.h +++ /dev/null @@ -1,447 +0,0 @@ -#ifndef SRC_QUIC_NODE_QUIC_SESSION_INL_H_ -#define SRC_QUIC_NODE_QUIC_SESSION_INL_H_ - -#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS - -#include "debug_utils-inl.h" -#include "node_crypto.h" -#include "crypto/crypto_common.h" -#include "node_quic_crypto.h" -#include "node_quic_session.h" -#include "node_quic_socket-inl.h" -#include "node_quic_stream-inl.h" - -#include <openssl/ssl.h> -#include <memory> -#include <string> - -namespace node { - -namespace quic { - -void QuicSessionConfig::GenerateStatelessResetToken( - QuicSession* session, - const QuicCID& cid) { - transport_params.stateless_reset_token_present = 1; - StatelessResetToken token( - transport_params.stateless_reset_token, - session->socket()->session_reset_secret(), - cid); - Debug(session, "Generated stateless reset token %s for CID %s", token, cid); -} - -void QuicSessionConfig::GeneratePreferredAddressToken( - ConnectionIDStrategy connection_id_strategy, - QuicSession* session, - QuicCID* pscid) { - connection_id_strategy(session, pscid->cid(), kScidLen); - transport_params.preferred_address.cid = **pscid; - - StatelessResetToken( - transport_params.preferred_address.stateless_reset_token, - session->socket()->session_reset_secret(), - *pscid); -} - -void QuicSessionConfig::set_original_connection_id( - const QuicCID& ocid, - const QuicCID& scid) { - if (ocid) { - transport_params.original_dcid = *ocid; - transport_params.retry_scid = *scid; - transport_params.retry_scid_present = 1; - } else { - transport_params.original_dcid = *scid; - } -} - -void QuicSessionConfig::set_qlog(const ngtcp2_qlog_settings& qlog_) { - qlog = qlog_; -} - -QuicCryptoContext::QuicCryptoContext( - QuicSession* session, - BaseObjectPtr<crypto::SecureContext> secure_context, - ngtcp2_crypto_side side, - uint32_t options) : - session_(session), - secure_context_(secure_context), - side_(side), - options_(options) { - ssl_.reset(SSL_new(secure_context_->ctx_.get())); - CHECK(ssl_); -} - -void QuicCryptoContext::Initialize() { - InitializeTLS(session(), ssl_); -} - -// Cancels and frees any remaining outbound handshake data -// at each crypto level. -uint64_t QuicCryptoContext::Cancel() { - uint64_t len = handshake_[0].Cancel(); - len += handshake_[1].Cancel(); - len += handshake_[2].Cancel(); - return len; -} - -v8::MaybeLocal<v8::Value> QuicCryptoContext::ocsp_response() const { - Environment* env = session()->env(); - return crypto::GetSSLOCSPResponse( - env, - ssl_.get(), - v8::Undefined(env->isolate())); -} - -ngtcp2_crypto_level QuicCryptoContext::read_crypto_level() const { - return from_ossl_level(SSL_quic_read_level(ssl_.get())); -} - -ngtcp2_crypto_level QuicCryptoContext::write_crypto_level() const { - return from_ossl_level(SSL_quic_write_level(ssl_.get())); -} - -// TLS Keylogging is enabled per-QuicSession by attaching an handler to the -// "keylog" event. Each keylog line is emitted to JavaScript where it can -// be routed to whatever destination makes sense. Typically, this will be -// to a keylog file that can be consumed by tools like Wireshark to intercept -// and decrypt QUIC network traffic. -void QuicCryptoContext::Keylog(const char* line) { - if (UNLIKELY(session_->state_->keylog_enabled)) - session_->listener()->OnKeylog(line, strlen(line)); -} - -// Following a pause in the handshake for OCSP or client hello, we kickstart -// the handshake again here by triggering ngtcp2 to serialize data. -void QuicCryptoContext::ResumeHandshake() { - // We haven't received any actual new handshake data but calling - // this will trigger the handshake to continue. - Receive(read_crypto_level(), 0, nullptr, 0); - session_->SendPendingData(); -} - -// For 0RTT, this sets the TLS session data from the given buffer. -bool QuicCryptoContext::set_session(crypto::SSLSessionPointer session) { - if (side_ == NGTCP2_CRYPTO_SIDE_CLIENT && session != nullptr) { - set_early_data( - SSL_SESSION_get_max_early_data(session.get()) == 0xffffffffUL); - } - return crypto::SetTLSSession(ssl_, std::move(session)); -} - -v8::MaybeLocal<v8::Array> QuicCryptoContext::hello_ciphers() const { - return crypto::GetClientHelloCiphers(session()->env(), ssl_); -} - -v8::MaybeLocal<v8::Value> QuicCryptoContext::cipher_name() const { - return crypto::GetCipherName(session()->env(), ssl_); -} - -v8::MaybeLocal<v8::Value> QuicCryptoContext::cipher_version() const { - return crypto::GetCipherVersion(session()->env(), ssl_); -} - -const char* QuicCryptoContext::servername() const { - return crypto::GetServerName(ssl_.get()); -} - -const char* QuicCryptoContext::hello_alpn() const { - return crypto::GetClientHelloALPN(ssl_); -} - -const char* QuicCryptoContext::hello_servername() const { - return crypto::GetClientHelloServerName(ssl_); -} - -v8::MaybeLocal<v8::Object> QuicCryptoContext::ephemeral_key() const { - return crypto::GetEphemeralKey(session()->env(), ssl_); -} - -v8::MaybeLocal<v8::Value> QuicCryptoContext::peer_cert(bool abbreviated) const { - return crypto::GetPeerCert( - session()->env(), - ssl_, - abbreviated, - session()->is_server()); -} - -v8::MaybeLocal<v8::Value> QuicCryptoContext::cert() const { - return crypto::GetCert(session()->env(), ssl_); -} - -std::string QuicCryptoContext::selected_alpn() const { - const unsigned char* alpn_buf = nullptr; - unsigned int alpnlen; - SSL_get0_alpn_selected(ssl_.get(), &alpn_buf, &alpnlen); - return alpnlen ? - std::string(reinterpret_cast<const char*>(alpn_buf), alpnlen) : - std::string(); -} - -bool QuicCryptoContext::early_data() const { - return - (is_early_data() && - SSL_get_early_data_status(ssl_.get()) == SSL_EARLY_DATA_ACCEPTED) || - SSL_get_max_early_data(ssl_.get()) == 0xffffffffUL; -} - -void QuicCryptoContext::set_tls_alert(int err) { - Debug(session(), "TLS Alert [%d]: %s", err, SSL_alert_type_string_long(err)); - session_->set_last_error(QuicError(QUIC_ERROR_CRYPTO, err)); -} - -QuicApplication::QuicApplication(QuicSession* session) : session_(session) {} - -void QuicApplication::set_stream_fin(int64_t stream_id) { - BaseObjectPtr<QuicStream> stream = session()->FindStream(stream_id); - CHECK(stream); - stream->set_fin_sent(); -} - -std::unique_ptr<QuicPacket> QuicApplication::CreateStreamDataPacket() { - return QuicPacket::Create( - "stream data", - session()->max_packet_length()); -} - -Environment* QuicApplication::env() const { - return session()->env(); -} - -// Extends the stream-level flow control by the given number of bytes. -void QuicSession::ExtendStreamOffset(int64_t stream_id, size_t amount) { - Debug(this, "Extending max stream %" PRId64 " offset by %" PRId64 " bytes", - stream_id, amount); - ngtcp2_conn_extend_max_stream_offset( - connection(), - stream_id, - amount); -} - -// Extends the connection-level flow control for the entire session by -// the given number of bytes. -void QuicSession::ExtendOffset(size_t amount) { - Debug(this, "Extending session offset by %" PRId64 " bytes", amount); - ngtcp2_conn_extend_max_offset(connection(), amount); -} - -// Copies the local transport params into the given struct for serialization. -void QuicSession::GetLocalTransportParams(ngtcp2_transport_params* params) { - CHECK(!is_destroyed()); - ngtcp2_conn_get_local_transport_params(connection(), params); -} - -// Gets the QUIC version negotiated for this QuicSession -uint32_t QuicSession::negotiated_version() const { - CHECK(!is_destroyed()); - return ngtcp2_conn_get_negotiated_version(connection()); -} - -bool QuicSession::is_handshake_completed() const { - DCHECK(!is_destroyed()); - return ngtcp2_conn_get_handshake_completed(connection()); -} - -void QuicSession::ShutdownStream(int64_t stream_id, uint64_t code) { - if (is_in_closing_period() || - is_in_draining_period() || - is_silent_closing()) { - return; // Nothing to do because we can't send any frames. - } - SendSessionScope send_scope(this); - ngtcp2_conn_shutdown_stream(connection(), stream_id, 0); -} - -// When a QuicSession hits the idle timeout, it is to be silently and -// immediately closed without attempting to send any additional data to -// the peer. All existing streams are abandoned and closed. -void QuicSession::OnIdleTimeout() { - if (!is_destroyed()) { - if (state_->idle_timeout == 1) { - Debug(this, "Idle timeout"); - Close(QuicSessionListener::SESSION_CLOSE_FLAG_SILENT); - return; - } - state_->idle_timeout = 1; - UpdateClosingTimer(); - } -} - -QuicCID QuicSession::dcid() const { - return QuicCID(ngtcp2_conn_get_dcid(connection())); -} - -void QuicSession::CheckAllocatedSize(size_t previous_size) const { - CHECK_GE(current_ngtcp2_memory_, previous_size); -} - -void QuicSession::IncreaseAllocatedSize(size_t size) { - current_ngtcp2_memory_ += size; -} - -void QuicSession::DecreaseAllocatedSize(size_t size) { - current_ngtcp2_memory_ -= size; -} - -uint64_t QuicSession::max_data_left() const { - return ngtcp2_conn_get_max_data_left(connection()); -} - -uint64_t QuicSession::max_local_streams_uni() const { - return ngtcp2_conn_get_max_local_streams_uni(connection()); -} - -void QuicSession::set_last_error(QuicError error) { - last_error_ = error; -} - -void QuicSession::set_last_error(int32_t family, uint64_t code) { - set_last_error({ family, code }); -} - -void QuicSession::set_last_error(int32_t family, int code) { - set_last_error({ family, code }); -} - -bool QuicSession::is_in_closing_period() const { - return ngtcp2_conn_is_in_closing_period(connection()); -} - -bool QuicSession::is_in_draining_period() const { - return ngtcp2_conn_is_in_draining_period(connection()); -} - -bool QuicSession::HasStream(int64_t id) const { - return streams_.find(id) != std::end(streams_); -} - -bool QuicSession::allow_early_data() const { - // TODO(@jasnell): For now, we always allow early data. - // Later there will be reasons we do not want to allow - // it, such as lack of available system resources. - return true; -} - -void QuicSession::SetSessionTicketAppData( - const SessionTicketAppData& app_data) { - application_->SetSessionTicketAppData(app_data); -} - -SessionTicketAppData::Status QuicSession::GetSessionTicketAppData( - const SessionTicketAppData& app_data, - SessionTicketAppData::Flag flag) { - return application_->GetSessionTicketAppData(app_data, flag); -} - -bool QuicSession::is_server() const { - return crypto_context_->side() == NGTCP2_CRYPTO_SIDE_SERVER; -} - -void QuicSession::StartGracefulClose() { - set_graceful_closing(); - RecordTimestamp(&QuicSessionStats::closing_at); -} - -// The connection ID Strategy is a function that generates -// connection ID values. By default these are generated randomly. -void QuicSession::set_connection_id_strategy(ConnectionIDStrategy strategy) { - CHECK_NOT_NULL(strategy); - connection_id_strategy_ = strategy; -} - -bool QuicSession::is_unable_to_send_packets() { - return NgCallbackScope::InNgCallbackScope(this) || - is_destroyed() || - is_in_draining_period() || - (is_server() && is_in_closing_period()) || - socket() == nullptr; -} - -void QuicSession::set_preferred_address_strategy( - PreferredAddressStrategy strategy) { - preferred_address_strategy_ = strategy; -} - -QuicSocket* QuicSession::socket() const { - return socket_.get(); -} - -// Indicates that the stream is blocked from transmitting any -// data. The specific handling of this is application specific. -// By default, we keep track of statistics but leave it up to -// the application to perform specific handling. -void QuicSession::StreamDataBlocked(int64_t stream_id) { - IncrementStat(&QuicSessionStats::block_count); - listener_->OnStreamBlocked(stream_id); -} - -// When a server advertises a preferred address in its initial -// transport parameters, ngtcp2 on the client side will trigger -// the OnSelectPreferredAdddress callback which will call this. -// The paddr argument contains the advertised preferred address. -// If the new address is going to be used, it needs to be copied -// over to dest, otherwise dest is left alone. There are two -// possible strategies that we currently support via user -// configuration: use the preferred address or ignore it. -void QuicSession::SelectPreferredAddress( - const PreferredAddress& preferred_address) { - CHECK(!is_server()); - preferred_address_strategy_(this, preferred_address); -} - -// This variant of SendPacket is used by QuicApplication -// instances to transmit a packet and update the network -// path used at the same time. -bool QuicSession::SendPacket( - std::unique_ptr<QuicPacket> packet, - const ngtcp2_path_storage& path) { - UpdateEndpoint(path.path); - return SendPacket(std::move(packet)); -} - -// Set the transport parameters received from the remote peer -void QuicSession::set_remote_transport_params() { - DCHECK(!is_destroyed()); - ngtcp2_conn_get_remote_transport_params(connection(), &transport_params_); - set_transport_params_set(); -} - -// Submits information headers only if the selected application -// supports headers. -bool QuicSession::SubmitInformation( - int64_t stream_id, - v8::Local<v8::Array> headers) { - return application_->SubmitInformation(stream_id, headers); -} - -// Submits initial headers only if the selected application -// supports headers. For http3, for instance, this is the -// method used to submit both request and response headers. -bool QuicSession::SubmitHeaders( - int64_t stream_id, - v8::Local<v8::Array> headers, - uint32_t flags) { - return application_->SubmitHeaders(stream_id, headers, flags); -} - -// Submits trailing headers only if the selected application -// supports headers. -bool QuicSession::SubmitTrailers( - int64_t stream_id, - v8::Local<v8::Array> headers) { - return application_->SubmitTrailers(stream_id, headers); -} - -// Submits a new push stream -BaseObjectPtr<QuicStream> QuicSession::SubmitPush( - int64_t stream_id, - v8::Local<v8::Array> headers) { - return application_->SubmitPush(stream_id, headers); -} - -} // namespace quic -} // namespace node - -#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS - -#endif // SRC_QUIC_NODE_QUIC_SESSION_INL_H_ diff --git a/src/quic/node_quic_session.cc b/src/quic/node_quic_session.cc deleted file mode 100644 index fce6b658a43..00000000000 --- a/src/quic/node_quic_session.cc +++ /dev/null @@ -1,3963 +0,0 @@ -#include "node_quic_session-inl.h" // NOLINT(build/include) -#include "aliased_buffer.h" -#include "aliased_struct-inl.h" -#include "allocated_buffer-inl.h" -#include "async_wrap-inl.h" -#include "debug_utils-inl.h" -#include "env-inl.h" -#include "crypto/crypto_common.h" -#include "ngtcp2/ngtcp2.h" -#include "nghttp3/nghttp3.h" // NGHTTP3_ALPN_H3 -#include "ngtcp2/ngtcp2_crypto.h" -#include "ngtcp2/ngtcp2_crypto_openssl.h" -#include "node.h" -#include "node_buffer.h" -#include "node_crypto.h" -#include "node_errors.h" -#include "node_internals.h" -#include "node_http_common-inl.h" -#include "node_mem-inl.h" -#include "node_process.h" -#include "node_quic_buffer-inl.h" -#include "node_quic_crypto.h" -#include "node_quic_socket-inl.h" -#include "node_quic_stream-inl.h" -#include "node_quic_util-inl.h" -#include "node_quic_default_application.h" -#include "node_quic_http3_application.h" -#include "node_sockaddr-inl.h" -#include "stream_base-inl.h" -#include "v8.h" -#include "uv.h" - -#include <algorithm> -#include <memory> -#include <vector> -#include <string> -#include <utility> - -namespace node { - -using crypto::EntropySource; -using crypto::SecureContext; - -using v8::Array; -using v8::ArrayBufferView; -using v8::Context; -using v8::FunctionCallbackInfo; -using v8::FunctionTemplate; -using v8::HandleScope; -using v8::Integer; -using v8::Local; -using v8::Number; -using v8::Object; -using v8::ObjectTemplate; -using v8::PropertyAttribute; -using v8::String; -using v8::Undefined; -using v8::Value; - -using TryCatchScope = node::errors::TryCatchScope; - -namespace quic { - -QuicCallbackScope::QuicCallbackScope(QuicSession* session) - : session_(session), - private_(new InternalCallbackScope( - session->env(), - session->object(), - { - session->get_async_id(), - session->get_trigger_async_id() - })), - try_catch_(session->env()->isolate()) { - try_catch_.SetVerbose(true); -} - -QuicCallbackScope::~QuicCallbackScope() { - Environment* env = session_->env(); - if (UNLIKELY(try_catch_.HasCaught())) { - session_->crypto_context()->set_in_client_hello(false); - session_->crypto_context()->set_in_ocsp_request(false); - if (!try_catch_.HasTerminated() && env->can_call_into_js()) { - session_->set_last_error({ - QUIC_ERROR_SESSION, - uint64_t{NGTCP2_INTERNAL_ERROR} - }); - session_->Close(); - CHECK(session_->is_destroyed()); - } - private_->MarkAsFailed(); - } -} - -typedef ssize_t(*ngtcp2_close_fn)( - ngtcp2_conn* conn, - ngtcp2_path* path, - uint8_t* dest, - size_t destlen, - uint64_t error_code, - ngtcp2_tstamp ts); - -namespace { -void SetConfig(QuicState* quic_state, int idx, uint64_t* val) { - AliasedFloat64Array& buffer = quic_state->quicsessionconfig_buffer; - uint64_t flags = static_cast<uint64_t>(buffer[IDX_QUIC_SESSION_CONFIG_COUNT]); - if (flags & (1ULL << idx)) - *val = static_cast<uint64_t>(buffer[idx]); -} - -// Forwards detailed(verbose) debugging information from ngtcp2. Enabled using -// the NODE_DEBUG_NATIVE=NGTCP2_DEBUG category. -void Ngtcp2DebugLog(void* user_data, const char* fmt, ...) { - va_list ap; - va_start(ap, fmt); - std::string format(fmt, strlen(fmt) + 1); - format[strlen(fmt)] = '\n'; - // Debug() does not work with the va_list here. So we use vfprintf - // directly instead. Ngtcp2DebugLog is only enabled when the debug - // category is enabled. - vfprintf(stderr, format.c_str(), ap); - va_end(ap); -} - -void CopyPreferredAddress( - uint8_t* dest, - size_t destlen, - uint16_t* port, - const sockaddr* addr) { - const sockaddr_in* src = reinterpret_cast<const sockaddr_in*>(addr); - memcpy(dest, &src->sin_addr, destlen); - *port = SocketAddress::GetPort(addr); -} - -ngtcp2_close_fn SelectCloseFn(uint32_t family) { - return family == QUIC_ERROR_APPLICATION ? - ngtcp2_conn_write_application_close : - ngtcp2_conn_write_connection_close; -} - -} // namespace - -std::string QuicSession::RemoteTransportParamsDebug::ToString() const { - ngtcp2_transport_params params; - ngtcp2_conn_get_remote_transport_params(session->connection(), ¶ms); - std::string out = "Remote Transport Params:\n"; - out += " Ack Delay Exponent: " + - std::to_string(params.ack_delay_exponent) + "\n"; - out += " Active Connection ID Limit: " + - std::to_string(params.active_connection_id_limit) + "\n"; - out += " Disable Active Migration: " + - std::string(params.disable_active_migration ? "Yes" : "No") + "\n"; - out += " Initial Max Data: " + - std::to_string(params.initial_max_data) + "\n"; - out += " Initial Max Stream Data Bidi Local: " + - std::to_string(params.initial_max_stream_data_bidi_local) + "\n"; - out += " Initial Max Stream Data Bidi Remote: " + - std::to_string(params.initial_max_stream_data_bidi_remote) + "\n"; - out += " Initial Max Stream Data Uni: " + - std::to_string(params.initial_max_stream_data_uni) + "\n"; - out += " Initial Max Streams Bidi: " + - std::to_string(params.initial_max_streams_bidi) + "\n"; - out += " Initial Max Streams Uni: " + - std::to_string(params.initial_max_streams_uni) + "\n"; - out += " Max Ack Delay: " + - std::to_string(params.max_ack_delay) + "\n"; - out += " Max Idle Timeout: " + - std::to_string(params.max_idle_timeout) + "\n"; - out += " Max Packet Size: " + - std::to_string(params.max_udp_payload_size) + "\n"; - - if (!session->is_server()) { - if (params.retry_scid_present) { - QuicCID cid(params.original_dcid); - QuicCID retry(params.retry_scid); - out += " Original Connection ID: " + cid.ToString() + "\n"; - out += " Retry SCID: " + retry.ToString() + "\n"; - } else { - out += " Original Connection ID: N/A \n"; - } - - if (params.preferred_address_present) { - out += " Preferred Address Present: Yes\n"; - // TODO(@jasnell): Serialize the IPv4 and IPv6 address options - } else { - out += " Preferred Address Present: No\n"; - } - - if (params.stateless_reset_token_present) { - StatelessResetToken token(params.stateless_reset_token); - out += " Stateless Reset Token: " + token.ToString() + "\n"; - } else { - out += " Stateless Reset Token: N/A"; - } - } - return out; -} - -void QuicSessionConfig::ResetToDefaults(QuicState* quic_state) { - ngtcp2_settings_default(this); - initial_ts = uv_hrtime(); - // Detailed(verbose) logging provided by ngtcp2 is only enabled - // when the NODE_DEBUG_NATIVE=NGTCP2_DEBUG category is used. - if (UNLIKELY(quic_state->env()->enabled_debug_list()->enabled( - DebugCategory::NGTCP2_DEBUG))) { - log_printf = Ngtcp2DebugLog; - } - transport_params.active_connection_id_limit = - DEFAULT_ACTIVE_CONNECTION_ID_LIMIT; - transport_params.initial_max_stream_data_bidi_local = - DEFAULT_MAX_STREAM_DATA_BIDI_LOCAL; - transport_params.initial_max_stream_data_bidi_remote = - DEFAULT_MAX_STREAM_DATA_BIDI_REMOTE; - transport_params.initial_max_stream_data_uni = - DEFAULT_MAX_STREAM_DATA_UNI; - transport_params.initial_max_streams_bidi = - DEFAULT_MAX_STREAMS_BIDI; - transport_params.initial_max_streams_uni = - DEFAULT_MAX_STREAMS_UNI; - transport_params.initial_max_data = DEFAULT_MAX_DATA; - transport_params.max_idle_timeout = DEFAULT_MAX_IDLE_TIMEOUT; - transport_params.max_udp_payload_size = - NGTCP2_DEFAULT_MAX_UDP_PAYLOAD_SIZE; - transport_params.max_ack_delay = - NGTCP2_DEFAULT_MAX_ACK_DELAY; - transport_params.disable_active_migration = 0; - transport_params.preferred_address_present = 0; - transport_params.stateless_reset_token_present = 0; - cc_algo = NGTCP2_CC_ALGO_RENO; -} - -// Sets the QuicSessionConfig using an AliasedBuffer for efficiency. -void QuicSessionConfig::Set( - QuicState* quic_state, - const sockaddr* preferred_addr) { - ResetToDefaults(quic_state); - SetConfig(quic_state, IDX_QUIC_SESSION_ACTIVE_CONNECTION_ID_LIMIT, - &transport_params.active_connection_id_limit); - SetConfig(quic_state, IDX_QUIC_SESSION_MAX_STREAM_DATA_BIDI_LOCAL, - &transport_params.initial_max_stream_data_bidi_local); - SetConfig(quic_state, IDX_QUIC_SESSION_MAX_STREAM_DATA_BIDI_REMOTE, - &transport_params.initial_max_stream_data_bidi_remote); - SetConfig(quic_state, IDX_QUIC_SESSION_MAX_STREAM_DATA_UNI, - &transport_params.initial_max_stream_data_uni); - SetConfig(quic_state, IDX_QUIC_SESSION_MAX_DATA, - &transport_params.initial_max_data); - SetConfig(quic_state, IDX_QUIC_SESSION_MAX_STREAMS_BIDI, - &transport_params.initial_max_streams_bidi); - SetConfig(quic_state, IDX_QUIC_SESSION_MAX_STREAMS_UNI, - &transport_params.initial_max_streams_uni); - SetConfig(quic_state, IDX_QUIC_SESSION_MAX_IDLE_TIMEOUT, - &transport_params.max_idle_timeout); - SetConfig(quic_state, IDX_QUIC_SESSION_MAX_UDP_PAYLOAD_SIZE, - &transport_params.max_udp_payload_size); - SetConfig(quic_state, IDX_QUIC_SESSION_MAX_ACK_DELAY, - &transport_params.max_ack_delay); - SetConfig(quic_state, - IDX_QUIC_SESSION_CC_ALGO, - reinterpret_cast<uint64_t *>(&cc_algo)); - - transport_params.max_idle_timeout = - transport_params.max_idle_timeout * 1000000000; - - // TODO(@jasnell): QUIC allows both IPv4 and IPv6 addresses to be - // specified. Here we're specifying one or the other. Need to - // determine if that's what we want or should we support both. - // - // TODO(@jasnell): Currently, this is specified as a single value - // that is used for all connections. In the future, it may be - // necessary to determine the preferred address based on the - // remote address. The trick, however, is that the preferred - // address must be selected before the QuicSession is created, - // before the handshake can be started. That is, it may need - // to be an optional callback on QuicSocket. That would incur - // a performance penalty so we'd really have to be sure of the - // utility. - if (preferred_addr != nullptr) { - transport_params.preferred_address_present = 1; - switch (preferred_addr->sa_family) { - case AF_INET: { - CopyPreferredAddress( - transport_params.preferred_address.ipv4_addr, - sizeof(transport_params.preferred_address.ipv4_addr), - &transport_params.preferred_address.ipv4_port, - preferred_addr); - break; - } - case AF_INET6: { - CopyPreferredAddress( - transport_params.preferred_address.ipv6_addr, - sizeof(transport_params.preferred_address.ipv6_addr), - &transport_params.preferred_address.ipv6_port, - preferred_addr); - break; - } - default: - UNREACHABLE(); - } - } -} - -void QuicSessionListener::OnKeylog(const char* line, size_t len) { - if (previous_listener_ != nullptr) - previous_listener_->OnKeylog(line, len); -} - -void QuicSessionListener::OnClientHello( - const char* alpn, - const char* server_name) { - if (previous_listener_ != nullptr) - previous_listener_->OnClientHello(alpn, server_name); -} - -QuicSessionListener::~QuicSessionListener() { - if (session_) - session_->RemoveListener(this); -} - -void QuicSessionListener::OnCert(const char* server_name) { - if (previous_listener_ != nullptr) - previous_listener_->OnCert(server_name); -} - -void QuicSessionListener::OnOCSP(Local<Value> ocsp) { - if (previous_listener_ != nullptr) - previous_listener_->OnOCSP(ocsp); -} - -void QuicSessionListener::OnStreamHeaders( - int64_t stream_id, - int kind, - const std::vector<std::unique_ptr<QuicHeader>>& headers, - int64_t push_id) { - if (previous_listener_ != nullptr) - previous_listener_->OnStreamHeaders(stream_id, kind, headers, push_id); -} - -void QuicSessionListener::OnStreamClose( - int64_t stream_id, - uint64_t app_error_code) { - if (previous_listener_ != nullptr) - previous_listener_->OnStreamClose(stream_id, app_error_code); -} - -void QuicSessionListener::OnStreamReset( - int64_t stream_id, - uint64_t app_error_code) { - if (previous_listener_ != nullptr) - previous_listener_->OnStreamReset(stream_id, app_error_code); -} - -void QuicSessionListener::OnSessionClose(QuicError error, int flags) { - if (previous_listener_ != nullptr) - previous_listener_->OnSessionClose(error, flags); -} - -void QuicSessionListener::OnStreamReady(BaseObjectPtr<QuicStream> stream) { - if (previous_listener_ != nullptr) - previous_listener_->OnStreamReady(stream); -} - -void QuicSessionListener::OnHandshakeCompleted() { - if (previous_listener_ != nullptr) - previous_listener_->OnHandshakeCompleted(); -} - -void QuicSessionListener::OnPathValidation( - ngtcp2_path_validation_result res, - const sockaddr* local, - const sockaddr* remote) { - if (previous_listener_ != nullptr) - previous_listener_->OnPathValidation(res, local, remote); -} - -void QuicSessionListener::OnSessionTicket(int size, SSL_SESSION* session) { - if (previous_listener_ != nullptr) { - previous_listener_->OnSessionTicket(size, session); - } -} - -void QuicSessionListener::OnStreamBlocked(int64_t stream_id) { - if (previous_listener_ != nullptr) { - previous_listener_->OnStreamBlocked(stream_id); - } -} - -void QuicSessionListener::OnUsePreferredAddress( - int family, - const PreferredAddress& preferred_address) { - if (previous_listener_ != nullptr) - previous_listener_->OnUsePreferredAddress(family, preferred_address); -} - -void QuicSessionListener::OnVersionNegotiation( - uint32_t supported_version, - const uint32_t* versions, - size_t vcnt) { - if (previous_listener_ != nullptr) - previous_listener_->OnVersionNegotiation(supported_version, versions, vcnt); -} - -void QuicSessionListener::OnQLog(QLogStream* qlog_stream) { - if (previous_listener_ != nullptr) - previous_listener_->OnQLog(qlog_stream); -} - -void JSQuicSessionListener::OnKeylog(const char* line, size_t len) { - Environment* env = session()->env(); - - HandleScope handle_scope(env->isolate()); - Context::Scope context_scope(env->context()); - - QuicCallbackScope cb_scope(session()); - - Local<Value> line_buf; - if (!Buffer::Copy(env, line, 1 + len).ToLocal(&line_buf)) - return; - - char* data = Buffer::Data(line_buf); - data[len] = '\n'; - - // Grab a shared pointer to this to prevent the QuicSession - // from being freed while the MakeCallback is running. - BaseObjectPtr<QuicSession> ptr(session()); - USE(env->quic_on_session_keylog_function()->Call( - env->context(), - session()->object(), - 1, - &line_buf)); -} - -void JSQuicSessionListener::OnStreamBlocked(int64_t stream_id) { - Environment* env = session()->env(); - - HandleScope handle_scope(env->isolate()); - Context::Scope context_scope(env->context()); - - QuicCallbackScope cb_scope(session()); - - BaseObjectPtr<QuicStream> stream = session()->FindStream(stream_id); - USE(env->quic_on_stream_blocked_function()->Call( - env->context(), - stream->object(), - 0, nullptr)); -} - -void JSQuicSessionListener::OnClientHello( - const char* alpn, - const char* server_name) { - - Environment* env = session()->env(); - HandleScope scope(env->isolate()); - Context::Scope context_scope(env->context()); - - // Why this instead of using MakeCallback? We need to catch any - // errors that happen both when preparing the arguments and - // invoking the callback so that we can properly signal a failure - // to the peer. - QuicCallbackScope cb_scope(session()); - - Local<Array> ciphers; - Local<Value> alpn_string = Undefined(env->isolate()); - Local<Value> servername = Undefined(env->isolate()); - - if (!session()->crypto_context()->hello_ciphers().ToLocal(&ciphers) || - (alpn != nullptr && - !String::NewFromUtf8( - env->isolate(), - alpn).ToLocal(&alpn_string)) || - (server_name != nullptr && - !String::NewFromUtf8( - env->isolate(), - server_name).ToLocal(&servername))) { - return; - } - - Local<Value> argv[] = { - alpn_string, - servername, - ciphers - }; - - // Grab a shared pointer to this to prevent the QuicSession - // from being freed while the MakeCallback is running. - BaseObjectPtr<QuicSession> ptr(session()); - USE(env->quic_on_session_client_hello_function()->Call( - env->context(), - session()->object(), - arraysize(argv), - argv)); -} - -void JSQuicSessionListener::OnCert(const char* server_name) { - Environment* env = session()->env(); - HandleScope handle_scope(env->isolate()); - Context::Scope context_scope(env->context()); - - QuicCallbackScope cb_scope(session()); - - Local<Value> servername = Undefined(env->isolate()); - if (UNLIKELY(server_name != nullptr && - !String::NewFromUtf8( - env->isolate(), - server_name, - v8::NewStringType::kNormal, - strlen(server_name)).ToLocal(&servername))) { - return; - } - - BaseObjectPtr<QuicSession> ptr(session()); - - USE(env->quic_on_session_cert_function()->Call( - env->context(), - session()->object(), - 1, &servername)); -} - -void JSQuicSessionListener::OnStreamHeaders( - int64_t stream_id, - int kind, - const std::vector<std::unique_ptr<QuicHeader>>& headers, - int64_t push_id) { - Environment* env = session()->env(); - HandleScope scope(env->isolate()); - Context::Scope context_scope(env->context()); - MaybeStackBuffer<Local<Value>, 16> head(headers.size()); - size_t n = 0; - - QuicCallbackScope cb_scope(session()); - - for (const auto& header : headers) { - Local<Value> pair[2]; - - if (UNLIKELY(!header->GetName(session()->application()).ToLocal(&pair[0]))) - return; - - if (UNLIKELY(!header->GetValue(session()->application()).ToLocal(&pair[1]))) - return; - - head[n++] = Array::New(env->isolate(), pair, arraysize(pair)); - } - - Local<Value> argv[] = { - Number::New(env->isolate(), static_cast<double>(stream_id)), - Array::New(env->isolate(), head.out(), n), - Integer::New(env->isolate(), kind), - Undefined(env->isolate()) - }; - - if (kind == QUICSTREAM_HEADERS_KIND_PUSH) - argv[3] = Number::New(env->isolate(), static_cast<double>(push_id)); - - BaseObjectPtr<QuicSession> ptr(session()); - - USE(env->quic_on_stream_headers_function()->Call( - env->context(), - session()->object(), - arraysize(argv), - argv)); -} - -void JSQuicSessionListener::OnOCSP(Local<Value> ocsp) { - Environment* env = session()->env(); - HandleScope scope(env->isolate()); - Context::Scope context_scope(env->context()); - QuicCallbackScope cb_scope(session()); - BaseObjectPtr<QuicSession> ptr(session()); - USE(env->quic_on_session_status_function()->Call( - env->context(), - session()->object(), - 1, &ocsp)); -} - -void JSQuicSessionListener::OnStreamClose( - int64_t stream_id, - uint64_t app_error_code) { - Environment* env = session()->env(); - HandleScope scope(env->isolate()); - Context::Scope context_scope(env->context()); - - QuicCallbackScope cb_scope(session()); - - Local<Value> argv[] = { - Number::New(env->isolate(), static_cast<double>(stream_id)), - Number::New(env->isolate(), static_cast<double>(app_error_code)) - }; - - // Grab a shared pointer to this to prevent the QuicSession - // from being freed while the MakeCallback is running. - BaseObjectPtr<QuicSession> ptr(session()); - - USE(env->quic_on_stream_close_function()->Call( - env->context(), - session()->object(), - arraysize(argv), - argv)); -} - -void JSQuicSessionListener::OnStreamReset( - int64_t stream_id, - uint64_t app_error_code) { - Environment* env = session()->env(); - HandleScope scope(env->isolate()); - Context::Scope context_scope(env->context()); - - QuicCallbackScope cb_scope(session()); - - Local<Value> argv[] = { - Number::New(env->isolate(), static_cast<double>(stream_id)), - Number::New(env->isolate(), static_cast<double>(app_error_code)) - }; - // Grab a shared pointer to this to prevent the QuicSession - // from being freed while the MakeCallback is running. - BaseObjectPtr<QuicSession> ptr(session()); - - USE(env->quic_on_stream_reset_function()->Call( - env->context(), - session()->object(), - arraysize(argv), - argv)); -} - -void JSQuicSessionListener::OnSessionClose(QuicError error, int flags) { - Environment* env = session()->env(); - HandleScope scope(env->isolate()); - Context::Scope context_scope(env->context()); - - QuicCallbackScope cb_scope(session()); - - Local<Value> argv[] = { - Number::New(env->isolate(), static_cast<double>(error.code)), - Integer::New(env->isolate(), error.family), - flags & SESSION_CLOSE_FLAG_SILENT - ? v8::True(env->isolate()) - : v8::False(env->isolate()), - flags & SESSION_CLOSE_FLAG_STATELESS_RESET - ? v8::True(env->isolate()) - : v8::False(env->isolate()) - }; - - // Grab a shared pointer to this to prevent the QuicSession - // from being freed while the MakeCallback is running. - BaseObjectPtr<QuicSession> ptr(session()); - USE(env->quic_on_session_close_function()->Call( - env->context(), - session()->object(), - arraysize(argv), - argv)); -} - -void JSQuicSessionListener::OnStreamReady(BaseObjectPtr<QuicStream> stream) { - Environment* env = session()->env(); - HandleScope scope(env->isolate()); - Context::Scope context_scope(env->context()); - - QuicCallbackScope cb_scope(session()); - - Local<Value> argv[] = { - stream->object(), - Number::New(env->isolate(), static_cast<double>(stream->id())), - Number::New(env->isolate(), static_cast<double>(stream->push_id())) - }; - - // Grab a shared pointer to this to prevent the QuicSession - // from being freed while the MakeCallback is running. - BaseObjectPtr<QuicSession> ptr(session()); - - USE(env->quic_on_stream_ready_function()->Call( - env->context(), - session()->object(), - arraysize(argv), - argv)); -} - -void JSQuicSessionListener::OnHandshakeCompleted() { - Environment* env = session()->env(); - HandleScope scope(env->isolate()); - Context::Scope context_scope(env->context()); - - QuicCryptoContext* ctx = session()->crypto_context(); - - QuicCallbackScope cb_scope(session()); - - Local<Value> servername = Undefined(env->isolate()); - Local<Value> validationErrorReason = v8::Null(env->isolate()); - Local<Value> validationErrorCode = v8::Null(env->isolate()); - Local<Value> cipher_name; - Local<Value> cipher_version; - - const char* hostname = ctx->servername(); - if (hostname != nullptr && - !String::NewFromUtf8(env->isolate(), hostname).ToLocal(&servername)) { - return; - } - - if (!ctx->cipher_name().ToLocal(&cipher_name) || - !ctx->cipher_version().ToLocal(&cipher_version)) { - return; - } - - int err = ctx->VerifyPeerIdentity(); - if (err != X509_V_OK && - (!crypto::GetValidationErrorReason(env, err) - .ToLocal(&validationErrorReason) || - !crypto::GetValidationErrorCode(env, err) - .ToLocal(&validationErrorCode))) { - return; - } - - Local<Value> argv[] = { - servername, - GetALPNProtocol(*session()), - cipher_name, - cipher_version, - Integer::New(env->isolate(), session()->max_pktlen_), - validationErrorReason, - validationErrorCode, - session()->crypto_context()->early_data() ? - v8::True(env->isolate()) : - v8::False(env->isolate()) - }; - - // Grab a shared pointer to this to prevent the QuicSession - // from being freed while the MakeCallback is running. - BaseObjectPtr<QuicSession> ptr(session()); - - USE(env->quic_on_session_handshake_function()->Call( - env->context(), - session()->object(), - arraysize(argv), - argv)); -} - -void JSQuicSessionListener::OnPathValidation( - ngtcp2_path_validation_result res, - const sockaddr* local, - const sockaddr* remote) { - // This is a fairly expensive operation because both the local and - // remote addresses have to converted into JavaScript objects. We - // only do this if a pathValidation handler is registered. - Environment* env = session()->env(); - HandleScope scope(env->isolate()); - Local<Context> context = env->context(); - Context::Scope context_scope(context); - - QuicCallbackScope cb_scope(session()); - - Local<Value> argv[] = { - Integer::New(env->isolate(), res), - AddressToJS(env, local), - AddressToJS(env, remote) - }; - // Grab a shared pointer to this to prevent the QuicSession - // from being freed while the MakeCallback is running. - BaseObjectPtr<QuicSession> ptr(session()); - - USE(env->quic_on_session_path_validation_function()->Call( - env->context(), - session()->object(), - arraysize(argv), - argv)); -} - -void JSQuicSessionListener::OnSessionTicket(int size, SSL_SESSION* sess) { - Environment* env = session()->env(); - HandleScope scope(env->isolate()); - Context::Scope context_scope(env->context()); - - QuicCallbackScope cb_scope(session()); - - Local<Value> argv[] = { - v8::Undefined(env->isolate()), - v8::Undefined(env->isolate()) - }; - - if (size > 0) { - AllocatedBuffer session_ticket = - AllocatedBuffer::AllocateManaged(env, size); - unsigned char* session_data = - reinterpret_cast<unsigned char*>(session_ticket.data()); - memset(session_data, 0, size); - if (i2d_SSL_SESSION(sess, &session_data) > 0 && - !session_ticket.ToBuffer().ToLocal(&argv[0])) { - return; - } - } - - if (session()->is_transport_params_set() && - !Buffer::Copy(env, - reinterpret_cast<const char*>(&session()->transport_params_), - sizeof(session()->transport_params_)).ToLocal(&argv[1])) { - return; - } - - // Grab a shared pointer to this to prevent the QuicSession - // from being freed while the MakeCallback is running. - BaseObjectPtr<QuicSession> ptr(session()); - - USE(env->quic_on_session_ticket_function()->Call( - env->context(), - session()->object(), - arraysize(argv), - argv)); -} - -void JSQuicSessionListener::OnUsePreferredAddress( - int family, - const PreferredAddress& preferred_address) { - Environment* env = session()->env(); - HandleScope scope(env->isolate()); - Local<Context> context = env->context(); - Context::Scope context_scope(context); - - QuicCallbackScope cb_scope(session()); - - std::string hostname = family == AF_INET ? - preferred_address.ipv4_address(): - preferred_address.ipv6_address(); - uint16_t port = - family == AF_INET ? - preferred_address.ipv4_port() : - preferred_address.ipv6_port(); - - Local<Value> argv[] = { - OneByteString(env->isolate(), hostname.c_str()), - Integer::NewFromUnsigned(env->isolate(), port), - Integer::New(env->isolate(), family) - }; - - BaseObjectPtr<QuicSession> ptr(session()); - - USE(env->quic_on_session_use_preferred_address_function()->Call( - env->context(), - session()->object(), - arraysize(argv), - argv)); -} - -void JSQuicSessionListener::OnVersionNegotiation( - uint32_t supported_version, - const uint32_t* vers, - size_t vcnt) { - Environment* env = session()->env(); - HandleScope scope(env->isolate()); - Local<Context> context = env->context(); - Context::Scope context_scope(context); - - QuicCallbackScope cb_scope(session()); - - MaybeStackBuffer<Local<Value>, 4> versions(vcnt); - for (size_t n = 0; n < vcnt; n++) - versions[n] = Integer::New(env->isolate(), vers[n]); - - // Currently, we only support one version of QUIC but in - // the future that may change. The callback below passes - // an array back to the JavaScript side to future-proof. - Local<Value> supported = - Integer::New(env->isolate(), supported_version); - - Local<Value> argv[] = { - Integer::New(env->isolate(), NGTCP2_PROTO_VER), - Array::New(env->isolate(), versions.out(), vcnt), - Array::New(env->isolate(), &supported, 1) - }; - - // Grab a shared pointer to this to prevent the QuicSession - // from being freed while the MakeCallback is running. - BaseObjectPtr<QuicSession> ptr(session()); - USE(env->quic_on_session_version_negotiation_function()->Call( - env->context(), - session()->object(), - arraysize(argv), - argv)); -} - -void JSQuicSessionListener::OnQLog(QLogStream* qlog_stream) { - CHECK_NOT_NULL(qlog_stream); - Environment* env = session()->env(); - HandleScope handle_scope(env->isolate()); - Context::Scope context_scope(env->context()); - QuicCallbackScope cb_scope(session()); - Local<Value> obj = qlog_stream->object(); - USE(env->quic_on_session_qlog_function()->Call( - env->context(), - session()->object(), - 1, &obj)); -} - -// Generates a new random connection ID. -void QuicSession::RandomConnectionIDStrategy( - QuicSession* session, - ngtcp2_cid* cid, - size_t cidlen) { - // CID min and max length is determined by the QUIC specification. - CHECK_LE(cidlen, NGTCP2_MAX_CIDLEN); - CHECK_GE(cidlen, NGTCP2_MIN_CIDLEN); - cid->datalen = cidlen; - // cidlen shouldn't ever be zero here but just in case that - // behavior changes in ngtcp2 in the future... - if (LIKELY(cidlen > 0)) - EntropySource(cid->data, cidlen); -} - -// Check required capabilities were not excluded from the OpenSSL build: -// - OPENSSL_NO_SSL_TRACE excludes SSL_trace() -// - OPENSSL_NO_STDIO excludes BIO_new_fp() -// HAVE_SSL_TRACE is available on the internal tcp_wrap binding for the tests. -#if defined(OPENSSL_NO_SSL_TRACE) || defined(OPENSSL_NO_STDIO) -# define HAVE_SSL_TRACE 0 -#else -# define HAVE_SSL_TRACE 1 -#endif - -QuicCryptoContext::~QuicCryptoContext() { - // Free any remaining crypto handshake data (if any) - Cancel(); -} - -void QuicCryptoContext::MemoryInfo(MemoryTracker* tracker) const { - tracker->TrackField("initial_crypto", handshake_[0]); - tracker->TrackField("handshake_crypto", handshake_[1]); - tracker->TrackField("app_crypto", handshake_[2]); - tracker->TrackField("ocsp_response", ocsp_response_); -} - -bool QuicCryptoContext::SetSecrets( - ngtcp2_crypto_level level, - const uint8_t* rx_secret, - const uint8_t* tx_secret, - size_t secretlen) { - - static constexpr int kCryptoKeylen = 64; - static constexpr int kCryptoIvlen = 64; - static constexpr char kQuicClientEarlyTrafficSecret[] = - "QUIC_CLIENT_EARLY_TRAFFIC_SECRET"; - static constexpr char kQuicClientHandshakeTrafficSecret[] = - "QUIC_CLIENT_HANDSHAKE_TRAFFIC_SECRET"; - static constexpr char kQuicClientTrafficSecret0[] = - "QUIC_CLIENT_TRAFFIC_SECRET_0"; - static constexpr char kQuicServerHandshakeTrafficSecret[] = - "QUIC_SERVER_HANDSHAKE_TRAFFIC_SECRET"; - static constexpr char kQuicServerTrafficSecret[] = - "QUIC_SERVER_TRAFFIC_SECRET_0"; - - uint8_t rx_key[kCryptoKeylen]; - uint8_t rx_hp[kCryptoKeylen]; - uint8_t tx_key[kCryptoKeylen]; - uint8_t tx_hp[kCryptoKeylen]; - uint8_t rx_iv[kCryptoIvlen]; - uint8_t tx_iv[kCryptoIvlen]; - - if (NGTCP2_ERR(ngtcp2_crypto_derive_and_install_rx_key( - session()->connection(), - rx_key, - rx_iv, - rx_hp, - level, - rx_secret, - secretlen))) { - return false; - } - - if (NGTCP2_ERR(ngtcp2_crypto_derive_and_install_tx_key( - session()->connection(), - tx_key, - tx_iv, - tx_hp, - level, - tx_secret, - secretlen))) { - return false; - } - - switch (level) { - case NGTCP2_CRYPTO_LEVEL_EARLY: - crypto::LogSecret( - ssl_, - kQuicClientEarlyTrafficSecret, - rx_secret, - secretlen); - break; - case NGTCP2_CRYPTO_LEVEL_HANDSHAKE: - crypto::LogSecret( - ssl_, - kQuicClientHandshakeTrafficSecret, - rx_secret, - secretlen); - crypto::LogSecret( - ssl_, - kQuicServerHandshakeTrafficSecret, - tx_secret, - secretlen); - break; - case NGTCP2_CRYPTO_LEVEL_APP: - crypto::LogSecret( - ssl_, - kQuicClientTrafficSecret0, - rx_secret, - secretlen); - crypto::LogSecret( - ssl_, - kQuicServerTrafficSecret, - tx_secret, - secretlen); - break; - default: - UNREACHABLE(); - } - - return true; -} - -void QuicCryptoContext::AcknowledgeCryptoData( - ngtcp2_crypto_level level, - uint64_t datalen) { - // It is possible for the QuicSession to have been destroyed but not yet - // deconstructed. In such cases, we want to ignore the callback as there - // is nothing to do but wait for further cleanup to happen. - if (UNLIKELY(session_->is_destroyed())) - return; - Debug(session(), - "Acknowledging %" PRIu64 " crypto bytes for %s level", - datalen, - crypto_level_name(level)); - - // Consumes (frees) the given number of bytes in the handshake buffer. - handshake_[level].Consume(static_cast<size_t>(datalen)); - - // Update the statistics for the handshake, allowing us to track - // how long the handshake is taking to be acknowledged. A malicious - // peer could potentially force the QuicSession to hold on to - // crypto data for a long time by not sending an acknowledgement. - // The histogram will allow us to track the time periods between - // acknowlegements. - session()->RecordAck(&QuicSessionStats::handshake_acked_at); -} - -void QuicCryptoContext::EnableTrace() { -#if HAVE_SSL_TRACE - if (!bio_trace_) { - bio_trace_.reset(BIO_new_fp(stderr, BIO_NOCLOSE | BIO_FP_TEXT)); - SSL_set_msg_callback( - ssl_.get(), - [](int write_p, - int version, - int content_type, - const void* buf, - size_t len, - SSL* ssl, - void* arg) -> void { - crypto::MarkPopErrorOnReturn mark_pop_error_on_return; - SSL_trace(write_p, version, content_type, buf, len, ssl, arg); - }); - SSL_set_msg_callback_arg(ssl_.get(), bio_trace_.get()); - } -#endif -} - -// If a 'clientHello' event listener is registered on the JavaScript -// QuicServerSession object, the STATE_CLIENT_HELLO_ENABLED state -// will be set and the OnClientHello will cause the 'clientHello' -// event to be emitted. -// -// The 'clientHello' callback will be given it's own callback function -// that must be called when the client has completed handling the event. -// The handshake will not continue until it is called. -// -// The intent here is to allow user code the ability to modify or -// replace the SecurityContext based on the server name, ALPN, or -// other handshake characteristics. -// -// The user can also set a 'cert' event handler that will be called -// when the peer certificate is received, allowing additional tweaks -// and verifications to be performed. -int QuicCryptoContext::OnClientHello() { - if (LIKELY(session_->state_->client_hello_enabled == 0)) - return 0; - - TLSCallbackScope callback_scope(this); - - if (is_in_client_hello()) - return -1; - set_in_client_hello(); - - QuicCryptoContext* ctx = session_->crypto_context(); - session_->listener()->OnClientHello( - ctx->hello_alpn(), - ctx->hello_servername()); - - // Returning -1 here will keep the TLS handshake paused until the - // client hello callback is invoked. Returning 0 means that the - // handshake is ready to proceed. When the OnClientHello callback - // is called above, it may be resolved synchronously or asynchronously. - // In case it is resolved synchronously, we need the check below. - return is_in_client_hello() ? -1 : 0; -} - -// The OnCert callback provides an opportunity to prompt the server to -// perform on OCSP request on behalf of the client (when the client -// requests it). If there is a listener for the 'OCSPRequest' event -// on the JavaScript side, the IDX_QUIC_SESSION_STATE_CERT_ENABLED -// session state slot will equal 1, which will cause the callback to -// be invoked. The callback will be given a reference to a JavaScript -// function that must be called in order for the TLS handshake to -// continue. -int QuicCryptoContext::OnOCSP() { - if (LIKELY(session_->state_->ocsp_enabled == 0)) { - Debug(session(), "No OCSPRequest handler registered"); - return 1; - } - - if (!session_->is_server()) - return 1; - - Debug(session(), "Client is requesting an OCSP Response"); - TLSCallbackScope callback_scope(this); - - // As in node_crypto.cc, this is not an error, but does suspend the - // handshake to continue when OnOCSP is complete. - if (is_in_ocsp_request()) - return -1; - set_in_ocsp_request(); - - session_->listener()->OnCert(session_->crypto_context()->servername()); - - // Returning -1 here means that we are still waiting for the OCSP - // request to be completed. When the OnCert handler is invoked - // above, it can be resolve synchronously or asynchonously. If - // resolved synchronously, we need the check below. - return is_in_ocsp_request() ? -1 : 1; -} - -void QuicCryptoContext::OnClientHelloDone( - BaseObjectPtr<SecureContext> context) { - Debug(session(), - "ClientHello completed. Context Provided? %s\n", - context ? "Yes" : "No"); - - // Continue the TLS handshake when this function exits - // otherwise it will stall and fail. - TLSHandshakeScope handshake_scope( - this, - [&]() { set_in_client_hello(false); }); - - // Disable the callback at this point so we don't loop continuously - session_->state_->client_hello_enabled = 0; - - if (context) { - int err = crypto::UseSNIContext(ssl_, context); - if (!err) { - unsigned long err = ERR_get_error(); // NOLINT(runtime/int) - return !err ? - THROW_ERR_QUIC_FAILURE_SETTING_SNI_CONTEXT(session_->env()) : - crypto::ThrowCryptoError(session_->env(), err); - } - secure_context_ = context; - } -} - -// The OnCertDone function is called by the QuicSessionOnCertDone -// function when usercode is done handling the OCSPRequest event. -void QuicCryptoContext::OnOCSPDone(Local<Value> ocsp_response) { - Debug(session(), - "OCSPRequest completed. Response Provided? %s", - ocsp_response->IsArrayBufferView() ? "Yes" : "No"); - // Continue the TLS handshake when this function exits - // otherwise it will stall and fail. - TLSHandshakeScope handshake_scope( - this, - [&]() { set_in_ocsp_request(false); }); - - // Disable the callback at this point so we don't loop continuously - session_->state_->ocsp_enabled = 0; - - if (ocsp_response->IsArrayBufferView()) { - ocsp_response_.Reset( - session_->env()->isolate(), - ocsp_response.As<ArrayBufferView>()); - } -} - -// At this point in time, the TLS handshake secrets have been -// generated by openssl for this end of the connection and are -// ready to be used. Within this function, we need to install -// the secrets into the ngtcp2 connection object, store the -// remote transport parameters, and begin initialization of -// the QuicApplication that was selected. -bool QuicCryptoContext::OnSecrets( - ngtcp2_crypto_level level, - const uint8_t* rx_secret, - const uint8_t* tx_secret, - size_t secretlen) { - - Debug(session(), - "Received secrets for %s crypto level", - crypto_level_name(level)); - - if (!SetSecrets(level, rx_secret, tx_secret, secretlen)) - return false; - - if (level == NGTCP2_CRYPTO_LEVEL_APP) { - session_->set_remote_transport_params(); - if (!session()->InitApplication()) - return false; - } - - return true; -} - -// When the client has requested OSCP, this function will be called to provide -// the OSCP response. The OnCert() callback should have already been called -// by this point if any data is to be provided. If it hasn't, and ocsp_response_ -// is empty, no OCSP response will be sent. -int QuicCryptoContext::OnTLSStatus() { - Environment* env = session_->env(); - HandleScope scope(env->isolate()); - Context::Scope context_scope(env->context()); - switch (side_) { - case NGTCP2_CRYPTO_SIDE_SERVER: { - if (ocsp_response_.IsEmpty()) { - Debug(session(), "There is no OCSP response"); - return SSL_TLSEXT_ERR_NOACK; - } - - Local<ArrayBufferView> obj = - PersistentToLocal::Default( - env->isolate(), - ocsp_response_); - size_t len = obj->ByteLength(); - - unsigned char* data = crypto::MallocOpenSSL<unsigned char>(len); - obj->CopyContents(data, len); - - Debug(session(), "There is an OCSP response of %d bytes", len); - - if (!SSL_set_tlsext_status_ocsp_resp(ssl_.get(), data, len)) - OPENSSL_free(data); - - ocsp_response_.Reset(); - - return SSL_TLSEXT_ERR_OK; - } - case NGTCP2_CRYPTO_SIDE_CLIENT: { - // Only invoke the callback if the ocsp handler is actually set - if (LIKELY(session_->state_->ocsp_enabled == 0)) - return 1; - Local<Value> res; - if (ocsp_response().ToLocal(&res)) - session_->listener()->OnOCSP(res); - return 1; - } - default: - UNREACHABLE(); - } -} - -// Called by ngtcp2 when a chunk of peer TLS handshake data is received. -// For every chunk, we move the TLS handshake further along until it -// is complete. -int QuicCryptoContext::Receive( - ngtcp2_crypto_level crypto_level, - uint64_t offset, - const uint8_t* data, - size_t datalen) { - if (UNLIKELY(session_->is_destroyed())) - return NGTCP2_ERR_CALLBACK_FAILURE; - - // Statistics are collected so we can monitor how long the - // handshake is taking to operate and complete. - if (session_->GetStat(&QuicSessionStats::handshake_start_at) == 0) - session_->RecordTimestamp(&QuicSessionStats::handshake_start_at); - session_->RecordTimestamp(&QuicSessionStats::handshake_continue_at); - - Debug(session(), "Receiving %d bytes of crypto data", datalen); - - // Internally, this passes the handshake data off to openssl - // for processing. The handshake may or may not complete. - int ret = ngtcp2_crypto_read_write_crypto_data( - session_->connection(), - crypto_level, - data, - datalen); - switch (ret) { - case 0: - return 0; - // In either of following cases, the handshake is being - // paused waiting for user code to take action (for instance - // OCSP requests or client hello modification) - case NGTCP2_CRYPTO_ERR_TLS_WANT_X509_LOOKUP: - Debug(session(), "TLS handshake wants X509 Lookup"); - return 0; - case NGTCP2_CRYPTO_ERR_TLS_WANT_CLIENT_HELLO_CB: - Debug(session(), "TLS handshake wants client hello callback"); - return 0; - default: - return ret; - } -} - -// Triggers key update to begin. This will fail and return false -// if either a previous key update is in progress and has not been -// confirmed or if the initial handshake has not yet been confirmed. -bool QuicCryptoContext::InitiateKeyUpdate() { - if (UNLIKELY(session_->is_destroyed())) - return false; - - // There's no user code that should be able to run while UpdateKey - // is running, but we need to gate on it just to be safe. - auto leave = OnScopeLeave([&]() { set_in_key_update(false); }); - CHECK(!is_in_key_update()); - set_in_key_update(); - Debug(session(), "Initiating Key Update"); - - session_->IncrementStat(&QuicSessionStats::keyupdate_count); - - return ngtcp2_conn_initiate_key_update( - session_->connection(), - uv_hrtime()) == 0; -} - -int QuicCryptoContext::VerifyPeerIdentity() { - return crypto::VerifyPeerCertificate(ssl_); -} - -// Write outbound TLS handshake data into the ngtcp2 connection -// to prepare it to be serialized. The outbound data must be -// stored in the handshake_ until it is acknowledged by the -// remote peer. It's important to keep in mind that there is -// a potential security risk here -- that is, a malicious peer -// can cause the local session to keep sent handshake data in -// memory by failing to acknowledge it or slowly acknowledging -// it. We currently do not track how much data is being buffered -// here but we do record statistics on how long the handshake -// data is foreced to be kept in memory. -void QuicCryptoContext::WriteHandshake( - ngtcp2_crypto_level level, - const uint8_t* data, - size_t datalen) { - Debug(session(), - "Writing %d bytes of %s handshake data.", - datalen, - crypto_level_name(level)); - std::unique_ptr<QuicBufferChunk> buffer = - std::make_unique<QuicBufferChunk>(datalen); - memcpy(buffer->out(), data, datalen); - session_->RecordTimestamp(&QuicSessionStats::handshake_send_at); - CHECK_EQ( - ngtcp2_conn_submit_crypto_data( - session_->connection(), - level, - buffer->out(), - datalen), 0); - handshake_[level].Push(std::move(buffer)); -} - -void QuicApplication::Acknowledge( - int64_t stream_id, - uint64_t offset, - size_t datalen) { - BaseObjectPtr<QuicStream> stream = session()->FindStream(stream_id); - if (LIKELY(stream)) { - stream->Acknowledge(offset, datalen); - ResumeStream(stream_id); - } -} - -bool QuicApplication::SendPendingData() { - // The maximum number of packets to send per call - static constexpr size_t kMaxPackets = 16; - QuicPathStorage path; - std::unique_ptr<QuicPacket> packet; - uint8_t* pos = nullptr; - size_t packets_sent = 0; - int err; - - for (;;) { - ssize_t ndatalen; - StreamData stream_data; - err = GetStreamData(&stream_data); - if (err < 0) { - session()->set_last_error(QUIC_ERROR_APPLICATION, err); - return false; - } - - // If stream_data.id is -1, then we're not serializing any data for any - // specific stream. We still need to process QUIC session packets tho. - if (stream_data.id > -1) - Debug(session(), "Serializing packets for stream id %" PRId64, - stream_data.id); - else - Debug(session(), "Serializing session packets"); - - // If the packet was sent previously, then packet will have been reset. - if (!packet) { - packet = CreateStreamDataPacket(); - pos = packet->data(); - } - - ssize_t nwrite = WriteVStream(&path, pos, &ndatalen, stream_data); - - if (nwrite <= 0) { - switch (nwrite) { - case 0: - goto congestion_limited; - case NGTCP2_ERR_PKT_NUM_EXHAUSTED: - // There is a finite number of packets that can be sent - // per connection. Once those are exhausted, there's - // absolutely nothing we can do except immediately - // and silently tear down the QuicSession. This has - // to be silent because we can't even send a - // CONNECTION_CLOSE since even those require a - // packet number. - session()->Close(QuicSessionListener::SESSION_CLOSE_FLAG_SILENT); - return false; - case NGTCP2_ERR_STREAM_DATA_BLOCKED: - session()->StreamDataBlocked(stream_data.id); - if (session()->max_data_left() == 0) - goto congestion_limited; - // Fall through - case NGTCP2_ERR_STREAM_SHUT_WR: - if (UNLIKELY(!BlockStream(stream_data.id))) - return false; - continue; - case NGTCP2_ERR_STREAM_NOT_FOUND: - continue; - case NGTCP2_ERR_WRITE_MORE: - CHECK_GT(ndatalen, 0); - CHECK(StreamCommit(&stream_data, ndatalen)); - pos += ndatalen; - continue; - } - session()->set_last_error(QUIC_ERROR_SESSION, static_cast<int>(nwrite)); - return false; - } - - pos += nwrite; - - if (ndatalen >= 0) - CHECK(StreamCommit(&stream_data, ndatalen)); - - Debug(session(), "Sending %" PRIu64 " bytes in serialized packet", nwrite); - packet->set_length(nwrite); - if (!session()->SendPacket(std::move(packet), path)) - return false; - packet.reset(); - pos = nullptr; - MaybeSetFin(stream_data); - if (++packets_sent == kMaxPackets) - break; - } - return true; - -congestion_limited: - // We are either congestion limited or done. - if (pos - packet->data()) { - // Some data was serialized into the packet. We need to send it. - packet->set_length(pos - packet->data()); - Debug(session(), "Congestion limited, but %" PRIu64 " bytes pending", - packet->length()); - if (!session()->SendPacket(std::move(packet), path)) - return false; - } - return true; -} - -ssize_t QuicApplication::WriteVStream( - QuicPathStorage* path, - uint8_t* buf, - ssize_t* ndatalen, - const StreamData& stream_data) { - CHECK_LE(stream_data.count, kMaxVectorCount); - - uint32_t flags = NGTCP2_WRITE_STREAM_FLAG_NONE; - if (stream_data.remaining > 0) - flags |= NGTCP2_WRITE_STREAM_FLAG_MORE; - if (stream_data.fin) - flags |= NGTCP2_WRITE_STREAM_FLAG_FIN; - - return ngtcp2_conn_writev_stream( - session()->connection(), - &path->path, - buf, - session()->max_packet_length(), - ndatalen, - flags, - stream_data.id, - stream_data.buf, - stream_data.count, - uv_hrtime()); -} - -void QuicApplication::MaybeSetFin(const StreamData& stream_data) { - if (ShouldSetFin(stream_data)) - set_stream_fin(stream_data.id); -} - -void QuicApplication::StreamHeaders( - int64_t stream_id, - int kind, - const std::vector<std::unique_ptr<QuicHeader>>& headers, - int64_t push_id) { - session()->listener()->OnStreamHeaders(stream_id, kind, headers, push_id); -} - -void QuicApplication::StreamClose( - int64_t stream_id, - uint64_t app_error_code) { - BaseObjectPtr<QuicStream> stream = session()->FindStream(stream_id); - if (stream) { - CHECK(!stream->is_destroyed()); // Should not be possible - stream->set_destroyed(); - stream->CancelPendingWrites(); - session()->RemoveStream(stream_id); - session()->listener()->OnStreamClose(stream_id, app_error_code); - } -} - -void QuicApplication::StreamReset( - int64_t stream_id, - uint64_t app_error_code) { - session()->listener()->OnStreamReset(stream_id, app_error_code); -} - -// Determines which QuicApplication variant the QuicSession will be using -// based on the alpn configured for the application. For now, this is -// determined through configuration when tghe QuicSession is created -// and is not negotiable. In the future, we may allow it to be negotiated. -QuicApplication* QuicSession::SelectApplication(QuicSession* session) { - std::string alpn = session->alpn(); - if (alpn == NGHTTP3_ALPN_H3) { - Debug(this, "Selecting HTTP/3 Application"); - return new Http3Application(session); - } - // In the future, we may end up supporting additional - // QUIC protocols. As they are added, extend the cases - // here to create and return them. - - Debug(this, "Selecting Default Application"); - return new DefaultApplication(session); -} - -// Server QuicSession Constructor -QuicSession::QuicSession( - QuicSocket* socket, - const QuicSessionConfig& config, - Local<Object> wrap, - const SocketAddress& local_addr, - const SocketAddress& remote_addr, - const QuicCID& dcid, - const QuicCID& scid, - const QuicCID& ocid, - uint32_t version, - const std::string& alpn, - uint32_t options, - QlogMode qlog) - : QuicSession( - NGTCP2_CRYPTO_SIDE_SERVER, - socket, - wrap, - socket->server_secure_context(), - AsyncWrap::PROVIDER_QUICSERVERSESSION, - alpn, - std::string(""), // empty hostname. not used on server side - dcid, - options, - nullptr) { - // The config is copied by assignment in the call below. - InitServer(config, local_addr, remote_addr, dcid, scid, ocid, version, qlog); -} - -// Client QuicSession Constructor -QuicSession::QuicSession( - QuicSocket* socket, - v8::Local<v8::Object> wrap, - const SocketAddress& local_addr, - const SocketAddress& remote_addr, - BaseObjectPtr<SecureContext> secure_context, - ngtcp2_transport_params* early_transport_params, - crypto::SSLSessionPointer early_session_ticket, - Local<Value> dcid, - PreferredAddressStrategy preferred_address_strategy, - const std::string& alpn, - const std::string& hostname, - uint32_t options, - QlogMode qlog) - : QuicSession( - NGTCP2_CRYPTO_SIDE_CLIENT, - socket, - wrap, - secure_context, - AsyncWrap::PROVIDER_QUICCLIENTSESSION, - alpn, - hostname, - QuicCID(), - options, - preferred_address_strategy) { - set_wrapped(); - InitClient( - local_addr, - remote_addr, - early_transport_params, - std::move(early_session_ticket), - dcid, - qlog); -} - -// QuicSession is an abstract base class that defines the code used by both -// server and client sessions. -QuicSession::QuicSession( - ngtcp2_crypto_side side, - QuicSocket* socket, - Local<Object> wrap, - BaseObjectPtr<SecureContext> secure_context, - AsyncWrap::ProviderType provider_type, - const std::string& alpn, - const std::string& hostname, - const QuicCID& dcid, - uint32_t options, - PreferredAddressStrategy preferred_address_strategy) - : AsyncWrap(socket->env(), wrap, provider_type), - StatsBase(socket->env(), wrap, - HistogramOptions::ACK | - HistogramOptions::RATE), - alloc_info_(MakeAllocator()), - socket_(socket), - alpn_(alpn), - hostname_(hostname), - idle_(socket->env(), [this]() { OnIdleTimeout(); }), - retransmit_(socket->env(), [this]() { OnRetransmitTimeout(); }), - dcid_(dcid), - state_(env()->isolate()), - quic_state_(socket->quic_state()) { - PushListener(&default_listener_); - set_connection_id_strategy(RandomConnectionIDStrategy); - set_preferred_address_strategy(preferred_address_strategy); - crypto_context_ = std::make_unique<QuicCryptoContext>( - - this, - secure_context, - side, - options); - application_.reset(SelectApplication(this)); - - wrap->DefineOwnProperty( - env()->context(), - env()->state_string(), - state_.GetArrayBuffer(), - PropertyAttribute::ReadOnly).Check(); - - idle_.Unref(); - retransmit_.Unref(); - - // TODO(@jasnell): memory accounting - // env_->isolate()->AdjustAmountOfExternalAllocatedMemory(kExternalSize); -} - -QuicSession::~QuicSession() { - CHECK(!NgCallbackScope::InNgCallbackScope(this)); - - // The next write should be the final one - if (qlog_stream_) - qlog_stream_->End(); - - QuicSessionListener* listener_ = listener(); - if (listener_ == listener()) - RemoveListener(listener_); - - // Stop and free the idle and retransmission timers if they are active. - // In a clean shutdown, using Close(), these will have already been - // stopped, but if Close() was not called and we're being destroyed - // in GC, for instance, we need to make sure they get stopped here. - idle_.Stop(); - retransmit_.Stop(); - - DebugStats(); -} - -template <typename Fn> -void QuicSessionStatsTraits::ToString(const QuicSession& ptr, Fn&& add_field) { -#define V(_n, name, label) \ - add_field(label, ptr.GetStat(&QuicSessionStats::name)); - SESSION_STATS(V) -#undef V -} - -void QuicSession::PushListener(QuicSessionListener* listener) { - CHECK_NOT_NULL(listener); - CHECK(!listener->session_); - - listener->previous_listener_ = listener_; - listener->session_.reset(this); - - listener_ = listener; -} - -void QuicSession::RemoveListener(QuicSessionListener* listener) { - CHECK_NOT_NULL(listener); - - QuicSessionListener* previous; - QuicSessionListener* current; - - for (current = listener_, previous = nullptr; - /* No loop condition because we want a crash if listener is not found */ - ; previous = current, current = current->previous_listener_) { - CHECK_NOT_NULL(current); - if (current == listener) { - if (previous != nullptr) - previous->previous_listener_ = current->previous_listener_; - else - listener_ = listener->previous_listener_; - break; - } - } - - listener->session_.reset(); - listener->previous_listener_ = nullptr; -} - -// The diagnostic_name is used in Debug output. -std::string QuicSession::diagnostic_name() const { - return std::string("QuicSession ") + - (is_server() ? "Server" : "Client") + - " (" + alpn().substr(1) + ", " + - std::to_string(static_cast<int64_t>(get_async_id())) + ")"; -} - -// Locate the QuicStream with the given id or return nullptr -BaseObjectPtr<QuicStream> QuicSession::FindStream(int64_t id) const { - auto it = streams_.find(id); - return it == std::end(streams_) ? BaseObjectPtr<QuicStream>() : it->second; -} - -// Invoked when ngtcp2 receives an acknowledgement for stream data. -void QuicSession::AckedStreamDataOffset( - int64_t stream_id, - uint64_t offset, - uint64_t datalen) { - Debug(this, - "Received acknowledgement for %" PRIu64 - " bytes of stream %" PRId64 " data", - datalen, stream_id); - - application_->AcknowledgeStreamData( - stream_id, - offset, - static_cast<size_t>(datalen)); -} - -// Attaches the session to the given QuicSocket. The complexity -// here is that any CID's associated with the session have to -// be associated with the new QuicSocket. -void QuicSession::AddToSocket(QuicSocket* socket) { - CHECK_NOT_NULL(socket); - Debug(this, "Adding QuicSession to %s", socket->diagnostic_name()); - socket->AddSession(scid_, BaseObjectPtr<QuicSession>(this)); - switch (crypto_context_->side()) { - case NGTCP2_CRYPTO_SIDE_SERVER: { - socket->AssociateCID(dcid_, scid_); - socket->AssociateCID(pscid_, scid_); - break; - } - case NGTCP2_CRYPTO_SIDE_CLIENT: { - std::vector<ngtcp2_cid> cids(ngtcp2_conn_get_num_scid(connection())); - ngtcp2_conn_get_scid(connection(), cids.data()); - for (const ngtcp2_cid& cid : cids) { - socket->AssociateCID(QuicCID(&cid), scid_); - } - break; - } - default: - UNREACHABLE(); - } - - std::vector<ngtcp2_cid_token> tokens( - ngtcp2_conn_get_num_active_dcid(connection())); - ngtcp2_conn_get_active_dcid(connection(), tokens.data()); - for (const ngtcp2_cid_token& token : tokens) { - if (token.token_present) { - socket->AssociateStatelessResetToken( - StatelessResetToken(token.token), - BaseObjectPtr<QuicSession>(this)); - } - } -} - -// Add the given QuicStream to this QuicSession's collection of streams. All -// streams added must be removed before the QuicSession instance is freed. -void QuicSession::AddStream(BaseObjectPtr<QuicStream> stream) { - DCHECK(!is_graceful_closing()); - Debug(this, "Adding stream %" PRId64 " to session", stream->id()); - streams_.emplace(stream->id(), stream); - - // Update tracking statistics for the number of streams associated with - // this session. - switch (stream->origin()) { - case QuicStreamOrigin::QUIC_STREAM_CLIENT: - if (is_server()) - IncrementStat(&QuicSessionStats::streams_in_count); - else - IncrementStat(&QuicSessionStats::streams_out_count); - break; - case QuicStreamOrigin::QUIC_STREAM_SERVER: - if (is_server()) - IncrementStat(&QuicSessionStats::streams_out_count); - else - IncrementStat(&QuicSessionStats::streams_in_count); - } - IncrementStat(&QuicSessionStats::streams_out_count); - switch (stream->direction()) { - case QuicStreamDirection::QUIC_STREAM_BIRECTIONAL: - IncrementStat(&QuicSessionStats::bidi_stream_count); - break; - case QuicStreamDirection::QUIC_STREAM_UNIDIRECTIONAL: - IncrementStat(&QuicSessionStats::uni_stream_count); - break; - } -} - -// Creates a new stream object and passes it off to the javascript side. -// This has to be called from within a handlescope/contextscope. -BaseObjectPtr<QuicStream> QuicSession::CreateStream(int64_t stream_id) { - CHECK(!is_destroyed()); - CHECK(!is_graceful_closing()); - CHECK(!is_closing()); - - BaseObjectPtr<QuicStream> stream = QuicStream::New(this, stream_id); - CHECK(stream); - listener()->OnStreamReady(stream); - return stream; -} - -// Initiate a shutdown of the QuicSession. -void QuicSession::Close(int close_flags) { - if (is_destroyed()) - return; - bool silent = close_flags & QuicSessionListener::SESSION_CLOSE_FLAG_SILENT; - bool stateless_reset = is_stateless_reset(); - - // If we're not running within a ngtcp2 callback scope, schedule - // a CONNECTION_CLOSE to be sent when Close exits. If we are - // within a ngtcp2 callback scope, sending the CONNECTION_CLOSE - // will be deferred. - ConnectionCloseScope close_scope(this, silent); - - // Once Close has been called, we cannot re-enter - if (UNLIKELY(is_closing())) - return; - - set_closing(); - set_silent_closing(silent); - - if (stateless_reset && silent) - close_flags |= QuicSessionListener::SESSION_CLOSE_FLAG_STATELESS_RESET; - - QuicError error = last_error(); - Debug(this, "Closing with code %" PRIu64 - " (family: %s, silent: %s, stateless reset: %s)", - error.code, - error.family_name(), - silent ? "Y" : "N", - stateless_reset ? "Y" : "N"); - - // Ensure that the QuicSession is not freed at least until after we - // exit this scope. - BaseObjectPtr<QuicSession> ptr(this); - - // If the QuicSession has been wrapped by a JS object, we have to - // notify the JavaScript side that the session is being closed. - // If it hasn't yet been wrapped, we can skip the call and and - // go straight to destroy. - if (is_wrapped()) - listener()->OnSessionClose(error, close_flags); - else - Destroy(); -} - -// Mark the QuicSession instance destroyed. This will either be invoked -// synchronously within the callstack of the QuicSession::Close() method -// or not. If it is invoked within QuicSession::Close(), the -// QuicSession::Close() will handle sending the CONNECTION_CLOSE -// frame. -void QuicSession::Destroy() { - if (is_destroyed()) - return; - - Debug(this, "Destroying the QuicSession"); - - // Mark the session destroyed. - set_destroyed(); - set_closing(false); - set_graceful_closing(false); - - // TODO(@jasnell): Allow overriding the close code - - // If we're not already in a ConnectionCloseScope, schedule - // sending a CONNECTION_CLOSE when destroy exits. If we are - // running within an ngtcp2 callback scope, sending the - // CONNECTION_CLOSE will be deferred. - ConnectionCloseScope close_scope(this, is_silent_closing()); - - // All existing streams should have already been destroyed - CHECK(streams_.empty()); - - // Stop and free the idle and retransmission timers if they are active. - idle_.Stop(); - retransmit_.Stop(); - - // The QuicSession instances are kept alive using - // BaseObjectPtr. The only persistent BaseObjectPtr - // is the map in the associated QuicSocket. Removing - // the QuicSession from the QuicSocket will free - // that pointer, allowing the QuicSession to be - // deconstructed once the stack unwinds and any - // remaining BaseObjectPtr<QuicSession> instances - // fall out of scope. - RemoveFromSocket(); -} - -// Generates and associates a new connection ID for this QuicSession. -// ngtcp2 will call this multiple times at the start of a new connection -// in order to build a pool of available CIDs. -void QuicSession::GetNewConnectionID( - ngtcp2_cid* cid, - uint8_t* token, - size_t cidlen) { - CHECK_NOT_NULL(connection_id_strategy_); - connection_id_strategy_(this, cid, cidlen); - QuicCID cid_(cid); - StatelessResetToken(token, socket()->session_reset_secret(), cid_); - socket()->AssociateCID(cid_, scid_); -} - -void QuicSession::HandleError() { - if (is_destroyed()) - return; - - // If the QuicSession is a server, send a CONNECTION_CLOSE. In either - // case, the closing timer will be set and the QuicSession will be - // destroyed. - if (is_server()) - SendConnectionClose(); - else - UpdateClosingTimer(); -} - -// The the retransmit libuv timer fires, it will call OnRetransmitTimeout, -// which determines whether or not we need to retransmit data to -// to packet loss or ack delay. -void QuicSession::OnRetransmitTimeout() { - if (is_destroyed()) - return; - uint64_t now = uv_hrtime(); - - if (ngtcp2_conn_loss_detection_expiry(connection()) <= now) { - Debug(this, "Retransmitting due to loss detection"); - IncrementStat(&QuicSessionStats::loss_retransmit_count); - } - - if (ngtcp2_conn_ack_delay_expiry(connection()) <= now) { - Debug(this, "Retransmitting due to ack delay"); - IncrementStat(&QuicSessionStats::ack_delay_retransmit_count); - } - - int rv = ngtcp2_conn_handle_expiry(connection(), now); - if (rv != 0) { - Debug(this, "Error handling retransmit timeout: %s", ngtcp2_strerror(rv)); - set_last_error(QUIC_ERROR_SESSION, rv); - HandleError(); - } - - SendPendingData(); -} - -bool QuicSession::OpenBidirectionalStream(int64_t* stream_id) { - DCHECK(!is_destroyed()); - DCHECK(!is_closing()); - DCHECK(!is_graceful_closing()); - return ngtcp2_conn_open_bidi_stream(connection(), stream_id, nullptr) == 0; -} - -bool QuicSession::OpenUnidirectionalStream(int64_t* stream_id) { - DCHECK(!is_destroyed()); - DCHECK(!is_closing()); - DCHECK(!is_graceful_closing()); - return ngtcp2_conn_open_uni_stream(connection(), stream_id, nullptr) == 0; -} - -// When ngtcp2 receives a successful response to a PATH_CHALLENGE, -// it will trigger the OnPathValidation callback which will, in turn -// invoke this. There's really nothing to do here but update stats and -// and optionally notify the javascript side if there is a handler registered. -// Notifying the JavaScript side is purely informational. -void QuicSession::PathValidation( - const ngtcp2_path* path, - ngtcp2_path_validation_result res) { - if (res == NGTCP2_PATH_VALIDATION_RESULT_SUCCESS) { - IncrementStat(&QuicSessionStats::path_validation_success_count); - } else { - IncrementStat(&QuicSessionStats::path_validation_failure_count); - } - - // Only emit the callback if there is a handler for the pathValidation - // event on the JavaScript QuicSession object. - if (UNLIKELY(state_->path_validated_enabled == 1)) { - listener_->OnPathValidation( - res, - reinterpret_cast<const sockaddr*>(path->local.addr), - reinterpret_cast<const sockaddr*>(path->remote.addr)); - } -} - -// Calling Ping will trigger the ngtcp2_conn to serialize any -// packets it currently has pending along with a probe frame -// that should keep the connection alive. This is a fire and -// forget and any errors that may occur will be ignored. The -// idle_timeout and retransmit timers will be updated. If Ping -// is called while processing an ngtcp2 callback, or if the -// closing or draining period has started, this is a non-op. -void QuicSession::Ping() { - if (NgCallbackScope::InNgCallbackScope(this) || - is_destroyed() || - is_closing() || - is_in_closing_period() || - is_in_draining_period()) { - return; - } - // TODO(@jasnell): We might want to revisit whether to handle - // errors right here. For now, we're ignoring them with the - // intent of capturing them elsewhere. - WritePackets("ping"); - UpdateIdleTimer(); - ScheduleRetransmit(); -} - -// When the QuicSocket receives a QUIC packet, it is forwarded on to here -// for processing. -bool QuicSession::Receive( - ssize_t nread, - const uint8_t* data, - const SocketAddress& local_addr, - const SocketAddress& remote_addr, - unsigned int flags) { - - CHECK(!is_destroyed()); - - Debug(this, "Receiving QUIC packet"); - IncrementStat(&QuicSessionStats::bytes_received, nread); - - if (is_in_closing_period() && is_server()) { - Debug(this, "Packet received while in closing period"); - IncrementConnectionCloseAttempts(); - // For server QuicSession instances, we serialize the connection close - // packet once but may sent it multiple times. If the client keeps - // transmitting, then the connection close may have gotten lost. - // We don't want to send the connection close in response to - // every received packet, however, so we use an exponential - // backoff, increasing the ratio of packets received to connection - // close frame sent with every one we send. - if (UNLIKELY(ShouldAttemptConnectionClose() && - !SendConnectionClose())) { - Debug(this, "Failure sending another connection close"); - return false; - } - } - - { - // These are within a scope to ensure that the InternalCallbackScope - // and HandleScope are both exited before continuing on with the - // function. This allows any nextTicks and queued tasks to be processed - // before we continue. - auto update_stats = OnScopeLeave([&](){ - UpdateDataStats(); - }); - HandleScope handle_scope(env()->isolate()); - InternalCallbackScope callback_scope(this); - remote_address_ = remote_addr; - QuicPath path(local_addr, remote_address_); - if (!ReceivePacket(&path, data, nread)) { - HandleError(); - return false; - } - } - - // Only send pending data if we haven't entered draining mode. - // We enter the draining period when a CONNECTION_CLOSE has been - // received from the remote peer. - if (is_in_draining_period()) { - Debug(this, "In draining period after processing packet"); - // If processing the packet puts us into draining period, there's - // absolutely nothing left for us to do except silently close - // and destroy this QuicSession, which we do by updating the - // closing timer. - GetConnectionCloseInfo(); - UpdateClosingTimer(); - return true; - } - - if (!is_destroyed()) - UpdateIdleTimer(); - SendPendingData(); - Debug(this, "Successfully processed received packet"); - return true; -} - -// Performs intake processing on a received QUIC packet. The received -// data is passed on to ngtcp2 for parsing and processing. ngtcp2 will, -// in turn, invoke a series of callbacks to handle the received packet. -bool QuicSession::ReceivePacket( - ngtcp2_path* path, - const uint8_t* data, - ssize_t nread) { - CHECK(!is_destroyed()); - - uint64_t now = uv_hrtime(); - SetStat(&QuicSessionStats::received_at, now); - int err = ngtcp2_conn_read_pkt(connection(), path, data, nread, now); - if (err < 0) { - switch (err) { - case NGTCP2_ERR_CALLBACK_FAILURE: - case NGTCP2_ERR_DRAINING: - case NGTCP2_ERR_RECV_VERSION_NEGOTIATION: - break; - case NGTCP2_ERR_RETRY: - // This should only ever happen on the server - CHECK(is_server()); - socket()->SendRetry(scid_, dcid_, local_address_, remote_address_); - Close(QuicSessionListener::SESSION_CLOSE_FLAG_SILENT); - break; - case NGTCP2_ERR_DROP_CONN: - Close(QuicSessionListener::SESSION_CLOSE_FLAG_SILENT); - break; - default: - set_last_error(QUIC_ERROR_SESSION, err); - return false; - } - } - - // If the QuicSession has been destroyed but it is not - // in the closing period, a CONNECTION_CLOSE has not yet - // been sent to the peer. Let's attempt to send one. This - // will have the effect of setting the idle timer to the - // closing/draining period, after which the QuicSession - // will be destroyed. - if (is_destroyed() && !is_in_closing_period()) { - Debug(this, "Session was destroyed while processing the packet"); - return SendConnectionClose(); - } - - return true; -} - -// Called by ngtcp2 when a chunk of stream data has been received. If -// the stream does not yet exist, it is created, then the data is -// forwarded on. -bool QuicSession::ReceiveStreamData( - uint32_t flags, - int64_t stream_id, - const uint8_t* data, - size_t datalen, - uint64_t offset) { - auto leave = OnScopeLeave([&]() { - // Unconditionally extend the flow control window for the entire - // session but not for the individual Stream. - ExtendOffset(datalen); - }); - - return application_->ReceiveStreamData( - flags, - stream_id, - data, - datalen, - offset); -} - -// Removes the QuicSession from the current socket. This is -// done with when the session is being destroyed or being -// migrated to another QuicSocket. It is important to keep in mind -// that the QuicSocket uses a BaseObjectPtr for the QuicSession. -// If the session is removed and there are no other references held, -// the session object will be destroyed automatically. -void QuicSession::RemoveFromSocket() { - CHECK(socket_); - Debug(this, "Removing QuicSession from %s", socket_->diagnostic_name()); - if (is_server()) { - socket_->DisassociateCID(dcid_); - socket_->DisassociateCID(pscid_); - } - - std::vector<ngtcp2_cid> cids(ngtcp2_conn_get_num_scid(connection())); - std::vector<ngtcp2_cid_token> tokens( - ngtcp2_conn_get_num_active_dcid(connection())); - ngtcp2_conn_get_scid(connection(), cids.data()); - ngtcp2_conn_get_active_dcid(connection(), tokens.data()); - - for (const ngtcp2_cid& cid : cids) - socket_->DisassociateCID(QuicCID(&cid)); - - for (const ngtcp2_cid_token& token : tokens) { - if (token.token_present) { - socket_->DisassociateStatelessResetToken( - StatelessResetToken(token.token)); - } - } - - Debug(this, "Removed from the QuicSocket"); - BaseObjectPtr<QuicSocket> socket = std::move(socket_); - socket->RemoveSession(scid_, remote_address_); -} - -// Removes the given stream from the QuicSession. All streams must -// be removed before the QuicSession is destroyed. -void QuicSession::RemoveStream(int64_t stream_id) { - Debug(this, "Removing stream %" PRId64, stream_id); - - // ngtcp2 does not extend the max streams count automatically - // except in very specific conditions, none of which apply - // once we've gotten this far. We need to manually extend when - // a remote peer initiated stream is removed. - if (!is_in_draining_period() && - !is_in_closing_period() && - !is_silent_closing() && - !ngtcp2_conn_is_local_stream(connection_.get(), stream_id)) { - if (ngtcp2_is_bidi_stream(stream_id)) - ngtcp2_conn_extend_max_streams_bidi(connection_.get(), 1); - else - ngtcp2_conn_extend_max_streams_uni(connection_.get(), 1); - } - - // This will have the side effect of destroying the QuicStream - // instance. - streams_.erase(stream_id); -} - -// The retransmit timer allows us to trigger retransmission -// of packets in case they are considered lost. The exact amount -// of time is determined internally by ngtcp2 according to the -// guidelines established by the QUIC spec but we use a libuv -// timer to actually monitor. -void QuicSession::ScheduleRetransmit() { - uint64_t now = uv_hrtime(); - uint64_t expiry = ngtcp2_conn_get_expiry(connection()); - // now and expiry are in nanoseconds, interval is milliseconds - uint64_t interval = (expiry < now) ? 1 : (expiry - now) / 1000000UL; - // If interval ends up being 0, the repeating timer won't be - // scheduled, so set it to 1 instead. - if (interval == 0) interval = 1; - Debug(this, "Scheduling the retransmit timer for %" PRIu64, interval); - UpdateRetransmitTimer(interval); -} - -bool QuicSession::InitApplication() { - Debug(this, "Initializing application handler for ALPN %s", - alpn().c_str() + 1); - return application_->Initialize(); -} - -// Captures the error code and family information from a received -// connection close frame. -void QuicSession::GetConnectionCloseInfo() { - ngtcp2_connection_close_error_code close_code; - ngtcp2_conn_get_connection_close_error_code(connection(), &close_code); - set_last_error(QuicError(close_code)); -} - -// The HandshakeCompleted function is called by ngtcp2 once it -// determines that the TLS Handshake is done. The only thing we -// need to do at this point is let the javascript side know. -void QuicSession::HandshakeCompleted() { - RemoteTransportParamsDebug transport_params(this); - Debug(this, "Handshake is completed. %s", transport_params); - RecordTimestamp(&QuicSessionStats::handshake_completed_at); - if (is_server()) HandshakeConfirmed(); - listener()->OnHandshakeCompleted(); -} - -void QuicSession::HandshakeConfirmed() { - Debug(this, "Handshake is confirmed"); - RecordTimestamp(&QuicSessionStats::handshake_confirmed_at); - state_->handshake_confirmed = 1; -} - -// Every QUIC session has a remote address and local address. -// Those endpoints can change through the lifetime of a connection, -// so whenever a packet is successfully processed, or when a -// response is to be sent, we have to keep track of the path -// and update as we go. -void QuicSession::UpdateEndpoint(const ngtcp2_path& path) { - remote_address_.Update(path.remote.addr, path.remote.addrlen); - local_address_.Update(path.local.addr, path.local.addrlen); - - // If the updated remote address is IPv6, set the flow label - if (remote_address_.family() == AF_INET6) { - // TODO(@jasnell): Currently, this reuses the session reset secret. - // That may or may not be a good idea, we need to verify and may - // need to have a distinct secret for flow labels. - uint32_t flow_label = - GenerateFlowLabel( - local_address_, - remote_address_, - scid_, - socket()->session_reset_secret(), - NGTCP2_STATELESS_RESET_TOKENLEN); - remote_address_.set_flow_label(flow_label); - } -} - -// Called by the OnVersionNegotiation callback when a version -// negotiation frame has been received by the client. The sv -// parameter is an array of versions supported by the remote peer. -void QuicSession::VersionNegotiation(const uint32_t* sv, size_t nsv) { - CHECK(!is_server()); - listener()->OnVersionNegotiation(NGTCP2_PROTO_VER, sv, nsv); -} - -// The retransmit timer allows us to trigger retransmission -// of packets in case they are considered lost. The exact amount -// of time is determined internally by ngtcp2 according to the -// guidelines established by the QUIC spec but we use a libuv -// timer to actually monitor. Here we take the calculated timeout -// and extend out the libuv timer. -void QuicSession::UpdateRetransmitTimer(uint64_t timeout) { - retransmit_.Update(timeout, timeout); -} - -void QuicSession::IncrementConnectionCloseAttempts() { - if (connection_close_attempts_ < kMaxSizeT) - connection_close_attempts_++; -} - -bool QuicSession::ShouldAttemptConnectionClose() { - if (connection_close_attempts_ == connection_close_limit_) { - if (connection_close_limit_ * 2 <= kMaxSizeT) - connection_close_limit_ *= 2; - else - connection_close_limit_ = kMaxSizeT; - return true; - } - return false; -} - -// Transmits either a protocol or application connection -// close to the peer. The choice of which is send is -// based on the current value of last_error_. -bool QuicSession::SendConnectionClose() { - CHECK(!NgCallbackScope::InNgCallbackScope(this)); - - // Do not send any frames at all if we're in the draining period - // or in the middle of a silent close - if (is_in_draining_period() || is_silent_closing()) - return true; - - // The specific handling of connection close varies for client - // and server QuicSession instances. For servers, we will - // serialize the connection close once but may end up transmitting - // it multiple times; whereas for clients, we will serialize it - // once and send once only. - QuicError error = last_error(); - Debug(this, "Sending connection close with code: %" PRIu64 " (family: %s)", - error.code, error.family_name()); - - UpdateClosingTimer(); - - // If initial keys have not yet been installed, use the alternative - // ImmediateConnectionClose to send a stateless connection close to - // the peer. - if (crypto_context()->write_crypto_level() == - NGTCP2_CRYPTO_LEVEL_INITIAL) { - socket()->ImmediateConnectionClose( - dcid(), - scid_, - local_address_, - remote_address_, - error.code); - return true; - } - - switch (crypto_context_->side()) { - case NGTCP2_CRYPTO_SIDE_SERVER: { - if (!is_in_closing_period() && !StartClosingPeriod()) { - Close(QuicSessionListener::SESSION_CLOSE_FLAG_SILENT); - return false; - } - CHECK_GT(conn_closebuf_->length(), 0); - return SendPacket(QuicPacket::Copy(conn_closebuf_)); - } - case NGTCP2_CRYPTO_SIDE_CLIENT: { - auto packet = QuicPacket::Create("client connection close"); - - ssize_t nwrite = - SelectCloseFn(error.family)( - connection(), - nullptr, - packet->data(), - max_pktlen_, - error.code, - uv_hrtime()); - if (UNLIKELY(nwrite < 0)) { - Debug(this, "Error writing connection close: %d", nwrite); - set_last_error(QUIC_ERROR_SESSION, static_cast<int>(nwrite)); - Close(QuicSessionListener::SESSION_CLOSE_FLAG_SILENT); - return false; - } - packet->set_length(nwrite); - return SendPacket(std::move(packet)); - } - default: - UNREACHABLE(); - } -} - -void QuicSession::IgnorePreferredAddressStrategy( - QuicSession* session, - const PreferredAddress& preferred_address) { - Debug(session, "Ignoring server preferred address"); -} - -void QuicSession::UsePreferredAddressStrategy( - QuicSession* session, - const PreferredAddress& preferred_address) { - int family = session->socket()->local_address().family(); - if (preferred_address.Use(family)) { - Debug(session, "Using server preferred address"); - // Emit only if the QuicSession has a usePreferredAddress handler - // on the JavaScript side. - if (UNLIKELY(session->state_->use_preferred_address_enabled == 1)) { - session->listener()->OnUsePreferredAddress(family, preferred_address); - } - } else { - // If Use returns false, the advertised preferred address does not - // match the current local preferred endpoint IP version. - Debug(session, - "Not using server preferred address due to IP version mismatch"); - } -} - -// Passes a serialized packet to the associated QuicSocket. -bool QuicSession::SendPacket(std::unique_ptr<QuicPacket> packet) { - CHECK(!is_in_draining_period()); - - // There's nothing to send. - if (packet->length() == 0) - return true; - - IncrementStat(&QuicSessionStats::bytes_sent, packet->length()); - RecordTimestamp(&QuicSessionStats::sent_at); -// ScheduleRetransmit(); - - Debug(this, "Sending %" PRIu64 " bytes to %s from %s", - packet->length(), - remote_address_, - local_address_); - - int err = socket()->SendPacket( - local_address_, - remote_address_, - std::move(packet), - BaseObjectPtr<QuicSession>(this)); - - if (err != 0) { - set_last_error(QUIC_ERROR_SESSION, err); - return false; - } - - return true; -} - -// Sends any pending handshake or session packet data. -void QuicSession::SendPendingData() { - if (is_unable_to_send_packets()) - return; - - Debug(this, "Sending pending data"); - if (!application_->SendPendingData()) { - Debug(this, "Error sending QUIC application data"); - HandleError(); - } - ScheduleRetransmit(); -} - -// When completing the TLS handshake, the TLS session information -// is provided to the QuicSession so that the session ticket and -// the remote transport parameters can be captured to support 0RTT -// session resumption. -int QuicSession::set_session(SSL_SESSION* session) { - CHECK(!is_server()); - CHECK(!is_destroyed()); - int size = i2d_SSL_SESSION(session, nullptr); - if (size > SecureContext::kMaxSessionSize) - return 0; - listener_->OnSessionTicket(size, session); - return 0; -} - -// A client QuicSession can be migrated to a different QuicSocket instance. -bool QuicSession::set_socket(QuicSocket* socket, bool nat_rebinding) { - CHECK(!is_server()); - CHECK(!is_destroyed()); - - if (is_graceful_closing()) - return false; - - if (socket == nullptr || socket == socket_.get()) - return true; - - Debug(this, "Migrating to %s", socket->diagnostic_name()); - - // Ensure that we maintain a reference to keep this from being - // destroyed while we are starting the migration. - BaseObjectPtr<QuicSession> ptr(this); - - // Step 1: Remove the session from the current socket - RemoveFromSocket(); - - // Step 2: Add this Session to the given Socket - AddToSocket(socket); - - // Step 3: Update the internal references and make sure - // we are listening. - socket_.reset(socket); - socket->ReceiveStart(); - - // Step 4: Update ngtcp2 - auto local_address = socket->local_address(); - - // The nat_rebinding option here should rarely, if ever - // be used in a real application. It is intended to serve - // as a way of simulating a silent local address change, - // such as when the NAT binding changes. Currently, Node.js - // does not really have an effective way of detecting that. - // Manual user code intervention to handle the migration - // to the new QuicSocket is required, which should always - // trigger path validation using the ngtcp2_conn_initiate_migration. - if (LIKELY(!nat_rebinding)) { - SendSessionScope send(this); - QuicPath path(local_address, remote_address_); - return ngtcp2_conn_initiate_migration( - connection(), - &path, - uv_hrtime()) == 0; - } else { - ngtcp2_addr addr; - ngtcp2_conn_set_local_addr( - connection(), - ngtcp2_addr_init( - &addr, - local_address.data(), - local_address.length(), - nullptr)); - } - - return true; -} - -void QuicSession::ResumeStream(int64_t stream_id) { - application()->ResumeStream(stream_id); -} - -// Begin connection close by serializing the CONNECTION_CLOSE packet. -// There are two variants: one to serialize an application close, the -// other to serialize a protocol close. The frames are generally -// identical with the exception of a bit in the header. On server -// QuicSessions, we serialize the frame once and may retransmit it -// multiple times. On client QuicSession instances, we only ever -// serialize the connection close once. -bool QuicSession::StartClosingPeriod() { - if (is_destroyed()) - return false; - if (is_in_closing_period()) - return true; - - QuicError error = last_error(); - Debug(this, "Closing period has started. Error %d", error.code); - - // Once the CONNECTION_CLOSE packet is written, - // is_in_closing_period will return true. - conn_closebuf_ = QuicPacket::Create("server connection close"); - ssize_t nwrite = - SelectCloseFn(error.family)( - connection(), - nullptr, - conn_closebuf_->data(), - max_pktlen_, - error.code, - uv_hrtime()); - if (nwrite < 0) { - set_last_error(QUIC_ERROR_SESSION, static_cast<int>(nwrite)); - return false; - } - conn_closebuf_->set_length(nwrite); - return true; -} - -// Called by ngtcp2 when a stream has been closed. If the stream does -// not exist, the close is ignored. -void QuicSession::StreamClose(int64_t stream_id, uint64_t app_error_code) { - Debug(this, "Closing stream %" PRId64 " with code %" PRIu64, - stream_id, - app_error_code); - - application_->StreamClose(stream_id, app_error_code); -} - -// Called when the QuicSession has received a RESET_STREAM frame from the -// peer, indicating that it will no longer send additional frames for the -// stream. If the stream is not yet known, reset is ignored. If the stream -// has already received a STREAM frame with fin set, the stream reset is -// ignored (the QUIC spec permits implementations to handle this situation -// however they want.) If the stream has not yet received a STREAM frame -// with the fin set, then the RESET_STREAM causes the readable side of the -// stream to be abruptly closed and any additional stream frames that may -// be received will be discarded if their offset is greater than final_size. -// On the JavaScript side, receiving a C is undistinguishable from -// a normal end-of-stream. No additional data events will be emitted, the -// end event will be emitted, and the readable side of the duplex will be -// closed. -// -// If the stream is still writable, no additional action is taken. If, -// however, the writable side of the stream has been closed (or was never -// open in the first place as in the case of peer-initiated unidirectional -// streams), the reset will cause the stream to be immediately destroyed. -void QuicSession::StreamReset( - int64_t stream_id, - uint64_t final_size, - uint64_t app_error_code) { - Debug(this, - "Reset stream %" PRId64 " with code %" PRIu64 - " and final size %" PRIu64, - stream_id, - app_error_code, - final_size); - - BaseObjectPtr<QuicStream> stream = FindStream(stream_id); - - if (stream) { - stream->set_final_size(final_size); - application_->StreamReset(stream_id, app_error_code); - } -} - -void QuicSession::UpdateConnectionID( - int type, - const QuicCID& cid, - const StatelessResetToken& token) { - switch (type) { - case NGTCP2_CONNECTION_ID_STATUS_TYPE_ACTIVATE: - socket_->AssociateStatelessResetToken( - token, - BaseObjectPtr<QuicSession>(this)); - break; - case NGTCP2_CONNECTION_ID_STATUS_TYPE_DEACTIVATE: - socket_->DisassociateStatelessResetToken(token); - break; - } -} - -// Updates the idle timer timeout. If the idle timer fires, the connection -// will be silently closed. It is important to update this as activity -// occurs to keep the idle timer from firing. -void QuicSession::UpdateIdleTimer() { - if (is_closing_timer_enabled()) - return; - uint64_t now = uv_hrtime(); - uint64_t expiry = ngtcp2_conn_get_idle_expiry(connection()); - // nano to millis - uint64_t timeout = expiry > now ? (expiry - now) / 1000000ULL : 1; - if (timeout == 0) timeout = 1; - Debug(this, "Updating idle timeout to %" PRIu64, timeout); - idle_.Update(timeout, timeout); -} - -void QuicSession::UpdateClosingTimer() { - set_closing_timer_enabled(true); - uint64_t timeout = - is_server() ? (ngtcp2_conn_get_pto(connection()) / 1000000ULL) * 3 : 0; - Debug(this, "Setting closing timeout to %" PRIu64, timeout); - retransmit_.Stop(); - idle_.Update(timeout, 0); - idle_.Ref(); -} - -// Write any packets current pending for the ngtcp2 connection based on -// the current state of the QuicSession. If the QuicSession is in the -// closing period, only CONNECTION_CLOSE packets may be written. If the -// QuicSession is in the draining period, no packets may be written. -// -// Packets are flushed to the underlying QuicSocket uv_udp_t as soon -// as they are written. The WritePackets method may cause zero or more -// packets to be serialized. -// -// If there are any acks or retransmissions pending, those will be -// serialized at this point as well. However, WritePackets does not -// serialize stream data that is being sent initially. -bool QuicSession::WritePackets(const char* diagnostic_label) { - CHECK(!NgCallbackScope::InNgCallbackScope(this)); - - // During either the draining or closing period, - // we are not permitted to send any additional packets. - if (is_in_draining_period() || is_in_closing_period()) - return true; - - // Otherwise, serialize and send pending frames - QuicPathStorage path; - for (;;) { - auto packet = QuicPacket::Create(diagnostic_label, max_pktlen_); - // ngtcp2_conn_write_pkt will fill the created QuicPacket up - // as much as possible, and then should be called repeatedly - // until it returns 0 or fatally errors. On each call, it - // will return the number of bytes encoded into the packet. - ssize_t nwrite = - ngtcp2_conn_write_pkt( - connection(), - &path.path, - packet->data(), - max_pktlen_, - uv_hrtime()); - - if (nwrite <= 0) { - switch (nwrite) { - case 0: - // There was nothing to write. - return true; - case NGTCP2_ERR_PKT_NUM_EXHAUSTED: - // There is a finite number of packets that can be sent - // per connection. Once those are exhausted, there's - // absolutely nothing we can do except immediately - // and silently tear down the QuicSession. This has - // to be silent because we can't even send a - // CONNECTION_CLOSE since even those require a - // packet number. - Close(QuicSessionListener::SESSION_CLOSE_FLAG_SILENT); - return false; - default: - set_last_error(QUIC_ERROR_SESSION, static_cast<int>(nwrite)); - return false; - } - } - - packet->set_length(nwrite); - UpdateEndpoint(path.path); - UpdateDataStats(); - - if (!SendPacket(std::move(packet))) - return false; - } -} - -void QuicSession::MemoryInfo(MemoryTracker* tracker) const { - tracker->TrackField("crypto_context", crypto_context_.get()); - tracker->TrackField("alpn", alpn_); - tracker->TrackField("hostname", hostname_); - tracker->TrackField("idle", idle_); - tracker->TrackField("retransmit", retransmit_); - tracker->TrackField("streams", streams_); - tracker->TrackFieldWithSize("current_ngtcp2_memory", current_ngtcp2_memory_); - tracker->TrackField("conn_closebuf", conn_closebuf_); - tracker->TrackField("application", application_); - tracker->TrackField("quic_state", quic_state_); - tracker->TrackField("qlog_stream", qlog_stream_); - StatsBase::StatsMemoryInfo(tracker); -} - -void QuicSession::ExtendMaxStreamsBidi(uint64_t max_streams) { - state_->max_streams_bidi = max_streams; -} - -void QuicSession::ExtendMaxStreamsUni(uint64_t max_streams) { - state_->max_streams_uni = max_streams; -} - -void QuicSession::ExtendMaxStreamsRemoteUni(uint64_t max_streams) { - Debug(this, "Extend remote max unidirectional streams: %" PRIu64, - max_streams); - application_->ExtendMaxStreamsRemoteUni(max_streams); -} - -void QuicSession::ExtendMaxStreamsRemoteBidi(uint64_t max_streams) { - Debug(this, "Extend remote max bidirectional streams: %" PRIu64, - max_streams); - application_->ExtendMaxStreamsRemoteBidi(max_streams); -} - -void QuicSession::ExtendMaxStreamData(int64_t stream_id, uint64_t max_data) { - Debug(this, - "Extending max stream %" PRId64 " data to %" PRIu64, - stream_id, max_data); - application_->ExtendMaxStreamData(stream_id, max_data); -} - -// Static function to create a new server QuicSession instance -BaseObjectPtr<QuicSession> QuicSession::CreateServer( - QuicSocket* socket, - const QuicSessionConfig& config, - const SocketAddress& local_addr, - const SocketAddress& remote_addr, - const QuicCID& dcid, - const QuicCID& scid, - const QuicCID& ocid, - uint32_t version, - const std::string& alpn, - uint32_t options, - QlogMode qlog) { - Local<Object> obj; - if (!socket->env() - ->quicserversession_instance_template() - ->NewInstance(socket->env()->context()).ToLocal(&obj)) { - return {}; - } - BaseObjectPtr<QuicSession> session = - MakeDetachedBaseObject<QuicSession>( - socket, - config, - obj, - local_addr, - remote_addr, - dcid, - scid, - ocid, - version, - alpn, - options, - qlog); - - session->AddToSocket(socket); - return session; -} - -// Initializes a newly created server QuicSession. -void QuicSession::InitServer( - QuicSessionConfig config, - const SocketAddress& local_addr, - const SocketAddress& remote_addr, - const QuicCID& dcid, - const QuicCID& scid, - const QuicCID& ocid, - uint32_t version, - QlogMode qlog) { - - CHECK_NULL(connection_); - - ExtendMaxStreamsBidi(DEFAULT_MAX_STREAMS_BIDI); - ExtendMaxStreamsUni(DEFAULT_MAX_STREAMS_UNI); - - local_address_ = local_addr; - remote_address_ = remote_addr; - max_pktlen_ = GetMaxPktLen(remote_addr); - - config.set_original_connection_id(ocid, scid); - - connection_id_strategy_(this, scid_.cid(), kScidLen); - - config.GenerateStatelessResetToken(this, scid_); - config.GeneratePreferredAddressToken(connection_id_strategy_, this, &pscid_); - - QuicPath path(local_addr, remote_address_); - - // NOLINTNEXTLINE(readability/pointer_notation) - if (qlog == QlogMode::kEnabled) config.set_qlog({ *ocid, OnQlogWrite }); - - ngtcp2_conn* conn; - CHECK_EQ( - ngtcp2_conn_server_new( - &conn, - dcid.cid(), - scid_.cid(), - &path, - version, - &callbacks[crypto_context_->side()], - &config, - &alloc_info_, - static_cast<QuicSession*>(this)), 0); - - connection_.reset(conn); - - crypto_context_->Initialize(); - UpdateDataStats(); - UpdateIdleTimer(); -} - -namespace { -void QuicSessionOnClientHelloDone(const FunctionCallbackInfo<Value>& args) { - Environment* env = Environment::GetCurrent(args); - QuicSession* session; - ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder()); - Local<FunctionTemplate> cons = env->secure_context_constructor_template(); - SecureContext* context = nullptr; - if (args[0]->IsObject() && cons->HasInstance(args[0])) - context = Unwrap<SecureContext>(args[0].As<Object>()); - session->crypto_context()->OnClientHelloDone( - BaseObjectPtr<SecureContext>(context)); -} - -void QuicSessionOnCertDone(const FunctionCallbackInfo<Value>& args) { - QuicSession* session; - ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder()); - session->crypto_context()->OnOCSPDone(args[0]); -} -} // namespace - - -// Data stats are used to allow user code to keep track of important -// statistics such as amount of data in flight through the lifetime -// of a connection. -void QuicSession::UpdateDataStats() { - if (is_destroyed()) - return; - state_->max_data_left = ngtcp2_conn_get_max_data_left(connection()); - - ngtcp2_conn_stat stat; - ngtcp2_conn_get_conn_stat(connection(), &stat); - - SetStat(&QuicSessionStats::latest_rtt, stat.latest_rtt); - SetStat(&QuicSessionStats::min_rtt, stat.min_rtt); - SetStat(&QuicSessionStats::smoothed_rtt, stat.smoothed_rtt); - SetStat(&QuicSessionStats::receive_rate, stat.recv_rate_sec); - SetStat(&QuicSessionStats::send_rate, stat.delivery_rate_sec); - SetStat(&QuicSessionStats::cwnd, stat.cwnd); - - state_->bytes_in_flight = stat.bytes_in_flight; - // The max_bytes_in_flight is a highwater mark that can be used - // in performance analysis operations. - if (stat.bytes_in_flight > GetStat(&QuicSessionStats::max_bytes_in_flight)) - SetStat(&QuicSessionStats::max_bytes_in_flight, stat.bytes_in_flight); -} - -// Static method for creating a new client QuicSession instance. -BaseObjectPtr<QuicSession> QuicSession::CreateClient( - QuicSocket* socket, - const SocketAddress& local_addr, - const SocketAddress& remote_addr, - BaseObjectPtr<SecureContext> secure_context, - ngtcp2_transport_params* early_transport_params, - crypto::SSLSessionPointer early_session_ticket, - Local<Value> dcid, - PreferredAddressStrategy preferred_address_strategy, - const std::string& alpn, - const std::string& hostname, - uint32_t options, - QlogMode qlog) { - Local<Object> obj; - if (!socket->env() - ->quicclientsession_instance_template() - ->NewInstance(socket->env()->context()).ToLocal(&obj)) { - return {}; - } - - BaseObjectPtr<QuicSession> session = - MakeDetachedBaseObject<QuicSession>( - socket, - obj, - local_addr, - remote_addr, - secure_context, - early_transport_params, - std::move(early_session_ticket), - dcid, - preferred_address_strategy, - alpn, - hostname, - options, - qlog); - - session->AddToSocket(socket); - - return session; -} - -// Initialize a newly created client QuicSession. -// The early_transport_params and session_ticket are optional to -// perform a 0RTT resumption of a prior session. -// The dcid_value parameter is optional to allow user code the -// ability to provide an explicit dcid (this should be rare) -void QuicSession::InitClient( - const SocketAddress& local_addr, - const SocketAddress& remote_addr, - ngtcp2_transport_params* early_transport_params, - crypto::SSLSessionPointer early_session_ticket, - Local<Value> dcid_value, - QlogMode qlog) { - CHECK_NULL(connection_); - - local_address_ = local_addr; - remote_address_ = remote_addr; - Debug(this, "Initializing connection from %s to %s", - local_address_, - remote_address_); - - // The maximum packet length is determined largely - // by the IP version (IPv4 vs IPv6). Packet sizes - // should be limited to the maximum MTU necessary to - // prevent IP fragmentation. - max_pktlen_ = GetMaxPktLen(remote_address_); - - QuicSessionConfig config(quic_state()); - ExtendMaxStreamsBidi(DEFAULT_MAX_STREAMS_BIDI); - ExtendMaxStreamsUni(DEFAULT_MAX_STREAMS_UNI); - - connection_id_strategy_(this, scid_.cid(), NGTCP2_MAX_CIDLEN); - - ngtcp2_cid dcid; - if (dcid_value->IsArrayBufferView()) { - ArrayBufferViewContents<uint8_t> sbuf( - dcid_value.As<ArrayBufferView>()); - CHECK_LE(sbuf.length(), NGTCP2_MAX_CIDLEN); - CHECK_GE(sbuf.length(), NGTCP2_MIN_CIDLEN); - memcpy(dcid.data, sbuf.data(), sbuf.length()); - dcid.datalen = sbuf.length(); - } else { - connection_id_strategy_(this, &dcid, NGTCP2_MAX_CIDLEN); - } - - QuicPath path(local_address_, remote_address_); - - if (qlog == QlogMode::kEnabled) config.set_qlog({ dcid, OnQlogWrite }); - - ngtcp2_conn* conn; - CHECK_EQ( - ngtcp2_conn_client_new( - &conn, - &dcid, - scid_.cid(), - &path, - NGTCP2_PROTO_VER, - &callbacks[crypto_context_->side()], - &config, - &alloc_info_, - static_cast<QuicSession*>(this)), 0); - - - connection_.reset(conn); - - crypto_context_->Initialize(); - - if (early_transport_params != nullptr) - ngtcp2_conn_set_early_remote_transport_params(conn, early_transport_params); - crypto_context_->set_session(std::move(early_session_ticket)); - - UpdateIdleTimer(); - UpdateDataStats(); -} - -// Static ngtcp2 callbacks are registered when ngtcp2 when a new ngtcp2_conn is -// created. These are static functions that, for the most part, simply defer to -// a QuicSession instance that is passed through as user_data. - -// Called by ngtcp2 for both client and server connections when -// TLS handshake data has been received and needs to be processed. -// This will be called multiple times during the TLS handshake -// process and may be called during key updates. -int QuicSession::OnReceiveCryptoData( - ngtcp2_conn* conn, - ngtcp2_crypto_level crypto_level, - uint64_t offset, - const uint8_t* data, - size_t datalen, - void* user_data) { - QuicSession* session = static_cast<QuicSession*>(user_data); - - if (UNLIKELY(session->is_destroyed())) - return NGTCP2_ERR_CALLBACK_FAILURE; - - QuicSession::NgCallbackScope callback_scope(session); - int ret = session->crypto_context()->Receive( - crypto_level, - offset, - data, - datalen); - return ret == 0 ? 0 : NGTCP2_ERR_CALLBACK_FAILURE; -} - -// Called by ngtcp2 for both client and server connections -// when a request to extend the maximum number of bidirectional -// streams has been received. -int QuicSession::OnExtendMaxStreamsBidi( - ngtcp2_conn* conn, - uint64_t max_streams, - void* user_data) { - QuicSession* session = static_cast<QuicSession*>(user_data); - - if (UNLIKELY(session->is_destroyed())) - return NGTCP2_ERR_CALLBACK_FAILURE; - - QuicSession::NgCallbackScope callback_scope(session); - session->ExtendMaxStreamsBidi(max_streams); - return 0; -} - -// Called by ngtcp2 for both client and server connections -// when a request to extend the maximum number of unidirectional -// streams has been received -int QuicSession::OnExtendMaxStreamsUni( - ngtcp2_conn* conn, - uint64_t max_streams, - void* user_data) { - QuicSession* session = static_cast<QuicSession*>(user_data); - - if (UNLIKELY(session->is_destroyed())) - return NGTCP2_ERR_CALLBACK_FAILURE; - - QuicSession::NgCallbackScope callback_scope(session); - session->ExtendMaxStreamsUni(max_streams); - return 0; -} - -// Triggered by ngtcp2 when the local peer has received an -// indication from the remote peer indicating that additional -// unidirectional streams may be sent. The max_streams parameter -// identifies the highest unidirectional stream ID that may be -// opened. -int QuicSession::OnExtendMaxStreamsRemoteUni( - ngtcp2_conn* conn, - uint64_t max_streams, - void* user_data) { - QuicSession* session = static_cast<QuicSession*>(user_data); - - if (UNLIKELY(session->is_destroyed())) - return NGTCP2_ERR_CALLBACK_FAILURE; - - QuicSession::NgCallbackScope callback_scope(session); - session->ExtendMaxStreamsRemoteUni(max_streams); - return 0; -} - -// Triggered by ngtcp2 when the local peer has received an -// indication from the remote peer indicating that additional -// bidirectional streams may be sent. The max_streams parameter -// identifies the highest bidirectional stream ID that may be -// opened. -int QuicSession::OnExtendMaxStreamsRemoteBidi( - ngtcp2_conn* conn, - uint64_t max_streams, - void* user_data) { - QuicSession* session = static_cast<QuicSession*>(user_data); - - if (UNLIKELY(session->is_destroyed())) - return NGTCP2_ERR_CALLBACK_FAILURE; - - QuicSession::NgCallbackScope callback_scope(session); - session->ExtendMaxStreamsRemoteUni(max_streams); - return 0; -} - -// Triggered by ngtcp2 when the local peer has received a flow -// control signal from the remote peer indicating that additional -// data can be sent. The max_data parameter identifies the maximum -// data offset that may be sent. That is, a value of 99 means that -// out of a stream of 1000 bytes, only the first 100 may be sent. -// (offsets 0 through 99). -int QuicSession::OnExtendMaxStreamData( - ngtcp2_conn* conn, - int64_t stream_id, - uint64_t max_data, - void* user_data, - void* stream_user_data) { - QuicSession* session = static_cast<QuicSession*>(user_data); - - if (UNLIKELY(session->is_destroyed())) - return NGTCP2_ERR_CALLBACK_FAILURE; - - QuicSession::NgCallbackScope callback_scope(session); - session->ExtendMaxStreamData(stream_id, max_data); - return 0; -} - -int QuicSession::OnConnectionIDStatus( - ngtcp2_conn* conn, - int type, - uint64_t seq, - const ngtcp2_cid* cid, - const uint8_t* token, - void* user_data) { - QuicSession* session = static_cast<QuicSession*>(user_data); - - if (UNLIKELY(session->is_destroyed())) - return NGTCP2_ERR_CALLBACK_FAILURE; - - if (token != nullptr) { - QuicCID qcid(cid); - Debug(session, "Updating connection ID %s with reset token", qcid); - session->UpdateConnectionID(type, qcid, StatelessResetToken(token)); - } - return 0; -} - -// Called by ngtcp2 for both client and server connections -// when ngtcp2 has determined that the TLS handshake has -// been completed. It is important to understand that this -// is only an indication of the local peer's handshake state. -// The remote peer might not yet have completed its part -// of the handshake. -int QuicSession::OnHandshakeCompleted( - ngtcp2_conn* conn, - void* user_data) { - - QuicSession* session = static_cast<QuicSession*>(user_data); - - if (UNLIKELY(session->is_destroyed())) - return NGTCP2_ERR_CALLBACK_FAILURE; - - QuicSession::NgCallbackScope callback_scope(session); - session->HandshakeCompleted(); - return 0; -} - -// Called by ngtcp2 for clients when the handshake has been -// confirmed. Confirmation occurs *after* handshake completion. -int QuicSession::OnHandshakeConfirmed( - ngtcp2_conn* conn, - void* user_data) { - QuicSession* session = static_cast<QuicSession*>(user_data); - - if (UNLIKELY(session->is_destroyed())) - return NGTCP2_ERR_CALLBACK_FAILURE; - - QuicSession::NgCallbackScope callback_scope(session); - session->HandshakeConfirmed(); - return 0; -} - -// Called by ngtcp2 when a chunk of stream data has been received. -// Currently, ngtcp2 ensures that this callback is always called -// with an offset parameter strictly larger than the previous call's -// offset + datalen (that is, data will never be delivered out of -// order). That behavior may change in the future but only via a -// configuration option. -int QuicSession::OnReceiveStreamData( - ngtcp2_conn* conn, - uint32_t flags, - int64_t stream_id, - uint64_t offset, - const uint8_t* data, - size_t datalen, - void* user_data, - void* stream_user_data) { - QuicSession* session = static_cast<QuicSession*>(user_data); - - if (UNLIKELY(session->is_destroyed())) - return NGTCP2_ERR_CALLBACK_FAILURE; - - QuicSession::NgCallbackScope callback_scope(session); - return session->ReceiveStreamData( - flags, - stream_id, - data, - datalen, - offset) ? 0 : NGTCP2_ERR_CALLBACK_FAILURE; -} - -// Called by ngtcp2 when a new stream has been opened -int QuicSession::OnStreamOpen( - ngtcp2_conn* conn, - int64_t stream_id, - void* user_data) { - QuicSession* session = static_cast<QuicSession*>(user_data); - - if (UNLIKELY(session->is_destroyed())) - return NGTCP2_ERR_CALLBACK_FAILURE; - - // We currently do not do anything with this callback. - // QuicStream instances are created implicitly once the - // first chunk of stream data is received. - - return 0; -} - -// Called by ngtcp2 when an acknowledgement for a chunk of -// TLS handshake data has been received by the remote peer. -// This is only an indication that data was received, not that -// it was successfully processed. Acknowledgements are a key -// part of the QUIC reliability mechanism. -int QuicSession::OnAckedCryptoOffset( - ngtcp2_conn* conn, - ngtcp2_crypto_level crypto_level, - uint64_t offset, - uint64_t datalen, - void* user_data) { - QuicSession* session = static_cast<QuicSession*>(user_data); - - if (UNLIKELY(session->is_destroyed())) - return NGTCP2_ERR_CALLBACK_FAILURE; - - QuicSession::NgCallbackScope callback_scope(session); - session->crypto_context()->AcknowledgeCryptoData(crypto_level, datalen); - return 0; -} - -// Called by ngtcp2 when an acknowledgement for a chunk of -// stream data has been received successfully by the remote peer. -// This is only an indication that data was received, not that -// it was successfully processed. Acknowledgements are a key -// part of the QUIC reliability mechanism. -int QuicSession::OnAckedStreamDataOffset( - ngtcp2_conn* conn, - int64_t stream_id, - uint64_t offset, - uint64_t datalen, - void* user_data, - void* stream_user_data) { - QuicSession* session = static_cast<QuicSession*>(user_data); - - if (UNLIKELY(session->is_destroyed())) - return NGTCP2_ERR_CALLBACK_FAILURE; - - QuicSession::NgCallbackScope callback_scope(session); - session->AckedStreamDataOffset(stream_id, offset, datalen); - return 0; -} - -// Called by ngtcp2 for a client connection when the server -// has indicated a preferred address in the transport -// params. -// For now, there are two modes: we can accept the preferred address -// or we can reject it. Later, we may want to implement a callback -// to ask the user if they want to accept the preferred address or -// not. -int QuicSession::OnSelectPreferredAddress( - ngtcp2_conn* conn, - ngtcp2_addr* dest, - const ngtcp2_preferred_addr* paddr, - void* user_data) { - QuicSession* session = static_cast<QuicSession*>(user_data); - - if (UNLIKELY(session->is_destroyed())) - return NGTCP2_ERR_CALLBACK_FAILURE; - - QuicSession::NgCallbackScope callback_scope(session); - - // The paddr parameter contains the server advertised preferred - // address. The dest parameter contains the address that is - // actually being used. If the preferred address is selected, - // then the contents of paddr are copied over to dest. It is - // important to remember that SelectPreferredAddress should - // return true regardless of whether the preferred address was - // selected or not. It should only return false if there was - // an actual failure processing things. Note, however, that - // even in such a failure, we debug log and ignore it. - // If the preferred address is not selected, dest remains - // unchanged. - PreferredAddress preferred_address(session->env(), dest, paddr); - session->SelectPreferredAddress(preferred_address); - return 0; -} - -// Called by ngtcp2 when a stream has been closed. -int QuicSession::OnStreamClose( - ngtcp2_conn* conn, - int64_t stream_id, - uint64_t app_error_code, - void* user_data, - void* stream_user_data) { - QuicSession* session = static_cast<QuicSession*>(user_data); - - if (UNLIKELY(session->is_destroyed())) - return NGTCP2_ERR_CALLBACK_FAILURE; - - QuicSession::NgCallbackScope callback_scope(session); - session->StreamClose(stream_id, app_error_code); - return 0; -} - -// Stream reset means the remote peer will no longer send data -// on the identified stream. It is essentially a premature close. -// The final_size parameter is important here in that it identifies -// exactly how much data the *remote peer* is aware that it sent. -// If there are lost packets, then the local peer's idea of the final -// size might not match. -int QuicSession::OnStreamReset( - ngtcp2_conn* conn, - int64_t stream_id, - uint64_t final_size, - uint64_t app_error_code, - void* user_data, - void* stream_user_data) { - QuicSession* session = static_cast<QuicSession*>(user_data); - - if (UNLIKELY(session->is_destroyed())) - return NGTCP2_ERR_CALLBACK_FAILURE; - - QuicSession::NgCallbackScope callback_scope(session); - session->StreamReset(stream_id, final_size, app_error_code); - return 0; -} - -// Called by ngtcp2 when it needs to generate some random data. -// We currently do not use it, but the ngtcp2_rand_ctx identifies -// why the random data is necessary. When ctx is equal to -// NGTCP2_RAND_CTX_NONE, it typically means the random data -// is being used during the TLS handshake. When ctx is equal to -// NGTCP2_RAND_CTX_PATH_CHALLENGE, the random data is being -// used to construct a PATH_CHALLENGE. These *might* need more -// secure and robust random number generation given the -// sensitivity of PATH_CHALLENGE operations (an attacker -// could use a compromised PATH_CHALLENGE to trick an endpoint -// into redirecting traffic). -// -// The ngtcp2_rand_ctx tells us what the random data is used for. -// Currently, there is only one use. In the future, we'll want to -// explore whether we want to handle the different cases uses. -int QuicSession::OnRand( - uint8_t* dest, - size_t destlen, - ngtcp2_rand_ctx ctx) { - EntropySource(dest, destlen); - return 0; -} - -// When a new client connection is established, ngtcp2 will call -// this multiple times to generate a pool of connection IDs to use. -int QuicSession::OnGetNewConnectionID( - ngtcp2_conn* conn, - ngtcp2_cid* cid, - uint8_t* token, - size_t cidlen, - void* user_data) { - QuicSession* session = static_cast<QuicSession*>(user_data); - - if (UNLIKELY(session->is_destroyed())) - return NGTCP2_ERR_CALLBACK_FAILURE; - - CHECK(!NgCallbackScope::InNgCallbackScope(session)); - session->GetNewConnectionID(cid, token, cidlen); - return 0; -} - -// When a connection is closed, ngtcp2 will call this multiple -// times to retire connection IDs. It's also possible for this -// to be called at times throughout the lifecycle of the connection -// to remove a CID from the availability pool. -int QuicSession::OnRemoveConnectionID( - ngtcp2_conn* conn, - const ngtcp2_cid* cid, - void* user_data) { - QuicSession* session = static_cast<QuicSession*>(user_data); - - if (UNLIKELY(session->is_destroyed())) - return NGTCP2_ERR_CALLBACK_FAILURE; - - if (session->is_server()) - session->socket()->DisassociateCID(QuicCID(cid)); - return 0; -} - -// Called by ngtcp2 to perform path validation. Path validation -// is necessary to ensure that a packet is originating from the -// expected source. If the res parameter indicates success, it -// means that the path specified has been verified as being -// valid. -// -// Validity here means only that there has been a successful -// exchange of PATH_CHALLENGE information between the peers. -// It's critical to understand that the validity of a path -// can change at any timee so this is only an indication of -// validity at a specific point in time. -int QuicSession::OnPathValidation( - ngtcp2_conn* conn, - const ngtcp2_path* path, - ngtcp2_path_validation_result res, - void* user_data) { - QuicSession* session = static_cast<QuicSession*>(user_data); - - if (UNLIKELY(session->is_destroyed())) - return NGTCP2_ERR_CALLBACK_FAILURE; - - QuicSession::NgCallbackScope callback_scope(session); - session->PathValidation(path, res); - return 0; -} - -// Triggered by ngtcp2 when a version negotiation is received. -// What this means is that the remote peer does not support the -// QUIC version requested. The only thing we can do here (per -// the QUIC specification) is silently discard the connection -// and notify the JavaScript side that a different version of -// QUIC should be used. The sv parameter does list the QUIC -// versions advertised as supported by the remote peer. -int QuicSession::OnVersionNegotiation( - ngtcp2_conn* conn, - const ngtcp2_pkt_hd* hd, - const uint32_t* sv, - size_t nsv, - void* user_data) { - QuicSession* session = static_cast<QuicSession*>(user_data); - - if (UNLIKELY(session->is_destroyed())) - return NGTCP2_ERR_CALLBACK_FAILURE; - - QuicSession::NgCallbackScope callback_scope(session); - session->VersionNegotiation(sv, nsv); - return 0; -} - -// Triggered by ngtcp2 when a stateless reset is received. What this -// means is that the remote peer might recognize the CID but has lost -// all state necessary to successfully process it. The only thing we -// can do is silently close the connection. For server sessions, this -// means all session state is shut down and discarded, even on the -// JavaScript side. For client sessions, we discard session state at -// the C++ layer but -- at least in the future -- we can retain some -// state at the JavaScript level to allow for automatic session -// resumption. -int QuicSession::OnStatelessReset( - ngtcp2_conn* conn, - const ngtcp2_pkt_stateless_reset* sr, - void* user_data) { - QuicSession* session = static_cast<QuicSession*>(user_data); - - if (UNLIKELY(session->is_destroyed())) - return NGTCP2_ERR_CALLBACK_FAILURE; - - session->set_stateless_reset(); - return 0; -} - -const ngtcp2_conn_callbacks QuicSession::callbacks[2] = { - // NGTCP2_CRYPTO_SIDE_CLIENT - { - ngtcp2_crypto_client_initial_cb, - nullptr, - OnReceiveCryptoData, - OnHandshakeCompleted, - OnVersionNegotiation, - ngtcp2_crypto_encrypt_cb, - ngtcp2_crypto_decrypt_cb, - ngtcp2_crypto_hp_mask_cb, - OnReceiveStreamData, - OnAckedCryptoOffset, - OnAckedStreamDataOffset, - OnStreamOpen, - OnStreamClose, - OnStatelessReset, - ngtcp2_crypto_recv_retry_cb, - OnExtendMaxStreamsBidi, - OnExtendMaxStreamsUni, - OnRand, - OnGetNewConnectionID, - OnRemoveConnectionID, - ngtcp2_crypto_update_key_cb, - OnPathValidation, - OnSelectPreferredAddress, - OnStreamReset, - OnExtendMaxStreamsRemoteBidi, - OnExtendMaxStreamsRemoteUni, - OnExtendMaxStreamData, - OnConnectionIDStatus, - OnHandshakeConfirmed, - nullptr, // recv_new_token - ngtcp2_crypto_delete_crypto_aead_ctx_cb, - ngtcp2_crypto_delete_crypto_cipher_ctx_cb, - }, - // NGTCP2_CRYPTO_SIDE_SERVER - { - nullptr, - ngtcp2_crypto_recv_client_initial_cb, - OnReceiveCryptoData, - OnHandshakeCompleted, - nullptr, // recv_version_negotiation - ngtcp2_crypto_encrypt_cb, - ngtcp2_crypto_decrypt_cb, - ngtcp2_crypto_hp_mask_cb, - OnReceiveStreamData, - OnAckedCryptoOffset, - OnAckedStreamDataOffset, - OnStreamOpen, - OnStreamClose, - OnStatelessReset, - nullptr, // recv_retry - nullptr, // extend_max_streams_bidi - nullptr, // extend_max_streams_uni - OnRand, - OnGetNewConnectionID, - OnRemoveConnectionID, - ngtcp2_crypto_update_key_cb, - OnPathValidation, - nullptr, // select_preferred_addr - OnStreamReset, - OnExtendMaxStreamsRemoteBidi, - OnExtendMaxStreamsRemoteUni, - OnExtendMaxStreamData, - OnConnectionIDStatus, - nullptr, // handshake_confirmed - nullptr, // recv_new_token - ngtcp2_crypto_delete_crypto_aead_ctx_cb, - ngtcp2_crypto_delete_crypto_cipher_ctx_cb, - } -}; - -BaseObjectPtr<QLogStream> QuicSession::qlog_stream() { - if (!qlog_stream_) { - CHECK(qlog_stream_ = QLogStream::Create(env())); - listener_->OnQLog(qlog_stream_.get()); - } - return qlog_stream_; -} - -void QuicSession::OnQlogWrite( - void* user_data, - uint32_t flags, - const void* data, - size_t len) { - QuicSession* session = static_cast<QuicSession*>(user_data); - Environment* env = session->env(); - - // Fun fact... ngtcp2 does not emit the final qlog statement until the - // ngtcp2_conn object is destroyed. Ideally, destroying is explicit, - // but sometimes the QuicSession object can be garbage collected without - // being explicitly destroyed. During those times, we cannot call out - // to JavaScript. Because we don't know for sure if we're in in a GC - // when this is called, it is safer to just defer writes to immediate. - BaseObjectPtr<QLogStream> ptr = session->qlog_stream(); - std::vector<uint8_t> buffer(len); - memcpy(buffer.data(), data, len); - env->SetImmediate([ptr = std::move(ptr), - buffer = std::move(buffer), - flags](Environment*) { - ptr->Emit(buffer.data(), buffer.size(), flags); - }); -} - -BaseObjectPtr<QLogStream> QLogStream::Create(Environment* env) { - HandleScope scope(env->isolate()); - - // TODO(@jasnell): There is identical code in heap_utils for the - // HeapSnapshotStream. We can consolidate the two. - if (env->qlogoutputstream_constructor_template().IsEmpty()) { - // Create FunctionTemplate for QLogStream - Local<FunctionTemplate> os = FunctionTemplate::New(env->isolate()); - os->Inherit(AsyncWrap::GetConstructorTemplate(env)); - Local<ObjectTemplate> ost = os->InstanceTemplate(); - ost->SetInternalFieldCount(StreamBase::kInternalFieldCount); - os->SetClassName( - FIXED_ONE_BYTE_STRING(env->isolate(), "QLogStream")); - StreamBase::AddMethods(env, os); - env->set_qlogoutputstream_constructor_template(ost); - } - - Local<Object> obj; - if (!env->qlogoutputstream_constructor_template() - ->NewInstance(env->context()) - .ToLocal(&obj)) { - return {}; - } - - return MakeBaseObject<QLogStream>(env, obj); -} - -QLogStream::QLogStream(Environment* env, v8::Local<Object> obj) - : AsyncWrap(env, obj, AsyncWrap::PROVIDER_QLOGSTREAM), - StreamBase(env) { - MakeWeak(); - StreamBase::AttachToObject(GetObject()); -} - -void QLogStream::Emit(const uint8_t* data, size_t len, uint32_t flags) { - size_t remaining = len; - while (remaining != 0) { - uv_buf_t buf = EmitAlloc(len); - ssize_t avail = std::min<size_t>(remaining, buf.len); - memcpy(buf.base, data, avail); - remaining -= avail; - data += avail; - EmitRead(avail, buf); - } - - if (ended_ && flags & NGTCP2_QLOG_WRITE_FLAG_FIN) - EmitRead(UV_EOF); -} - -int QLogStream::DoShutdown(ShutdownWrap* req_wrap) { - UNREACHABLE(); -} - -int QLogStream::DoWrite( - WriteWrap* w, - uv_buf_t* bufs, - size_t count, - uv_stream_t* send_handle) { - UNREACHABLE(); -} - -bool QLogStream::IsAlive() { return true; } -bool QLogStream::IsClosing() { return false; } - -// JavaScript API - -namespace { -void QuicSessionSetSocket(const FunctionCallbackInfo<Value>& args) { - QuicSession* session; - QuicSocket* socket; - ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder()); - CHECK(args[0]->IsObject()); - ASSIGN_OR_RETURN_UNWRAP(&socket, args[0].As<Object>()); - args.GetReturnValue().Set(session->set_socket(socket, args[1]->IsTrue())); -} - -// GracefulClose flips a flag that prevents new local streams -// from being opened and new remote streams from being received. It is -// important to note that this does *NOT* send a CONNECTION_CLOSE packet -// to the peer. Existing streams are permitted to close gracefully. -void QuicSessionGracefulClose(const FunctionCallbackInfo<Value>& args) { - QuicSession* session; - ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder()); - session->StartGracefulClose(); -} - -// Destroying the QuicSession will trigger sending of a CONNECTION_CLOSE -// packet, after which the QuicSession will be immediately torn down. -void QuicSessionDestroy(const FunctionCallbackInfo<Value>& args) { - Environment* env = Environment::GetCurrent(args); - QuicSession* session; - ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder()); - session->set_last_error(QuicError(env, args[0], args[1])); - session->Destroy(); -} - -void QuicSessionGetEphemeralKeyInfo(const FunctionCallbackInfo<Value>& args) { - QuicSession* session; - ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder()); - Local<Object> ret; - if (session->crypto_context()->ephemeral_key().ToLocal(&ret)) - args.GetReturnValue().Set(ret); -} - -void QuicSessionGetPeerCertificate(const FunctionCallbackInfo<Value>& args) { - QuicSession* session; - ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder()); - Local<Value> ret; - if (session->crypto_context()->peer_cert(!args[0]->IsTrue()).ToLocal(&ret)) - args.GetReturnValue().Set(ret); -} - -void QuicSessionGetRemoteAddress( - const FunctionCallbackInfo<Value>& args) { - QuicSession* session; - ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder()); - Environment* env = session->env(); - CHECK(args[0]->IsObject()); - args.GetReturnValue().Set( - session->remote_address().ToJS(env, args[0].As<Object>())); -} - -void QuicSessionGetCertificate( - const FunctionCallbackInfo<Value>& args) { - QuicSession* session; - ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder()); - Local<Value> ret; - if (session->crypto_context()->cert().ToLocal(&ret)) - args.GetReturnValue().Set(ret); -} - -void QuicSessionPing(const FunctionCallbackInfo<Value>& args) { - QuicSession* session; - ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder()); - session->Ping(); -} - -// Triggers a silent close of a QuicSession. This is currently only used -// (and should ever only be used) for testing purposes... -void QuicSessionSilentClose(const FunctionCallbackInfo<Value>& args) { - QuicSession* session; - ASSIGN_OR_RETURN_UNWRAP(&session, args[0].As<Object>()); - ProcessEmitWarning( - session->env(), - "Forcing silent close of QuicSession for testing purposes only"); - session->Close(QuicSessionListener::SESSION_CLOSE_FLAG_SILENT); -} - -// This is used purely for testing. -void QuicSessionRemoveFromSocket(const FunctionCallbackInfo<Value>& args) { - QuicSession* session; - ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder()); - session->RemoveFromSocket(); -} - -void QuicSessionUpdateKey(const FunctionCallbackInfo<Value>& args) { - QuicSession* session; - ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder()); - // Initiating a key update may fail if it is done too early (either - // before the TLS handshake has been confirmed or while a previous - // key update is being processed). When it fails, InitiateKeyUpdate() - // will return false. - args.GetReturnValue().Set(session->crypto_context()->InitiateKeyUpdate()); -} - -// When a client wishes to resume a prior TLS session, it must specify both -// the remember transport parameters and remembered TLS session ticket. Those -// will each be provided as a TypedArray. The DecodeTransportParams and -// DecodeSessionTicket functions handle those. If the argument is undefined, -// then resumption is not used. - -bool DecodeTransportParams( - Local<Value> value, - ngtcp2_transport_params* params) { - if (value->IsUndefined()) - return false; - CHECK(value->IsArrayBufferView()); - ArrayBufferViewContents<uint8_t> sbuf(value.As<ArrayBufferView>()); - if (sbuf.length() != sizeof(ngtcp2_transport_params)) - return false; - memcpy(params, sbuf.data(), sizeof(ngtcp2_transport_params)); - return true; -} - -crypto::SSLSessionPointer DecodeSessionTicket(Local<Value> value) { - if (value->IsUndefined()) - return {}; - CHECK(value->IsArrayBufferView()); - ArrayBufferViewContents<unsigned char> sbuf(value.As<ArrayBufferView>()); - return crypto::GetTLSSession(sbuf.data(), sbuf.length()); -} - -void NewQuicClientSession(const FunctionCallbackInfo<Value>& args) { - Environment* env = Environment::GetCurrent(args); - - QuicSocket* socket; - int32_t family; - uint32_t port; - SecureContext* sc; - SocketAddress remote_addr; - int32_t preferred_address_policy; - PreferredAddressStrategy preferred_address_strategy; - uint32_t options = QUICCLIENTSESSION_OPTION_VERIFY_HOSTNAME_IDENTITY; - std::string alpn(NGHTTP3_ALPN_H3); - - enum ARG_IDX : int { - SOCKET, - TYPE, - IP, - PORT, - SECURE_CONTEXT, - SNI, - REMOTE_TRANSPORT_PARAMS, - SESSION_TICKET, - DCID, - PREFERRED_ADDRESS_POLICY, - ALPN, - OPTIONS, - QLOG - }; - - CHECK(args[ARG_IDX::SOCKET]->IsObject()); - CHECK(args[ARG_IDX::SECURE_CONTEXT]->IsObject()); - CHECK(args[ARG_IDX::IP]->IsString()); - CHECK(args[ARG_IDX::ALPN]->IsString()); - CHECK(args[ARG_IDX::TYPE]->Int32Value(env->context()).To(&family)); - CHECK(args[ARG_IDX::PORT]->Uint32Value(env->context()).To(&port)); - CHECK(args[ARG_IDX::OPTIONS]->Uint32Value(env->context()).To(&options)); - if (!args[ARG_IDX::SNI]->IsUndefined()) - CHECK(args[ARG_IDX::SNI]->IsString()); - - ASSIGN_OR_RETURN_UNWRAP(&socket, args[ARG_IDX::SOCKET].As<Object>()); - ASSIGN_OR_RETURN_UNWRAP(&sc, args[ARG_IDX::SECURE_CONTEXT].As<Object>()); - - CHECK(args[ARG_IDX::PREFERRED_ADDRESS_POLICY]->Int32Value( - env->context()).To(&preferred_address_policy)); - switch (preferred_address_policy) { - case QUIC_PREFERRED_ADDRESS_USE: - preferred_address_strategy = QuicSession::UsePreferredAddressStrategy; - break; - default: - preferred_address_strategy = QuicSession::IgnorePreferredAddressStrategy; - } - - node::Utf8Value address(env->isolate(), args[ARG_IDX::IP]); - node::Utf8Value servername(env->isolate(), args[ARG_IDX::SNI]); - - if (!SocketAddress::New(family, *address, port, &remote_addr)) - return args.GetReturnValue().Set(ERR_FAILED_TO_CREATE_SESSION); - - // ALPN is a string prefixed by the length, followed by values - Utf8Value val(env->isolate(), args[ARG_IDX::ALPN]); - alpn = val.length(); - alpn += *val; - - crypto::SSLSessionPointer early_session_ticket = - DecodeSessionTicket(args[ARG_IDX::SESSION_TICKET]); - ngtcp2_transport_params early_transport_params; - bool has_early_transport_params = - DecodeTransportParams( - args[ARG_IDX::REMOTE_TRANSPORT_PARAMS], - &early_transport_params); - - socket->ReceiveStart(); - - BaseObjectPtr<QuicSession> session = - QuicSession::CreateClient( - socket, - socket->local_address(), - remote_addr, - BaseObjectPtr<SecureContext>(sc), - has_early_transport_params ? &early_transport_params : nullptr, - std::move(early_session_ticket), - args[ARG_IDX::DCID], - preferred_address_strategy, - alpn, - std::string(*servername), - options, - args[ARG_IDX::QLOG]->IsTrue() ? - QlogMode::kEnabled : - QlogMode::kDisabled); - session->SendPendingData(); - if (session->is_destroyed()) - return args.GetReturnValue().Set(ERR_FAILED_TO_CREATE_SESSION); - - args.GetReturnValue().Set(session->object()); -} - -// Add methods that are shared by both client and server QuicSessions -void AddMethods(Environment* env, Local<FunctionTemplate> session) { - env->SetProtoMethod(session, "destroy", QuicSessionDestroy); - env->SetProtoMethod(session, "getRemoteAddress", QuicSessionGetRemoteAddress); - env->SetProtoMethod(session, "getCertificate", QuicSessionGetCertificate); - env->SetProtoMethod(session, "getPeerCertificate", - QuicSessionGetPeerCertificate); - env->SetProtoMethod(session, "gracefulClose", QuicSessionGracefulClose); - env->SetProtoMethod(session, "updateKey", QuicSessionUpdateKey); - env->SetProtoMethod(session, "ping", QuicSessionPing); - env->SetProtoMethod(session, "removeFromSocket", QuicSessionRemoveFromSocket); - env->SetProtoMethod(session, "onClientHelloDone", - QuicSessionOnClientHelloDone); - env->SetProtoMethod(session, "onCertDone", QuicSessionOnCertDone); -} -} // namespace - -void QuicSession::Initialize( - Environment* env, - Local<Object> target, - Local<Context> context) { - { - Local<String> class_name = - FIXED_ONE_BYTE_STRING(env->isolate(), "QuicServerSession"); - Local<FunctionTemplate> session = FunctionTemplate::New(env->isolate()); - session->SetClassName(class_name); - session->Inherit(AsyncWrap::GetConstructorTemplate(env)); - Local<ObjectTemplate> sessiont = session->InstanceTemplate(); - sessiont->SetInternalFieldCount(QuicSession::kInternalFieldCount); - sessiont->Set(env->owner_symbol(), Null(env->isolate())); - AddMethods(env, session); - env->set_quicserversession_instance_template(sessiont); - } - - { - Local<String> class_name = - FIXED_ONE_BYTE_STRING(env->isolate(), "QuicClientSession"); - Local<FunctionTemplate> session = FunctionTemplate::New(env->isolate()); - session->SetClassName(class_name); - session->Inherit(AsyncWrap::GetConstructorTemplate(env)); - Local<ObjectTemplate> sessiont = session->InstanceTemplate(); - sessiont->SetInternalFieldCount(QuicSession::kInternalFieldCount); - sessiont->Set(env->owner_symbol(), Null(env->isolate())); - AddMethods(env, session); - env->SetProtoMethod(session, - "getEphemeralKeyInfo", - QuicSessionGetEphemeralKeyInfo); - env->SetProtoMethod(session, - "setSocket", - QuicSessionSetSocket); - env->set_quicclientsession_instance_template(sessiont); - - env->SetMethod(target, "createClientSession", NewQuicClientSession); - env->SetMethod(target, "silentCloseSession", QuicSessionSilentClose); - } -} - -} // namespace quic -} // namespace node diff --git a/src/quic/node_quic_session.h b/src/quic/node_quic_session.h deleted file mode 100644 index 10e16cc2689..00000000000 --- a/src/quic/node_quic_session.h +++ /dev/null @@ -1,1531 +0,0 @@ -#ifndef SRC_QUIC_NODE_QUIC_SESSION_H_ -#define SRC_QUIC_NODE_QUIC_SESSION_H_ - -#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS - -#include "aliased_buffer.h" -#include "aliased_struct.h" -#include "async_wrap.h" -#include "env.h" -#include "handle_wrap.h" -#include "node.h" -#include "node_crypto.h" -#include "node_http_common.h" -#include "node_mem.h" -#include "node_quic_state.h" -#include "node_quic_buffer-inl.h" -#include "node_quic_crypto.h" -#include "node_quic_util.h" -#include "node_sockaddr.h" -#include "stream_base.h" -#include "timer_wrap.h" -#include "v8.h" -#include "uv.h" - -#include <ngtcp2/ngtcp2.h> -#include <ngtcp2/ngtcp2_crypto.h> -#include <nghttp3/nghttp3.h> -#include <openssl/ssl.h> - -#include <unordered_map> -#include <string> -#include <vector> - -namespace node { -namespace quic { - -using ConnectionPointer = DeleteFnPtr<ngtcp2_conn, ngtcp2_conn_del>; - -class QuicApplication; -class QuicPacket; -class QuicSocket; -class QuicStream; - -using QuicHeader = NgHeaderBase<QuicApplication>; - -using StreamsMap = std::unordered_map<int64_t, BaseObjectPtr<QuicStream>>; - -enum class QlogMode { - kDisabled, - kEnabled -}; - -typedef void(*ConnectionIDStrategy)( - QuicSession* session, - ngtcp2_cid* cid, - size_t cidlen); - -typedef void(*PreferredAddressStrategy)( - QuicSession* session, - const PreferredAddress& preferred_address); - -// The QuicSessionConfig class holds the initial transport parameters and -// configuration options set by the JavaScript side when either a -// client or server QuicSession is created. Instances are -// stack created and use a combination of an AliasedBuffer to pass -// the numeric settings quickly (see node_quic_state.h) and passed -// in non-numeric settings (e.g. preferred_addr). -class QuicSessionConfig final : public ngtcp2_settings { - public: - QuicSessionConfig() = default; - - explicit QuicSessionConfig(QuicState* quic_state) { - Set(quic_state); - } - - QuicSessionConfig(const QuicSessionConfig& config) { - initial_ts = uv_hrtime(); - initial_rtt = config.initial_rtt; - transport_params = config.transport_params; - max_udp_payload_size = config.max_udp_payload_size; - cc_algo = config.cc_algo; - cc = config.cc; - qlog = config.qlog; - log_printf = config.log_printf; - token = config.token; - } - - void ResetToDefaults(QuicState* quic_state); - - // QuicSessionConfig::Set() pulls values out of the AliasedBuffer - // defined in node_quic_state.h and stores the values in settings_. - // If preferred_addr is not nullptr, it is copied into the - // settings_.preferred_addr field - void Set(QuicState* quic_state, - const struct sockaddr* preferred_addr = nullptr); - - inline void set_original_connection_id( - const QuicCID& ocid, - const QuicCID& scid); - - // Generates the stateless reset token for the settings_ - inline void GenerateStatelessResetToken( - QuicSession* session, - const QuicCID& cid); - - // If the preferred address is set, generates the associated tokens - inline void GeneratePreferredAddressToken( - ConnectionIDStrategy connection_id_strategy, - QuicSession* session, - QuicCID* pscid); - - inline void set_qlog(const ngtcp2_qlog_settings& qlog); -}; - -// Options to alter the behavior of various functions on the -// server QuicSession. These are set on the QuicSocket when -// the listen() function is called and are passed to the -// constructor of the server QuicSession. -enum QuicServerSessionOptions : uint32_t { - // When set, instructs the server QuicSession to reject - // client authentication certs that cannot be verified. - QUICSERVERSESSION_OPTION_REJECT_UNAUTHORIZED = 0x1, - - // When set, instructs the server QuicSession to request - // a client authentication cert - QUICSERVERSESSION_OPTION_REQUEST_CERT = 0x2 -}; - -// Options to alter the behavior of various functions on the -// client QuicSession. These are set on the client QuicSession -// constructor. -enum QuicClientSessionOptions : uint32_t { - // When set, instructs the client QuicSession to include an - // OCSP request in the initial TLS handshake - QUICCLIENTSESSION_OPTION_REQUEST_OCSP = 0x1, - - // When set, instructs the client QuicSession to verify the - // hostname identity. This is required by QUIC and enabled - // by default. We allow disabling it only for debugging - // purposes. - QUICCLIENTSESSION_OPTION_VERIFY_HOSTNAME_IDENTITY = 0x2, - - // When set, instructs the client QuicSession to perform - // additional checks on TLS session resumption. - QUICCLIENTSESSION_OPTION_RESUME = 0x4 -}; - -#define QUICSESSION_SHARED_STATE(V) \ - V(KEYLOG_ENABLED, keylog_enabled, uint8_t) \ - V(CLIENT_HELLO_ENABLED, client_hello_enabled, uint8_t) \ - V(OCSP_ENABLED, ocsp_enabled, uint8_t) \ - V(PATH_VALIDATED_ENABLED, path_validated_enabled, uint8_t) \ - V(USE_PREFERRED_ADDRESS_ENABLED, use_preferred_address_enabled, uint8_t) \ - V(HANDSHAKE_CONFIRMED, handshake_confirmed, uint8_t) \ - V(IDLE_TIMEOUT, idle_timeout, uint8_t) \ - V(MAX_STREAMS_BIDI, max_streams_bidi, uint64_t) \ - V(MAX_STREAMS_UNI, max_streams_uni, uint64_t) \ - V(MAX_DATA_LEFT, max_data_left, uint64_t) \ - V(BYTES_IN_FLIGHT, bytes_in_flight, uint64_t) - -#define V(_, name, type) type name; -struct QuicSessionState { - QUICSESSION_SHARED_STATE(V) -}; -#undef V - -#define V(id, name, _) \ - IDX_QUICSESSION_STATE_##id = offsetof(QuicSessionState, name), -enum QuicSessionStateFields { - QUICSESSION_SHARED_STATE(V) - IDX_QUICSESSION_STATE_END -}; -#undef V - -#define SESSION_STATS(V) \ - V(CREATED_AT, created_at, "Created At") \ - V(HANDSHAKE_START_AT, handshake_start_at, "Handshake Started") \ - V(HANDSHAKE_SEND_AT, handshake_send_at, "Handshke Last Sent") \ - V(HANDSHAKE_CONTINUE_AT, handshake_continue_at, "Handshke Continued") \ - V(HANDSHAKE_COMPLETED_AT, handshake_completed_at, "Handshake Completed") \ - V(HANDSHAKE_CONFIRMED_AT, handshake_confirmed_at, "Handshake Confirmed") \ - V(HANDSHAKE_ACKED_AT, handshake_acked_at, "Handshake Last Acknowledged") \ - V(SENT_AT, sent_at, "Last Sent At") \ - V(RECEIVED_AT, received_at, "Last Received At") \ - V(CLOSING_AT, closing_at, "Closing") \ - V(DESTROYED_AT, destroyed_at, "Destroyed At") \ - V(BYTES_RECEIVED, bytes_received, "Bytes Received") \ - V(BYTES_SENT, bytes_sent, "Bytes Sent") \ - V(BIDI_STREAM_COUNT, bidi_stream_count, "Bidi Stream Count") \ - V(UNI_STREAM_COUNT, uni_stream_count, "Uni Stream Count") \ - V(STREAMS_IN_COUNT, streams_in_count, "Streams In Count") \ - V(STREAMS_OUT_COUNT, streams_out_count, "Streams Out Count") \ - V(KEYUPDATE_COUNT, keyupdate_count, "Key Update Count") \ - V(LOSS_RETRANSMIT_COUNT, loss_retransmit_count, "Loss Retransmit Count") \ - V(ACK_DELAY_RETRANSMIT_COUNT, \ - ack_delay_retransmit_count, \ - "Ack Delay Retransmit Count") \ - V(PATH_VALIDATION_SUCCESS_COUNT, \ - path_validation_success_count, \ - "Path Validation Success Count") \ - V(PATH_VALIDATION_FAILURE_COUNT, \ - path_validation_failure_count, \ - "Path Validation Failure Count") \ - V(MAX_BYTES_IN_FLIGHT, max_bytes_in_flight, "Max Bytes In Flight") \ - V(BLOCK_COUNT, block_count, "Block Count") \ - V(MIN_RTT, min_rtt, "Minimum RTT") \ - V(LATEST_RTT, latest_rtt, "Latest RTT") \ - V(SMOOTHED_RTT, smoothed_rtt, "Smoothed RTT") \ - V(CWND, cwnd, "Cwnd") \ - V(RECEIVE_RATE, receive_rate, "Receive Rate / Sec") \ - V(SEND_RATE, send_rate, "Send Rate Sec") \ - -#define V(name, _, __) IDX_QUIC_SESSION_STATS_##name, -enum QuicSessionStatsIdx : int { - SESSION_STATS(V) - IDX_QUIC_SESSION_STATS_COUNT -}; -#undef V - -#define V(_, name, __) uint64_t name; -struct QuicSessionStats { - SESSION_STATS(V) -}; -#undef V - -struct QuicSessionStatsTraits { - using Stats = QuicSessionStats; - using Base = QuicSession; - - template <typename Fn> - static void ToString(const Base& ptr, Fn&& add_field); -}; - -class QLogStream final : public AsyncWrap, - public StreamBase { - public: - static BaseObjectPtr<QLogStream> Create(Environment* env); - - QLogStream(Environment* env, v8::Local<v8::Object> obj); - - void Emit(const uint8_t* data, size_t len, uint32_t flags); - - void End() { ended_ = true; } - - int ReadStart() override { return 0; } - int ReadStop() override { return 0; } - int DoShutdown(ShutdownWrap* req_wrap) override; - int DoWrite(WriteWrap* w, - uv_buf_t* bufs, - size_t count, - uv_stream_t* send_handle) override; - bool IsAlive() override; - bool IsClosing() override; - AsyncWrap* GetAsyncWrap() override { return this; } - - SET_NO_MEMORY_INFO(); - SET_MEMORY_INFO_NAME(QLogStream); - SET_SELF_SIZE(QLogStream); - - private: - bool ended_ = false; -}; - -class QuicSessionListener { - public: - enum SessionCloseFlags { - SESSION_CLOSE_FLAG_NONE, - SESSION_CLOSE_FLAG_SILENT, - SESSION_CLOSE_FLAG_STATELESS_RESET - }; - - virtual ~QuicSessionListener(); - - virtual void OnKeylog(const char* str, size_t size); - virtual void OnClientHello( - const char* alpn, - const char* server_name); - virtual void OnCert(const char* server_name); - virtual void OnOCSP(v8::Local<v8::Value> ocsp); - virtual void OnStreamHeaders( - int64_t stream_id, - int kind, - const std::vector<std::unique_ptr<QuicHeader>>& headers, - int64_t push_id); - virtual void OnStreamClose( - int64_t stream_id, - uint64_t app_error_code); - virtual void OnStreamReset( - int64_t stream_id, - uint64_t app_error_code); - virtual void OnSessionClose( - QuicError error, - int flags = SESSION_CLOSE_FLAG_NONE); - virtual void OnStreamReady(BaseObjectPtr<QuicStream> stream); - virtual void OnHandshakeCompleted(); - virtual void OnPathValidation( - ngtcp2_path_validation_result res, - const sockaddr* local, - const sockaddr* remote); - virtual void OnUsePreferredAddress( - int family, - const PreferredAddress& preferred_address); - virtual void OnSessionTicket(int size, SSL_SESSION* session); - virtual void OnStreamBlocked(int64_t stream_id); - virtual void OnVersionNegotiation( - uint32_t supported_version, - const uint32_t* versions, - size_t vcnt); - virtual void OnQLog(QLogStream* qlog_stream); - - QuicSession* session() const { return session_.get(); } - - private: - BaseObjectWeakPtr<QuicSession> session_; - QuicSessionListener* previous_listener_ = nullptr; - friend class QuicSession; -}; - -class JSQuicSessionListener final : public QuicSessionListener { - public: - void OnKeylog(const char* str, size_t size) override; - void OnClientHello( - const char* alpn, - const char* server_name) override; - void OnCert(const char* server_name) override; - void OnOCSP(v8::Local<v8::Value> ocsp) override; - void OnStreamHeaders( - int64_t stream_id, - int kind, - const std::vector<std::unique_ptr<QuicHeader>>& headers, - int64_t push_id) override; - void OnStreamClose( - int64_t stream_id, - uint64_t app_error_code) override; - void OnStreamReset( - int64_t stream_id, - uint64_t app_error_code) override; - void OnSessionClose( - QuicError error, - int flags = SESSION_CLOSE_FLAG_NONE) override; - void OnStreamReady(BaseObjectPtr<QuicStream> stream) override; - void OnHandshakeCompleted() override; - void OnPathValidation( - ngtcp2_path_validation_result res, - const sockaddr* local, - const sockaddr* remote) override; - void OnSessionTicket(int size, SSL_SESSION* session) override; - void OnUsePreferredAddress( - int family, - const PreferredAddress& preferred_address) override; - void OnStreamBlocked(int64_t stream_id) override; - void OnVersionNegotiation( - uint32_t supported_version, - const uint32_t* versions, - size_t vcnt) override; - void OnQLog(QLogStream* qlog_stream) override; - - private: - friend class QuicSession; -}; - -#define QUICCRYPTOCONTEXT_FLAGS(V) \ - V(IN_TLS_CALLBACK, in_tls_callback) \ - V(IN_KEY_UPDATE, in_key_update) \ - V(IN_OCSP_RESPONSE, in_ocsp_request) \ - V(IN_CLIENT_HELLO, in_client_hello) \ - V(EARLY_DATA, early_data) - -// The QuicCryptoContext class encapsulates all of the crypto/TLS -// handshake details on behalf of a QuicSession. -class QuicCryptoContext final : public MemoryRetainer { - public: - inline QuicCryptoContext( - QuicSession* session, - BaseObjectPtr<crypto::SecureContext> secure_context, - ngtcp2_crypto_side side, - uint32_t options); - - ~QuicCryptoContext() override; - - inline uint64_t Cancel(); - - // Outgoing crypto data must be retained in memory until it is - // explicitly acknowledged. AcknowledgeCryptoData will be invoked - // when ngtcp2 determines that it has received an acknowledgement - // for crypto data at the specified level. This is our indication - // that the data for that level can be released. - void AcknowledgeCryptoData(ngtcp2_crypto_level level, uint64_t datalen); - - inline void Initialize(); - - // Enables openssl's TLS tracing mechanism for this session only. - void EnableTrace(); - - // Returns the server's prepared OCSP response for transmission. This - // is not used by client QuicSession instances. - inline v8::MaybeLocal<v8::Value> ocsp_response() const; - - // Returns ngtcp2's understanding of the current inbound crypto level - inline ngtcp2_crypto_level read_crypto_level() const; - - // Returns ngtcp2's understanding of the current outbound crypto level - inline ngtcp2_crypto_level write_crypto_level() const; - - inline bool early_data() const; - - bool is_option_set(uint32_t option) const { return options_ & option; } - - // Emits a single keylog line to the JavaScript layer - inline void Keylog(const char* line); - - int OnClientHello(); - - void OnClientHelloDone(BaseObjectPtr<crypto::SecureContext> context); - - int OnOCSP(); - - void OnOCSPDone(v8::Local<v8::Value> ocsp_response); - - bool OnSecrets( - ngtcp2_crypto_level level, - const uint8_t* rx_secret, - const uint8_t* tx_secret, - size_t secretlen); - - int OnTLSStatus(); - - // Receives and processes TLS handshake details - int Receive( - ngtcp2_crypto_level crypto_level, - uint64_t offset, - const uint8_t* data, - size_t datalen); - - // Resumes the TLS handshake following a client hello or - // OCSP callback - inline void ResumeHandshake(); - - inline v8::MaybeLocal<v8::Value> cert() const; - inline v8::MaybeLocal<v8::Value> cipher_name() const; - inline v8::MaybeLocal<v8::Value> cipher_version() const; - inline v8::MaybeLocal<v8::Object> ephemeral_key() const; - inline const char* hello_alpn() const; - inline v8::MaybeLocal<v8::Array> hello_ciphers() const; - inline const char* hello_servername() const; - inline v8::MaybeLocal<v8::Value> peer_cert(bool abbreviated) const; - inline std::string selected_alpn() const; - inline const char* servername() const; - - void set_option(uint32_t option, bool on = true) { - if (on) - options_ |= option; - else - options_ &= ~option; - } - -#define V(id, name) \ - inline bool is_##name() const { \ - return flags_ & (1 << QUICCRYPTOCONTEXT_FLAG_##id); } \ - inline void set_##name(bool on = true) { \ - if (on) \ - flags_ |= (1 << QUICCRYPTOCONTEXT_FLAG_##id); \ - else \ - flags_ &= ~(1 << QUICCRYPTOCONTEXT_FLAG_##id); \ - } - QUICCRYPTOCONTEXT_FLAGS(V) -#undef V - - inline bool set_session(crypto::SSLSessionPointer session); - - inline void set_tls_alert(int err); - - ngtcp2_crypto_side side() const { return side_; } - - void WriteHandshake( - ngtcp2_crypto_level level, - const uint8_t* data, - size_t datalen); - - bool InitiateKeyUpdate(); - - int VerifyPeerIdentity(); - - QuicSession* session() const { return session_.get(); } - - void MemoryInfo(MemoryTracker* tracker) const override; - - SET_MEMORY_INFO_NAME(QuicCryptoContext) - SET_SELF_SIZE(QuicCryptoContext) - - private: - bool SetSecrets( - ngtcp2_crypto_level level, - const uint8_t* rx_secret, - const uint8_t* tx_secret, - size_t secretlen); - - BaseObjectWeakPtr<QuicSession> session_; - BaseObjectPtr<crypto::SecureContext> secure_context_; - ngtcp2_crypto_side side_; - crypto::SSLPointer ssl_; - QuicBuffer handshake_[3]; - uint32_t options_; - uint32_t flags_ = 0; - - v8::Global<v8::ArrayBufferView> ocsp_response_; - crypto::BIOPointer bio_trace_; - -#define V(id, _) QUICCRYPTOCONTEXT_FLAG_##id, - enum QuicCryptoContextFlags : uint32_t { - QUICCRYPTOCONTEXT_FLAGS(V) - QUICCRYPTOCONTEXT_FLAG_COUNT - }; -#undef V - - class TLSCallbackScope { - public: - explicit TLSCallbackScope(QuicCryptoContext* context) : - context_(context) { - context_->set_in_tls_callback(); - } - - ~TLSCallbackScope() { - context_->set_in_tls_callback(false); - } - - static bool is_in_callback(QuicCryptoContext* context) { - return context->is_in_tls_callback(); - } - - private: - QuicCryptoContext* context_; - }; - - class TLSHandshakeScope { - public: - using DoneCB = std::function<void()>; - TLSHandshakeScope( - QuicCryptoContext* context, - DoneCB done) : - context_(context), - done_(done) {} - - ~TLSHandshakeScope() { - if (!is_handshake_suspended()) - return; - - done_(); - // Only continue the TLS handshake if we are not currently running - // synchronously within the TLS handshake function. This can happen - // when the callback function passed to the clientHello and cert - // event handlers is called synchronously. If the function is called - // asynchronously, then we have to manually continue the handshake. - if (!TLSCallbackScope::is_in_callback(context_)) - context_->ResumeHandshake(); - } - - private: - bool is_handshake_suspended() const { - return context_->is_in_ocsp_request() || context_->is_in_client_hello(); - } - - - QuicCryptoContext* context_; - DoneCB done_; - }; - - friend class QuicSession; -}; - -// A QuicApplication encapsulates the specific details of -// working with a specific QUIC application (e.g. http/3). -class QuicApplication : public MemoryRetainer, - public mem::NgLibMemoryManagerBase { - public: - inline explicit QuicApplication(QuicSession* session); - virtual ~QuicApplication() = default; - - // The QuicSession will call Initialize as soon as the TLS - // secrets have been set. See QuicCryptoContext::OnSecrets - virtual bool Initialize() = 0; - - // QuicSession will forward all received stream data immediately - // on to the QuicApplication. The only additional processing the - // QuicSession does is to automatically adjust the QuicSession-level - // flow control window. It is up to the QuicApplication to do - // the same for the QuicStream-level flow control. - // - // flags are passed on directly from ngtcp2. The most important - // of which here is NGTCP2_STREAM_DATA_FLAG_FIN, which indicates - // that this is the final chunk of data that the peer will send - // for this stream. - // - // It is also possible for the NGTCP2_STREAM_DATA_FLAG_0RTT flag - // to be set, indicating that this chunk of data was received in - // a 0RTT packet before the TLS handshake completed. This would - // indicate that it is not as secure and could be replayed by - // an attacker. We're not currently making use of that flag. - virtual bool ReceiveStreamData( - uint32_t flags, - int64_t stream_id, - const uint8_t* data, - size_t datalen, - uint64_t offset) = 0; - - virtual void AcknowledgeStreamData( - int64_t stream_id, - uint64_t offset, - size_t datalen) { - Acknowledge(stream_id, offset, datalen); - } - - virtual bool BlockStream(int64_t id) { return true; } - - virtual void ExtendMaxStreamsRemoteUni(uint64_t max_streams) {} - - virtual void ExtendMaxStreamsRemoteBidi(uint64_t max_streams) {} - - virtual void ExtendMaxStreamData(int64_t stream_id, uint64_t max_data) {} - - virtual void ResumeStream(int64_t stream_id) {} - - // Different QUIC applications may set some application data in - // the session ticket (e.g. http/3 would set server settings in the - // application data). By default, there's nothing to set. - virtual void SetSessionTicketAppData(const SessionTicketAppData& app_data) {} - - // Different QUIC applications may set some application data in - // the session ticket (e.g. http/3 would set server settings in the - // application data). By default, there's nothing to get. - virtual SessionTicketAppData::Status GetSessionTicketAppData( - const SessionTicketAppData& app_data, - SessionTicketAppData::Flag flag) { - return flag == SessionTicketAppData::Flag::STATUS_RENEW ? - SessionTicketAppData::Status::TICKET_USE_RENEW : - SessionTicketAppData::Status::TICKET_USE; - } - - virtual void StreamHeaders( - int64_t stream_id, - int kind, - const std::vector<std::unique_ptr<QuicHeader>>& headers, - int64_t push_id = 0); - - virtual void StreamClose( - int64_t stream_id, - uint64_t app_error_code); - - virtual void StreamReset( - int64_t stream_id, - uint64_t app_error_code); - - virtual bool SubmitInformation( - int64_t stream_id, - v8::Local<v8::Array> headers) { return false; } - - virtual bool SubmitHeaders( - int64_t stream_id, - v8::Local<v8::Array> headers, - uint32_t flags) { return false; } - - virtual bool SubmitTrailers( - int64_t stream_id, - v8::Local<v8::Array> headers) { return false; } - - virtual BaseObjectPtr<QuicStream> SubmitPush( - int64_t stream_id, - v8::Local<v8::Array> headers) { - // By default, push streams are not supported - // by an application. - return {}; - } - - inline Environment* env() const; - - bool SendPendingData(); - size_t max_header_pairs() const { return max_header_pairs_; } - size_t max_header_length() const { return max_header_length_; } - - protected: - QuicSession* session() const { return session_.get(); } - bool needs_init() const { return needs_init_; } - void set_init_done() { needs_init_ = false; } - inline void set_stream_fin(int64_t stream_id); - void set_max_header_pairs(size_t max) { max_header_pairs_ = max; } - void set_max_header_length(size_t max) { max_header_length_ = max; } - inline std::unique_ptr<QuicPacket> CreateStreamDataPacket(); - - struct StreamData { - size_t count = 0; - size_t remaining = 0; - int64_t id = -1; - int fin = 0; - ngtcp2_vec data[kMaxVectorCount] {}; - ngtcp2_vec* buf = nullptr; - BaseObjectPtr<QuicStream> stream; - StreamData() { buf = data; } - }; - - void Acknowledge( - int64_t stream_id, - uint64_t offset, - size_t datalen); - virtual int GetStreamData(StreamData* data) = 0; - virtual bool StreamCommit(StreamData* data, size_t datalen) = 0; - virtual bool ShouldSetFin(const StreamData& data) = 0; - - ssize_t WriteVStream( - QuicPathStorage* path, - uint8_t* buf, - ssize_t* ndatalen, - const StreamData& stream_data); - - private: - void MaybeSetFin(const StreamData& stream_data); - BaseObjectWeakPtr<QuicSession> session_; - bool needs_init_ = true; - size_t max_header_pairs_ = 0; - size_t max_header_length_ = 0; -}; - -// QUICSESSION_FLAGS are converted into is_{name}() and set_{name}(bool on) -// accessors on the QuicSession class. -#define QUICSESSION_FLAGS(V) \ - V(WRAPPED, wrapped) \ - V(CLOSING, closing) \ - V(GRACEFUL_CLOSING, graceful_closing) \ - V(DESTROYED, destroyed) \ - V(TRANSPORT_PARAMS_SET, transport_params_set) \ - V(NGTCP2_CALLBACK, in_ngtcp2_callback) \ - V(CONNECTION_CLOSE_SCOPE, in_connection_close_scope) \ - V(SILENT_CLOSE, silent_closing) \ - V(STATELESS_RESET, stateless_reset) \ - V(CLOSING_TIMER_ENABLED, closing_timer_enabled) - -// QUIC sessions are logical connections that exchange data -// back and forth between peer endpoints via UDP. Every QuicSession -// has an associated TLS context and all data transferred between -// the peers is always encrypted. Unlike TLS over TCP, however, -// The QuicSession uses a session identifier that is independent -// of both the local *and* peer IP address, allowing a QuicSession -// to persist across changes in the network (one of the key features -// of QUIC). QUIC sessions also support 0RTT, implement error -// correction mechanisms to recover from lost packets, and flow -// control. In other words, there's quite a bit going on within -// a QuicSession object. -class QuicSession final : public AsyncWrap, - public mem::NgLibMemoryManager< - QuicSession, - ngtcp2_mem>, - public StatsBase<QuicSessionStatsTraits> { - public: - // The default preferred address strategy is to ignore it - static void IgnorePreferredAddressStrategy( - QuicSession* session, - const PreferredAddress& preferred_address); - - static void UsePreferredAddressStrategy( - QuicSession* session, - const PreferredAddress& preferred_address); - - static void Initialize( - Environment* env, - v8::Local<v8::Object> target, - v8::Local<v8::Context> context); - - static BaseObjectPtr<QuicSession> CreateServer( - QuicSocket* socket, - const QuicSessionConfig& config, - const SocketAddress& local_addr, - const SocketAddress& remote_addr, - const QuicCID& dcid, - const QuicCID& scid, - const QuicCID& ocid, - uint32_t version, - const std::string& alpn = NGHTTP3_ALPN_H3, - uint32_t options = 0, - QlogMode qlog = QlogMode::kDisabled); - - static BaseObjectPtr<QuicSession> CreateClient( - QuicSocket* socket, - const SocketAddress& local_addr, - const SocketAddress& remote_addr, - BaseObjectPtr<crypto::SecureContext> secure_context, - ngtcp2_transport_params* early_transport_params, - crypto::SSLSessionPointer early_session_ticket, - v8::Local<v8::Value> dcid, - PreferredAddressStrategy preferred_address_strategy = - IgnorePreferredAddressStrategy, - const std::string& alpn = NGHTTP3_ALPN_H3, - const std::string& hostname = "", - uint32_t options = 0, - QlogMode qlog = QlogMode::kDisabled); - - static const int kInitialClientBufferLength = 4096; - - QuicSession( - ngtcp2_crypto_side side, - // The QuicSocket that created this session. Note that - // it is possible to replace this socket later, after - // the TLS handshake has completed. The QuicSession - // should never assume that the socket will always - // remain the same. - QuicSocket* socket, - v8::Local<v8::Object> wrap, - BaseObjectPtr<crypto::SecureContext> secure_context, - AsyncWrap::ProviderType provider_type, - // QUIC is generally just a transport. The ALPN identifier - // is used to specify the application protocol that is - // layered on top. If not specified, this will default - // to the HTTP/3 identifier. For QUIC, the alpn identifier - // is always required. - const std::string& alpn, - const std::string& hostname, - const QuicCID& dcid, - uint32_t options = 0, - PreferredAddressStrategy preferred_address_strategy = - IgnorePreferredAddressStrategy); - - // Server Constructor - QuicSession( - QuicSocket* socket, - const QuicSessionConfig& config, - v8::Local<v8::Object> wrap, - const SocketAddress& local_addr, - const SocketAddress& remote_addr, - const QuicCID& dcid, - const QuicCID& scid, - const QuicCID& ocid, - uint32_t version, - const std::string& alpn, - uint32_t options, - QlogMode qlog); - - // Client Constructor - QuicSession( - QuicSocket* socket, - v8::Local<v8::Object> wrap, - const SocketAddress& local_addr, - const SocketAddress& remote_addr, - BaseObjectPtr<crypto::SecureContext> secure_context, - ngtcp2_transport_params* early_transport_params, - crypto::SSLSessionPointer early_session_ticket, - v8::Local<v8::Value> dcid, - PreferredAddressStrategy preferred_address_strategy, - const std::string& alpn, - const std::string& hostname, - uint32_t options, - QlogMode qlog); - - ~QuicSession() override; - - std::string diagnostic_name() const override; - - inline QuicCID dcid() const; - - QuicApplication* application() const { return application_.get(); } - - QuicCryptoContext* crypto_context() const { return crypto_context_.get(); } - - QuicSessionListener* listener() const { return listener_; } - - BaseObjectPtr<QuicStream> CreateStream(int64_t id); - - BaseObjectPtr<QuicStream> FindStream(int64_t id) const; - - inline bool HasStream(int64_t id) const; - - inline bool allow_early_data() const; - -#define V(id, name) \ - bool is_##name() const { return flags_ & (1 << QUICSESSION_FLAG_##id); } \ - void set_##name(bool on = true) { \ - if (on) \ - flags_ |= (1 << QUICSESSION_FLAG_##id); \ - else \ - flags_ &= ~(1 << QUICSESSION_FLAG_##id); \ - } - QUICSESSION_FLAGS(V) -#undef V - - // Returns true if the QuicSession has entered the - // closing period after sending a CONNECTION_CLOSE. - // While true, the QuicSession is only permitted to - // transmit CONNECTION_CLOSE frames until either the - // idle timeout period elapses or until the QuicSession - // is explicitly destroyed. - inline bool is_in_closing_period() const; - - // Returns true if the QuicSession has received a - // CONNECTION_CLOSE frame from the peer. Once in - // the draining period, the QuicSession is not - // permitted to send any frames to the peer. The - // QuicSession will be silently closed after either - // the idle timeout period elapses or until the - // QuicSession is explicitly destroyed. - inline bool is_in_draining_period() const; - - inline bool is_server() const; - - // Starting a GracefulClose disables the ability to open or accept - // new streams for this session. Existing streams are allowed to - // close naturally on their own. Once called, the QuicSession will - // be immediately closed once there are no remaining streams. Note - // that no notification is given to the connecting peer that we're - // in a graceful closing state. A CONNECTION_CLOSE will be sent only - // once Close() is called. - inline void StartGracefulClose(); - - QuicError last_error() const { return last_error_; } - - size_t max_packet_length() const { return max_pktlen_; } - - BaseObjectPtr<QLogStream> qlog_stream(); - - // Get the ALPN protocol identifier configured for this QuicSession. - // For server sessions, this will be compared against the client requested - // ALPN identifier to determine if there is a protocol match. - const std::string& alpn() const { return alpn_; } - - // Get the hostname configured for this QuicSession. This is generally - // only used by client sessions. - const std::string& hostname() const { return hostname_; } - - // Returns the associated peer's address. Note that this - // value can change over the lifetime of the QuicSession. - // The fact that the session is not tied intrinsically to - // a single address is one of the benefits of QUIC. - const SocketAddress& remote_address() const { return remote_address_; } - - inline QuicSocket* socket() const; - - ngtcp2_conn* connection() const { return connection_.get(); } - - void AddStream(BaseObjectPtr<QuicStream> stream); - - void AddToSocket(QuicSocket* socket); - - // Immediately discards the state of the QuicSession - // and renders the QuicSession instance completely - // unusable. - void Destroy(); - - // Extends the QUIC stream flow control window. This is - // called after received data has been consumed and we - // want to allow the peer to send more data. - inline void ExtendStreamOffset(int64_t stream_id, size_t amount); - - // Extends the QUIC session flow control window - inline void ExtendOffset(size_t amount); - - // Retrieve the local transport parameters established for - // this ngtcp2_conn - inline void GetLocalTransportParams(ngtcp2_transport_params* params); - - // The QUIC version that has been negotiated for this session - inline uint32_t negotiated_version() const; - - // True only if ngtcp2 considers the TLS handshake to be completed - inline bool is_handshake_completed() const; - - // Checks to see if data needs to be retransmitted - void OnRetransmitTimeout(); - - // Called when the session has been determined to have been - // idle for too long and needs to be torn down. - inline void OnIdleTimeout(); - - bool OpenBidirectionalStream(int64_t* stream_id); - - bool OpenUnidirectionalStream(int64_t* stream_id); - - // Ping causes the QuicSession to serialize any currently - // pending frames in it's queue, including any necessary - // PROBE packets. This is a best attempt, fire-and-forget - // type of operation. There is no way to listen for a ping - // response. The main intent of using Ping is to either keep - // the connection from becoming idle or to update RTT stats. - void Ping(); - - // Receive and process a QUIC packet received from the peer - bool Receive( - ssize_t nread, - const uint8_t* data, - const SocketAddress& local_addr, - const SocketAddress& remote_addr, - unsigned int flags); - - // Receive a chunk of QUIC stream data received from the peer - bool ReceiveStreamData( - uint32_t flags, - int64_t stream_id, - const uint8_t* data, - size_t datalen, - uint64_t offset); - - void RemoveStream(int64_t stream_id); - - void RemoveFromSocket(); - - // Causes pending ngtcp2 frames to be serialized and sent - void SendPendingData(); - - inline void ShutdownStream(int64_t stream_id, uint64_t code); - - inline bool SendPacket( - std::unique_ptr<QuicPacket> packet, - const ngtcp2_path_storage& path); - - inline uint64_t max_data_left() const; - - inline uint64_t max_local_streams_uni() const; - - inline void set_last_error( - QuicError error = { - uint32_t{QUIC_ERROR_SESSION}, - uint64_t{NGTCP2_NO_ERROR} - }); - - inline void set_last_error(int32_t family, uint64_t error_code); - - inline void set_last_error(int32_t family, int error_code); - - inline void set_remote_transport_params(); - - bool set_socket(QuicSocket* socket, bool nat_rebinding = false); - - int set_session(SSL_SESSION* session); - - const StreamsMap& streams() const { return streams_; } - - void ResumeStream(int64_t stream_id); - - // Submits informational headers to the QUIC Application - // implementation. If headers are not supported, false - // will be returned. Otherwise, returns true - inline bool SubmitInformation( - int64_t stream_id, - v8::Local<v8::Array> headers); - - // Submits initial headers to the QUIC Application - // implementation. If headers are not supported, false - // will be returned. Otherwise, returns true - inline bool SubmitHeaders( - int64_t stream_id, - v8::Local<v8::Array> headers, - uint32_t flags); - - // Submits trailing headers to the QUIC Application - // implementation. If headers are not supported, false - // will be returned. Otherwise, returns true - inline bool SubmitTrailers( - int64_t stream_id, - v8::Local<v8::Array> headers); - - inline BaseObjectPtr<QuicStream> SubmitPush( - int64_t stream_id, - v8::Local<v8::Array> headers); - - // Error handling for the QuicSession. client and server - // instances will do different things here, but ultimately - // an error means that the QuicSession - // should be torn down. - void HandleError(); - - bool SendConnectionClose(); - - bool IsResetToken( - const QuicCID& cid, - const uint8_t* data, - size_t datalen); - - // Implementation for mem::NgLibMemoryManager - inline void CheckAllocatedSize(size_t previous_size) const; - - inline void IncreaseAllocatedSize(size_t size); - - inline void DecreaseAllocatedSize(size_t size); - - // Initiate closing of the QuicSession. This will round trip - // through JavaScript, causing all currently opened streams - // to be closed. If the SESSION_CLOSE_FLAG_SILENT flag is - // set, the connected peer will not be notified, otherwise - // an attempt will be made to send a CONNECTION_CLOSE frame - // to the peer. If Close is called while within the ngtcp2 - // callback scope, sending the CONNECTION_CLOSE will be - // deferred until the ngtcp2 callback scope exits. - void Close( - int close_flags = QuicSessionListener::SESSION_CLOSE_FLAG_NONE); - - void PushListener(QuicSessionListener* listener); - - void RemoveListener(QuicSessionListener* listener); - - inline bool is_unable_to_send_packets(); - - inline void set_connection_id_strategy( - ConnectionIDStrategy strategy); - - inline void set_preferred_address_strategy( - PreferredAddressStrategy strategy); - - inline void SetSessionTicketAppData( - const SessionTicketAppData& app_data); - - inline SessionTicketAppData::Status GetSessionTicketAppData( - const SessionTicketAppData& app_data, - SessionTicketAppData::Flag flag); - - inline void SelectPreferredAddress( - const PreferredAddress& preferred_address); - - // Report that the stream data is flow control blocked - inline void StreamDataBlocked(int64_t stream_id); - - // SendSessionScope triggers SendPendingData() when not executing - // within the context of an ngtcp2 callback. When within an ngtcp2 - // callback, SendPendingData will always be called when the callbacks - // complete. - class SendSessionScope final { - public: - explicit SendSessionScope(QuicSession* session) - : session_(session) { - CHECK(session_); - } - - SendSessionScope(const SendSessionScope& other) = delete; - - ~SendSessionScope() { - if (NgCallbackScope::InNgCallbackScope(session_.get()) || - session_->is_in_closing_period() || - session_->is_in_draining_period()) { - return; - } - session_->SendPendingData(); - } - - private: - BaseObjectPtr<QuicSession> session_; - }; - - // ConnectionCloseScope triggers sending a CONNECTION_CLOSE - // when not executing within the context of an ngtcp2 callback - // and the session is in the correct state. - class ConnectionCloseScope final { - public: - ConnectionCloseScope(QuicSession* session, bool silent = false) - : session_(session), - silent_(silent) { - CHECK(session_); - // If we are already in a ConnectionCloseScope, ignore. - if (session_->is_in_connection_close_scope()) - silent_ = true; - else - session_->set_in_connection_close_scope(); - } - - ConnectionCloseScope(const ConnectionCloseScope& other) = delete; - - ~ConnectionCloseScope() { - if (silent_ || - NgCallbackScope::InNgCallbackScope(session_.get()) || - session_->is_in_closing_period() || - session_->is_in_draining_period()) { - return; - } - session_->set_in_connection_close_scope(false); - session_->SendConnectionClose(); - } - - private: - BaseObjectPtr<QuicSession> session_; - bool silent_ = false; - }; - - // Tracks whether or not we are currently within an ngtcp2 callback - // function. Certain ngtcp2 APIs are not supposed to be called when - // within a callback. We use this as a gate to check. - class NgCallbackScope final { - public: - explicit NgCallbackScope(QuicSession* session) : session_(session) { - CHECK(session_); - CHECK(!InNgCallbackScope(session)); - session_->set_in_ngtcp2_callback(); - } - - NgCallbackScope(const NgCallbackScope& other) = delete; - - ~NgCallbackScope() { - session_->set_in_ngtcp2_callback(false); - } - - static bool InNgCallbackScope(QuicSession* session) { - return session->is_in_ngtcp2_callback(); - } - - private: - BaseObjectPtr<QuicSession> session_; - }; - - QuicState* quic_state() { return quic_state_.get(); } - - void MemoryInfo(MemoryTracker* tracker) const override; - SET_MEMORY_INFO_NAME(QuicSession) - SET_SELF_SIZE(QuicSession) - - private: - static void RandomConnectionIDStrategy( - QuicSession* session, - ngtcp2_cid* cid, - size_t cidlen); - - // Initialize the QuicSession as a server - void InitServer( - QuicSessionConfig config, - const SocketAddress& local_addr, - const SocketAddress& remote_addr, - const QuicCID& dcid, - const QuicCID& scid, - const QuicCID& ocid, - uint32_t version, - QlogMode qlog); - - // Initialize the QuicSession as a client - void InitClient( - const SocketAddress& local_addr, - const SocketAddress& remote_addr, - ngtcp2_transport_params* early_transport_params, - crypto::SSLSessionPointer early_session_ticket, - v8::Local<v8::Value> dcid, - QlogMode qlog); - - bool InitApplication(); - - void AckedStreamDataOffset( - int64_t stream_id, - uint64_t offset, - uint64_t datalen); - - void ExtendMaxStreamData(int64_t stream_id, uint64_t max_data); - - void ExtendMaxStreams(bool bidi, uint64_t max_streams); - - void ExtendMaxStreamsUni(uint64_t max_streams); - - void ExtendMaxStreamsBidi(uint64_t max_streams); - - void ExtendMaxStreamsRemoteUni(uint64_t max_streams); - - void ExtendMaxStreamsRemoteBidi(uint64_t max_streams); - - void GetNewConnectionID(ngtcp2_cid* cid, uint8_t* token, size_t cidlen); - - void GetConnectionCloseInfo(); - - void HandshakeCompleted(); - - void HandshakeConfirmed(); - - void PathValidation( - const ngtcp2_path* path, - ngtcp2_path_validation_result res); - - bool ReceivePacket(ngtcp2_path* path, const uint8_t* data, ssize_t nread); - - void ScheduleRetransmit(); - - bool SendPacket(std::unique_ptr<QuicPacket> packet); - - void StreamClose(int64_t stream_id, uint64_t app_error_code); - - void StreamReset( - int64_t stream_id, - uint64_t final_size, - uint64_t app_error_code); - - bool WritePackets(const char* diagnostic_label = nullptr); - - void UpdateConnectionID( - int type, - const QuicCID& cid, - const StatelessResetToken& token); - - void UpdateDataStats(); - - void UpdateEndpoint(const ngtcp2_path& path); - - void VersionNegotiation(const uint32_t* sv, size_t nsv); - - void UpdateIdleTimer(); - - void UpdateClosingTimer(); - - void UpdateRetransmitTimer(uint64_t timeout); - - bool StartClosingPeriod(); - - void IncrementConnectionCloseAttempts(); - - bool ShouldAttemptConnectionClose(); - - static int OnReceiveCryptoData( - ngtcp2_conn* conn, - ngtcp2_crypto_level crypto_level, - uint64_t offset, - const uint8_t* data, - size_t datalen, - void* user_data); - - static int OnHandshakeCompleted( - ngtcp2_conn* conn, - void* user_data); - - static int OnHandshakeConfirmed( - ngtcp2_conn* conn, - void* user_data); - - static int OnReceiveStreamData( - ngtcp2_conn* conn, - uint32_t flags, - int64_t stream_id, - uint64_t offset, - const uint8_t* data, - size_t datalen, - void* user_data, - void* stream_user_data); - - static int OnAckedCryptoOffset( - ngtcp2_conn* conn, - ngtcp2_crypto_level crypto_level, - uint64_t offset, - uint64_t datalen, - void* user_data); - - static int OnAckedStreamDataOffset( - ngtcp2_conn* conn, - int64_t stream_id, - uint64_t offset, - uint64_t datalen, - void* user_data, - void* stream_user_data); - - static int OnSelectPreferredAddress( - ngtcp2_conn* conn, - ngtcp2_addr* dest, - const ngtcp2_preferred_addr* paddr, - void* user_data); - - static int OnStreamClose( - ngtcp2_conn* conn, - int64_t stream_id, - uint64_t app_error_code, - void* user_data, - void* stream_user_data); - - static int OnStreamOpen( - ngtcp2_conn* conn, - int64_t stream_id, - void* user_data); - - static int OnStreamReset( - ngtcp2_conn* conn, - int64_t stream_id, - uint64_t final_size, - uint64_t app_error_code, - void* user_data, - void* stream_user_data); - - static int OnRand( - uint8_t* dest, - size_t destlen, - ngtcp2_rand_ctx ctx); - - static int OnGetNewConnectionID( - ngtcp2_conn* conn, - ngtcp2_cid* cid, - uint8_t* token, - size_t cidlen, - void* user_data); - - static int OnRemoveConnectionID( - ngtcp2_conn* conn, - const ngtcp2_cid* cid, - void* user_data); - - static int OnPathValidation( - ngtcp2_conn* conn, - const ngtcp2_path* path, - ngtcp2_path_validation_result res, - void* user_data); - - static int OnExtendMaxStreamsUni( - ngtcp2_conn* conn, - uint64_t max_streams, - void* user_data); - - static int OnExtendMaxStreamsBidi( - ngtcp2_conn* conn, - uint64_t max_streams, - void* user_data); - - static int OnExtendMaxStreamData( - ngtcp2_conn* conn, - int64_t stream_id, - uint64_t max_data, - void* user_data, - void* stream_user_data); - - static int OnVersionNegotiation( - ngtcp2_conn* conn, - const ngtcp2_pkt_hd* hd, - const uint32_t* sv, - size_t nsv, - void* user_data); - - static int OnStatelessReset( - ngtcp2_conn* conn, - const ngtcp2_pkt_stateless_reset* sr, - void* user_data); - - static int OnExtendMaxStreamsRemoteUni( - ngtcp2_conn* conn, - uint64_t max_streams, - void* user_data); - - static int OnExtendMaxStreamsRemoteBidi( - ngtcp2_conn* conn, - uint64_t max_streams, - void* user_data); - - static int OnConnectionIDStatus( - ngtcp2_conn* conn, - int type, - uint64_t seq, - const ngtcp2_cid* cid, - const uint8_t* token, - void* user_data); - - static void OnQlogWrite( - void* user_data, - uint32_t flags, - const void* data, - size_t len); - -#define V(id, _) QUICSESSION_FLAG_##id, - enum QuicSessionFlags : uint32_t { - QUICSESSION_FLAGS(V) - QUICSESSION_FLAG_COUNT - }; -#undef V - - // Select the QUIC Application based on the configured ALPN identifier - QuicApplication* SelectApplication(QuicSession* session); - - ngtcp2_mem alloc_info_; - std::unique_ptr<QuicCryptoContext> crypto_context_; - std::unique_ptr<QuicApplication> application_; - BaseObjectWeakPtr<QuicSocket> socket_; - std::string alpn_; - std::string hostname_; - QuicError last_error_ = { - uint32_t{QUIC_ERROR_SESSION}, - uint64_t{NGTCP2_NO_ERROR} - }; - ConnectionPointer connection_; - SocketAddress local_address_{}; - SocketAddress remote_address_{}; - uint32_t flags_ = 0; - size_t max_pktlen_ = 0; - size_t current_ngtcp2_memory_ = 0; - size_t connection_close_attempts_ = 0; - size_t connection_close_limit_ = 1; - - ConnectionIDStrategy connection_id_strategy_ = nullptr; - PreferredAddressStrategy preferred_address_strategy_ = nullptr; - - QuicSessionListener* listener_ = nullptr; - JSQuicSessionListener default_listener_; - - TimerWrapHandle idle_; - TimerWrapHandle retransmit_; - - QuicCID scid_; - QuicCID dcid_; - QuicCID pscid_; - ngtcp2_transport_params transport_params_; - - std::unique_ptr<QuicPacket> conn_closebuf_; - - StreamsMap streams_; - - AliasedStruct<QuicSessionState> state_; - - struct RemoteTransportParamsDebug { - QuicSession* session; - explicit RemoteTransportParamsDebug(QuicSession* session_) - : session(session_) {} - std::string ToString() const; - }; - - static const ngtcp2_conn_callbacks callbacks[2]; - - BaseObjectPtr<QuicState> quic_state_; - BaseObjectWeakPtr<QLogStream> qlog_stream_; - - friend class QuicCryptoContext; - friend class QuicSessionListener; - friend class JSQuicSessionListener; -}; - -class QuicCallbackScope { - public: - explicit QuicCallbackScope(QuicSession* session); - ~QuicCallbackScope(); - - void operator=(const QuicCallbackScope&) = delete; - void operator=(QuicCallbackScope&&) = delete; - QuicCallbackScope(const QuicCallbackScope&) = delete; - QuicCallbackScope(QuicCallbackScope&&) = delete; - - private: - BaseObjectPtr<QuicSession> session_; - std::unique_ptr<InternalCallbackScope> private_; - v8::TryCatch try_catch_; -}; - -} // namespace quic -} // namespace node - -#endif // NODE_WANT_INTERNALS -#endif // SRC_QUIC_NODE_QUIC_SESSION_H_ diff --git a/src/quic/node_quic_socket-inl.h b/src/quic/node_quic_socket-inl.h deleted file mode 100644 index 8e7bc65d784..00000000000 --- a/src/quic/node_quic_socket-inl.h +++ /dev/null @@ -1,192 +0,0 @@ -#ifndef SRC_QUIC_NODE_QUIC_SOCKET_INL_H_ -#define SRC_QUIC_NODE_QUIC_SOCKET_INL_H_ - -#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS - -#include "node_quic_socket.h" -#include "node_sockaddr-inl.h" -#include "node_quic_session.h" -#include "node_crypto.h" -#include "debug_utils-inl.h" - -namespace node { - -using crypto::EntropySource; - -namespace quic { - -std::unique_ptr<QuicPacket> QuicPacket::Create( - const char* diagnostic_label, - size_t len) { - CHECK_LE(len, MAX_PKTLEN); - return std::make_unique<QuicPacket>(diagnostic_label, len); -} - -std::unique_ptr<QuicPacket> QuicPacket::Copy( - const std::unique_ptr<QuicPacket>& other) { - return std::make_unique<QuicPacket>(*other.get()); -} - -void QuicPacket::set_length(size_t len) { - CHECK_LE(len, MAX_PKTLEN); - len_ = len; -} - -int QuicEndpoint::Send( - uv_buf_t* buf, - size_t len, - const sockaddr* addr) { - int ret = static_cast<int>(udp_->Send(buf, len, addr)); - if (ret == 0) - IncrementPendingCallbacks(); - return ret; -} - -int QuicEndpoint::ReceiveStart() { - return udp_->RecvStart(); -} - -int QuicEndpoint::ReceiveStop() { - return udp_->RecvStop(); -} - -void QuicEndpoint::WaitForPendingCallbacks() { - if (!has_pending_callbacks()) { - listener_->OnEndpointDone(this); - return; - } - waiting_for_callbacks_ = true; -} - -void QuicSocket::AssociateCID( - const QuicCID& cid, - const QuicCID& scid) { - if (cid && scid) - dcid_to_scid_[cid] = scid; -} - -void QuicSocket::DisassociateCID(const QuicCID& cid) { - if (cid) { - Debug(this, "Removing association for cid %s", cid); - dcid_to_scid_.erase(cid); - } -} - -void QuicSocket::AssociateStatelessResetToken( - const StatelessResetToken& token, - BaseObjectPtr<QuicSession> session) { - Debug(this, "Associating stateless reset token %s", token); - token_map_[token] = session; -} - -SocketAddress QuicSocket::local_address() const { - DCHECK(preferred_endpoint_); - return preferred_endpoint_->local_address(); -} - -void QuicSocket::DisassociateStatelessResetToken( - const StatelessResetToken& token) { - Debug(this, "Removing stateless reset token %s", token); - token_map_.erase(token); -} - -void QuicSocket::ReceiveStart() { - for (const auto& endpoint : endpoints_) - CHECK_EQ(endpoint->ReceiveStart(), 0); -} - -void QuicSocket::ReceiveStop() { - for (const auto& endpoint : endpoints_) - CHECK_EQ(endpoint->ReceiveStop(), 0); -} - -void QuicSocket::RemoveSession( - const QuicCID& cid, - const SocketAddress& addr) { - DecrementSocketAddressCounter(addr); - sessions_.erase(cid); -} - -void QuicSocket::ReportSendError(int error) { - listener_->OnError(error); -} - -void QuicSocket::IncrementStatelessResetCounter(const SocketAddress& addr) { - addrLRU_.Upsert(addr)->reset_count++; -} - -void QuicSocket::IncrementSocketAddressCounter(const SocketAddress& addr) { - addrLRU_.Upsert(addr)->active_connections++; -} - -void QuicSocket::DecrementSocketAddressCounter(const SocketAddress& addr) { - SocketAddressInfo* counts = addrLRU_.Peek(addr); - if (counts != nullptr && counts->active_connections > 0) - counts->active_connections--; -} - -size_t QuicSocket::GetCurrentSocketAddressCounter(const SocketAddress& addr) { - SocketAddressInfo* counts = addrLRU_.Peek(addr); - return counts != nullptr ? counts->active_connections : 0; -} - -size_t QuicSocket::GetCurrentStatelessResetCounter(const SocketAddress& addr) { - SocketAddressInfo* counts = addrLRU_.Peek(addr); - return counts != nullptr ? counts->reset_count : 0; -} - -void QuicSocket::ServerBusy(bool on) { - Debug(this, "Turning Server Busy Response %s", on ? "on" : "off"); - state_->server_busy = on ? 1 : 0; - listener_->OnServerBusy(); -} - -bool QuicSocket::is_diagnostic_packet_loss(double prob) const { - if (LIKELY(prob == 0.0)) return false; - unsigned char c = 255; - EntropySource(&c, 1); - return (static_cast<double>(c) / 255) < prob; -} - -void QuicSocket::set_diagnostic_packet_loss(double rx, double tx) { - rx_loss_ = rx; - tx_loss_ = tx; -} - -void QuicSocket::set_validated_address(const SocketAddress& addr) { - addrLRU_.Upsert(addr)->validated = true; -} - -bool QuicSocket::is_validated_address(const SocketAddress& addr) const { - auto info = addrLRU_.Peek(addr); - return info != nullptr ? info->validated : false; -} - -void QuicSocket::AddSession( - const QuicCID& cid, - BaseObjectPtr<QuicSession> session) { - sessions_[cid] = session; - IncrementSocketAddressCounter(session->remote_address()); - IncrementStat( - session->is_server() ? - &QuicSocketStats::server_sessions : - &QuicSocketStats::client_sessions); -} - -void QuicSocket::AddEndpoint( - BaseObjectPtr<QuicEndpoint> endpoint_, - bool preferred) { - Debug(this, "Adding %sendpoint", preferred ? "preferred " : ""); - if (preferred || endpoints_.empty()) - preferred_endpoint_ = endpoint_; - endpoints_.emplace_back(endpoint_); - if (state_->server_listening) - endpoint_->ReceiveStart(); -} - -} // namespace quic -} // namespace node - -#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS - -#endif // SRC_QUIC_NODE_QUIC_SOCKET_INL_H_ diff --git a/src/quic/node_quic_socket.cc b/src/quic/node_quic_socket.cc deleted file mode 100644 index 810705014ca..00000000000 --- a/src/quic/node_quic_socket.cc +++ /dev/null @@ -1,1202 +0,0 @@ -#include "node_quic_socket-inl.h" // NOLINT(build/include) -#include "aliased_struct-inl.h" -#include "allocated_buffer-inl.h" -#include "async_wrap-inl.h" -#include "debug_utils-inl.h" -#include "env-inl.h" -#include "memory_tracker-inl.h" -#include "nghttp2/nghttp2.h" -#include "nghttp3/nghttp3.h" -#include "node.h" -#include "node_buffer.h" -#include "node_crypto.h" -#include "node_internals.h" -#include "node_mem-inl.h" -#include "node_quic_crypto.h" -#include "node_quic_session-inl.h" -#include "node_quic_util-inl.h" -#include "node_sockaddr-inl.h" -#include "req_wrap-inl.h" -#include "util.h" -#include "uv.h" -#include "v8.h" - -#include <random> - -namespace node { - -using crypto::EntropySource; -using crypto::SecureContext; - -using v8::ArrayBufferView; -using v8::Context; -using v8::FunctionCallbackInfo; -using v8::FunctionTemplate; -using v8::HandleScope; -using v8::Isolate; -using v8::Local; -using v8::Number; -using v8::Object; -using v8::ObjectTemplate; -using v8::PropertyAttribute; -using v8::Value; - -namespace quic { - -namespace { -// The reserved version is a mechanism QUIC endpoints -// can use to ensure correct handling of version -// negotiation. It is defined by the QUIC spec in -// https://tools.ietf.org/html/draft-ietf-quic-transport-24#section-6.3 -// Specifically, any version that follows the pattern -// 0x?a?a?a?a may be used to force version negotiation. -inline uint32_t GenerateReservedVersion( - const SocketAddress& addr, - uint32_t version) { - socklen_t addrlen = addr.length(); - uint32_t h = 0x811C9DC5u; - const uint8_t* p = addr.raw(); - const uint8_t* ep = p + addrlen; - for (; p != ep; ++p) { - h ^= *p; - h *= 0x01000193u; - } - version = htonl(version); - p = reinterpret_cast<const uint8_t*>(&version); - ep = p + sizeof(version); - for (; p != ep; ++p) { - h ^= *p; - h *= 0x01000193u; - } - h &= 0xf0f0f0f0u; - h |= 0x0a0a0a0au; - return h; -} - -bool IsShortHeader( - uint32_t version, - const uint8_t* pscid, - size_t pscidlen) { - return version == NGTCP2_PROTO_VER && - pscid == nullptr && - pscidlen == 0; -} -} // namespace - -QuicPacket::QuicPacket(const char* diagnostic_label, size_t len) - : data_{0}, - len_(len), - diagnostic_label_(diagnostic_label) { - CHECK_LE(len, MAX_PKTLEN); -} - -QuicPacket::QuicPacket(const QuicPacket& other) : - QuicPacket(other.diagnostic_label_, other.len_) { - memcpy(&data_, &other.data_, other.len_); -} - -const char* QuicPacket::diagnostic_label() const { - return diagnostic_label_ != nullptr ? - diagnostic_label_ : "unspecified"; -} - -QuicSocketListener::~QuicSocketListener() { - if (socket_) - socket_->RemoveListener(this); -} - -void QuicSocketListener::OnError(ssize_t code) { - if (previous_listener_ != nullptr) - previous_listener_->OnError(code); -} - -void QuicSocketListener::OnSessionReady(BaseObjectPtr<QuicSession> session) { - if (previous_listener_ != nullptr) - previous_listener_->OnSessionReady(session); -} - -void QuicSocketListener::OnServerBusy() { - if (previous_listener_ != nullptr) - previous_listener_->OnServerBusy(); -} - -void QuicSocketListener::OnEndpointDone(QuicEndpoint* endpoint) { - if (previous_listener_ != nullptr) - previous_listener_->OnEndpointDone(endpoint); -} - -void QuicSocketListener::OnDestroy() { - if (previous_listener_ != nullptr) - previous_listener_->OnDestroy(); -} - -void JSQuicSocketListener::OnError(ssize_t code) { - Environment* env = socket()->env(); - HandleScope scope(env->isolate()); - Context::Scope context_scope(env->context()); - Local<Value> arg = Number::New(env->isolate(), static_cast<double>(code)); - socket()->MakeCallback(env->quic_on_socket_close_function(), 1, &arg); -} - -void JSQuicSocketListener::OnSessionReady(BaseObjectPtr<QuicSession> session) { - Environment* env = socket()->env(); - Local<Value> arg = session->object(); - Context::Scope context_scope(env->context()); - socket()->MakeCallback(env->quic_on_session_ready_function(), 1, &arg); -} - -void JSQuicSocketListener::OnServerBusy() { - Environment* env = socket()->env(); - HandleScope handle_scope(env->isolate()); - Context::Scope context_scope(env->context()); - socket()->MakeCallback( - env->quic_on_socket_server_busy_function(), 0, nullptr); -} - -void JSQuicSocketListener::OnEndpointDone(QuicEndpoint* endpoint) { - Environment* env = socket()->env(); - HandleScope scope(env->isolate()); - Context::Scope context_scope(env->context()); - MakeCallback( - env->isolate(), - endpoint->object(), - env->ondone_string(), - 0, nullptr); -} - -void JSQuicSocketListener::OnDestroy() { - // Do nothing here. -} - -QuicEndpoint::QuicEndpoint( - QuicState* quic_state, - Local<Object> wrap, - QuicSocket* listener, - Local<Object> udp_wrap) - : BaseObject(quic_state->env(), wrap), - listener_(listener), - quic_state_(quic_state) { - MakeWeak(); - udp_ = static_cast<UDPWrapBase*>( - udp_wrap->GetAlignedPointerFromInternalField( - UDPWrapBase::kUDPWrapBaseField)); - CHECK_NOT_NULL(udp_); - udp_->set_listener(this); - strong_ptr_.reset(udp_->GetAsyncWrap()); -} - -QuicEndpoint::~QuicEndpoint() { - udp_->set_listener(nullptr); -} - -uv_buf_t QuicEndpoint::OnAlloc(size_t suggested_size) { - return AllocatedBuffer::AllocateManaged(env(), suggested_size).release(); -} - -void QuicEndpoint::OnRecv( - ssize_t nread, - const uv_buf_t& buf_, - const sockaddr* addr, - unsigned int flags) { - AllocatedBuffer buf(env(), buf_); - - if (nread <= 0) { - if (nread < 0) - listener_->OnError(this, nread); - return; - } - - listener_->OnReceive( - nread, - std::move(buf), - local_address(), - SocketAddress(addr), - flags); -} - -ReqWrap<uv_udp_send_t>* QuicEndpoint::CreateSendWrap(size_t msg_size) { - return listener_->OnCreateSendWrap(msg_size); -} - -void QuicEndpoint::OnSendDone(ReqWrap<uv_udp_send_t>* wrap, int status) { - DecrementPendingCallbacks(); - listener_->OnSendDone(wrap, status); - if (!has_pending_callbacks() && waiting_for_callbacks_) - listener_->OnEndpointDone(this); -} - -void QuicEndpoint::OnAfterBind() { - listener_->OnBind(this); -} - -template <typename Fn> -void QuicSocketStatsTraits::ToString(const QuicSocket& ptr, Fn&& add_field) { -#define V(_n, name, label) \ - add_field(label, ptr.GetStat(&QuicSocketStats::name)); - SOCKET_STATS(V) -#undef V -} - -QuicSocket::QuicSocket( - QuicState* quic_state, - Local<Object> wrap, - uint64_t retry_token_expiration, - size_t max_connections, - size_t max_connections_per_host, - size_t max_stateless_resets_per_host, - uint32_t options, - QlogMode qlog, - const uint8_t* session_reset_secret, - bool disable_stateless_reset) - : AsyncWrap(quic_state->env(), wrap, AsyncWrap::PROVIDER_QUICSOCKET), - StatsBase(quic_state->env(), wrap), - alloc_info_(MakeAllocator()), - block_list_(SocketAddressBlockListWrap::New(quic_state->env())), - options_(options), - state_(quic_state->env()->isolate()), - max_connections_(max_connections), - max_connections_per_host_(max_connections_per_host), - max_stateless_resets_per_host_(max_stateless_resets_per_host), - retry_token_expiration_(retry_token_expiration), - qlog_(qlog), - server_alpn_(NGHTTP3_ALPN_H3), - addrLRU_(DEFAULT_MAX_SOCKETADDRESS_LRU_SIZE), - quic_state_(quic_state) { - MakeWeak(); - PushListener(&default_listener_); - - Debug(this, "New QuicSocket created"); - - EntropySource(token_secret_, kTokenSecretLen); - - wrap->DefineOwnProperty( - env()->context(), - env()->block_list_string(), - block_list_->object(), - PropertyAttribute::ReadOnly).Check(); - - wrap->DefineOwnProperty( - env()->context(), - env()->state_string(), - state_.GetArrayBuffer(), - PropertyAttribute::ReadOnly).Check(); - - if (disable_stateless_reset) - state_->stateless_reset_disabled = 1; - - // Set the session reset secret to the one provided or random. - // Note that a random secret is going to make it exceedingly - // difficult for the session reset token to be useful. - if (session_reset_secret != nullptr) { - memcpy(reset_token_secret_, - session_reset_secret, - NGTCP2_STATELESS_RESET_TOKENLEN); - } else { - EntropySource(reset_token_secret_, NGTCP2_STATELESS_RESET_TOKENLEN); - } -} - -QuicSocket::~QuicSocket() { - QuicSocketListener* listener = listener_; - listener_->OnDestroy(); - if (listener == listener_) - RemoveListener(listener_); - - // In a clean shutdown, all QuicSessions associated with the QuicSocket - // would have been destroyed explicitly. However, if the QuicSocket is - // garbage collected / freed before Destroy having been called, there - // may be sessions remaining. This is not really a good thing. - Debug(this, "Destroying with %d sessions remaining", sessions_.size()); - - DebugStats(); -} - -void QuicSocket::MemoryInfo(MemoryTracker* tracker) const { - tracker->TrackField("endpoints", endpoints_); - tracker->TrackField("sessions", sessions_); - tracker->TrackField("dcid_to_scid", dcid_to_scid_); - tracker->TrackField("address_counts", addrLRU_); - tracker->TrackField("token_map", token_map_); - StatsBase::StatsMemoryInfo(tracker); - tracker->TrackFieldWithSize( - "current_ngtcp2_memory", - current_ngtcp2_memory_); -} - -void QuicSocket::Listen( - BaseObjectPtr<SecureContext> sc, - const sockaddr* preferred_address, - const std::string& alpn, - uint32_t options) { - CHECK(sc); - CHECK_NE(state_->server_listening, 1); - Debug(this, "Starting to listen"); - server_session_config_.Set(quic_state(), preferred_address); - server_secure_context_ = sc; - server_alpn_ = alpn; - server_options_ = options; - state_->server_listening = 1; - RecordTimestamp(&QuicSocketStats::listen_at); - ReceiveStart(); -} - -void QuicSocket::OnError(QuicEndpoint* endpoint, ssize_t error) { - // TODO(@jasnell): What should we do with the endpoint? - Debug(this, "Reading data from UDP socket failed. Error %" PRId64, error); - listener_->OnError(error); -} - -ReqWrap<uv_udp_send_t>* QuicSocket::OnCreateSendWrap(size_t msg_size) { - HandleScope handle_scope(env()->isolate()); - Local<Object> obj; - if (!env()->quicsocketsendwrap_instance_template() - ->NewInstance(env()->context()).ToLocal(&obj)) return nullptr; - return last_created_send_wrap_ = new SendWrap(quic_state(), obj, msg_size); -} - -void QuicSocket::OnEndpointDone(QuicEndpoint* endpoint) { - Debug(this, "Endpoint has no pending callbacks"); - listener_->OnEndpointDone(endpoint); -} - -void QuicSocket::OnBind(QuicEndpoint* endpoint) { - SocketAddress local_address = endpoint->local_address(); - bound_endpoints_[local_address] = - BaseObjectWeakPtr<QuicEndpoint>(endpoint); - Debug(this, "Endpoint %s bound", local_address); - RecordTimestamp(&QuicSocketStats::bound_at); -} - -BaseObjectPtr<QuicSession> QuicSocket::FindSession(const QuicCID& cid) { - BaseObjectPtr<QuicSession> session; - auto session_it = sessions_.find(cid); - if (session_it == std::end(sessions_)) { - auto scid_it = dcid_to_scid_.find(cid); - if (scid_it != std::end(dcid_to_scid_)) { - session_it = sessions_.find(scid_it->second); - CHECK_NE(session_it, std::end(sessions_)); - session = session_it->second; - } - } else { - session = session_it->second; - } - return session; -} - -// When a received packet contains a QUIC short header but cannot be -// matched to a known QuicSession, it is either (a) garbage, -// (b) a valid packet for a connection we no longer have state -// for, or (c) a stateless reset. Because we do not yet know if -// we are going to process the packet, we need to try to quickly -// determine -- with as little cost as possible -- whether the -// packet contains a reset token. We do so by checking the final -// NGTCP2_STATELESS_RESET_TOKENLEN bytes in the packet to see if -// they match one of the known reset tokens previously given by -// the remote peer. If there's a match, then it's a reset token, -// if not, we move on the to the next check. It is very important -// that this check be as inexpensive as possible to avoid a DOS -// vector. -bool QuicSocket::MaybeStatelessReset( - const QuicCID& dcid, - const QuicCID& scid, - ssize_t nread, - const uint8_t* data, - const SocketAddress& local_addr, - const SocketAddress& remote_addr, - unsigned int flags) { - if (UNLIKELY(state_->stateless_reset_disabled || nread < 16)) - return false; - StatelessResetToken possible_token( - data + nread - NGTCP2_STATELESS_RESET_TOKENLEN); - Debug(this, "Possible stateless reset token: %s", possible_token); - auto it = token_map_.find(possible_token); - if (it == token_map_.end()) - return false; - Debug(this, "Received a stateless reset token %s", possible_token); - return it->second->Receive(nread, data, local_addr, remote_addr, flags); -} - -// When a packet is received here, we do not yet know if we can -// process it successfully as a QUIC packet or not. Given the -// nature of UDP, we may receive a great deal of garbage here -// so it is extremely important not to commit resources until -// we're certain we can process the data we received as QUIC -// packet. -// Any packet we choose not to process must be ignored. -void QuicSocket::OnReceive( - ssize_t nread, - AllocatedBuffer buf, - const SocketAddress& local_addr, - const SocketAddress& remote_addr, - unsigned int flags) { - Debug(this, "Receiving %d bytes from the UDP socket", nread); - - // When diagnostic packet loss is enabled, the packet will be randomly - // dropped based on the rx_loss_ probability. - if (UNLIKELY(is_diagnostic_packet_loss(rx_loss_))) { - Debug(this, "Simulating received packet loss"); - return; - } - - if (UNLIKELY(block_list_->Apply(remote_addr))) { - Debug(this, "Ignoring blocked remote address: %s", remote_addr); - IncrementStat(&QuicSocketStats::packets_ignored); - return; - } - - IncrementStat(&QuicSocketStats::bytes_received, nread); - - const uint8_t* data = reinterpret_cast<const uint8_t*>(buf.data()); - - uint32_t pversion; - const uint8_t* pdcid; - size_t pdcidlen; - const uint8_t* pscid; - size_t pscidlen; - - // This is our first check to see if the received data can be - // processed as a QUIC packet. If this fails, then the QUIC packet - // header is invalid and cannot be processed; all we can do is ignore - // it. It's questionable whether we should even increment the - // packets_ignored statistic here but for now we do. If it succeeds, - // we have a valid QUIC header but there's still no guarantee that - // the packet can be successfully processed. - if (ngtcp2_pkt_decode_version_cid( - &pversion, - &pdcid, - &pdcidlen, - &pscid, - &pscidlen, - data, nread, kScidLen) < 0) { - IncrementStat(&QuicSocketStats::packets_ignored); - return; - } - - // QUIC currently requires CID lengths of max NGTCP2_MAX_CIDLEN. The - // ngtcp2 API allows non-standard lengths, and we may want to allow - // non-standard lengths later. But for now, we're going to ignore any - // packet with a non-standard CID length. - if (pdcidlen > NGTCP2_MAX_CIDLEN || pscidlen > NGTCP2_MAX_CIDLEN) { - IncrementStat(&QuicSocketStats::packets_ignored); - return; - } - - QuicCID dcid(pdcid, pdcidlen); - QuicCID scid(pscid, pscidlen); - - Debug(this, "Received a QUIC packet for dcid %s", dcid); - - BaseObjectPtr<QuicSession> session = FindSession(dcid); - - // If a session is not found, there are four possible reasons: - // 1. The session has not been created yet - // 2. The session existed once but we've lost the local state for it - // 3. The packet is a stateless reset sent by the peer - // 4. This is a malicious or malformed packet. - if (!session) { - Debug(this, "There is no existing session for dcid %s", dcid); - bool is_short_header = IsShortHeader(pversion, pscid, pscidlen); - - // Handle possible reception of a stateless reset token... - // If it is a stateless reset, the packet will be handled with - // no additional action necessary here. We want to return immediately - // without committing any further resources. - if (is_short_header && - MaybeStatelessReset( - dcid, - scid, - nread, - data, - local_addr, - remote_addr, - flags)) { - Debug(this, "Handled stateless reset"); - return; - } - - // AcceptInitialPacket will first validate that the packet can be - // accepted, then create a new server QuicSession instance if able - // to do so. If a new instance cannot be created (for any reason), - // the session BaseObjectPtr will be empty on return. - session = AcceptInitialPacket( - pversion, - dcid, - scid, - nread, - data, - local_addr, - remote_addr, - flags); - - // There are many reasons why a server QuicSession could not be - // created. The most common will be invalid packets or incorrect - // QUIC version. In any of these cases, however, to prevent a - // potential attacker from causing us to consume resources, - // we're just going to ignore the packet. It is possible that - // the AcceptInitialPacket sent a version negotiation packet, - // or a CONNECTION_CLOSE packet. - if (!session) { - Debug(this, "Unable to create a new server QuicSession"); - // If the packet contained a short header, we might need to send - // a stateless reset. The stateless reset contains a token derived - // from the received destination connection ID. - // - // TODO(@jasnell): Stateless resets are generated programmatically - // using HKDF with the sender provided dcid and a locally provided - // secret as input. It is entirely possible that a malicious - // peer could send multiple stateless reset eliciting packets - // with the specific intent of using the returned stateless - // reset to guess the stateless reset token secret used by - // the server. Once guessed, the malicious peer could use - // that secret as a DOS vector against other peers. We currently - // implement some mitigations for this by limiting the number - // of stateless resets that can be sent to a specific remote - // address but there are other possible mitigations, such as - // including the remote address as input in the generation of - // the stateless token. - if (is_short_header && - SendStatelessReset(dcid, local_addr, remote_addr, nread)) { - Debug(this, "Sent stateless reset"); - IncrementStat(&QuicSocketStats::stateless_reset_count); - return; - } - IncrementStat(&QuicSocketStats::packets_ignored); - return; - } - } - - CHECK(session); - // If the QuicSession is already destroyed, there's nothing to do. - if (session->is_destroyed()) - return IncrementStat(&QuicSocketStats::packets_ignored); - - // If the packet could not successfully processed for any reason (possibly - // due to being malformed or malicious in some way) we mark it ignored. - if (!session->Receive(nread, data, local_addr, remote_addr, flags)) { - IncrementStat(&QuicSocketStats::packets_ignored); - return; - } - - IncrementStat(&QuicSocketStats::packets_received); -} - -// Generates and sends a version negotiation packet. This is -// terminal for the connection and is sent only when a QUIC -// packet is received for an unsupported Node.js version. -// It is possible that a malicious packet triggered this -// so we need to be careful not to commit too many resources. -// Currently, we only support one QUIC version at a time. -void QuicSocket::SendVersionNegotiation( - uint32_t version, - const QuicCID& dcid, - const QuicCID& scid, - const SocketAddress& local_addr, - const SocketAddress& remote_addr) { - uint32_t sv[2]; - sv[0] = GenerateReservedVersion(remote_addr, version); - sv[1] = NGTCP2_PROTO_VER; - - uint8_t unused_random; - EntropySource(&unused_random, 1); - - size_t pktlen = dcid.length() + scid.length() + (sizeof(sv)) + 7; - - auto packet = QuicPacket::Create("version negotiation", pktlen); - ssize_t nwrite = ngtcp2_pkt_write_version_negotiation( - packet->data(), - NGTCP2_MAX_PKTLEN_IPV6, - unused_random, - dcid.data(), - dcid.length(), - scid.data(), - scid.length(), - sv, - arraysize(sv)); - if (nwrite <= 0) - return; - packet->set_length(nwrite); - SocketAddress remote_address(remote_addr); - SendPacket(local_addr, remote_address, std::move(packet)); -} - -// Possible generates and sends a stateless reset packet. -// This is terminal for the connection. It is possible -// that a malicious packet triggered this so we need to -// be careful not to commit too many resources. -bool QuicSocket::SendStatelessReset( - const QuicCID& cid, - const SocketAddress& local_addr, - const SocketAddress& remote_addr, - size_t source_len) { - if (UNLIKELY(state_->stateless_reset_disabled)) - return false; - constexpr static size_t kRandlen = NGTCP2_MIN_STATELESS_RESET_RANDLEN * 5; - constexpr static size_t kMinStatelessResetLen = 41; - uint8_t random[kRandlen]; - - // Per the QUIC spec, we need to protect against sending too - // many stateless reset tokens to an endpoint to prevent - // endless looping. - if (GetCurrentStatelessResetCounter(remote_addr) >= - max_stateless_resets_per_host_) { - return false; - } - // Per the QUIC spec, a stateless reset token must be strictly - // smaller than the packet that triggered it. This is one of the - // mechanisms to prevent infinite looping exchange of stateless - // tokens with the peer. - // An endpoint should never send a stateless reset token smaller than - // 41 bytes per the QUIC spec. The reason is that packets less than - // 41 bytes may allow an observer to determine that it's a stateless - // reset. - size_t pktlen = source_len - 1; - if (pktlen < kMinStatelessResetLen) - return false; - - StatelessResetToken token(reset_token_secret_, cid); - EntropySource(random, kRandlen); - - auto packet = QuicPacket::Create("stateless reset", pktlen); - ssize_t nwrite = - ngtcp2_pkt_write_stateless_reset( - packet->data(), - NGTCP2_MAX_PKTLEN_IPV4, - const_cast<uint8_t*>(token.data()), - random, - kRandlen); - if (nwrite < static_cast<ssize_t>(kMinStatelessResetLen)) - return false; - packet->set_length(nwrite); - SocketAddress remote_address(remote_addr); - IncrementStatelessResetCounter(remote_address); - return SendPacket(local_addr, remote_address, std::move(packet)) == 0; -} - -// Generates and sends a retry packet. This is terminal -// for the connection. Retry packets are used to force -// explicit path validation by issuing a token to the -// peer that it must thereafter include in all subsequent -// initial packets. Upon receiving a retry packet, the -// peer must termination it's initial attempt to -// establish a connection and start a new attempt. -// -// Retry packets will only ever be generated by QUIC servers, -// and only if the QuicSocket is configured for explicit path -// validation. There is no way for a client to force a retry -// packet to be created. However, once a client determines that -// explicit path validation is enabled, it could attempt to -// DOS by sending a large number of malicious initial packets -// to intentionally ellicit retry packets (It can do so by -// intentionally sending initial packets that ignore the retry -// token). To help mitigate that risk, we limit the number of -// retries we send to a given remote endpoint. -bool QuicSocket::SendRetry( - const QuicCID& dcid, - const QuicCID& scid, - const SocketAddress& local_addr, - const SocketAddress& remote_addr) { - auto info = addrLRU_.Upsert(remote_addr); - // Do not send a retry if the retry count is greater - // than the retry limit. - // TODO(@jasnell): Make the retry limit configurable. - if (++(info->retry_count) > DEFAULT_MAX_RETRY_LIMIT) - return true; - std::unique_ptr<QuicPacket> packet = - GenerateRetryPacket(token_secret_, dcid, scid, local_addr, remote_addr); - return packet ? - SendPacket(local_addr, remote_addr, std::move(packet)) == 0 : false; -} - -// Shutdown a connection prematurely, before a QuicSession is created. -// This should only be called t the start of a session before the crypto -// keys have been established. -void QuicSocket::ImmediateConnectionClose( - const QuicCID& scid, - const QuicCID& dcid, - const SocketAddress& local_addr, - const SocketAddress& remote_addr, - int64_t reason) { - Debug(this, "Sending stateless connection close to %s", scid); - auto packet = QuicPacket::Create("immediate connection close"); - ssize_t nwrite = ngtcp2_crypto_write_connection_close( - packet->data(), - packet->length(), - scid.cid(), - dcid.cid(), - reason); - if (nwrite > 0) { - packet->set_length(nwrite); - SendPacket(local_addr, remote_addr, std::move(packet)); - } -} - -// Inspects the packet and possibly accepts it as a new -// initial packet creating a new QuicSession instance. -// If the packet is not acceptable, it is very important -// not to commit resources. -BaseObjectPtr<QuicSession> QuicSocket::AcceptInitialPacket( - uint32_t version, - const QuicCID& dcid, - const QuicCID& scid, - ssize_t nread, - const uint8_t* data, - const SocketAddress& local_addr, - const SocketAddress& remote_addr, - unsigned int flags) { - HandleScope handle_scope(env()->isolate()); - Context::Scope context_scope(env()->context()); - ngtcp2_pkt_hd hd; - QuicCID ocid; - - // If the QuicSocket is not listening, the paket will be ignored. - if (!state_->server_listening) { - Debug(this, "QuicSocket is not listening"); - return {}; - } - - switch (ngtcp2_accept(&hd, data, static_cast<size_t>(nread))) { - case 1: - // Send Version Negotiation - SendVersionNegotiation(version, dcid, scid, local_addr, remote_addr); - // Fall through - case -1: - // Either a version negotiation packet was sent or the packet is - // an invalid initial packet. Either way, there's nothing more we - // can do here. - return {}; - } - - // If the server is busy, new connections will be shut down immediately - // after the initial keys are installed. The busy state is controlled - // entirely by local user code. It is important to understand that - // a QuicSession is created and resources are committed even though - // the QuicSession will be torn down as quickly as possible. - // Else, check to see if the number of connections total for this QuicSocket - // has been exceeded. If the count has been exceeded, shutdown the connection - // immediately after the initial keys are installed. - if (UNLIKELY(state_->server_busy == 1) || - sessions_.size() >= max_connections_ || - GetCurrentSocketAddressCounter(remote_addr) >= - max_connections_per_host_) { - Debug(this, "QuicSocket is busy or connection count exceeded"); - IncrementStat(&QuicSocketStats::server_busy_count); - ImmediateConnectionClose( - QuicCID(hd.scid), - QuicCID(hd.dcid), - local_addr, - remote_addr, - NGTCP2_CONNECTION_REFUSED); - return {}; - } - - // QUIC has address validation built in to the handshake but allows for - // an additional explicit validation request using RETRY frames. If we - // are using explicit validation, we check for the existence of a valid - // retry token in the packet. If one does not exist, we send a retry with - // a new token. If it does exist, and if it's valid, we grab the original - // cid and continue. - if (!is_validated_address(remote_addr)) { - switch (hd.type) { - case NGTCP2_PKT_INITIAL: - if (has_option_validate_address() || hd.token.len > 0) { - Debug(this, "Performing explicit address validation"); - if (hd.token.len == 0) { - Debug(this, "No retry token was detected. Generating one"); - SendRetry(dcid, scid, local_addr, remote_addr); - // Sending a retry token terminates this connection attempt. - return {}; - } - if (InvalidRetryToken( - hd.token, - remote_addr, - &ocid, - token_secret_, - retry_token_expiration_)) { - Debug(this, "Invalid retry token was detected. Failing"); - ImmediateConnectionClose( - QuicCID(hd.scid), - QuicCID(hd.dcid), - local_addr, - remote_addr); - return {}; - } - } - break; - case NGTCP2_PKT_0RTT: - SendRetry(dcid, scid, local_addr, remote_addr); - return {}; - } - } - - BaseObjectPtr<QuicSession> session = - QuicSession::CreateServer( - this, - server_session_config_, - local_addr, - remote_addr, - scid, - dcid, - ocid, - version, - server_alpn_, - server_options_, - qlog_); - CHECK(session); - - listener_->OnSessionReady(session); - - // It's possible that the session was destroyed while processing - // the ready callback. If it was, then we need to send an early - // CONNECTION_CLOSE. - if (session->is_destroyed()) { - ImmediateConnectionClose( - QuicCID(hd.scid), - QuicCID(hd.dcid), - local_addr, - remote_addr, - NGTCP2_CONNECTION_REFUSED); - } else { - session->set_wrapped(); - } - - return session; -} - -QuicSocket::SendWrap::SendWrap( - QuicState* quic_state, - Local<Object> req_wrap_obj, - size_t total_length) - : ReqWrap(quic_state->env(), req_wrap_obj, PROVIDER_QUICSOCKET), - total_length_(total_length), - quic_state_(quic_state) { -} - -std::string QuicSocket::SendWrap::MemoryInfoName() const { - return "QuicSendWrap"; -} - -void QuicSocket::SendWrap::MemoryInfo(MemoryTracker* tracker) const { - tracker->TrackField("session", session_); - tracker->TrackField("packet", packet_); -} - -int QuicSocket::SendPacket( - const SocketAddress& local_addr, - const SocketAddress& remote_addr, - std::unique_ptr<QuicPacket> packet, - BaseObjectPtr<QuicSession> session) { - // If the packet is empty, there's nothing to do - if (packet->length() == 0) - return 0; - - Debug(this, "Sending %" PRIu64 " bytes to %s from %s (label: %s)", - packet->length(), - remote_addr, - local_addr, - packet->diagnostic_label()); - - // If DiagnosticPacketLoss returns true, it will call Done() internally - if (UNLIKELY(is_diagnostic_packet_loss(tx_loss_))) { - Debug(this, "Simulating transmitted packet loss"); - return 0; - } - - last_created_send_wrap_ = nullptr; - uv_buf_t buf = packet->buf(); - - auto endpoint = bound_endpoints_.find(local_addr); - CHECK_NE(endpoint, bound_endpoints_.end()); - int err = endpoint->second->Send(&buf, 1, remote_addr.data()); - - if (err != 0) { - if (err > 0) err = 0; - OnSend(err, packet.get()); - } else { - CHECK_NOT_NULL(last_created_send_wrap_); - last_created_send_wrap_->set_packet(std::move(packet)); - if (session) - last_created_send_wrap_->set_session(session); - } - return err; -} - -void QuicSocket::OnSend(int status, QuicPacket* packet) { - if (status == 0) { - Debug(this, "Sent %" PRIu64 " bytes (label: %s)", - packet->length(), - packet->diagnostic_label()); - IncrementStat(&QuicSocketStats::bytes_sent, packet->length()); - IncrementStat(&QuicSocketStats::packets_sent); - } else { - Debug(this, "Failed to send %" PRIu64 " bytes (status: %d, label: %s)", - packet->length(), - status, - packet->diagnostic_label()); - } -} - -void QuicSocket::OnSendDone(ReqWrap<uv_udp_send_t>* wrap, int status) { - std::unique_ptr<SendWrap> req_wrap(static_cast<SendWrap*>(wrap)); - OnSend(status, req_wrap->packet()); -} - -void QuicSocket::CheckAllocatedSize(size_t previous_size) const { - CHECK_GE(current_ngtcp2_memory_, previous_size); -} - -void QuicSocket::IncreaseAllocatedSize(size_t size) { - current_ngtcp2_memory_ += size; -} - -void QuicSocket::DecreaseAllocatedSize(size_t size) { - current_ngtcp2_memory_ -= size; -} - -void QuicSocket::PushListener(QuicSocketListener* listener) { - CHECK_NOT_NULL(listener); - CHECK(!listener->socket_); - - listener->previous_listener_ = listener_; - listener->socket_.reset(this); - - listener_ = listener; -} - -void QuicSocket::RemoveListener(QuicSocketListener* listener) { - CHECK_NOT_NULL(listener); - - QuicSocketListener* previous; - QuicSocketListener* current; - - for (current = listener_, previous = nullptr; - /* No loop condition because we want a crash if listener is not found */ - ; previous = current, current = current->previous_listener_) { - CHECK_NOT_NULL(current); - if (current == listener) { - if (previous != nullptr) - previous->previous_listener_ = current->previous_listener_; - else - listener_ = listener->previous_listener_; - break; - } - } - - listener->socket_.reset(); - listener->previous_listener_ = nullptr; -} - -bool QuicSocket::SocketAddressInfoTraits::CheckExpired( - const SocketAddress& address, - const Type& type) { - return (uv_hrtime() - type.timestamp) > 1e10; // 10 seconds. -} - -void QuicSocket::SocketAddressInfoTraits::Touch( - const SocketAddress& address, - Type* type) { - type->timestamp = uv_hrtime(); -} - -// JavaScript API -namespace { -void NewQuicEndpoint(const FunctionCallbackInfo<Value>& args) { - QuicState* state = Environment::GetBindingData<QuicState>(args); - CHECK(args.IsConstructCall()); - CHECK(args[0]->IsObject()); - QuicSocket* socket; - ASSIGN_OR_RETURN_UNWRAP(&socket, args[0].As<Object>()); - CHECK(args[1]->IsObject()); - CHECK_GE(args[1].As<Object>()->InternalFieldCount(), - UDPWrapBase::kUDPWrapBaseField); - new QuicEndpoint(state, args.This(), socket, args[1].As<Object>()); -} - -void NewQuicSocket(const FunctionCallbackInfo<Value>& args) { - QuicState* state = Environment::GetBindingData<QuicState>(args); - Environment* env = state->env(); - CHECK(args.IsConstructCall()); - - uint32_t options; - uint32_t retry_token_expiration; - uint32_t max_connections; - uint32_t max_connections_per_host; - uint32_t max_stateless_resets_per_host; - - if (!args[0]->Uint32Value(env->context()).To(&options) || - !args[1]->Uint32Value(env->context()).To(&retry_token_expiration) || - !args[2]->Uint32Value(env->context()).To(&max_connections) || - !args[3]->Uint32Value(env->context()).To(&max_connections_per_host) || - !args[4]->Uint32Value(env->context()) - .To(&max_stateless_resets_per_host)) { - return; - } - CHECK_GE(retry_token_expiration, MIN_RETRYTOKEN_EXPIRATION); - CHECK_LE(retry_token_expiration, MAX_RETRYTOKEN_EXPIRATION); - - const uint8_t* session_reset_secret = nullptr; - if (args[6]->IsArrayBufferView()) { - ArrayBufferViewContents<uint8_t> buf(args[6].As<ArrayBufferView>()); - CHECK_EQ(buf.length(), kTokenSecretLen); - session_reset_secret = buf.data(); - } - - new QuicSocket( - state, - args.This(), - retry_token_expiration, - max_connections, - max_connections_per_host, - max_stateless_resets_per_host, - options, - args[5]->IsTrue() ? QlogMode::kEnabled : QlogMode::kDisabled, - session_reset_secret, - args[7]->IsTrue()); -} - -void QuicSocketAddEndpoint(const FunctionCallbackInfo<Value>& args) { - QuicSocket* socket; - ASSIGN_OR_RETURN_UNWRAP(&socket, args.Holder()); - CHECK(args[0]->IsObject()); - QuicEndpoint* endpoint; - ASSIGN_OR_RETURN_UNWRAP(&endpoint, args[0].As<Object>()); - socket->AddEndpoint( - BaseObjectPtr<QuicEndpoint>(endpoint), - args[1]->IsTrue()); -} - -// Enabling diagnostic packet loss enables a mode where the QuicSocket -// instance will randomly ignore received packets in order to simulate -// packet loss. This is not an API that should be enabled in production -// but is useful when debugging and diagnosing performance issues. -// Diagnostic packet loss is enabled by setting either the tx or rx -// arguments to a value between 0.0 and 1.0. Setting both values to 0.0 -// disables the mechanism. -void QuicSocketSetDiagnosticPacketLoss( - const FunctionCallbackInfo<Value>& args) { - Environment* env = Environment::GetCurrent(args); - QuicSocket* socket; - ASSIGN_OR_RETURN_UNWRAP(&socket, args.Holder()); - double rx, tx; - if (!args[0]->NumberValue(env->context()).To(&rx) || - !args[1]->NumberValue(env->context()).To(&tx)) return; - CHECK_GE(rx, 0.0f); - CHECK_GE(tx, 0.0f); - CHECK_LE(rx, 1.0f); - CHECK_LE(tx, 1.0f); - socket->set_diagnostic_packet_loss(rx, tx); -} - -void QuicSocketDestroy(const FunctionCallbackInfo<Value>& args) { - QuicSocket* socket; - ASSIGN_OR_RETURN_UNWRAP(&socket, args.Holder()); - socket->ReceiveStop(); -} - -void QuicSocketListen(const FunctionCallbackInfo<Value>& args) { - Environment* env = Environment::GetCurrent(args); - QuicSocket* socket; - ASSIGN_OR_RETURN_UNWRAP(&socket, args.Holder(), - args.GetReturnValue().Set(UV_EBADF)); - CHECK(args[0]->IsObject() && - env->secure_context_constructor_template()->HasInstance(args[0])); - SecureContext* sc; - ASSIGN_OR_RETURN_UNWRAP(&sc, args[0].As<Object>(), - args.GetReturnValue().Set(UV_EBADF)); - - sockaddr_storage preferred_address_storage; - const sockaddr* preferred_address = nullptr; - if (args[1]->IsString()) { - node::Utf8Value preferred_address_host(args.GetIsolate(), args[1]); - int32_t preferred_address_family; - uint32_t preferred_address_port; - if (!args[2]->Int32Value(env->context()).To(&preferred_address_family) || - !args[3]->Uint32Value(env->context()).To(&preferred_address_port)) - return; - if (SocketAddress::ToSockAddr( - preferred_address_family, - *preferred_address_host, - preferred_address_port, - &preferred_address_storage)) { - preferred_address = - reinterpret_cast<const sockaddr*>(&preferred_address_storage); - } - } - - std::string alpn(NGHTTP3_ALPN_H3); - if (args[4]->IsString()) { - Utf8Value val(env->isolate(), args[4]); - alpn = val.length(); - alpn += *val; - } - - uint32_t options = 0; - if (!args[5]->Uint32Value(env->context()).To(&options)) return; - - socket->Listen( - BaseObjectPtr<SecureContext>(sc), - preferred_address, - alpn, - options); -} - -void QuicEndpointWaitForPendingCallbacks( - const FunctionCallbackInfo<Value>& args) { - QuicEndpoint* endpoint; - ASSIGN_OR_RETURN_UNWRAP(&endpoint, args.Holder()); - endpoint->WaitForPendingCallbacks(); -} - -} // namespace - -void QuicEndpoint::Initialize( - Environment* env, - Local<Object> target, - Local<Context> context) { - Isolate* isolate = env->isolate(); - Local<FunctionTemplate> endpoint = env->NewFunctionTemplate(NewQuicEndpoint); - endpoint->Inherit(BaseObject::GetConstructorTemplate(env)); - endpoint->InstanceTemplate()->SetInternalFieldCount( - QuicEndpoint::kInternalFieldCount); - env->SetProtoMethod(endpoint, - "waitForPendingCallbacks", - QuicEndpointWaitForPendingCallbacks); - endpoint->InstanceTemplate()->Set(env->owner_symbol(), Null(isolate)); - - env->SetConstructorFunction(target, "QuicEndpoint", endpoint); -} - -void QuicSocket::Initialize( - Environment* env, - Local<Object> target, - Local<Context> context) { - Isolate* isolate = env->isolate(); - Local<FunctionTemplate> socket = env->NewFunctionTemplate(NewQuicSocket); - socket->Inherit(AsyncWrap::GetConstructorTemplate(env)); - socket->InstanceTemplate()->SetInternalFieldCount( - QuicSocket::kInternalFieldCount); - socket->InstanceTemplate()->Set(env->owner_symbol(), Null(isolate)); - env->SetProtoMethod(socket, - "addEndpoint", - QuicSocketAddEndpoint); - env->SetProtoMethod(socket, - "destroy", - QuicSocketDestroy); - env->SetProtoMethod(socket, - "listen", - QuicSocketListen); - env->SetProtoMethod(socket, - "setDiagnosticPacketLoss", - QuicSocketSetDiagnosticPacketLoss); - socket->Inherit(HandleWrap::GetConstructorTemplate(env)); - env->SetConstructorFunction(target, "QuicSocket", socket); - - Local<FunctionTemplate> sendwrap_ctor = FunctionTemplate::New(isolate); - sendwrap_ctor->Inherit(AsyncWrap::GetConstructorTemplate(env)); - sendwrap_ctor->SetClassName(FIXED_ONE_BYTE_STRING(isolate, "SendWrap")); - Local<ObjectTemplate> sendwrap_template = sendwrap_ctor->InstanceTemplate(); - sendwrap_template->SetInternalFieldCount(SendWrap::kInternalFieldCount); - env->set_quicsocketsendwrap_instance_template(sendwrap_template); -} - -} // namespace quic -} // namespace node diff --git a/src/quic/node_quic_socket.h b/src/quic/node_quic_socket.h deleted file mode 100644 index 63bb49091b1..00000000000 --- a/src/quic/node_quic_socket.h +++ /dev/null @@ -1,609 +0,0 @@ -#ifndef SRC_QUIC_NODE_QUIC_SOCKET_H_ -#define SRC_QUIC_NODE_QUIC_SOCKET_H_ - -#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS - -#include "aliased_struct.h" -#include "base_object.h" -#include "node.h" -#include "node_crypto.h" -#include "node_internals.h" -#include "ngtcp2/ngtcp2.h" -#include "nghttp3/nghttp3.h" -#include "node_quic_state.h" -#include "node_quic_session.h" -#include "node_quic_util.h" -#include "node_sockaddr.h" -#include "env.h" -#include "udp_wrap.h" -#include "v8.h" -#include "uv.h" - -#include <deque> -#include <map> -#include <string> -#include <vector> - -namespace node { - -using v8::Context; -using v8::Local; -using v8::Object; - -namespace quic { - -class QuicSocket; -class QuicEndpoint; - -constexpr size_t DEFAULT_MAX_SOCKETADDRESS_LRU_SIZE = 1000; -constexpr size_t DEFAULT_MAX_RETRY_LIMIT = 10; - -#define QUICSOCKET_OPTIONS(V) \ - V(VALIDATE_ADDRESS, validate_address) - -#define V(id, _) QUICSOCKET_OPTIONS_##id, -enum QuicSocketOptions : uint32_t { - QUICSOCKET_OPTIONS(V) - QUICSOCKET_OPTIONS_COUNT -}; -#undef V - -#define QUICSOCKET_SHARED_STATE(V) \ - V(SERVER_LISTENING, server_listening, uint8_t) \ - V(SERVER_BUSY, server_busy, uint8_t) \ - V(STATELESS_RESET_DISABLED, stateless_reset_disabled, uint8_t) - -#define V(_, name, type) type name; -struct QuicSocketState { - QUICSOCKET_SHARED_STATE(V) -}; -#undef V - -#define V(id, name, _) \ - IDX_QUICSOCKET_STATE_##id = offsetof(QuicSocketState, name), -enum QuicSocketStateFields { - QUICSOCKET_SHARED_STATE(V) - IDX_QUICSOCKET_STATE_END -}; -#undef V - -#define SOCKET_STATS(V) \ - V(CREATED_AT, created_at, "Created At") \ - V(BOUND_AT, bound_at, "Bound At") \ - V(LISTEN_AT, listen_at, "Listen At") \ - V(DESTROYED_AT, destroyed_at, "Destroyed At") \ - V(BYTES_RECEIVED, bytes_received, "Bytes Received") \ - V(BYTES_SENT, bytes_sent, "Bytes Sent") \ - V(PACKETS_RECEIVED, packets_received, "Packets Received") \ - V(PACKETS_IGNORED, packets_ignored, "Packets Ignored") \ - V(PACKETS_SENT, packets_sent, "Packets Sent") \ - V(SERVER_SESSIONS, server_sessions, "Server Sessions") \ - V(CLIENT_SESSIONS, client_sessions, "Client Sessions") \ - V(STATELESS_RESET_COUNT, stateless_reset_count, "Stateless Reset Count") \ - V(SERVER_BUSY_COUNT, server_busy_count, "Server Busy Count") - -#define V(name, _, __) IDX_QUIC_SOCKET_STATS_##name, -enum QuicSocketStatsIdx : int { - SOCKET_STATS(V) - IDX_QUIC_SOCKET_STATS_COUNT -}; -#undef V - -#define V(_, name, __) uint64_t name; -struct QuicSocketStats { - SOCKET_STATS(V) -}; -#undef V - -struct QuicSocketStatsTraits { - using Stats = QuicSocketStats; - using Base = QuicSocket; - - template <typename Fn> - static void ToString(const Base& ptr, Fn&& add_field); -}; - -// This is the generic interface for objects that control QuicSocket -// instances. The default `JSQuicSocketListener` emits events to -// JavaScript -class QuicSocketListener { - public: - virtual ~QuicSocketListener(); - - virtual void OnError(ssize_t code); - virtual void OnSessionReady(BaseObjectPtr<QuicSession> session); - virtual void OnServerBusy(); - virtual void OnEndpointDone(QuicEndpoint* endpoint); - virtual void OnDestroy(); - - QuicSocket* socket() const { return socket_.get(); } - - private: - BaseObjectWeakPtr<QuicSocket> socket_; - QuicSocketListener* previous_listener_ = nullptr; - friend class QuicSocket; -}; - -class JSQuicSocketListener final : public QuicSocketListener { - public: - void OnError(ssize_t code) override; - void OnSessionReady(BaseObjectPtr<QuicSession> session) override; - void OnServerBusy() override; - void OnEndpointDone(QuicEndpoint* endpoint) override; - void OnDestroy() override; -}; - -// This is just a formality as the QUIC spec normatively -// defines that the ipv4 max pktlen is always going to be -// larger than the ipv6 max pktlen, but in the off chance -// ever changes (which is unlikely) we check here. -constexpr size_t MAX_PKTLEN = - std::max<size_t>(NGTCP2_MAX_PKTLEN_IPV4, NGTCP2_MAX_PKTLEN_IPV6); - -// A serialized QuicPacket to be sent by a QuicSocket instance. -// QuicPackets are intended to be transient. They are created, -// filled with the contents of a serialized packet, and passed -// off immediately to the QuicSocket to be sent. As soon as -// the packet is sent, it is freed. -class QuicPacket : public MemoryRetainer { - public: - // Creates a new QuicPacket. By default the packet will be - // stack allocated with a max size of NGTCP2_MAX_PKTLEN_IPV4. - // If a larger packet size is specified, it will be heap - // allocated. A QUIC packet should never be larger than the - // current MTU to avoid IP fragmentation. - // - // The content of a QuicPacket is provided by ngtcp2 and is - // opaque for us. The typical use pattern is to create a QuicPacket - // instance and then pass a pointer to it's internal buffer and max - // size in to an ngtcp2 function that serializes the data. - // ngtcp2 will fill the buffer as much as possible then return - // the number of bytes serialized. User code is then responsible - // for calling set_length() to set the final length of the - // QuicPacket prior to sending it off to the QuicSocket. - // - // The diagnostic label is used in NODE_DEBUG_NATIVE output - // to differentiate send operations. This should always be - // a statically allocated string or nullptr (in which case - // the value "unspecified" is used in the debug output). - // - // Instances of std::unique_ptr<QuicPacket> are moved through - // QuicSocket and ultimately become the responsibility of the - // SendWrap instance. When the SendWrap is cleaned up, the - // QuicPacket instance will be freed. - static inline std::unique_ptr<QuicPacket> Create( - const char* diagnostic_label = nullptr, - size_t len = MAX_PKTLEN); - - // Copy the data of the QuicPacket to a new one. Currently, - // this is only used when retransmitting close connection - // packets from a QuicServer. - static inline std::unique_ptr<QuicPacket> Copy( - const std::unique_ptr<QuicPacket>& other); - - QuicPacket(const char* diagnostic_label, size_t len); - QuicPacket(const QuicPacket& other); - - uint8_t* data() { return data_; } - - size_t length() const { return len_; } - - uv_buf_t buf() const { - return uv_buf_init( - const_cast<char*>(reinterpret_cast<const char*>(&data_)), - length()); - } - - inline void set_length(size_t len); - - const char* diagnostic_label() const; - - SET_NO_MEMORY_INFO(); - SET_MEMORY_INFO_NAME(QuicPacket); - SET_SELF_SIZE(QuicPacket); - - private: - uint8_t data_[MAX_PKTLEN]; - size_t len_ = MAX_PKTLEN; - const char* diagnostic_label_ = nullptr; -}; - -// QuicEndpointListener listens to events generated by a QuicEndpoint. -class QuicEndpointListener { - public: - virtual void OnError(QuicEndpoint* endpoint, ssize_t error) = 0; - virtual void OnReceive( - ssize_t nread, - AllocatedBuffer buf, - const SocketAddress& local_addr, - const SocketAddress& remote_addr, - unsigned int flags) = 0; - virtual ReqWrap<uv_udp_send_t>* OnCreateSendWrap(size_t msg_size) = 0; - virtual void OnSendDone(ReqWrap<uv_udp_send_t>* wrap, int status) = 0; - virtual void OnBind(QuicEndpoint* endpoint) = 0; - virtual void OnEndpointDone(QuicEndpoint* endpoint) = 0; -}; - -// A QuicEndpoint wraps a UDPBaseWrap. A single QuicSocket may -// have multiple QuicEndpoints, the lifecycles of which are -// attached to the QuicSocket. -class QuicEndpoint final : public BaseObject, - public UDPListener { - public: - static void Initialize( - Environment* env, - Local<Object> target, - Local<Context> context); - - QuicEndpoint( - QuicState* quic_state, - Local<Object> wrap, - QuicSocket* listener, - Local<Object> udp_wrap); - - ~QuicEndpoint() override; - - SocketAddress local_address() const { - return udp_->GetSockName(); - } - - // Implementation for UDPListener - uv_buf_t OnAlloc(size_t suggested_size) override; - - void OnRecv(ssize_t nread, - const uv_buf_t& buf, - const sockaddr* addr, - unsigned int flags) override; - - ReqWrap<uv_udp_send_t>* CreateSendWrap(size_t msg_size) override; - - void OnSendDone(ReqWrap<uv_udp_send_t>* wrap, int status) override; - - void OnAfterBind() override; - - inline int ReceiveStart(); - - inline int ReceiveStop(); - - inline int Send( - uv_buf_t* buf, - size_t len, - const sockaddr* addr); - - void IncrementPendingCallbacks() { pending_callbacks_++; } - void DecrementPendingCallbacks() { pending_callbacks_--; } - bool has_pending_callbacks() const { return pending_callbacks_ > 0; } - inline void WaitForPendingCallbacks(); - - QuicState* quic_state() const { return quic_state_.get(); } - - SET_NO_MEMORY_INFO(); - SET_MEMORY_INFO_NAME(QuicEndpoint) - SET_SELF_SIZE(QuicEndpoint) - - private: - BaseObjectWeakPtr<QuicSocket> listener_; - UDPWrapBase* udp_; - BaseObjectPtr<AsyncWrap> strong_ptr_; - size_t pending_callbacks_ = 0; - bool waiting_for_callbacks_ = false; - BaseObjectPtr<QuicState> quic_state_; -}; - -// QuicSocket manages the flow of data from the UDP socket to the -// QuicSession. It is responsible for managing the lifecycle of the -// UDP sockets, listening for new server QuicSession instances, and -// passing data two and from the remote peer. -class QuicSocket : public AsyncWrap, - public QuicEndpointListener, - public mem::NgLibMemoryManager<QuicSocket, ngtcp2_mem>, - public StatsBase<QuicSocketStatsTraits> { - public: - static void Initialize( - Environment* env, - Local<Object> target, - Local<Context> context); - - QuicSocket( - QuicState* quic_state, - Local<Object> wrap, - // A retry token should only be valid for a small window of time. - // The retry_token_expiration specifies the number of seconds a - // retry token is permitted to be valid. - uint64_t retry_token_expiration, - // To prevent malicious clients from opening too many concurrent - // connections, we limit the maximum number per remote sockaddr. - size_t max_connections, - size_t max_connections_per_host, - size_t max_stateless_resets_per_host - = DEFAULT_MAX_STATELESS_RESETS_PER_HOST, - uint32_t options = 0, - QlogMode qlog = QlogMode::kDisabled, - const uint8_t* session_reset_secret = nullptr, - bool disable_session_reset = false); - - ~QuicSocket() override; - - // Returns the default/preferred local address. Additional - // QuicEndpoint instances may be associated with the - // QuicSocket bound to other local addresses. - inline SocketAddress local_address() const; - - void MaybeClose(); - - inline void AddSession( - const QuicCID& cid, - BaseObjectPtr<QuicSession> session); - - inline void AssociateCID( - const QuicCID& cid, - const QuicCID& scid); - - inline void DisassociateCID( - const QuicCID& cid); - - inline void AssociateStatelessResetToken( - const StatelessResetToken& token, - BaseObjectPtr<QuicSession> session); - - inline void DisassociateStatelessResetToken( - const StatelessResetToken& token); - - void Listen( - BaseObjectPtr<crypto::SecureContext> context, - const sockaddr* preferred_address = nullptr, - const std::string& alpn = NGHTTP3_ALPN_H3, - uint32_t options = 0); - - inline void ReceiveStart(); - - inline void ReceiveStop(); - - inline void RemoveSession( - const QuicCID& cid, - const SocketAddress& addr); - - inline void ReportSendError(int error); - - int SendPacket( - const SocketAddress& local_addr, - const SocketAddress& remote_addr, - std::unique_ptr<QuicPacket> packet, - BaseObjectPtr<QuicSession> session = BaseObjectPtr<QuicSession>()); - -#define V(id, name) \ - bool has_option_##name() const { \ - return options_ & (1 << QUICSOCKET_OPTIONS_##id); } - QUICSOCKET_OPTIONS(V) -#undef V - - // Allows the server busy status to be enabled from C++. A notification - // will be sent to the JavaScript side informing that the status has - // changed. - inline void ServerBusy(bool on); - - inline void set_diagnostic_packet_loss(double rx = 0.0, double tx = 0.0); - - BaseObjectPtr<crypto::SecureContext> server_secure_context() const { - return server_secure_context_; - } - - QuicState* quic_state() { return quic_state_.get(); } - - void MemoryInfo(MemoryTracker* tracker) const override; - SET_MEMORY_INFO_NAME(QuicSocket) - SET_SELF_SIZE(QuicSocket) - - // Implementation for mem::NgLibMemoryManager - void CheckAllocatedSize(size_t previous_size) const; - - void IncreaseAllocatedSize(size_t size); - - void DecreaseAllocatedSize(size_t size); - - const uint8_t* session_reset_secret() { return reset_token_secret_; } - - // Implementation for QuicListener - ReqWrap<uv_udp_send_t>* OnCreateSendWrap(size_t msg_size) override; - - // Implementation for QuicListener - void OnSendDone(ReqWrap<uv_udp_send_t>* wrap, int status) override; - - // Implementation for QuicListener - void OnBind(QuicEndpoint* endpoint) override; - - // Implementation for QuicListener - void OnReceive( - ssize_t nread, - AllocatedBuffer buf, - const SocketAddress& local_addr, - const SocketAddress& remote_addr, - unsigned int flags) override; - - // Implementation for QuicListener - void OnError(QuicEndpoint* endpoint, ssize_t error) override; - - // Implementation for QuicListener - void OnEndpointDone(QuicEndpoint* endpoint) override; - - // Serializes and transmits a RETRY packet to the connected peer. - bool SendRetry( - const QuicCID& dcid, - const QuicCID& scid, - const SocketAddress& local_addr, - const SocketAddress& remote_addr); - - // Serializes and transmits a Stateless Reset to the connected peer. - bool SendStatelessReset( - const QuicCID& cid, - const SocketAddress& local_addr, - const SocketAddress& remote_addr, - size_t source_len); - - // Serializes and transmits a Version Negotiation packet to the - // connected peer. - void SendVersionNegotiation( - uint32_t version, - const QuicCID& dcid, - const QuicCID& scid, - const SocketAddress& local_addr, - const SocketAddress& remote_addr); - - void PushListener(QuicSocketListener* listener); - - void RemoveListener(QuicSocketListener* listener); - - inline void AddEndpoint( - BaseObjectPtr<QuicEndpoint> endpoint, - bool preferred = false); - - void ImmediateConnectionClose( - const QuicCID& scid, - const QuicCID& dcid, - const SocketAddress& local_addr, - const SocketAddress& remote_addr, - int64_t reason = NGTCP2_INVALID_TOKEN); - - private: - static void OnAlloc( - uv_handle_t* handle, - size_t suggested_size, - uv_buf_t* buf); - - void OnSend(int status, QuicPacket* packet); - - inline void set_validated_address(const SocketAddress& addr); - - inline bool is_validated_address(const SocketAddress& addr) const; - - bool MaybeStatelessReset( - const QuicCID& dcid, - const QuicCID& scid, - ssize_t nread, - const uint8_t* data, - const SocketAddress& local_addr, - const SocketAddress& remote_addr, - unsigned int flags); - - BaseObjectPtr<QuicSession> AcceptInitialPacket( - uint32_t version, - const QuicCID& dcid, - const QuicCID& scid, - ssize_t nread, - const uint8_t* data, - const SocketAddress& local_addr, - const SocketAddress& remote_addr, - unsigned int flags); - - BaseObjectPtr<QuicSession> FindSession(const QuicCID& cid); - - inline void IncrementSocketAddressCounter(const SocketAddress& addr); - - inline void DecrementSocketAddressCounter(const SocketAddress& addr); - - inline void IncrementStatelessResetCounter(const SocketAddress& addr); - - inline size_t GetCurrentSocketAddressCounter(const SocketAddress& addr); - - inline size_t GetCurrentStatelessResetCounter(const SocketAddress& addr); - - // Returns true if, and only if, diagnostic packet loss is enabled - // and the current packet should be artificially considered lost. - inline bool is_diagnostic_packet_loss(double prob) const; - - ngtcp2_mem alloc_info_; - - std::vector<BaseObjectPtr<QuicEndpoint>> endpoints_; - SocketAddress::Map<BaseObjectWeakPtr<QuicEndpoint>> bound_endpoints_; - BaseObjectWeakPtr<QuicEndpoint> preferred_endpoint_; - BaseObjectPtr<SocketAddressBlockListWrap> block_list_; - - uint32_t flags_ = 0; - uint32_t options_ = 0; - uint32_t server_options_; - - AliasedStruct<QuicSocketState> state_; - - size_t max_connections_ = DEFAULT_MAX_CONNECTIONS; - size_t max_connections_per_host_ = DEFAULT_MAX_CONNECTIONS_PER_HOST; - size_t current_ngtcp2_memory_ = 0; - size_t max_stateless_resets_per_host_ = DEFAULT_MAX_STATELESS_RESETS_PER_HOST; - - uint64_t retry_token_expiration_; - - // Used to specify diagnostic packet loss probabilities - double rx_loss_ = 0.0; - double tx_loss_ = 0.0; - - QuicSocketListener* listener_; - JSQuicSocketListener default_listener_; - QuicSessionConfig server_session_config_; - QlogMode qlog_ = QlogMode::kDisabled; - BaseObjectPtr<crypto::SecureContext> server_secure_context_; - std::string server_alpn_; - QuicCID::Map<BaseObjectPtr<QuicSession>> sessions_; - QuicCID::Map<QuicCID> dcid_to_scid_; - - uint8_t token_secret_[kTokenSecretLen]; - uint8_t reset_token_secret_[NGTCP2_STATELESS_RESET_TOKENLEN]; - - struct SocketAddressInfo { - size_t active_connections; - size_t reset_count; - size_t retry_count; - bool validated; - uint64_t timestamp; - }; - - struct SocketAddressInfoTraits { - using Type = SocketAddressInfo; - - static bool CheckExpired(const SocketAddress& address, const Type& type); - static void Touch(const SocketAddress& address, Type* type); - }; - - SocketAddressLRU<SocketAddressInfoTraits> addrLRU_; - - StatelessResetToken::Map<QuicSession> token_map_; - - class SendWrap : public ReqWrap<uv_udp_send_t> { - public: - SendWrap(QuicState* quic_state, - v8::Local<v8::Object> req_wrap_obj, - size_t total_length_); - - void set_packet(std::unique_ptr<QuicPacket> packet) { - packet_ = std::move(packet); - } - - QuicPacket* packet() { return packet_.get(); } - - void set_session(BaseObjectPtr<QuicSession> session) { session_ = session; } - - size_t total_length() const { return total_length_; } - - QuicState* quic_state() { return quic_state_.get(); } - - SET_SELF_SIZE(SendWrap); - std::string MemoryInfoName() const override; - void MemoryInfo(MemoryTracker* tracker) const override; - - private: - BaseObjectPtr<QuicSession> session_; - std::unique_ptr<QuicPacket> packet_; - size_t total_length_; - BaseObjectPtr<QuicState> quic_state_; - }; - - SendWrap* last_created_send_wrap_ = nullptr; - BaseObjectPtr<QuicState> quic_state_; - - friend class QuicSocketListener; -}; - -} // namespace quic -} // namespace node - -#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS - -#endif // SRC_QUIC_NODE_QUIC_SOCKET_H_ diff --git a/src/quic/node_quic_state.h b/src/quic/node_quic_state.h deleted file mode 100644 index 6eb76529f89..00000000000 --- a/src/quic/node_quic_state.h +++ /dev/null @@ -1,82 +0,0 @@ -#ifndef SRC_QUIC_NODE_QUIC_STATE_H_ -#define SRC_QUIC_NODE_QUIC_STATE_H_ - -#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS - -#include "aliased_buffer.h" - -namespace node { -namespace quic { - -enum QuicSessionConfigIndex : int { - IDX_QUIC_SESSION_ACTIVE_CONNECTION_ID_LIMIT, - IDX_QUIC_SESSION_MAX_STREAM_DATA_BIDI_LOCAL, - IDX_QUIC_SESSION_MAX_STREAM_DATA_BIDI_REMOTE, - IDX_QUIC_SESSION_MAX_STREAM_DATA_UNI, - IDX_QUIC_SESSION_MAX_DATA, - IDX_QUIC_SESSION_MAX_STREAMS_BIDI, - IDX_QUIC_SESSION_MAX_STREAMS_UNI, - IDX_QUIC_SESSION_MAX_IDLE_TIMEOUT, - IDX_QUIC_SESSION_MAX_UDP_PAYLOAD_SIZE, - IDX_QUIC_SESSION_ACK_DELAY_EXPONENT, - IDX_QUIC_SESSION_DISABLE_MIGRATION, - IDX_QUIC_SESSION_MAX_ACK_DELAY, - IDX_QUIC_SESSION_CC_ALGO, - IDX_QUIC_SESSION_CONFIG_COUNT -}; - -enum Http3ConfigIndex : int { - IDX_HTTP3_QPACK_MAX_TABLE_CAPACITY, - IDX_HTTP3_QPACK_BLOCKED_STREAMS, - IDX_HTTP3_MAX_HEADER_LIST_SIZE, - IDX_HTTP3_MAX_PUSHES, - IDX_HTTP3_MAX_HEADER_PAIRS, - IDX_HTTP3_MAX_HEADER_LENGTH, - IDX_HTTP3_CONFIG_COUNT -}; - -class QuicState : public BaseObject { - public: - explicit QuicState(Environment* env, v8::Local<v8::Object> obj) - : BaseObject(env, obj), - root_buffer( - env->isolate(), - sizeof(quic_state_internal)), - quicsessionconfig_buffer( - env->isolate(), - offsetof(quic_state_internal, quicsessionconfig_buffer), - IDX_QUIC_SESSION_CONFIG_COUNT + 1, - root_buffer), - http3config_buffer( - env->isolate(), - offsetof(quic_state_internal, http3config_buffer), - IDX_HTTP3_CONFIG_COUNT + 1, - root_buffer) { - } - - AliasedUint8Array root_buffer; - AliasedFloat64Array quicsessionconfig_buffer; - AliasedFloat64Array http3config_buffer; - - bool warn_trace_tls = true; - - static constexpr FastStringKey binding_data_name { "quic" }; - - void MemoryInfo(MemoryTracker* tracker) const override; - SET_SELF_SIZE(QuicState) - SET_MEMORY_INFO_NAME(QuicState) - - private: - struct quic_state_internal { - // doubles first so that they are always sizeof(double)-aligned - double quicsessionconfig_buffer[IDX_QUIC_SESSION_CONFIG_COUNT + 1]; - double http3config_buffer[IDX_HTTP3_CONFIG_COUNT + 1]; - }; -}; - -} // namespace quic -} // namespace node - -#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS - -#endif // SRC_QUIC_NODE_QUIC_STATE_H_ diff --git a/src/quic/node_quic_stream-inl.h b/src/quic/node_quic_stream-inl.h deleted file mode 100644 index e79a326359c..00000000000 --- a/src/quic/node_quic_stream-inl.h +++ /dev/null @@ -1,180 +0,0 @@ -#ifndef SRC_QUIC_NODE_QUIC_STREAM_INL_H_ -#define SRC_QUIC_NODE_QUIC_STREAM_INL_H_ - -#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS - -#include "debug_utils-inl.h" -#include "node_quic_session.h" -#include "node_quic_stream.h" -#include "node_quic_buffer-inl.h" - -namespace node { -namespace quic { - -QuicStreamDirection QuicStream::direction() const { - return stream_id_ & 0b10 ? - QUIC_STREAM_UNIDIRECTIONAL : - QUIC_STREAM_BIRECTIONAL; -} - -QuicStreamOrigin QuicStream::origin() const { - return stream_id_ & 0b01 ? - QUIC_STREAM_SERVER : - QUIC_STREAM_CLIENT; -} - -void QuicStream::set_final_size(uint64_t final_size) { - // Only set the final size once. - if (state_->fin_received == 1) { - CHECK_LE(final_size, GetStat(&QuicStreamStats::final_size)); - return; - } - state_->fin_received = 1; - SetStat(&QuicStreamStats::final_size, final_size); -} - -bool QuicStream::was_ever_writable() const { - if (direction() == QUIC_STREAM_UNIDIRECTIONAL) { - return session_->is_server() ? - origin() == QUIC_STREAM_SERVER : - origin() == QUIC_STREAM_CLIENT; - } - return true; -} - -bool QuicStream::is_writable() const { - return was_ever_writable() && !streambuf_.is_ended(); -} - -bool QuicStream::was_ever_readable() const { - if (direction() == QUIC_STREAM_UNIDIRECTIONAL) { - return session_->is_server() ? - origin() == QUIC_STREAM_CLIENT : - origin() == QUIC_STREAM_SERVER; - } - - return true; -} - -void QuicStream::set_fin_sent() { - Debug(this, "final stream frame sent"); - state_->fin_sent = 1; - if (shutdown_done_ != nullptr) { - shutdown_done_(0); - } -} - -void QuicStream::set_destroyed() { - destroyed_ = true; -} - -bool QuicStream::is_readable() const { - return was_ever_readable() && state_->read_ended == 0; -} - -bool QuicStream::is_write_finished() const { - return state_->fin_sent == 1 && streambuf_.length() == 0; -} - -bool QuicStream::SubmitInformation(v8::Local<v8::Array> headers) { - return session_->SubmitInformation(stream_id_, headers); -} - -bool QuicStream::SubmitHeaders(v8::Local<v8::Array> headers, uint32_t flags) { - return session_->SubmitHeaders(stream_id_, headers, flags); -} - -bool QuicStream::SubmitTrailers(v8::Local<v8::Array> headers) { - return session_->SubmitTrailers(stream_id_, headers); -} - -BaseObjectPtr<QuicStream> QuicStream::SubmitPush( - v8::Local<v8::Array> headers) { - return session_->SubmitPush(stream_id_, headers); -} - -void QuicStream::EndHeaders(int64_t push_id) { - Debug(this, "End Headers"); - // Upon completion of a block of headers, convert the - // vector of Header objects into an array of name+value - // pairs, then call the on_stream_headers function. - session()->application()->StreamHeaders( - stream_id_, - headers_kind_, - headers_, - push_id); - headers_.clear(); -} - -void QuicStream::set_headers_kind(QuicStreamHeadersKind kind) { - headers_kind_ = kind; -} - -void QuicStream::BeginHeaders(QuicStreamHeadersKind kind) { - Debug(this, "Beginning Headers"); - // Upon start of a new block of headers, ensure that any - // previously collected ones are cleaned up. - headers_.clear(); - set_headers_kind(kind); -} - -void QuicStream::Commit(size_t amount) { - CHECK(!is_destroyed()); - streambuf_.Seek(amount); -} - -// ResetStream will cause ngtcp2 to queue a RESET_STREAM and STOP_SENDING -// frame, as appropriate, for the given stream_id. For a locally-initiated -// unidirectional stream, only a RESET_STREAM frame will be scheduled and -// the stream will be immediately closed. For a bidirectional stream, a -// STOP_SENDING frame will be sent. -void QuicStream::ResetStream(uint64_t app_error_code) { - QuicSession::SendSessionScope send_scope(session()); - session()->ShutdownStream(id(), app_error_code); - state_->read_ended = 1; - streambuf_.Cancel(); - streambuf_.End(); -} - -// StopSending will cause ngtcp2 to queue a STOP_SENDING frame if the -// stream is still inbound readable. -void QuicStream::StopSending(uint64_t app_error_code) { - QuicSession::SendSessionScope send_scope(session()); - ngtcp2_conn_shutdown_stream_read( - session()->connection(), - stream_id_, - app_error_code); - state_->read_ended = 1; -} - -void QuicStream::CancelPendingWrites() { - // In case this stream is scheduled for sending data, remove it - // from the schedule queue - Unschedule(); - - // If there is data currently buffered in the streambuf_, - // then cancel will call out to invoke an arbitrary - // JavaScript callback (the on write callback). Within - // that callback, however, the QuicStream will no longer - // be usable to send or receive data. - streambuf_.End(); - streambuf_.Cancel(); - CHECK_EQ(streambuf_.length(), 0); -} - -void QuicStream::Schedule(Queue* queue) { - if (!stream_queue_.IsEmpty()) // Already scheduled? - return; - queue->PushBack(this); -} - -void QuicStream::Unschedule() { - stream_queue_.Remove(); -} - -} // namespace quic -} // namespace node - -#endif // NODE_WANT_INTERNALS - -#endif // SRC_QUIC_NODE_QUIC_STREAM_INL_H_ diff --git a/src/quic/node_quic_stream.cc b/src/quic/node_quic_stream.cc deleted file mode 100644 index 57976ae50d9..00000000000 --- a/src/quic/node_quic_stream.cc +++ /dev/null @@ -1,548 +0,0 @@ -#include "node_quic_stream-inl.h" // NOLINT(build/include) -#include "aliased_struct-inl.h" -#include "async_wrap-inl.h" -#include "debug_utils-inl.h" -#include "env-inl.h" -#include "node.h" -#include "node_buffer.h" -#include "node_internals.h" -#include "stream_base-inl.h" -#include "node_sockaddr-inl.h" -#include "node_http_common-inl.h" -#include "node_quic_session-inl.h" -#include "node_quic_socket-inl.h" -#include "node_quic_util-inl.h" -#include "v8.h" -#include "uv.h" - -#include <algorithm> -#include <memory> -#include <string> -#include <utility> - -namespace node { - -using v8::Array; -using v8::Context; -using v8::FunctionCallbackInfo; -using v8::FunctionTemplate; -using v8::Local; -using v8::Object; -using v8::ObjectTemplate; -using v8::PropertyAttribute; -using v8::Value; - -namespace quic { - -QuicStream::QuicStream( - QuicSession* sess, - Local<Object> wrap, - int64_t stream_id, - int64_t push_id) - : AsyncWrap(sess->env(), wrap, AsyncWrap::PROVIDER_QUICSTREAM), - StreamBase(sess->env()), - StatsBase(sess->env(), wrap, - HistogramOptions::ACK | - HistogramOptions::RATE | - HistogramOptions::SIZE), - session_(sess), - stream_id_(stream_id), - push_id_(push_id), - state_(sess->env()->isolate()), - quic_state_(sess->quic_state()) { - CHECK_NOT_NULL(sess); - Debug(this, "Created"); - StreamBase::AttachToObject(GetObject()); - - wrap->DefineOwnProperty( - env()->context(), - env()->state_string(), - state_.GetArrayBuffer(), - PropertyAttribute::ReadOnly).Check(); - - ngtcp2_transport_params params; - ngtcp2_conn_get_local_transport_params(session()->connection(), ¶ms); - IncrementStat(&QuicStreamStats::max_offset, params.initial_max_data); -} - -QuicStream::~QuicStream() { - DebugStats(); -} - -template <typename Fn> -void QuicStreamStatsTraits::ToString(const QuicStream& ptr, Fn&& add_field) { -#define V(_n, name, label) \ - add_field(label, ptr.GetStat(&QuicStreamStats::name)); - STREAM_STATS(V) -#undef V -} - -// Acknowledge is called when ngtcp2 has received an acknowledgement -// for one or more stream frames for this QuicStream. This will cause -// data stored in the streambuf_ outbound queue to be consumed and may -// result in the JavaScript callback for the write to be invoked. -void QuicStream::Acknowledge(uint64_t offset, size_t datalen) { - if (is_destroyed()) - return; - - // ngtcp2 guarantees that offset must always be greater - // than the previously received offset, but let's just - // make sure that holds. - CHECK_GE(offset, GetStat(&QuicStreamStats::max_offset_ack)); - SetStat(&QuicStreamStats::max_offset_ack, offset); - - Debug(this, "Acknowledging %d bytes", datalen); - - // Consumes the given number of bytes in the buffer. This may - // have the side-effect of causing the onwrite callback to be - // invoked if a complete chunk of buffered data has been acknowledged. - streambuf_.Consume(datalen); - - RecordAck(&QuicStreamStats::acked_at); -} - -// While not all QUIC applications will support headers, QuicStream -// includes basic, generic support for storing them. -bool QuicStream::AddHeader(std::unique_ptr<QuicHeader> header) { - size_t len = header->length(); - QuicApplication* app = session()->application(); - // We cannot add the header if we've either reached - // * the max number of header pairs or - // * the max number of header bytes - if (headers_.size() == app->max_header_pairs() || - current_headers_length_ + len > app->max_header_length()) { - return false; - } - - current_headers_length_ += header->length(); - Debug(this, "Header - %s", header.get()); - headers_.emplace_back(std::move(header)); - return true; -} - -std::string QuicStream::diagnostic_name() const { - return std::string("QuicStream ") + std::to_string(stream_id_) + - " (" + std::to_string(static_cast<int64_t>(get_async_id())) + - ", " + session_->diagnostic_name() + ")"; -} - -void QuicStream::Destroy(QuicError* error) { - if (destroyed_) - return; - destroyed_ = true; - - if (is_writable() || is_readable()) - session()->ShutdownStream(id(), 0); - - CancelPendingWrites(); - - session_->RemoveStream(stream_id_); -} - -// Do shutdown is called when the JS stream writable side is closed. -// If we're not within an ngtcp2 callback, this will trigger the -// QuicSession to send any pending data. If a final stream frame -// has not already been sent, it will be after this. -int QuicStream::DoShutdown(ShutdownWrap* req_wrap) { - if (is_destroyed()) - return UV_EPIPE; - - // If the fin bit has already been sent, we can return - // immediately because there's nothing else to do. The - // _final callback will be invoked immediately. - if (state_->fin_sent || !is_writable()) { - Debug(this, "Shutdown write immediately"); - return 1; - } - Debug(this, "Deferred shutdown. Waiting for fin sent"); - - CHECK_NULL(shutdown_done_); - CHECK_NOT_NULL(req_wrap); - shutdown_done_ = [=](int status) { - CHECK_NOT_NULL(req_wrap); - shutdown_done_ = nullptr; - req_wrap->Done(status); - }; - - QuicSession::SendSessionScope send_scope(session()); - - Debug(this, "Shutdown writable side"); - RecordTimestamp(&QuicStreamStats::closing_at); - state_->write_ended = 1; - streambuf_.End(); - session()->ResumeStream(stream_id_); - - return 0; -} - -int QuicStream::DoWrite( - WriteWrap* req_wrap, - uv_buf_t* bufs, - size_t nbufs, - uv_stream_t* send_handle) { - CHECK_NULL(send_handle); - CHECK(!streambuf_.is_ended()); - - // A write should not have happened if we've been destroyed or - // the QuicStream is no longer (or was never) writable. - if (is_destroyed() || !is_writable()) { - req_wrap->Done(UV_EPIPE); - return 0; - } - - // Nothing to write. - size_t length = get_length(bufs, nbufs); - if (length == 0) { - req_wrap->Done(0); - return 0; - } - - QuicSession::SendSessionScope send_scope(session()); - - Debug(this, "Queuing %" PRIu64 " bytes of data from %d buffers", - length, nbufs); - IncrementStat(&QuicStreamStats::bytes_sent, static_cast<uint64_t>(length)); - - BaseObjectPtr<AsyncWrap> strong_ref{req_wrap->GetAsyncWrap()}; - // The list of buffers will be appended onto streambuf_ without - // copying. Those will remain in the buffer until the serialized - // stream frames are acknowledged. - // This callback function will be invoked once this - // complete batch of buffers has been acknowledged - // by the peer. This will have the side effect of - // blocking additional pending writes from the - // javascript side, so writing data to the stream - // will be throttled by how quickly the peer is - // able to acknowledge stream packets. This is good - // in the sense of providing back-pressure, but - // also means that writes will be significantly - // less performant unless written in batches. - streambuf_.Push( - bufs, - nbufs, - [req_wrap, strong_ref](int status) { - req_wrap->Done(status); - }); - - // If end() was called on the JS side, the write_ended flag - // will have been set. This allows us to know early if this - // is the final chunk. But this is only only to be triggered - // if end() was called with a final chunk of data to write. - // Otherwise, we have to wait for DoShutdown to be called. - if (state_->write_ended == 1) { - RecordTimestamp(&QuicStreamStats::closing_at); - streambuf_.End(); - } - - session()->ResumeStream(stream_id_); - - return 0; -} - -bool QuicStream::IsAlive() { - return !is_destroyed() && !IsClosing(); -} - -bool QuicStream::IsClosing() { - return !is_writable() && !is_readable(); -} - -int QuicStream::ReadStart() { - CHECK(!is_destroyed()); - CHECK(is_readable()); - state_->read_started = 1; - state_->read_paused = 0; - IncrementStat( - &QuicStreamStats::max_offset, - inbound_consumed_data_while_paused_); - session_->ExtendStreamOffset(id(), inbound_consumed_data_while_paused_); - return 0; -} - -int QuicStream::ReadStop() { - CHECK(!is_destroyed()); - CHECK(is_readable()); - state_->read_paused = 1; - return 0; -} - -void QuicStream::IncrementStats(size_t datalen) { - uint64_t len = static_cast<uint64_t>(datalen); - IncrementStat(&QuicStreamStats::bytes_received, len); - RecordRate(&QuicStreamStats::received_at); - RecordSize(len); -} - -void QuicStream::MemoryInfo(MemoryTracker* tracker) const { - tracker->TrackField("buffer", &streambuf_); - StatsBase::StatsMemoryInfo(tracker); - tracker->TrackField("headers", headers_); -} - -BaseObjectPtr<QuicStream> QuicStream::New( - QuicSession* session, - int64_t stream_id, - int64_t push_id) { - Local<Object> obj; - if (!session->env() - ->quicserverstream_instance_template() - ->NewInstance(session->env()->context()).ToLocal(&obj)) { - return {}; - } - BaseObjectPtr<QuicStream> stream = - MakeDetachedBaseObject<QuicStream>( - session, - obj, - stream_id, - push_id); - CHECK(stream); - session->AddStream(stream); - return stream; -} - -// Passes chunks of data on to the JavaScript side as soon as they are -// received but only if we're still readable. The caller of this must have a -// HandleScope. -// -// Note that this is pushing data to the JS side regardless of whether -// anything is listening. For flow-control, we only send window updates -// to the sending peer if the stream is in flowing mode, so the sender -// should not be sending too much data. -void QuicStream::ReceiveData( - uint32_t flags, - const uint8_t* data, - size_t datalen, - uint64_t offset) { - CHECK(!is_destroyed()); - Debug(this, "Receiving %d bytes. Final? %s. Readable? %s", - datalen, - flags & NGTCP2_STREAM_DATA_FLAG_FIN ? "yes" : "no", - is_readable() ? "yes" : "no"); - - // If the QuicStream is not (or was never) readable, just ignore the chunk. - if (!is_readable()) - return; - - // ngtcp2 guarantees that datalen will only be 0 if fin is set. - // Let's just make sure. - CHECK(datalen > 0 || flags & NGTCP2_STREAM_DATA_FLAG_FIN); - - // ngtcp2 guarantees that offset is always greater than the previously - // received offset. Let's just make sure. - CHECK_GE(offset, GetStat(&QuicStreamStats::max_offset_received)); - SetStat(&QuicStreamStats::max_offset_received, offset); - - if (datalen > 0) { - // IncrementStats will update the data_rx_rate_ and data_rx_size_ - // histograms. These will provide data necessary to detect and - // prevent Slow Send DOS attacks specifically by allowing us to - // see if a connection is sending very small chunks of data at very - // slow speeds. It is important to emphasize, however, that slow send - // rates may be perfectly legitimate so we cannot simply take blanket - // action when slow rates are detected. Nor can we reliably define what - // a slow rate even is! Will will need to determine some reasonable - // default and allow user code to change the default as well as determine - // what action to take. The current strategy will be to trigger an event - // on the stream when data transfer rates are likely to be considered too - // slow. - IncrementStats(datalen); - - while (datalen > 0) { - uv_buf_t buf = EmitAlloc(datalen); - size_t avail = std::min(static_cast<size_t>(buf.len), datalen); - - // For now, we're allocating and copying. Once we determine if we can - // safely switch to a non-allocated mode like we do with http2 streams, - // we can make this branch more efficient by using the LIKELY - // optimization. The way ngtcp2 currently works, however, we have - // to memcpy here. - if (UNLIKELY(buf.base == nullptr)) - buf.base = reinterpret_cast<char*>(const_cast<uint8_t*>(data)); - else - memcpy(buf.base, data, avail); - data += avail; - datalen -= avail; - // Capture read_paused before EmitRead in case user code callbacks - // alter the state when EmitRead is called. - bool read_paused = state_->read_paused == 1; - EmitRead(avail, buf); - // Reading can be paused while we are processing. If that's - // the case, we still want to acknowledge the current bytes - // so that pausing does not throw off our flow control. - if (read_paused) { - inbound_consumed_data_while_paused_ += avail; - } else { - IncrementStat(&QuicStreamStats::max_offset, avail); - session_->ExtendStreamOffset(id(), avail); - } - } - } - - // When fin != 0, we've received that last chunk of data for this - // stream, indicating that the stream will no longer be readable. - if (flags & NGTCP2_STREAM_DATA_FLAG_FIN) { - set_final_size(offset + datalen); - EmitRead(UV_EOF); - } -} - -int QuicStream::DoPull( - bob::Next<ngtcp2_vec> next, - int options, - ngtcp2_vec* data, - size_t count, - size_t max_count_hint) { - return streambuf_.Pull( - std::move(next), - options, - data, - count, - max_count_hint); -} - -// JavaScript API -namespace { -void QuicStreamGetID(const FunctionCallbackInfo<Value>& args) { - QuicStream* stream; - ASSIGN_OR_RETURN_UNWRAP(&stream, args.Holder()); - args.GetReturnValue().Set(static_cast<double>(stream->id())); -} - -void OpenUnidirectionalStream(const FunctionCallbackInfo<Value>& args) { - CHECK(!args.IsConstructCall()); - CHECK(args[0]->IsObject()); - QuicSession* session; - ASSIGN_OR_RETURN_UNWRAP(&session, args[0].As<Object>()); - - int64_t stream_id; - if (!session->OpenUnidirectionalStream(&stream_id)) - return; - - BaseObjectPtr<QuicStream> stream = QuicStream::New(session, stream_id); - args.GetReturnValue().Set(stream->object()); -} - -void OpenBidirectionalStream(const FunctionCallbackInfo<Value>& args) { - CHECK(!args.IsConstructCall()); - CHECK(args[0]->IsObject()); - QuicSession* session; - ASSIGN_OR_RETURN_UNWRAP(&session, args[0].As<Object>()); - - int64_t stream_id; - if (!session->OpenBidirectionalStream(&stream_id)) - return; - - BaseObjectPtr<QuicStream> stream = QuicStream::New(session, stream_id); - args.GetReturnValue().Set(stream->object()); -} - -void QuicStreamDestroy(const FunctionCallbackInfo<Value>& args) { - Environment* env = Environment::GetCurrent(args); - QuicStream* stream; - ASSIGN_OR_RETURN_UNWRAP(&stream, args.Holder()); - QuicError error(env, args[0], args[1], QUIC_ERROR_APPLICATION); - stream->Destroy(&error); -} - -void QuicStreamReset(const FunctionCallbackInfo<Value>& args) { - Environment* env = Environment::GetCurrent(args); - QuicStream* stream; - ASSIGN_OR_RETURN_UNWRAP(&stream, args.Holder()); - - QuicError error(env, args[0], args[1], QUIC_ERROR_APPLICATION); - - stream->ResetStream( - error.family == QUIC_ERROR_APPLICATION ? - error.code : static_cast<uint64_t>(NGTCP2_NO_ERROR)); -} - -void QuicStreamStopSending(const FunctionCallbackInfo<Value>& args) { - Environment* env = Environment::GetCurrent(args); - QuicStream* stream; - ASSIGN_OR_RETURN_UNWRAP(&stream, args.Holder()); - - QuicError error(env, args[0], args[1], QUIC_ERROR_APPLICATION); - - stream->StopSending( - error.family == QUIC_ERROR_APPLICATION ? - error.code : static_cast<uint64_t>(NGTCP2_NO_ERROR)); -} - -// Requests transmission of a block of informational headers. Not all -// QUIC Applications will support headers. If headers are not supported, -// This will set the return value to false, otherwise the return value -// is set to true -void QuicStreamSubmitInformation(const FunctionCallbackInfo<Value>& args) { - QuicStream* stream; - ASSIGN_OR_RETURN_UNWRAP(&stream, args.Holder()); - CHECK(args[0]->IsArray()); - args.GetReturnValue().Set(stream->SubmitInformation(args[0].As<Array>())); -} - -// Requests transmission of a block of initial headers. Not all -// QUIC Applications will support headers. If headers are not supported, -// this will set the return value to false, otherwise the return value -// is set to true. For http/3, these may be request or response headers. -void QuicStreamSubmitHeaders(const FunctionCallbackInfo<Value>& args) { - QuicStream* stream; - ASSIGN_OR_RETURN_UNWRAP(&stream, args.Holder()); - CHECK(args[0]->IsArray()); - uint32_t flags = QUICSTREAM_HEADER_FLAGS_NONE; - CHECK(args[1]->Uint32Value(stream->env()->context()).To(&flags)); - args.GetReturnValue().Set(stream->SubmitHeaders(args[0].As<Array>(), flags)); -} - -// Requests transmission of a block of trailing headers. Not all -// QUIC Applications will support headers. If headers are not supported, -// this will set the return value to false, otherwise the return value -// is set to true. -void QuicStreamSubmitTrailers(const FunctionCallbackInfo<Value>& args) { - QuicStream* stream; - ASSIGN_OR_RETURN_UNWRAP(&stream, args.Holder()); - CHECK(args[0]->IsArray()); - args.GetReturnValue().Set(stream->SubmitTrailers(args[0].As<Array>())); -} - -// Requests creation of a push stream. Not all QUIC Applications will -// support push streams. If pushes are not supported, the return value -// will be undefined, otherwise the return value will be the created -// QuicStream representing the push. -void QuicStreamSubmitPush(const FunctionCallbackInfo<Value>& args) { - QuicStream* stream; - ASSIGN_OR_RETURN_UNWRAP(&stream, args.Holder()); - CHECK(args[0]->IsArray()); - BaseObjectPtr<QuicStream> push_stream = - stream->SubmitPush(args[0].As<Array>()); - if (push_stream) - args.GetReturnValue().Set(push_stream->object()); -} - -} // namespace - -void QuicStream::Initialize( - Environment* env, - Local<Object> target, - Local<Context> context) { - Local<FunctionTemplate> stream = FunctionTemplate::New(env->isolate()); - stream->Inherit(AsyncWrap::GetConstructorTemplate(env)); - StreamBase::AddMethods(env, stream); - Local<ObjectTemplate> streamt = stream->InstanceTemplate(); - streamt->SetInternalFieldCount(StreamBase::kInternalFieldCount); - streamt->Set(env->owner_symbol(), Null(env->isolate())); - env->SetProtoMethod(stream, "destroy", QuicStreamDestroy); - env->SetProtoMethod(stream, "resetStream", QuicStreamReset); - env->SetProtoMethod(stream, "stopSending", QuicStreamStopSending); - env->SetProtoMethod(stream, "id", QuicStreamGetID); - env->SetProtoMethod(stream, "submitInformation", QuicStreamSubmitInformation); - env->SetProtoMethod(stream, "submitHeaders", QuicStreamSubmitHeaders); - env->SetProtoMethod(stream, "submitTrailers", QuicStreamSubmitTrailers); - env->SetProtoMethod(stream, "submitPush", QuicStreamSubmitPush); - env->set_quicserverstream_instance_template(streamt); - env->SetConstructorFunction(target, "QuicStream", stream); - - env->SetMethod(target, "openBidirectionalStream", OpenBidirectionalStream); - env->SetMethod(target, "openUnidirectionalStream", OpenUnidirectionalStream); -} - -} // namespace quic -} // namespace node diff --git a/src/quic/node_quic_stream.h b/src/quic/node_quic_stream.h deleted file mode 100644 index 6cbdef2416f..00000000000 --- a/src/quic/node_quic_stream.h +++ /dev/null @@ -1,409 +0,0 @@ -#ifndef SRC_QUIC_NODE_QUIC_STREAM_H_ -#define SRC_QUIC_NODE_QUIC_STREAM_H_ - -#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS - -#include "memory_tracker.h" -#include "aliased_struct.h" -#include "async_wrap.h" -#include "env.h" -#include "node_http_common.h" -#include "node_quic_state.h" -#include "node_quic_util.h" -#include "stream_base-inl.h" -#include "util-inl.h" -#include "v8.h" - -#include <string> -#include <vector> - -namespace node { -namespace quic { - -class QuicSession; -class QuicStream; -class QuicApplication; - -using QuicHeader = NgHeaderBase<QuicApplication>; - -enum QuicStreamHeaderFlags : uint32_t { - // No flags - QUICSTREAM_HEADER_FLAGS_NONE = 0, - - // Set if the initial headers are considered - // terminal (that is, the stream should be closed - // after transmitting the headers). If headers are - // not supported by the QUIC Application, flag is - // ignored. - QUICSTREAM_HEADER_FLAGS_TERMINAL = 1 -}; - -enum QuicStreamHeadersKind : int { - QUICSTREAM_HEADERS_KIND_NONE = 0, - QUICSTREAM_HEADERS_KIND_INFORMATIONAL, - QUICSTREAM_HEADERS_KIND_INITIAL, - QUICSTREAM_HEADERS_KIND_TRAILING, - QUICSTREAM_HEADERS_KIND_PUSH -}; - -#define STREAM_STATS(V) \ - V(CREATED_AT, created_at, "Created At") \ - V(RECEIVED_AT, received_at, "Last Received At") \ - V(ACKED_AT, acked_at, "Last Acknowledged At") \ - V(CLOSING_AT, closing_at, "Closing At") \ - V(DESTROYED_AT, destroyed_at, "Destroyed At") \ - V(BYTES_RECEIVED, bytes_received, "Bytes Received") \ - V(BYTES_SENT, bytes_sent, "Bytes Sent") \ - V(MAX_OFFSET, max_offset, "Max Offset") \ - V(MAX_OFFSET_ACK, max_offset_ack, "Max Acknowledged Offset") \ - V(MAX_OFFSET_RECV, max_offset_received, "Max Received Offset") \ - V(FINAL_SIZE, final_size, "Final Size") - -#define V(name, _, __) IDX_QUIC_STREAM_STATS_##name, -enum QuicStreamStatsIdx : int { - STREAM_STATS(V) - IDX_QUIC_STREAM_STATS_COUNT -}; -#undef V - -#define V(_, name, __) uint64_t name; -struct QuicStreamStats { - STREAM_STATS(V) -}; -#undef V - -struct QuicStreamStatsTraits { - using Stats = QuicStreamStats; - using Base = QuicStream; - - template <typename Fn> - static void ToString(const Base& ptr, Fn&& add_field); -}; - -#define QUICSTREAM_SHARED_STATE(V) \ - V(WRITE_ENDED, write_ended, uint8_t) \ - V(READ_STARTED, read_started, uint8_t) \ - V(READ_PAUSED, read_paused, uint8_t) \ - V(READ_ENDED, read_ended, uint8_t) \ - V(FIN_SENT, fin_sent, uint8_t) \ - V(FIN_RECEIVED, fin_received, uint8_t) - -#define V(_, name, type) type name; -struct QuicStreamState { - QUICSTREAM_SHARED_STATE(V); -}; -#undef V - -#define V(id, name, _) \ - IDX_QUICSTREAM_STATE_##id = offsetof(QuicStreamState, name), -enum QuicStreamStateFields { - QUICSTREAM_SHARED_STATE(V) - IDX_QUICSTREAM_STATE_END -}; -#undef V - -enum QuicStreamDirection { - // The QuicStream is readable and writable in both directions - QUIC_STREAM_BIRECTIONAL, - - // The QuicStream is writable and readable in only one direction. - // The direction depends on the QuicStreamOrigin. - QUIC_STREAM_UNIDIRECTIONAL -}; - -enum QuicStreamOrigin { - // The QuicStream was created by the server. - QUIC_STREAM_SERVER, - - // The QuicStream was created by the client. - QUIC_STREAM_CLIENT -}; - -// QuicStream's are simple data flows that, fortunately, do not -// require much. They may be: -// -// * Bidirectional or Unidirectional -// * Server or Client Initiated -// -// The flow direction and origin of the stream are important in -// determining the write and read state (Open or Closed). Specifically: -// -// A Unidirectional stream originating with the Server is: -// -// * Server Writable (Open) but not Client Writable (Closed) -// * Client Readable (Open) but not Server Readable (Closed) -// -// Likewise, a Unidirectional stream originating with the -// Client is: -// -// * Client Writable (Open) but not Server Writable (Closed) -// * Server Readable (Open) but not Client Readable (Closed) -// -// Bidirectional Stream States -// +------------+--------------+--------------------+---------------------+ -// | | Initiated By | Initial Read State | Initial Write State | -// +------------+--------------+--------------------+---------------------+ -// | On Server | Server | Open | Open | -// +------------+--------------+--------------------+---------------------+ -// | On Server | Client | Open | Open | -// +------------+--------------+--------------------+---------------------+ -// | On Client | Server | Open | Open | -// +------------+--------------+--------------------+---------------------+ -// | On Client | Client | Open | Open | -// +------------+--------------+--------------------+---------------------+ -// -// Unidirectional Stream States -// +------------+--------------+--------------------+---------------------+ -// | | Initiated By | Initial Read State | Initial Write State | -// +------------+--------------+--------------------+---------------------+ -// | On Server | Server | Closed | Open | -// +------------+--------------+--------------------+---------------------+ -// | On Server | Client | Open | Closed | -// +------------+--------------+--------------------+---------------------+ -// | On Client | Server | Open | Closed | -// +------------+--------------+--------------------+---------------------+ -// | On Client | Client | Closed | Open | -// +------------+--------------+--------------------+---------------------+ -// -// All data sent via the QuicStream is buffered internally until either -// receipt is acknowledged from the peer or attempts to send are abandoned. -// -// A QuicStream may be in a fully Closed (Read and Write) state but still -// have unacknowledged data in it's outbound queue. -// -// A QuicStream is gracefully closed when (a) both Read and Write states -// are Closed, (b) all queued data has been acknowledged. -// -// The JavaScript Writable side of the QuicStream may be shutdown before -// all pending queued data has been serialized to frames. During this state, -// no additional data may be queued to send. -// -// The Write state of a QuicStream will not be closed while there is still -// pending writes on the JavaScript side. -// -// The QuicStream may be forcefully closed immediately using destroy(err). -// This causes all queued data and pending JavaScript writes to be -// abandoned, and causes the QuicStream to be immediately closed at the -// ngtcp2 level. -class QuicStream : public AsyncWrap, - public bob::SourceImpl<ngtcp2_vec>, - public StreamBase, - public StatsBase<QuicStreamStatsTraits> { - public: - static void Initialize( - Environment* env, - v8::Local<v8::Object> target, - v8::Local<v8::Context> context); - - static BaseObjectPtr<QuicStream> New( - QuicSession* session, - int64_t stream_id, - int64_t push_id = 0); - - QuicStream( - QuicSession* session, - v8::Local<v8::Object> target, - int64_t stream_id, - int64_t push_id = 0); - - ~QuicStream() override; - - std::string diagnostic_name() const override; - - // The numeric identifier of the QuicStream. - int64_t id() const { return stream_id_; } - - // If the QuicStream is associated with a push promise, - // the numeric identifier of the promise. Currently only - // used by HTTP/3. - int64_t push_id() const { return push_id_; } - - QuicSession* session() const { return session_.get(); } - - // A QuicStream can be either uni- or bi-directional. - inline QuicStreamDirection direction() const; - - // A QuicStream can be initiated by either the client - // or the server. - inline QuicStreamOrigin origin() const; - - inline void set_fin_sent(); - - inline bool is_destroyed() const { return destroyed_; } - - inline void set_destroyed(); - - // A QuicStream will not be writable if: - // - The streambuf_ is ended - // - It is a Unidirectional stream originating from the peer - inline bool is_writable() const; - - // A QuicStream will not be readable if: - // - The read ended flag is set or - // - It is a Unidirectional stream originating from the local peer. - inline bool is_readable() const; - - // IsWriteFinished will return true if a final stream frame - // has been sent and all data has been acknowledged (the - // send buffer is empty). - inline bool is_write_finished() const; - - // Specifies the kind of headers currently being processed. - inline void set_headers_kind(QuicStreamHeadersKind kind); - - // Set the final size for the QuicStream. This only works - // the first time it is called. Subsequent calls will be - // ignored unless the subsequent size is greater than the - // prior set size, in which case we have a bug and we'll - // assert. - inline void set_final_size(uint64_t final_size); - - // The final size is the maximum amount of data that has been - // acknowleged to have been received for a QuicStream. - uint64_t final_size() const { - return GetStat(&QuicStreamStats::final_size); - } - - // Marks the given data range as having been acknowledged. - // This means that the data range may be released from - // memory. - void Acknowledge(uint64_t offset, size_t datalen); - - // Destroy the QuicStream and render it no longer usable. - void Destroy(QuicError* error = nullptr); - - inline void CancelPendingWrites(); - - // Buffers chunks of data to be written to the QUIC connection. - int DoWrite( - WriteWrap* req_wrap, - uv_buf_t* bufs, - size_t nbufs, - uv_stream_t* send_handle) override; - - // Returns false if the header cannot be added. This will - // typically only happen if a maximimum number of headers - // has been reached. - bool AddHeader(std::unique_ptr<QuicHeader> header); - - // Some QUIC applications support headers, others do not. - // The following methods allow consistent handling of - // headers at the QuicStream level regardless of the - // protocol. For applications that do not support headers, - // these are simply not used. - inline void BeginHeaders( - QuicStreamHeadersKind kind = QUICSTREAM_HEADERS_KIND_NONE); - - // Indicates an amount of unacknowledged data that has been - // submitted to the QUIC connection. - inline void Commit(size_t amount); - - inline void EndHeaders(int64_t push_id = 0); - - // Passes a chunk of data on to the QuicStream listener. - void ReceiveData( - uint32_t flags, - const uint8_t* data, - size_t datalen, - uint64_t offset); - - // Resets the QUIC stream, sending a signal to the peer that - // no additional data will be transmitted for this stream. - inline void ResetStream(uint64_t app_error_code = NGTCP2_NO_ERROR); - - inline void StopSending(uint64_t app_error_code = NGTCP2_NO_ERROR); - - // Submits informational headers. Returns false if headers are not - // supported on the underlying QuicApplication. - inline bool SubmitInformation(v8::Local<v8::Array> headers); - - // Submits initial headers. Returns false if headers are not - // supported on the underlying QuicApplication. - inline bool SubmitHeaders(v8::Local<v8::Array> headers, uint32_t flags); - - // Submits trailing headers. Returns false if headers are not - // supported on the underlying QuicApplication. - inline bool SubmitTrailers(v8::Local<v8::Array> headers); - - inline BaseObjectPtr<QuicStream> SubmitPush(v8::Local<v8::Array> headers); - - // Required for StreamBase - bool IsAlive() override; - - // Required for StreamBase - bool IsClosing() override; - - // Required for StreamBase - int ReadStart() override; - - // Required for StreamBase - int ReadStop() override; - - // Required for StreamBase - int DoShutdown(ShutdownWrap* req_wrap) override; - - AsyncWrap* GetAsyncWrap() override { return this; } - - QuicState* quic_state() { return quic_state_.get(); } - - // Required for MemoryRetainer - void MemoryInfo(MemoryTracker* tracker) const override; - SET_MEMORY_INFO_NAME(QuicStream) - SET_SELF_SIZE(QuicStream) - - protected: - int DoPull( - bob::Next<ngtcp2_vec> next, - int options, - ngtcp2_vec* data, - size_t count, - size_t max_count_hint) override; - - private: - // WasEverWritable returns true if it is a bidirectional stream, - // or a Unidirectional stream originating from the local peer. - // If was_ever_writable() is false, then no stream frames should - // ever be sent from the local peer, including final stream frames. - inline bool was_ever_writable() const; - - // WasEverReadable returns true if it is a bidirectional stream, - // or a Unidirectional stream originating from the remote - // peer. - inline bool was_ever_readable() const; - - void IncrementStats(size_t datalen); - - BaseObjectWeakPtr<QuicSession> session_; - QuicBuffer streambuf_; - - int64_t stream_id_ = 0; - int64_t push_id_ = 0; - bool destroyed_ = false; - AliasedStruct<QuicStreamState> state_; - DoneCB shutdown_done_ = nullptr; - - size_t inbound_consumed_data_while_paused_ = 0; - - std::vector<std::unique_ptr<QuicHeader>> headers_; - QuicStreamHeadersKind headers_kind_; - size_t current_headers_length_ = 0; - - ListNode<QuicStream> stream_queue_; - - BaseObjectPtr<QuicState> quic_state_; - - public: - // Linked List of QuicStream objects - using Queue = ListHead<QuicStream, &QuicStream::stream_queue_>; - - inline void Schedule(Queue* queue); - - inline void Unschedule(); -}; - -} // namespace quic -} // namespace node - -#endif // NODE_WANT_INTERNALS - -#endif // SRC_QUIC_NODE_QUIC_STREAM_H_ diff --git a/src/quic/node_quic_util-inl.h b/src/quic/node_quic_util-inl.h deleted file mode 100644 index 65b2e08823d..00000000000 --- a/src/quic/node_quic_util-inl.h +++ /dev/null @@ -1,435 +0,0 @@ -#ifndef SRC_QUIC_NODE_QUIC_UTIL_INL_H_ -#define SRC_QUIC_NODE_QUIC_UTIL_INL_H_ - -#include "debug_utils-inl.h" -#include "node_internals.h" -#include "node_quic_crypto.h" -#include "node_quic_util.h" -#include "memory_tracker-inl.h" -#include "env-inl.h" -#include "histogram-inl.h" -#include "string_bytes.h" -#include "util-inl.h" -#include "uv.h" - -#include <string> - -namespace node { - -namespace quic { - -QuicPath::QuicPath( - const SocketAddress& local, - const SocketAddress& remote) { - ngtcp2_addr_init( - &this->local, - local.data(), - local.length(), - const_cast<SocketAddress*>(&local)); - ngtcp2_addr_init( - &this->remote, - remote.data(), - remote.length(), - const_cast<SocketAddress*>(&remote)); -} - -size_t QuicCID::Hash::operator()(const QuicCID& token) const { - size_t hash = 0; - for (size_t n = 0; n < token->datalen; n++) { - hash ^= std::hash<uint8_t>{}(token->data[n]) + 0x9e3779b9 + - (hash << 6) + (hash >> 2); - } - return hash; -} - -QuicCID& QuicCID::operator=(const QuicCID& cid) { - if (this == &cid) return *this; - this->~QuicCID(); - return *new(this) QuicCID(std::move(cid)); -} - -bool QuicCID::operator==(const QuicCID& other) const { - return memcmp(cid()->data, other.cid()->data, cid()->datalen) == 0; -} - -bool QuicCID::operator!=(const QuicCID& other) const { - return !(*this == other); -} - -std::string QuicCID::ToString() const { - std::vector<char> dest(ptr_->datalen * 2 + 1); - dest[dest.size() - 1] = '\0'; - size_t written = StringBytes::hex_encode( - reinterpret_cast<const char*>(ptr_->data), - ptr_->datalen, - dest.data(), - dest.size()); - return std::string(dest.data(), written); -} - -size_t GetMaxPktLen(const SocketAddress& addr) { - return addr.family() == AF_INET6 ? - NGTCP2_MAX_PKTLEN_IPV6 : - NGTCP2_MAX_PKTLEN_IPV4; -} - -QuicError::QuicError( - int32_t family_, - uint64_t code_) : - family(family_), - code(code_) {} - -QuicError::QuicError( - int32_t family_, - int code_) : - family(family_) { - switch (family) { - case QUIC_ERROR_CRYPTO: - code_ |= NGTCP2_CRYPTO_ERROR; - // Fall-through... - case QUIC_ERROR_SESSION: - code = ngtcp2_err_infer_quic_transport_error_code(code_); - break; - case QUIC_ERROR_APPLICATION: - code = code_; - break; - default: - UNREACHABLE(); - } -} - -QuicError::QuicError(ngtcp2_connection_close_error_code ccec) : - family(QUIC_ERROR_SESSION), - code(ccec.error_code) { - switch (ccec.type) { - case NGTCP2_CONNECTION_CLOSE_ERROR_CODE_TYPE_APPLICATION: - family = QUIC_ERROR_APPLICATION; - break; - case NGTCP2_CONNECTION_CLOSE_ERROR_CODE_TYPE_TRANSPORT: - if (code & NGTCP2_CRYPTO_ERROR) - family = QUIC_ERROR_CRYPTO; - break; - default: - UNREACHABLE(); - } -} - -QuicError::QuicError( - Environment* env, - v8::Local<v8::Value> codeArg, - v8::Local<v8::Value> familyArg, - int32_t family_) : - family(family_), - code(NGTCP2_NO_ERROR) { - if (codeArg->IsBigInt()) { - code = codeArg.As<v8::BigInt>()->Int64Value(); - } else if (codeArg->IsNumber()) { - double num = 0; - CHECK(codeArg->NumberValue(env->context()).To(&num)); - code = static_cast<uint64_t>(num); - } - if (familyArg->IsNumber()) { - CHECK(familyArg->Int32Value(env->context()).To(&family)); - } -} - -const char* QuicError::family_name() { - switch (family) { - case QUIC_ERROR_SESSION: - return "Session"; - case QUIC_ERROR_APPLICATION: - return "Application"; - case QUIC_ERROR_CRYPTO: - return "Crypto"; - default: - UNREACHABLE(); - } -} - -const ngtcp2_cid* PreferredAddress::cid() const { - return &paddr_->cid; -} - -const uint8_t* PreferredAddress::stateless_reset_token() const { - return paddr_->stateless_reset_token; -} - -std::string PreferredAddress::ipv6_address() const { - char host[NI_MAXHOST]; - // Return an empty string if unable to convert... - if (uv_inet_ntop(AF_INET6, paddr_->ipv6_addr, host, sizeof(host)) != 0) - return std::string(); - - return std::string(host); -} -std::string PreferredAddress::ipv4_address() const { - char host[NI_MAXHOST]; - // Return an empty string if unable to convert... - if (uv_inet_ntop(AF_INET, paddr_->ipv4_addr, host, sizeof(host)) != 0) - return std::string(); - - return std::string(host); -} - -uint16_t PreferredAddress::ipv6_port() const { - return paddr_->ipv6_port; -} - -uint16_t PreferredAddress::ipv4_port() const { - return paddr_->ipv4_port; -} - -bool PreferredAddress::Use(int family) const { - uv_getaddrinfo_t req; - - if (!ResolvePreferredAddress(family, &req)) - return false; - - dest_->addrlen = req.addrinfo->ai_addrlen; - memcpy(dest_->addr, req.addrinfo->ai_addr, req.addrinfo->ai_addrlen); - uv_freeaddrinfo(req.addrinfo); - return true; -} - -bool PreferredAddress::ResolvePreferredAddress( - int local_address_family, - uv_getaddrinfo_t* req) const { - int af; - const uint8_t* binaddr; - uint16_t port; - switch (local_address_family) { - case AF_INET: - if (paddr_->ipv4_port > 0) { - af = AF_INET; - binaddr = paddr_->ipv4_addr; - port = paddr_->ipv4_port; - break; - } - return false; - case AF_INET6: - if (paddr_->ipv6_port > 0) { - af = AF_INET6; - binaddr = paddr_->ipv6_addr; - port = paddr_->ipv6_port; - break; - } - return false; - default: - UNREACHABLE(); - } - - char host[NI_MAXHOST]; - if (uv_inet_ntop(af, binaddr, host, sizeof(host)) != 0) - return false; - - addrinfo hints{}; - hints.ai_flags = AI_NUMERICHOST | AI_NUMERICSERV; - hints.ai_family = af; - hints.ai_socktype = SOCK_DGRAM; - - // Unfortunately 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( - env_->event_loop(), - req, - nullptr, - host, - std::to_string(port).c_str(), - &hints) == 0 && - req->addrinfo != nullptr; -} - -StatelessResetToken::StatelessResetToken( - uint8_t* token, - const uint8_t* secret, - const QuicCID& cid) { - GenerateResetToken(token, secret, cid); - memcpy(buf_, token, sizeof(buf_)); -} - -StatelessResetToken::StatelessResetToken( - const uint8_t* secret, - const QuicCID& cid) { - GenerateResetToken(buf_, secret, cid); -} - -StatelessResetToken::StatelessResetToken( - const uint8_t* token) { - memcpy(buf_, token, sizeof(buf_)); -} - -std::string StatelessResetToken::ToString() const { - std::vector<char> dest(NGTCP2_STATELESS_RESET_TOKENLEN * 2 + 1); - dest[dest.size() - 1] = '\0'; - size_t written = StringBytes::hex_encode( - reinterpret_cast<const char*>(buf_), - NGTCP2_STATELESS_RESET_TOKENLEN, - dest.data(), - dest.size()); - return std::string(dest.data(), written); -} - -size_t StatelessResetToken::Hash::operator()( - const StatelessResetToken& token) const { - size_t hash = 0; - for (size_t n = 0; n < NGTCP2_STATELESS_RESET_TOKENLEN; n++) - hash ^= std::hash<uint8_t>{}(token.buf_[n]) + 0x9e3779b9 + - (hash << 6) + (hash >> 2); - return hash; -} - -bool StatelessResetToken::operator==(const StatelessResetToken& other) const { - return memcmp(data(), other.data(), NGTCP2_STATELESS_RESET_TOKENLEN) == 0; -} - -bool StatelessResetToken::operator!=(const StatelessResetToken& other) const { - return !(*this == other); -} - -template <typename T> -StatsBase<T>::StatsBase( - Environment* env, - v8::Local<v8::Object> wrap, - int options) { - static constexpr uint64_t kMax = std::numeric_limits<int64_t>::max(); - - // Create the backing store for the statistics - size_t size = sizeof(Stats); - size_t count = size / sizeof(uint64_t); - stats_store_ = v8::ArrayBuffer::NewBackingStore(env->isolate(), size); - stats_ = new (stats_store_->Data()) Stats; - - DCHECK_NOT_NULL(stats_); - stats_->created_at = uv_hrtime(); - - // The stats buffer is exposed as a BigUint64Array on - // the JavaScript side to allow statistics to be monitored. - v8::Local<v8::ArrayBuffer> stats_buffer = - v8::ArrayBuffer::New(env->isolate(), stats_store_); - v8::Local<v8::BigUint64Array> stats_array = - v8::BigUint64Array::New(stats_buffer, 0, count); - USE(wrap->DefineOwnProperty( - env->context(), - env->stats_string(), - stats_array, - v8::PropertyAttribute::ReadOnly)); - - if (options & HistogramOptions::ACK) { - ack_ = HistogramBase::New(env, 1, kMax); - wrap->DefineOwnProperty( - env->context(), - env->ack_string(), - ack_->object(), - v8::PropertyAttribute::ReadOnly).Check(); - } - - if (options & HistogramOptions::RATE) { - rate_ = HistogramBase::New(env, 1, kMax); - wrap->DefineOwnProperty( - env->context(), - env->rate_string(), - rate_->object(), - v8::PropertyAttribute::ReadOnly).Check(); - } - - if (options & HistogramOptions::SIZE) { - size_ = HistogramBase::New(env, 1, kMax); - wrap->DefineOwnProperty( - env->context(), - env->size_string(), - size_->object(), - v8::PropertyAttribute::ReadOnly).Check(); - } -} - -template <typename T> -void StatsBase<T>::IncrementStat(uint64_t Stats::*member, uint64_t amount) { - static constexpr uint64_t kMax = std::numeric_limits<uint64_t>::max(); - stats_->*member += std::min(amount, kMax - stats_->*member); -} - -template <typename T> -void StatsBase<T>::SetStat(uint64_t Stats::*member, uint64_t value) { - stats_->*member = value; -} - -template <typename T> -void StatsBase<T>::RecordTimestamp(uint64_t Stats::*member) { - stats_->*member = uv_hrtime(); -} - -template <typename T> -uint64_t StatsBase<T>::GetStat(uint64_t Stats::*member) const { - return stats_->*member; -} - -template <typename T> -inline void StatsBase<T>::RecordRate(uint64_t Stats::*member) { - CHECK(rate_); - uint64_t received_at = GetStat(member); - uint64_t now = uv_hrtime(); - if (received_at > 0) - rate_->Record(now - received_at); - SetStat(member, now); -} - -template <typename T> -inline void StatsBase<T>::RecordSize(uint64_t val) { - CHECK(size_); - size_->Record(val); -} - -template <typename T> -inline void StatsBase<T>::RecordAck(uint64_t Stats::*member) { - CHECK(ack_); - uint64_t acked_at = GetStat(member); - uint64_t now = uv_hrtime(); - if (acked_at > 0) - ack_->Record(now - acked_at); - SetStat(member, now); -} - -template <typename T> -void StatsBase<T>::StatsMemoryInfo(MemoryTracker* tracker) const { - tracker->TrackField("stats_store", stats_store_); - tracker->TrackField("rate_histogram", rate_); - tracker->TrackField("size_histogram", size_); - tracker->TrackField("ack_histogram", ack_); -} - -template <typename T> -void StatsBase<T>::DebugStats() { - StatsDebug stats_debug(static_cast<typename T::Base*>(this)); - Debug(static_cast<typename T::Base*>(this), "Destroyed. %s", stats_debug); -} - -template <typename T> -std::string StatsBase<T>::StatsDebug::ToString() const { - std::string out = "Statistics:\n"; - auto add_field = [&out](const char* name, uint64_t val) { - out += " "; - out += std::string(name); - out += ": "; - out += std::to_string(val); - out += "\n"; - }; - add_field("Duration", uv_hrtime() - ptr->GetStat(&Stats::created_at)); - T::ToString(*ptr, add_field); - return out; -} - -template <typename T> -size_t get_length(const T* vec, size_t count) { - CHECK_NOT_NULL(vec); - size_t len = 0; - for (size_t n = 0; n < count; n++) - len += vec[n].len; - return len; -} - -} // namespace quic -} // namespace node - -#endif // SRC_QUIC_NODE_QUIC_UTIL_INL_H_ diff --git a/src/quic/node_quic_util.h b/src/quic/node_quic_util.h deleted file mode 100644 index b3afcb9483d..00000000000 --- a/src/quic/node_quic_util.h +++ /dev/null @@ -1,394 +0,0 @@ -#ifndef SRC_QUIC_NODE_QUIC_UTIL_H_ -#define SRC_QUIC_NODE_QUIC_UTIL_H_ - -#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS - -#include "node.h" -#include "node_sockaddr.h" -#include "uv.h" -#include "v8.h" -#include "histogram.h" -#include "memory_tracker.h" - -#include <ngtcp2/ngtcp2.h> -#include <openssl/ssl.h> - -#include <algorithm> -#include <functional> -#include <limits> -#include <string> -#include <unordered_map> - -namespace node { -namespace quic { - -// k-constants are used internally, all-caps constants -// are exposed to javascript as constants (see node_quic.cc) - -constexpr size_t kMaxSizeT = std::numeric_limits<size_t>::max(); -constexpr size_t kMaxValidateAddressLru = 10; -constexpr size_t kMinInitialQuicPktSize = 1200; -constexpr size_t kScidLen = NGTCP2_MAX_CIDLEN; -constexpr size_t kTokenRandLen = 16; -constexpr size_t kTokenSecretLen = 16; - -constexpr uint64_t DEFAULT_ACTIVE_CONNECTION_ID_LIMIT = 2; -constexpr uint64_t DEFAULT_MAX_CONNECTIONS = - std::min<uint64_t>(kMaxSizeT, kMaxSafeJsInteger); -constexpr uint64_t DEFAULT_MAX_CONNECTIONS_PER_HOST = 100; -constexpr uint64_t DEFAULT_MAX_STREAM_DATA_BIDI_LOCAL = 256 * 1024; -constexpr uint64_t DEFAULT_MAX_STREAM_DATA_BIDI_REMOTE = 256 * 1024; -constexpr uint64_t DEFAULT_MAX_STREAM_DATA_UNI = 256 * 1024; -constexpr uint64_t DEFAULT_MAX_DATA = 1 * 1024 * 1024; -constexpr uint64_t DEFAULT_MAX_STATELESS_RESETS_PER_HOST = 10; -constexpr uint64_t DEFAULT_MAX_STREAMS_BIDI = 100; -constexpr uint64_t DEFAULT_MAX_STREAMS_UNI = 3; -constexpr uint64_t DEFAULT_MAX_IDLE_TIMEOUT = 10; -constexpr uint64_t DEFAULT_RETRYTOKEN_EXPIRATION = 10; -constexpr uint64_t MIN_RETRYTOKEN_EXPIRATION = 1; -constexpr uint64_t MAX_RETRYTOKEN_EXPIRATION = 60; -constexpr uint64_t NGTCP2_APP_NOERROR = 0xff00; - -constexpr int ERR_FAILED_TO_CREATE_SESSION = -1; - -// The preferred address policy determines how a client QuicSession -// handles a server-advertised preferred address. As suggested, the -// preferred address is the address the server would prefer the -// client to use for subsequent communication for a QuicSession. -// The client may choose to ignore the preference but really shouldn't -// without good reason. We currently only support two options but -// additional options may be added later. -enum SelectPreferredAddressPolicy : int { - // Ignore the server-provided preferred address - QUIC_PREFERRED_ADDRESS_IGNORE, - - // Use the server-provided preferred address. - // With this policy in effect, when a client - // receives a preferred address from the server, - // the client QuicSession will be automatically - // switched to use the selected address if it - // matches the current local address family. - QUIC_PREFERRED_ADDRESS_USE -}; - -// QUIC error codes generally fall into two distinct namespaces: -// Connection Errors and Application Errors. Connection errors -// are further subdivided into Crypto and non-Crypto. Application -// errors are entirely specific to the QUIC application being -// used. An easy rule of thumb is that Application errors are -// semantically associated with the ALPN identifier negotiated -// for the QuicSession. So, if a connection is closed with -// family: QUIC_ERROR_APPLICATION and code: 123, you have to -// look at the ALPN identifier to determine exactly what it -// means. Connection (Session) and Crypto errors, on the other -// hand, share the same meaning regardless of the ALPN. -enum QuicErrorFamily : int32_t { - QUIC_ERROR_SESSION, - QUIC_ERROR_CRYPTO, - QUIC_ERROR_APPLICATION -}; - - -template <typename T> class StatsBase; - -template <typename T, typename Q> -struct StatsTraits { - using Stats = T; - using Base = Q; - - template <typename Fn> - static void ToString(const Q& ptr, Fn&& add_field); -}; - -// StatsBase is a utility help for classes (like QuicSession) -// that record performance statistics. The template takes a -// single Traits argument (see QuicStreamStatsTraits in -// node_quic_stream.h as an example). When the StatsBase -// is deconstructed, collected statistics are output to -// Debug automatically. -template <typename T> -class StatsBase { - public: - typedef typename T::Stats Stats; - - // A StatsBase instance may have one of three histogram - // instances. One that records rate of data flow, one - // that records size of data chunk, and one that records - // rate of data acknowledgement. These may be used in - // slightly different ways of different StatsBase - // instances or may be turned off entirely. - enum HistogramOptions { - NONE = 0, - RATE = 1, - SIZE = 2, - ACK = 4 - }; - - inline StatsBase( - Environment* env, - v8::Local<v8::Object> wrap, - int options = HistogramOptions::NONE); - - inline ~StatsBase() { if (stats_ != nullptr) stats_->~Stats(); } - - // The StatsDebug utility is used when StatsBase is destroyed - // to output statistical information to Debug. It is designed - // to only incur a performance cost constructing the debug - // output when Debug output is enabled. - struct StatsDebug { - typename T::Base* ptr; - explicit StatsDebug(typename T::Base* ptr_) : ptr(ptr_) {} - std::string ToString() const; - }; - - // Increments the given stat field by the given amount or 1 if - // no amount is specified. - inline void IncrementStat(uint64_t Stats::*member, uint64_t amount = 1); - - // Sets an entirely new value for the given stat field - inline void SetStat(uint64_t Stats::*member, uint64_t value); - - // Sets the given stat field to the current uv_hrtime() - inline void RecordTimestamp(uint64_t Stats::*member); - - // Gets the current value of the given stat field - inline uint64_t GetStat(uint64_t Stats::*member) const; - - // If the rate histogram is used, records the time elapsed - // between now and the timestamp specified by the member - // field. - inline void RecordRate(uint64_t Stats::*member); - - // If the size histogram is used, records the given size. - inline void RecordSize(uint64_t val); - - // If the ack rate histogram is used, records the time - // elapsed between now and the timestamp specified by - // the member field. - inline void RecordAck(uint64_t Stats::*member); - - inline void StatsMemoryInfo(MemoryTracker* tracker) const; - - inline void DebugStats(); - - private: - BaseObjectPtr<HistogramBase> rate_; - BaseObjectPtr<HistogramBase> size_; - BaseObjectPtr<HistogramBase> ack_; - std::shared_ptr<v8::BackingStore> stats_store_; - Stats* stats_ = nullptr; -}; - -// PreferredAddress is a helper class used only when a client QuicSession -// receives an advertised preferred address from a server. The helper provides -// information about the preferred address. The Use() function is used to let -// ngtcp2 know to use the preferred address for the given family. -class PreferredAddress { - public: - PreferredAddress( - Environment* env, - ngtcp2_addr* dest, - const ngtcp2_preferred_addr* paddr) : - env_(env), - dest_(dest), - paddr_(paddr) {} - - // When a preferred address is advertised by a server, the - // advertisement also includes a new CID and (optionally) - // a stateless reset token. If the preferred address is - // selected, then the client QuicSession will make use of - // these new values. Access to the cid and reset token - // are provided via the PreferredAddress class only as a - // convenience. - inline const ngtcp2_cid* cid() const; - - // The stateless reset token associated with the preferred - // address CID - inline const uint8_t* stateless_reset_token() const; - - // A preferred address advertisement may include both an - // IPv4 and IPv6 address. Only one of which will be used. - - inline std::string ipv4_address() const; - - inline uint16_t ipv4_port() const; - - inline std::string ipv6_address() const; - - inline uint16_t ipv6_port() const; - - // Instructs the QuicSession to use the advertised - // preferred address matching the given family. If - // the advertisement does not include a matching - // address, the preferred address is ignored. - inline bool Use(int family = AF_INET) const; - - private: - inline bool ResolvePreferredAddress( - int local_address_family, - uv_getaddrinfo_t* req) const; - - Environment* env_; - mutable ngtcp2_addr* dest_; - const ngtcp2_preferred_addr* paddr_; -}; - -// QuicError is a helper class used to encapsulate basic -// details about a QUIC protocol error. There are three -// basic types of errors (see QuicErrorFamily) -struct QuicError { - int32_t family; - uint64_t code; - inline QuicError( - int32_t family_ = QUIC_ERROR_SESSION, - int code_ = NGTCP2_NO_ERROR); - inline QuicError( - int32_t family_ = QUIC_ERROR_SESSION, - uint64_t code_ = NGTCP2_NO_ERROR); - explicit inline QuicError(ngtcp2_connection_close_error_code code); - inline QuicError( - Environment* env, - v8::Local<v8::Value> codeArg, - v8::Local<v8::Value> familyArg = v8::Local<v8::Object>(), - int32_t family_ = QUIC_ERROR_SESSION); - inline const char* family_name(); -}; - -// Helper function that returns the maximum QUIC packet size for -// the given socket address. -inline size_t GetMaxPktLen(const SocketAddress& addr); - -// QuicPath is a utility class that wraps ngtcp2_path to adapt -// it to work with SocketAddress -struct QuicPath : public ngtcp2_path { - inline QuicPath(const SocketAddress& local, const SocketAddress& remote); -}; - -struct QuicPathStorage : public ngtcp2_path_storage { - QuicPathStorage() { - ngtcp2_path_storage_zero(this); - } -}; - -// Simple wrapper for ngtcp2_cid that handles hex encoding -// CIDs are used to identify QuicSession instances and may -// be between 0 and 20 bytes in length. -class QuicCID : public MemoryRetainer { - public: - // Empty constructor - QuicCID() : ptr_(&cid_) {} - - // Copy constructor - QuicCID(const QuicCID& cid) : QuicCID(cid->data, cid->datalen) {} - - // Copy constructor - explicit QuicCID(const ngtcp2_cid& cid) : QuicCID(cid.data, cid.datalen) {} - - // Wrap constructor - explicit QuicCID(const ngtcp2_cid* cid) : ptr_(cid) {} - - QuicCID(const uint8_t* cid, size_t len) : QuicCID() { - ngtcp2_cid* ptr = this->cid(); - ngtcp2_cid_init(ptr, cid, len); - ptr_ = ptr; - } - - struct Hash { - inline size_t operator()(const QuicCID& cid) const; - }; - - inline bool operator==(const QuicCID& other) const; - inline bool operator!=(const QuicCID& other) const; - inline QuicCID& operator=(const QuicCID& cid); - const ngtcp2_cid& operator*() const { return *ptr_; } - const ngtcp2_cid* operator->() const { return ptr_; } - - inline std::string ToString() const; - - const ngtcp2_cid* cid() const { return ptr_; } - - const uint8_t* data() const { return ptr_->data; } - - operator bool() const { return ptr_->datalen > 0; } - - size_t length() const { return ptr_->datalen; } - - ngtcp2_cid* cid() { - CHECK_EQ(ptr_, &cid_); - return &cid_; - } - - unsigned char* data() { - return reinterpret_cast<unsigned char*>(cid()->data); - } - - void set_length(size_t length) { - cid()->datalen = length; - } - - SET_NO_MEMORY_INFO() - SET_MEMORY_INFO_NAME(QuicCID) - SET_SELF_SIZE(QuicCID) - - template <typename T> - using Map = std::unordered_map<QuicCID, T, QuicCID::Hash>; - - private: - ngtcp2_cid cid_{}; - const ngtcp2_cid* ptr_; -}; - -// A Stateless Reset Token is a mechanism by which a QUIC -// endpoint can discreetly signal to a peer that it has -// lost all state associated with a connection. This -// helper class is used to both store received tokens and -// provide storage when creating new tokens to send. -class StatelessResetToken : public MemoryRetainer { - public: - inline StatelessResetToken( - uint8_t* token, - const uint8_t* secret, - const QuicCID& cid); - - inline StatelessResetToken( - const uint8_t* secret, - const QuicCID& cid); - - explicit inline StatelessResetToken( - const uint8_t* token); - - inline std::string ToString() const; - - const uint8_t* data() const { return buf_; } - - struct Hash { - inline size_t operator()(const StatelessResetToken& token) const; - }; - - inline bool operator==(const StatelessResetToken& other) const; - inline bool operator!=(const StatelessResetToken& other) const; - - SET_NO_MEMORY_INFO() - SET_MEMORY_INFO_NAME(StatelessResetToken) - SET_SELF_SIZE(StatelessResetToken) - - template <typename T> - using Map = - std::unordered_map< - StatelessResetToken, - BaseObjectPtr<T>, - StatelessResetToken::Hash>; - - private: - uint8_t buf_[NGTCP2_STATELESS_RESET_TOKENLEN]{}; -}; - -template <typename T> -inline size_t get_length(const T*, size_t len); - -} // namespace quic -} // namespace node - -#endif // NOE_WANT_INTERNALS - -#endif // SRC_QUIC_NODE_QUIC_UTIL_H_ |