| // Copyright (c) 2020 The Chromium Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| #ifndef QUICHE_QUIC_CORE_QUIC_PATH_VALIDATOR_H_ | |
| #define QUICHE_QUIC_CORE_QUIC_PATH_VALIDATOR_H_ | |
| #include <cstddef> | |
| #include <cstdint> | |
| #include <memory> | |
| #include <optional> | |
| #include <ostream> | |
| #include "absl/container/inlined_vector.h" | |
| #include "quiche/quic/core/crypto/quic_random.h" | |
| #include "quiche/quic/core/quic_alarm.h" | |
| #include "quiche/quic/core/quic_alarm_factory.h" | |
| #include "quiche/quic/core/quic_arena_scoped_ptr.h" | |
| #include "quiche/quic/core/quic_clock.h" | |
| #include "quiche/quic/core/quic_connection_context.h" | |
| #include "quiche/quic/core/quic_one_block_arena.h" | |
| #include "quiche/quic/core/quic_packet_writer.h" | |
| #include "quiche/quic/core/quic_time.h" | |
| #include "quiche/quic/core/quic_types.h" | |
| #include "quiche/quic/platform/api/quic_socket_address.h" | |
| #include "quiche/common/platform/api/quiche_export.h" | |
| namespace quic { | |
| namespace test { | |
| class QuicPathValidatorPeer; | |
| } | |
| class QuicConnection; | |
| enum class PathValidationReason { | |
| kReasonUnknown, | |
| kMultiPort, | |
| kReversePathValidation, | |
| kServerPreferredAddressMigration, | |
| kPortMigration, | |
| kConnectionMigration, | |
| kNumReasons, | |
| }; | |
| struct QUICHE_EXPORT PathValidationFailure { | |
| enum class Reason : uint8_t { | |
| // Do not use this. Instead, create a new enum value specific to the new | |
| // failure scenario. | |
| kUnknown = 0, | |
| // The peer sent a stateless reset on the path. | |
| kStatelessReset = 1, | |
| // Do not use this, except for scenarios where the embedder triggers a new | |
| // validation without having first canceled the previous one with a | |
| // kNewerValidation* reason. | |
| kNewerValidation = 2, | |
| // The validation process hit the retry limit. | |
| kRetryTimeout = 3, | |
| // PATH_CHALLENGE can't be sent because the connection is no longer active. | |
| kNotConnected = 4, | |
| // No unused connection ID was available, so validation could not start. | |
| // No PATH_CHALLENGE was ever sent on this path. | |
| kNoAvailableConnectionId = 5, | |
| // Writing to the path failed. The writer belongs to the caller, so this is | |
| // normally reported by the caller while cancelling its own validation. | |
| kWriterError = 6, | |
| // Canceled by a newer validation. This also encodes what triggered the new | |
| // validation in the value itself. QUICHE embedders are expected to specify | |
| // one of these, by explicitly cancelling the previous validation (via | |
| // CancelPathValidation), prior to starting a newer validation. | |
| kNewerValidationOnNetworkConnected = 7, | |
| kNewerValidationOnNetworkDisconnected = 8, | |
| kNewerValidationOnWriteError = 9, | |
| kNewerValidationOnNetworkMadeDefault = 10, | |
| kNewerValidationOnMigrateBackToDefaultNetwork = 11, | |
| kNewerValidationOnChangeNetworkOnPathDegrading = 12, | |
| kNewerValidationOnChangePortOnPathDegrading = 13, | |
| kNewerValidationOnNewNetworkConnectedPostPathDegrading = 14, | |
| kNewerValidationOnServerPreferredAddressAvailable = 15, | |
| kNewerValidationOnMultiPortPath = 16, | |
| }; | |
| Reason reason; | |
| }; | |
| // Opaque handle for device-wide connection to a particular network. | |
| // For example an association with a particular WiFi network with a particular | |
| // SSID or a connection to particular cellular network. The meaning of this | |
| // handle is target-dependent. On Android QuicNetworkHandles are equivalent to: | |
| // On Lollipop, the framework's concept of NetIDs (e.g. Network.netId), and | |
| // On Marshmallow and newer releases, network handle (e.g. | |
| // Network.getNetworkHandle()). | |
| using QuicNetworkHandle = int64_t; | |
| // An invalid NetworkHandle. | |
| // Used on platforms which do not support network handles or to represent an | |
| // unusable network. | |
| inline constexpr QuicNetworkHandle kInvalidNetworkHandle = -1; | |
| // Interface to provide the information of the path to be validated. | |
| class QUICHE_EXPORT QuicPathValidationContext { | |
| public: | |
| QuicPathValidationContext(const QuicSocketAddress& self_address, | |
| const QuicSocketAddress& peer_address) | |
| : QuicPathValidationContext(self_address, peer_address, | |
| kInvalidNetworkHandle) {} | |
| QuicPathValidationContext(const QuicSocketAddress& self_address, | |
| const QuicSocketAddress& peer_address, | |
| QuicNetworkHandle network) | |
| : QuicPathValidationContext(self_address, peer_address, peer_address, | |
| network) {} | |
| QuicPathValidationContext(const QuicSocketAddress& self_address, | |
| const QuicSocketAddress& peer_address, | |
| const QuicSocketAddress& effective_peer_address, | |
| QuicNetworkHandle network) | |
| : self_address_(self_address), | |
| peer_address_(peer_address), | |
| effective_peer_address_(effective_peer_address), | |
| network_handle_(network) {} | |
| virtual ~QuicPathValidationContext() = default; | |
| virtual QuicPacketWriter* WriterToUse() = 0; | |
| // Returns true if the writer should be owned by the QUIC connection. | |
| // TODO(danzh): make it a pure virtual interface once | |
| // EnvoyQuicPathValidationContext implements it. | |
| virtual bool ShouldConnectionOwnWriter() const { return true; } | |
| QuicNetworkHandle network() const { return network_handle_; } | |
| const QuicSocketAddress& self_address() const { return self_address_; } | |
| const QuicSocketAddress& peer_address() const { return peer_address_; } | |
| const QuicSocketAddress& effective_peer_address() const { | |
| return effective_peer_address_; | |
| } | |
| void set_failure_reason(PathValidationFailure::Reason failure_reason) { | |
| failure_reason_ = failure_reason; | |
| } | |
| std::optional<PathValidationFailure::Reason> failure_reason() const { | |
| return failure_reason_; | |
| } | |
| private: | |
| QUICHE_EXPORT friend std::ostream& operator<<( | |
| std::ostream& os, const QuicPathValidationContext& context); | |
| QuicSocketAddress self_address_; | |
| // The address to send PATH_CHALLENGE. | |
| QuicSocketAddress peer_address_; | |
| // The actual peer address which is different from `peer_address_` if the peer | |
| // is behind a proxy. | |
| QuicSocketAddress effective_peer_address_; | |
| // See comments above for QuicNetworkHandle. | |
| // `kInvalidNetworkHandle`, if the platform doesn't expose handles for | |
| // networks, e.g. iOS and Linux. It shouldn't referred to in such case. | |
| QuicNetworkHandle network_handle_; | |
| // nullopt if pending or successful. | |
| std::optional<PathValidationFailure::Reason> failure_reason_; | |
| }; | |
| // Used to validate a path by sending up to 3 PATH_CHALLENGE frames before | |
| // declaring a path validation failure. | |
| class QUICHE_EXPORT QuicPathValidator { | |
| public: | |
| static const uint16_t kMaxRetryTimes = 2; | |
| // Used to write PATH_CHALLENGE on the path to be validated and to get retry | |
| // timeout. | |
| class QUICHE_EXPORT SendDelegate { | |
| public: | |
| virtual ~SendDelegate() = default; | |
| // Send a PATH_CHALLENGE with |data_buffer| as the frame payload using given | |
| // path information. Return false if the delegate doesn't want to continue | |
| // the validation. | |
| virtual bool SendPathChallenge( | |
| const QuicPathFrameBuffer& data_buffer, | |
| const QuicSocketAddress& self_address, | |
| const QuicSocketAddress& peer_address, | |
| const QuicSocketAddress& effective_peer_address, | |
| QuicPacketWriter* writer) = 0; | |
| // Return the time to retry sending PATH_CHALLENGE again based on given peer | |
| // address and writer. | |
| virtual QuicTime GetRetryTimeout(const QuicSocketAddress& peer_address, | |
| QuicPacketWriter* writer) const = 0; | |
| }; | |
| // Handles the validation result. | |
| // TODO(danzh) consider to simplify this interface and its life time to | |
| // outlive a validation. | |
| class QUICHE_EXPORT ResultDelegate { | |
| public: | |
| virtual ~ResultDelegate() = default; | |
| // Called when a PATH_RESPONSE is received with a matching PATH_CHALLANGE. | |
| // |start_time| is the time when the matching PATH_CHALLANGE was sent. | |
| virtual void OnPathValidationSuccess( | |
| std::unique_ptr<QuicPathValidationContext> context, | |
| QuicTime start_time) = 0; | |
| virtual void OnPathValidationFailure( | |
| std::unique_ptr<QuicPathValidationContext> context) = 0; | |
| }; | |
| QuicPathValidator(QuicAlarmFactory* alarm_factory, QuicConnectionArena* arena, | |
| SendDelegate* delegate, QuicRandom* random, | |
| const QuicClock* clock, QuicConnectionContext* context); | |
| // Send PATH_CHALLENGE and start the retry timer. | |
| void StartPathValidation(std::unique_ptr<QuicPathValidationContext> context, | |
| std::unique_ptr<ResultDelegate> result_delegate, | |
| PathValidationReason reason); | |
| // Called when a PATH_RESPONSE frame has been received. Matches the received | |
| // PATH_RESPONSE payload with the payloads previously sent in PATH_CHALLANGE | |
| // frames and the self address on which it was sent. | |
| void OnPathResponse(const QuicPathFrameBuffer& probing_data, | |
| QuicSocketAddress self_address); | |
| // Cancel the retry timer and reset the path and result delegate. | |
| void CancelPathValidation(PathValidationFailure::Reason failure_reason); | |
| bool HasPendingPathValidation() const; | |
| QuicPathValidationContext* GetContext() const; | |
| // Pass the ownership of path_validation context to the caller and reset the | |
| // validator. | |
| std::unique_ptr<QuicPathValidationContext> ReleaseContext(); | |
| PathValidationReason GetPathValidationReason() const { return reason_; } | |
| // Send another PATH_CHALLENGE on the same path. After retrying | |
| // |kMaxRetryTimes| times, fail the current path validation. | |
| void OnRetryTimeout(); | |
| bool IsValidatingPeerAddress(const QuicSocketAddress& effective_peer_address); | |
| // Called to send packet to |peer_address| if the path validation to this | |
| // address is pending. | |
| void MaybeWritePacketToAddress(const char* buffer, size_t buf_len, | |
| const QuicSocketAddress& peer_address); | |
| private: | |
| friend class test::QuicPathValidatorPeer; | |
| // Return the payload to be used in the next PATH_CHALLENGE frame. | |
| const QuicPathFrameBuffer& GeneratePathChallengePayload(); | |
| void SendPathChallengeAndSetAlarm(); | |
| void ResetPathValidation(); | |
| struct QUICHE_EXPORT ProbingData { | |
| explicit ProbingData(QuicTime send_time) : send_time(send_time) {} | |
| QuicPathFrameBuffer frame_buffer; | |
| QuicTime send_time; | |
| }; | |
| // Has at most 3 entries due to validation timeout. | |
| absl::InlinedVector<ProbingData, 3> probing_data_; | |
| SendDelegate* send_delegate_; | |
| QuicRandom* random_; | |
| const QuicClock* clock_; | |
| std::unique_ptr<QuicPathValidationContext> path_context_; | |
| std::unique_ptr<ResultDelegate> result_delegate_; | |
| QuicArenaScopedPtr<QuicAlarm> retry_timer_; | |
| size_t retry_count_; | |
| PathValidationReason reason_ = PathValidationReason::kReasonUnknown; | |
| }; | |
| } // namespace quic | |
| #endif // QUICHE_QUIC_CORE_QUIC_PATH_VALIDATOR_H_ |