From 6a781b9b473063857b4aaa08dff54265b0e5e65d Mon Sep 17 00:00:00 2001 From: James M Snell Date: Fri, 10 Apr 2020 14:41:39 -0700 Subject: [PATCH 1/5] quic: miscellaneous cleanups --- lib/internal/quic/util.js | 4 +- src/quic/node_quic.cc | 2 +- src/quic/node_quic_session-inl.h | 2 +- src/quic/node_quic_session.cc | 24 ++++----- src/quic/node_quic_session.h | 85 ++++++++++++++++++++++++++--- src/quic/node_quic_socket-inl.h | 5 ++ src/quic/node_quic_socket.h | 45 +++++++++++++--- src/quic/node_quic_stream.h | 19 ++++++- src/quic/node_quic_util-inl.h | 23 +++++--- src/quic/node_quic_util.h | 92 ++++++++++++++++++++++---------- 10 files changed, 234 insertions(+), 67 deletions(-) diff --git a/lib/internal/quic/util.js b/lib/internal/quic/util.js index d6e5dc1069..a394730cc5 100644 --- a/lib/internal/quic/util.js +++ b/lib/internal/quic/util.js @@ -63,7 +63,7 @@ const { NGTCP2_MAX_CIDLEN, NGTCP2_MIN_CIDLEN, QUIC_PREFERRED_ADDRESS_IGNORE, - QUIC_PREFERRED_ADDRESS_ACCEPT, + QUIC_PREFERRED_ADDRESS_USE, QUIC_ERROR_APPLICATION, } } = internalBinding('quic'); @@ -346,7 +346,7 @@ function validateQuicClientSessionOptions(options = {}) { port, preferredAddressPolicy: preferredAddressPolicy === 'accept' ? - QUIC_PREFERRED_ADDRESS_ACCEPT : + QUIC_PREFERRED_ADDRESS_USE : QUIC_PREFERRED_ADDRESS_IGNORE, remoteTransportParams, requestOCSP, diff --git a/src/quic/node_quic.cc b/src/quic/node_quic.cc index 4a639c40fc..e809fe78f4 100644 --- a/src/quic/node_quic.cc +++ b/src/quic/node_quic.cc @@ -191,7 +191,7 @@ void Initialize(Local target, V(QUIC_ERROR_APPLICATION) \ V(QUIC_ERROR_CRYPTO) \ V(QUIC_ERROR_SESSION) \ - V(QUIC_PREFERRED_ADDRESS_ACCEPT) \ + V(QUIC_PREFERRED_ADDRESS_USE) \ V(QUIC_PREFERRED_ADDRESS_IGNORE) \ V(QUICCLIENTSESSION_OPTION_REQUEST_OCSP) \ V(QUICCLIENTSESSION_OPTION_VERIFY_HOSTNAME_IDENTITY) \ diff --git a/src/quic/node_quic_session-inl.h b/src/quic/node_quic_session-inl.h index d7e5acbb76..d1546205f1 100644 --- a/src/quic/node_quic_session-inl.h +++ b/src/quic/node_quic_session-inl.h @@ -502,7 +502,7 @@ void QuicSession::StreamDataBlocked(int64_t stream_id) { // possible strategies that we currently support via user // configuration: use the preferred address or ignore it. void QuicSession::SelectPreferredAddress( - const QuicPreferredAddress& preferred_address) { + const PreferredAddress& preferred_address) { CHECK(!is_server()); preferred_address_strategy_(this, preferred_address); } diff --git a/src/quic/node_quic_session.cc b/src/quic/node_quic_session.cc index ed407f73c3..0421b988a1 100644 --- a/src/quic/node_quic_session.cc +++ b/src/quic/node_quic_session.cc @@ -330,7 +330,7 @@ void QuicSessionListener::OnSessionSilentClose( void QuicSessionListener::OnUsePreferredAddress( int family, - const QuicPreferredAddress& preferred_address) { + const PreferredAddress& preferred_address) { if (previous_listener_ != nullptr) previous_listener_->OnUsePreferredAddress(family, preferred_address); } @@ -677,23 +677,23 @@ void JSQuicSessionListener::OnSessionSilentClose( void JSQuicSessionListener::OnUsePreferredAddress( int family, - const QuicPreferredAddress& preferred_address) { + const PreferredAddress& preferred_address) { Environment* env = session()->env(); HandleScope scope(env->isolate()); Local context = env->context(); Context::Scope context_scope(context); std::string hostname = family == AF_INET ? - preferred_address.preferred_ipv4_address(): - preferred_address.preferred_ipv6_address(); - int16_t port = + preferred_address.ipv4_address(): + preferred_address.ipv6_address(); + uint16_t port = family == AF_INET ? - preferred_address.preferred_ipv4_port() : - preferred_address.preferred_ipv6_port(); + preferred_address.ipv4_port() : + preferred_address.ipv6_port(); Local argv[] = { OneByteString(env->isolate(), hostname.c_str()), - Integer::New(env->isolate(), port), + Integer::NewFromUnsigned(env->isolate(), port), Integer::New(env->isolate(), family) }; @@ -2203,13 +2203,13 @@ bool QuicSession::SendConnectionClose() { void QuicSession::IgnorePreferredAddressStrategy( QuicSession* session, - const QuicPreferredAddress& preferred_address) { + const PreferredAddress& preferred_address) { Debug(session, "Ignoring server preferred address"); } void QuicSession::UsePreferredAddressStrategy( QuicSession* session, - const QuicPreferredAddress& preferred_address) { + const PreferredAddress& preferred_address) { static constexpr int idx = IDX_QUIC_SESSION_STATE_USE_PREFERRED_ADDRESS_ENABLED; int family = session->socket()->local_address().family(); @@ -3203,7 +3203,7 @@ int QuicSession::OnSelectPreferredAddress( // even in such a failure, we debug log and ignore it. // If the preferred address is not selected, dest remains // unchanged. - QuicPreferredAddress preferred_address(session->env(), dest, paddr); + PreferredAddress preferred_address(session->env(), dest, paddr); session->SelectPreferredAddress(preferred_address); return 0; } @@ -3634,7 +3634,7 @@ void NewQuicClientSession(const FunctionCallbackInfo& args) { CHECK(args[ARG_IDX::PREFERRED_ADDRESS_POLICY]->Int32Value( env->context()).To(&preferred_address_policy)); switch (preferred_address_policy) { - case QUIC_PREFERRED_ADDRESS_ACCEPT: + case QUIC_PREFERRED_ADDRESS_USE: preferred_address_strategy = QuicSession::UsePreferredAddressStrategy; break; default: diff --git a/src/quic/node_quic_session.h b/src/quic/node_quic_session.h index f1b6bcec73..64f23c874f 100644 --- a/src/quic/node_quic_session.h +++ b/src/quic/node_quic_session.h @@ -53,7 +53,7 @@ typedef void(*ConnectionIDStrategy)( typedef void(*PreferredAddressStrategy)( QuicSession* session, - const QuicPreferredAddress& preferred_address); + const PreferredAddress& preferred_address); // The QuicSessionConfig class holds the initial transport parameters and // configuration options set by the JavaScript side when either a @@ -284,7 +284,7 @@ class QuicSessionListener { const sockaddr* remote); virtual void OnUsePreferredAddress( int family, - const QuicPreferredAddress& preferred_address); + const PreferredAddress& preferred_address); virtual void OnSessionTicket(int size, SSL_SESSION* session); virtual void OnSessionSilentClose( bool stateless_reset, @@ -335,7 +335,7 @@ class JSQuicSessionListener : public QuicSessionListener { void OnSessionSilentClose(bool stateless_reset, QuicError error) override; void OnUsePreferredAddress( int family, - const QuicPreferredAddress& preferred_address) override; + const PreferredAddress& preferred_address) override; void OnStreamBlocked(int64_t stream_id) override; void OnVersionNegotiation( uint32_t supported_version, @@ -684,11 +684,11 @@ class QuicSession : public AsyncWrap, // The default preferred address strategy is to ignore it static void IgnorePreferredAddressStrategy( QuicSession* session, - const QuicPreferredAddress& preferred_address); + const PreferredAddress& preferred_address); static void UsePreferredAddressStrategy( QuicSession* session, - const QuicPreferredAddress& preferred_address); + const PreferredAddress& preferred_address); static void Initialize( Environment* env, @@ -784,6 +784,7 @@ class QuicSession : public AsyncWrap, ~QuicSession() override; std::string diagnostic_name() const override; + inline QuicCID dcid() const; // When a client QuicSession is created, if the autoStart @@ -799,7 +800,9 @@ class QuicSession : public AsyncWrap, QuicSessionListener* listener() const { return listener_; } BaseObjectPtr CreateStream(int64_t id); + BaseObjectPtr FindStream(int64_t id) const; + inline bool HasStream(int64_t id) const; inline bool allow_early_data() const; @@ -866,6 +869,7 @@ class QuicSession : public AsyncWrap, ngtcp2_conn* connection() const { return connection_.get(); } void AddStream(BaseObjectPtr stream); + void AddToSocket(QuicSocket* socket); // Immediately discards the state of the QuicSession @@ -899,6 +903,7 @@ class QuicSession : public AsyncWrap, inline void OnIdleTimeout(); bool OpenBidirectionalStream(int64_t* stream_id); + bool OpenUnidirectionalStream(int64_t* stream_id); // Ping causes the QuicSession to serialize any currently @@ -926,6 +931,7 @@ class QuicSession : public AsyncWrap, uint64_t offset); void RemoveStream(int64_t stream_id); + void RemoveFromSocket(); // Causes pending ngtcp2 frames to be serialized and sent @@ -936,6 +942,7 @@ class QuicSession : public AsyncWrap, 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( @@ -943,11 +950,15 @@ class QuicSession : public AsyncWrap, 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_; } @@ -1018,6 +1029,7 @@ class QuicSession : public AsyncWrap, void HandleError(); bool SendConnectionClose(); + bool IsResetToken( const QuicCID& cid, const uint8_t* data, @@ -1025,7 +1037,9 @@ class QuicSession : public AsyncWrap, // Implementation for mem::NgLibMemoryManager inline void CheckAllocatedSize(size_t previous_size) const; + inline void IncreaseAllocatedSize(size_t size); + inline void DecreaseAllocatedSize(size_t size); // Immediately close the QuicSession. All currently open @@ -1062,21 +1076,24 @@ class QuicSession : public AsyncWrap, void SilentClose(); void PushListener(QuicSessionListener* listener); + void RemoveListener(QuicSessionListener* listener); 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 QuicPreferredAddress& preferred_address); + const PreferredAddress& preferred_address); // Report that the stream data is flow control blocked inline void StreamDataBlocked(int64_t stream_id); @@ -1169,40 +1186,67 @@ class QuicSession : public AsyncWrap, size_t datalen); inline void AssociateCID(const QuicCID& cid); + inline void DisassociateCID(const QuicCID& cid); + inline void ExtendMaxStreamData(int64_t stream_id, uint64_t max_data); + void ExtendMaxStreams(bool bidi, uint64_t max_streams); + inline void ExtendMaxStreamsUni(uint64_t max_streams); + inline void ExtendMaxStreamsBidi(uint64_t max_streams); + inline void ExtendMaxStreamsRemoteUni(uint64_t max_streams); + inline void ExtendMaxStreamsRemoteBidi(uint64_t max_streams); + bool GetNewConnectionID(ngtcp2_cid* cid, uint8_t* token, size_t cidlen); + inline void GetConnectionCloseInfo(); + inline void HandshakeCompleted(); + inline void HandshakeConfirmed(); + void PathValidation( const ngtcp2_path* path, ngtcp2_path_validation_result res); + bool ReceiveClientInitial(const QuicCID& dcid); + bool ReceivePacket(ngtcp2_path* path, const uint8_t* data, ssize_t nread); + bool ReceiveRetry(); + inline void RemoveConnectionID(const QuicCID& cid); + void ScheduleRetransmit(); + bool SendPacket(std::unique_ptr packet); + inline void set_local_address(const ngtcp2_addr* addr); + void StreamClose(int64_t stream_id, uint64_t app_error_code); + void StreamOpen(int64_t stream_id); + void StreamReset( int64_t stream_id, uint64_t final_size, uint64_t app_error_code); + bool WritePackets(const char* diagnostic_label = nullptr); + void UpdateRecoveryStats(); + void UpdateConnectionID( int type, const QuicCID& cid, const StatelessResetToken& token); + void UpdateDataStats(); + inline void UpdateEndpoint(const ngtcp2_path& path); inline void VersionNegotiation(const uint32_t* sv, size_t nsv); @@ -1211,10 +1255,12 @@ class QuicSession : public AsyncWrap, static int OnClientInitial( ngtcp2_conn* conn, void* user_data); + static int OnReceiveClientInitial( ngtcp2_conn* conn, const ngtcp2_cid* dcid, void* user_data); + static int OnReceiveCryptoData( ngtcp2_conn* conn, ngtcp2_crypto_level crypto_level, @@ -1222,12 +1268,15 @@ class QuicSession : public AsyncWrap, 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, int64_t stream_id, @@ -1237,17 +1286,20 @@ class QuicSession : public AsyncWrap, size_t datalen, void* user_data, void* stream_user_data); + static int OnReceiveRetry( ngtcp2_conn* conn, const ngtcp2_pkt_hd* hd, const ngtcp2_pkt_retry* retry, void* user_data); + static int OnAckedCryptoOffset( ngtcp2_conn* conn, ngtcp2_crypto_level crypto_level, uint64_t offset, size_t datalen, void* user_data); + static int OnAckedStreamDataOffset( ngtcp2_conn* conn, int64_t stream_id, @@ -1255,21 +1307,25 @@ class QuicSession : public AsyncWrap, size_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, @@ -1277,59 +1333,71 @@ class QuicSession : public AsyncWrap, uint64_t app_error_code, void* user_data, void* stream_user_data); + static int OnRand( ngtcp2_conn* conn, uint8_t* dest, size_t destlen, ngtcp2_rand_ctx ctx, void* user_data); + 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, @@ -1337,12 +1405,17 @@ class QuicSession : public AsyncWrap, const ngtcp2_cid* cid, const uint8_t* token, void* user_data); + static void OnQlogWrite(void* user_data, const void* data, size_t len); void UpdateIdleTimer(); + inline void UpdateRetransmitTimer(uint64_t timeout); + inline void StopRetransmitTimer(); + inline void StopIdleTimer(); + bool StartClosingPeriod(); enum QuicSessionFlags : uint32_t { diff --git a/src/quic/node_quic_socket-inl.h b/src/quic/node_quic_socket-inl.h index 64ffeebc48..3e9adab6f2 100644 --- a/src/quic/node_quic_socket-inl.h +++ b/src/quic/node_quic_socket-inl.h @@ -78,6 +78,11 @@ void QuicSocket::AssociateStatelessResetToken( token_map_[token] = session; } +const SocketAddress& QuicSocket::local_address() { + CHECK(preferred_endpoint_); + return preferred_endpoint_->local_address(); +} + void QuicSocket::DisassociateStatelessResetToken( const StatelessResetToken& token) { Debug(this, "Removing stateless reset token %s", token); diff --git a/src/quic/node_quic_socket.h b/src/quic/node_quic_socket.h index 03da304555..01f6e27656 100644 --- a/src/quic/node_quic_socket.h +++ b/src/quic/node_quic_socket.h @@ -283,50 +283,62 @@ class QuicSocket : public AsyncWrap, 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. - const SocketAddress& local_address() { - CHECK(preferred_endpoint_); - return preferred_endpoint_->local_address(); - } + inline const SocketAddress& local_address(); void MaybeClose(); inline void AddSession( const QuicCID& cid, BaseObjectPtr session); + inline void AssociateCID( const QuicCID& cid, const QuicCID& scid); + inline void DisassociateCID( const QuicCID& cid); + inline void AssociateStatelessResetToken( const StatelessResetToken& token, BaseObjectPtr session); + inline void DisassociateStatelessResetToken( const StatelessResetToken& token); + void Listen( BaseObjectPtr context, const sockaddr* preferred_address = nullptr, const std::string& alpn = NGTCP2_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 packet, BaseObjectPtr session = BaseObjectPtr()); + inline void SessionReady(BaseObjectPtr session); + inline void set_server_busy(bool on); + inline void set_diagnostic_packet_loss(double rx = 0.0, double tx = 0.0); + inline void StopListening(); // Toggles whether or not stateless reset is enabled or not. @@ -346,24 +358,34 @@ class QuicSocket : public AsyncWrap, // 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_; - } + const uint8_t* session_reset_secret() { return reset_token_secret_; } // Implementation for QuicListener ReqWrap* OnCreateSendWrap(size_t msg_size) override; + + // Implementation for QuicListener void OnSendDone(ReqWrap* 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. @@ -390,6 +412,7 @@ class QuicSocket : public AsyncWrap, const SocketAddress& remote_addr); void PushListener(QuicSocketListener* listener); + void RemoveListener(QuicSocketListener* listener); inline void AddEndpoint( @@ -412,6 +435,7 @@ class QuicSocket : public AsyncWrap, 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( @@ -436,9 +460,13 @@ class QuicSocket : public AsyncWrap, BaseObjectPtr 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 @@ -556,8 +584,11 @@ class QuicSocket : public AsyncWrap, void set_packet(std::unique_ptr packet) { packet_ = std::move(packet); } + QuicPacket* packet() { return packet_.get(); } + void set_session(BaseObjectPtr session) { session_ = session; } + size_t total_length() const { return total_length_; } QuicState* quic_state() { return quic_state_.get(); } diff --git a/src/quic/node_quic_stream.h b/src/quic/node_quic_stream.h index 6531b0589c..b159475f50 100644 --- a/src/quic/node_quic_stream.h +++ b/src/quic/node_quic_stream.h @@ -3,7 +3,7 @@ #if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS -#include "memory_tracker-inl.h" +#include "memory_tracker.h" #include "async_wrap.h" #include "env.h" #include "node_http_common.h" @@ -211,7 +211,12 @@ class QuicStream : public AsyncWrap, 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(); } @@ -321,9 +326,17 @@ class QuicStream : public AsyncWrap, // 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; } @@ -345,6 +358,7 @@ class QuicStream : public AsyncWrap, private: inline bool is_flag_set(int32_t flag) const; + inline void set_flag(int32_t flag, bool on = true); // WasEverWritable returns true if it is a bidirectional stream, @@ -375,10 +389,13 @@ class QuicStream : public AsyncWrap, ListNode stream_queue_; BaseObjectPtr quic_state_; + public: // Linked List of QuicStream objects using Queue = ListHead; + inline void Schedule(Queue* queue); + inline void Unschedule(); }; diff --git a/src/quic/node_quic_util-inl.h b/src/quic/node_quic_util-inl.h index 71b5ceab06..1d4558ae82 100644 --- a/src/quic/node_quic_util-inl.h +++ b/src/quic/node_quic_util-inl.h @@ -42,6 +42,12 @@ size_t QuicCID::Hash::operator()(const QuicCID& token) const { 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; } @@ -183,15 +189,15 @@ const char* QuicError::family_name() { } } -const ngtcp2_cid* QuicPreferredAddress::cid() const { +const ngtcp2_cid* PreferredAddress::cid() const { return &paddr_->cid; } -const uint8_t* QuicPreferredAddress::stateless_reset_token() const { +const uint8_t* PreferredAddress::stateless_reset_token() const { return paddr_->stateless_reset_token; } -std::string QuicPreferredAddress::preferred_ipv6_address() const { +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) @@ -199,7 +205,7 @@ std::string QuicPreferredAddress::preferred_ipv6_address() const { return std::string(host); } -std::string QuicPreferredAddress::preferred_ipv4_address() const { +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) @@ -208,14 +214,15 @@ std::string QuicPreferredAddress::preferred_ipv4_address() const { return std::string(host); } -int16_t QuicPreferredAddress::preferred_ipv6_port() const { +uint16_t PreferredAddress::ipv6_port() const { return paddr_->ipv6_port; } -int16_t QuicPreferredAddress::preferred_ipv4_port() const { + +uint16_t PreferredAddress::ipv4_port() const { return paddr_->ipv4_port; } -bool QuicPreferredAddress::Use(int family) const { +bool PreferredAddress::Use(int family) const { uv_getaddrinfo_t req; if (!ResolvePreferredAddress(family, &req)) @@ -227,7 +234,7 @@ bool QuicPreferredAddress::Use(int family) const { return true; } -bool QuicPreferredAddress::ResolvePreferredAddress( +bool PreferredAddress::ResolvePreferredAddress( int local_address_family, uv_getaddrinfo_t* req) const { int af; diff --git a/src/quic/node_quic_util.h b/src/quic/node_quic_util.h index 54a5696605..45358cc017 100644 --- a/src/quic/node_quic_util.h +++ b/src/quic/node_quic_util.h @@ -32,13 +32,10 @@ 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(kMaxSizeT, kMaxSafeJsInteger); constexpr uint64_t DEFAULT_MAX_CONNECTIONS_PER_HOST = 100; -constexpr uint64_t NGTCP2_APP_NOERROR = 0xff00; -constexpr uint64_t MIN_RETRYTOKEN_EXPIRATION = 1; -constexpr uint64_t MAX_RETRYTOKEN_EXPIRATION = 60; -constexpr uint64_t DEFAULT_ACTIVE_CONNECTION_ID_LIMIT = 2; 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; @@ -47,7 +44,10 @@ 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 = 10ULL; +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; @@ -55,14 +55,20 @@ constexpr int ERR_FAILED_TO_CREATE_SESSION = -1; // 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. -// We currently only support two options in the Node.js implementation, -// but additional options may be added later. +// 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, - // Accept the server-provided preferred address - QUIC_PREFERRED_ADDRESS_ACCEPT + + // 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: @@ -82,6 +88,7 @@ enum QuicErrorFamily : int32_t { QUIC_ERROR_APPLICATION }; + template class StatsBase; template @@ -170,14 +177,13 @@ class StatsBase { AliasedBigUint64Array stats_buffer_; }; -// QuicPreferredAddress 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 +// 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 QuicPreferredAddress { +class PreferredAddress { public: - QuicPreferredAddress( + PreferredAddress( Environment* env, ngtcp2_addr* dest, const ngtcp2_preferred_addr* paddr) : @@ -185,13 +191,34 @@ class QuicPreferredAddress { 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; - inline std::string preferred_ipv6_address() const; - inline std::string preferred_ipv4_address() const; - inline int16_t preferred_ipv6_port() const; - inline int16_t preferred_ipv4_port() 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: @@ -242,6 +269,8 @@ struct QuicPathStorage : public ngtcp2_path_storage { }; // 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 @@ -268,21 +297,18 @@ class QuicCID : public MemoryRetainer { 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; - // Copy assignment - QuicCID& operator=(const QuicCID& cid) { - if (this == &cid) return *this; - this->~QuicCID(); - return *new(this) QuicCID(std::move(cid)); - } - - const ngtcp2_cid& operator*() const { return *ptr_; } - const ngtcp2_cid* operator->() const { return ptr_; } 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() { @@ -342,20 +368,28 @@ class Timer final : public MemoryRetainer { using TimerPointer = DeleteFnPtr; +// 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 StatelessResetToken( const uint8_t* token) : token_(token) {} inline std::string ToString() const; + const uint8_t* data() const { return token_; } struct Hash { From a25093432598e7b5d9abe4561e53cc46217114e4 Mon Sep 17 00:00:00 2001 From: James M Snell Date: Sun, 12 Apr 2020 11:00:04 -0700 Subject: [PATCH 2/5] quic: remove unused operator overload --- src/quic/node_quic_util-inl.h | 6 ------ src/quic/node_quic_util.h | 1 - 2 files changed, 7 deletions(-) diff --git a/src/quic/node_quic_util-inl.h b/src/quic/node_quic_util-inl.h index 1d4558ae82..5753e833d7 100644 --- a/src/quic/node_quic_util-inl.h +++ b/src/quic/node_quic_util-inl.h @@ -42,12 +42,6 @@ size_t QuicCID::Hash::operator()(const QuicCID& token) const { 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; } diff --git a/src/quic/node_quic_util.h b/src/quic/node_quic_util.h index 45358cc017..e4c3b021fe 100644 --- a/src/quic/node_quic_util.h +++ b/src/quic/node_quic_util.h @@ -297,7 +297,6 @@ class QuicCID : public MemoryRetainer { 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_; } From 3eeb88f323f8b8ab5a8e2a9fb1a95eb968e32e78 Mon Sep 17 00:00:00 2001 From: James M Snell Date: Fri, 10 Apr 2020 16:41:16 -0700 Subject: [PATCH 3/5] quic: use backing store for StatsBase --- src/aliased_buffer.h | 33 +++++---------------- src/quic/node_quic_util-inl.h | 56 +++++++++++++++++++---------------- src/quic/node_quic_util.h | 20 +++++++------ 3 files changed, 50 insertions(+), 59 deletions(-) diff --git a/src/aliased_buffer.h b/src/aliased_buffer.h index f82ec153bd..8c9a6f5473 100644 --- a/src/aliased_buffer.h +++ b/src/aliased_buffer.h @@ -32,31 +32,6 @@ template ::value>> class AliasedBufferBase { public: - /** - * Create an AliasedBufferBase over an existing buffer - */ - AliasedBufferBase( - v8::Isolate* isolate, - const size_t count, - NativeT* buffer) : - isolate_(isolate), - count_(count), - byte_offset_(0), - buffer_(buffer) { - CHECK_GT(count, 0); - const v8::HandleScope handle_scope(isolate_); - const size_t size_in_bytes = - MultiplyWithOverflowCheck(sizeof(NativeT), count); - v8::Local ab = - v8::ArrayBuffer::New( - isolate_, - buffer, - size_in_bytes); - - v8::Local js_array = V8T::New(ab, byte_offset_, count); - js_array_ = v8::Global(isolate, js_array); - } - AliasedBufferBase(v8::Isolate* isolate, const size_t count) : isolate_(isolate), count_(count), byte_offset_(0) { CHECK_GT(count, 0); @@ -199,6 +174,14 @@ class AliasedBufferBase { return buffer_; } + /** + * Get the underlying native buffer as a cast to T. + **/ + template + inline T* GetNativeBuffer() { + return reinterpret_cast(&buffer_); + } + /** * Synonym for GetBuffer() */ diff --git a/src/quic/node_quic_util-inl.h b/src/quic/node_quic_util-inl.h index 5753e833d7..8fd4aceb73 100644 --- a/src/quic/node_quic_util-inl.h +++ b/src/quic/node_quic_util-inl.h @@ -324,23 +324,29 @@ template StatsBase::StatsBase( Environment* env, v8::Local wrap, - int options) - : stats_buffer_( - env->isolate(), - sizeof(typename T::Stats) / sizeof(uint64_t), - reinterpret_cast(&stats_)) { + int options) { static constexpr uint64_t kMax = std::numeric_limits::max(); - stats_.created_at = uv_hrtime(); - // TODO(@jasnell): The follow are checks instead of handling - // the error. Before this code moves out of experimental, - // these should be change to properly handle the error. - - wrap->DefineOwnProperty( + // 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 stats_buffer = + v8::ArrayBuffer::New(env->isolate(), stats_store_); + v8::Local stats_array = + v8::BigUint64Array::New(stats_buffer, 0, count); + USE(wrap->DefineOwnProperty( env->context(), env->stats_string(), - stats_buffer_.GetJSArray(), - v8::PropertyAttribute::ReadOnly).Check(); + stats_array, + v8::PropertyAttribute::ReadOnly)); if (options & HistogramOptions::ACK) { ack_ = HistogramBase::New(env, 1, kMax); @@ -371,28 +377,28 @@ StatsBase::StatsBase( } template -void StatsBase::IncrementStat(uint64_t T::Stats::*member, uint64_t amount) { +void StatsBase::IncrementStat(uint64_t Stats::*member, uint64_t amount) { static constexpr uint64_t kMax = std::numeric_limits::max(); - stats_.*member += std::min(amount, kMax - stats_.*member); + stats_->*member += std::min(amount, kMax - stats_->*member); } template -void StatsBase::SetStat(uint64_t T::Stats::*member, uint64_t value) { - stats_.*member = value; +void StatsBase::SetStat(uint64_t Stats::*member, uint64_t value) { + stats_->*member = value; } template -void StatsBase::RecordTimestamp(uint64_t T::Stats::*member) { - stats_.*member = uv_hrtime(); +void StatsBase::RecordTimestamp(uint64_t Stats::*member) { + stats_->*member = uv_hrtime(); } template -uint64_t StatsBase::GetStat(uint64_t T::Stats::*member) const { - return stats_.*member; +uint64_t StatsBase::GetStat(uint64_t Stats::*member) const { + return stats_->*member; } template -inline void StatsBase::RecordRate(uint64_t T::Stats::*member) { +inline void StatsBase::RecordRate(uint64_t Stats::*member) { CHECK(rate_); uint64_t received_at = GetStat(member); uint64_t now = uv_hrtime(); @@ -408,7 +414,7 @@ inline void StatsBase::RecordSize(uint64_t val) { } template -inline void StatsBase::RecordAck(uint64_t T::Stats::*member) { +inline void StatsBase::RecordAck(uint64_t Stats::*member) { CHECK(ack_); uint64_t acked_at = GetStat(member); uint64_t now = uv_hrtime(); @@ -419,7 +425,7 @@ inline void StatsBase::RecordAck(uint64_t T::Stats::*member) { template void StatsBase::StatsMemoryInfo(MemoryTracker* tracker) const { - tracker->TrackField("stats_buffer", stats_buffer_); + tracker->TrackField("stats_store", stats_store_); tracker->TrackField("rate_histogram", rate_); tracker->TrackField("size_histogram", size_); tracker->TrackField("ack_histogram", ack_); @@ -441,7 +447,7 @@ std::string StatsBase::StatsDebug::ToString() const { out += std::to_string(val); out += "\n"; }; - add_field("Duration", uv_hrtime() - ptr->GetStat(&T::Stats::created_at)); + add_field("Duration", uv_hrtime() - ptr->GetStat(&Stats::created_at)); T::ToString(*ptr, add_field); return out; } diff --git a/src/quic/node_quic_util.h b/src/quic/node_quic_util.h index e4c3b021fe..61829ee883 100644 --- a/src/quic/node_quic_util.h +++ b/src/quic/node_quic_util.h @@ -109,6 +109,8 @@ struct StatsTraits { template 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 @@ -127,7 +129,7 @@ class StatsBase { v8::Local wrap, int options = HistogramOptions::NONE); - inline ~StatsBase() = default; + 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 @@ -141,21 +143,21 @@ class StatsBase { // Increments the given stat field by the given amount or 1 if // no amount is specified. - inline void IncrementStat(uint64_t T::Stats::*member, uint64_t amount = 1); + 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 T::Stats::*member, uint64_t value); + 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 T::Stats::*member); + inline void RecordTimestamp(uint64_t Stats::*member); // Gets the current value of the given stat field - inline uint64_t GetStat(uint64_t T::Stats::*member) const; + 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 T::Stats::*member); + inline void RecordRate(uint64_t Stats::*member); // If the size histogram is used, records the given size. inline void RecordSize(uint64_t val); @@ -163,18 +165,18 @@ class StatsBase { // 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 T::Stats::*member); + inline void RecordAck(uint64_t Stats::*member); inline void StatsMemoryInfo(MemoryTracker* tracker) const; inline void DebugStats(); private: - typename T::Stats stats_{}; BaseObjectPtr rate_; BaseObjectPtr size_; BaseObjectPtr ack_; - AliasedBigUint64Array stats_buffer_; + std::shared_ptr stats_store_; + Stats* stats_ = nullptr; }; // PreferredAddress is a helper class used only when a client QuicSession From 4eaf34ee56694df16ea5e0731b324f84a7c4ca90 Mon Sep 17 00:00:00 2001 From: James M Snell Date: Sun, 12 Apr 2020 12:17:50 -0700 Subject: [PATCH 4/5] fixup! quic: use backing store for StatsBase --- src/quic/node_quic_util.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/quic/node_quic_util.h b/src/quic/node_quic_util.h index 61829ee883..63c2e77d9b 100644 --- a/src/quic/node_quic_util.h +++ b/src/quic/node_quic_util.h @@ -109,7 +109,7 @@ struct StatsTraits { template class StatsBase { public: - typedef typename T::Stats Stats; + typedef typename T::Stats Stats; // A StatsBase instance may have one of three histogram // instances. One that records rate of data flow, one From d2bfbc0e55cd706cf4791767d2d64df22c36db9c Mon Sep 17 00:00:00 2001 From: James M Snell Date: Sat, 18 Apr 2020 10:36:20 -0700 Subject: [PATCH 5/5] quic: move unused error and fix lint --- doc/api/errors.md | 7 ------- lib/internal/errors.js | 1 - lib/net.js | 1 - 3 files changed, 9 deletions(-) diff --git a/doc/api/errors.md b/doc/api/errors.md index 3d4f6c7462..812d6adf66 100644 --- a/doc/api/errors.md +++ b/doc/api/errors.md @@ -1703,13 +1703,6 @@ TBD TBD - -### `ERR_QUIC_UNAVAILABLE` - -> Stabililty: 1 - Experimental - -TBD - ### `ERR_QUICCLIENTSESSION_FAILED` diff --git a/lib/internal/errors.js b/lib/internal/errors.js index 5be38c49b8..f1883e97dc 100644 --- a/lib/internal/errors.js +++ b/lib/internal/errors.js @@ -1335,7 +1335,6 @@ E('ERR_QUIC_ERROR', function(code, family) { return `QUIC session closed with ${familyType} error code ${code}`; }, Error); E('ERR_QUIC_TLS13_REQUIRED', 'QUIC requires TLS version 1.3', Error); -E('ERR_QUIC_UNAVAILABLE', 'QUIC is unavailable', Error); E('ERR_REQUIRE_ESM', (filename, parentPath = null, packageJsonPath = null) => { let msg = `Must use import to load ES Module: ${filename}`; diff --git a/lib/net.js b/lib/net.js index fc7e68a294..edb508cfd5 100644 --- a/lib/net.js +++ b/lib/net.js @@ -92,7 +92,6 @@ const { ERR_INVALID_FD_TYPE, ERR_INVALID_IP_ADDRESS, ERR_INVALID_OPT_VALUE, - ERR_QUIC_UNAVAILABLE, ERR_SERVER_ALREADY_LISTEN, ERR_SERVER_NOT_RUNNING, ERR_SOCKET_CLOSED