Add unused QuicSession interfaces to initiating IETF connection migration: MigratePath(), ValidatePath(). These new interfaces are used by toy client in e2e tests. Add QuicPathValidator into QuicConnection to do IETF path validation. Behavior changes: Cancel path validation if there is any during connection close; Don't handle socket write error if sending PATH_CHALLENGE on the alternative socket or if the package sent to the alternative peer address exceeds path MTU; Propagate PATH_RESPONSE to path validator to complete path validation. Added and modified end-to-end tests to demonstrate connection migration with path validation works on client side. Protected by quic_reloadable_flag_quic_pass_path_response_to_validator. PiperOrigin-RevId: 343304329 Change-Id: I53dc52e82fbb9c69f5d2e90e5c90e1be45bb7bb6
diff --git a/quic/core/http/end_to_end_test.cc b/quic/core/http/end_to_end_test.cc index 6b188fc..df3b1f9 100644 --- a/quic/core/http/end_to_end_test.cc +++ b/quic/core/http/end_to_end_test.cc
@@ -2422,6 +2422,33 @@ SendSynchronousBarRequestAndCheckResponse(); } +TEST_P(EndToEndTest, AsynchronousConnectionMigrationClientIPChanged) { + ASSERT_TRUE(Initialize()); + if (!version_.HasIetfQuicFrames() || + !client_->client()->session()->connection()->use_path_validator()) { + return; + } + client_.reset(CreateQuicClient(nullptr)); + + SendSynchronousFooRequestAndCheckResponse(); + + // Store the client IP address which was used to send the first request. + QuicIpAddress old_host = + client_->client()->network_helper()->GetLatestClientAddress().host(); + + // Migrate socket to the new IP address. + QuicIpAddress new_host = TestLoopback(2); + EXPECT_NE(old_host, new_host); + ASSERT_TRUE(client_->client()->ValidateAndMigrateSocket(new_host)); + + while (client_->client()->HasPendingPathValidation()) { + client_->client()->WaitForEvents(); + } + EXPECT_EQ(new_host, client_->client()->session()->self_address().host()); + // Send a request using the new socket. + SendSynchronousBarRequestAndCheckResponse(); +} + TEST_P(EndToEndTest, ConnectionMigrationClientPortChanged) { // Tests that the client's port can change during an established QUIC // connection, and that doing so does not result in the connection being @@ -4397,8 +4424,6 @@ TEST_P(EndToEndPacketReorderingTest, ReorderedConnectivityProbing) { ASSERT_TRUE(Initialize()); if (version_.HasIetfQuicFrames()) { - // TODO(b/143909619): Reenable this test when supporting IETF connection - // migration. return; } @@ -4452,6 +4477,148 @@ client_connection->GetStats().num_connectivity_probing_received); } +// A writer which holds the next packet to be sent till ReleasePacket() is +// called. +class PacketHoldingWriter : public QuicPacketWriterWrapper { + public: + WriteResult WritePacket(const char* buffer, + size_t buf_len, + const QuicIpAddress& self_address, + const QuicSocketAddress& peer_address, + PerPacketOptions* options) override { + if (!hold_next_packet_) { + return QuicPacketWriterWrapper::WritePacket(buffer, buf_len, self_address, + peer_address, options); + } + QUIC_DLOG(INFO) << "Packet is held by the writer"; + packet_content_ = std::string(buffer, buf_len); + self_address_ = self_address; + peer_address_ = peer_address; + options_ = (options == nullptr ? nullptr : options->Clone()); + hold_next_packet_ = false; + return WriteResult(WRITE_STATUS_OK, buf_len); + } + + void HoldNextPacket() { + DCHECK(packet_content_.empty()) << "There is already one packet on hold."; + hold_next_packet_ = true; + } + + void ReleasePacket() { + QUIC_DLOG(INFO) << "Release packet"; + ASSERT_EQ(WRITE_STATUS_OK, + QuicPacketWriterWrapper::WritePacket( + packet_content_.data(), packet_content_.length(), + self_address_, peer_address_, options_.release()) + .status); + packet_content_.clear(); + } + + private: + bool hold_next_packet_{false}; + std::string packet_content_; + QuicIpAddress self_address_; + QuicSocketAddress peer_address_; + std::unique_ptr<PerPacketOptions> options_; +}; + +TEST_P(EndToEndPacketReorderingTest, ReorderedPathChallenge) { + ASSERT_TRUE(Initialize()); + if (!version_.HasIetfQuicFrames() || + !client_->client()->session()->connection()->use_path_validator()) { + return; + } + client_.reset(EndToEndTest::CreateQuicClient(nullptr)); + + // Finish one request to make sure handshake established. + EXPECT_EQ(kFooResponseBody, client_->SendSynchronousRequest("/foo")); + + // Wait for the connection to become idle, to make sure the packet gets + // delayed is the connectivity probing packet. + client_->WaitForDelayedAcks(); + + QuicSocketAddress old_addr = + client_->client()->network_helper()->GetLatestClientAddress(); + + // Migrate socket to the new IP address. + QuicIpAddress new_host = TestLoopback(2); + EXPECT_NE(old_addr.host(), new_host); + + // Setup writer wrapper to hold the probing packet. + auto holding_writer = new PacketHoldingWriter(); + client_->UseWriter(holding_writer); + // Write a connectivity probing after the next /foo request. + holding_writer->HoldNextPacket(); + + // A packet with PATH_CHALLENGE will be held in the writer. + ASSERT_TRUE(client_->client()->ValidateAndMigrateSocket(new_host)); + + // Send (on-hold) PATH_CHALLENGE after this request. + client_->SendRequest("/foo"); + holding_writer->ReleasePacket(); + + client_->WaitForResponse(); + + EXPECT_EQ(kFooResponseBody, client_->response_body()); + // Send yet another request after the PATH_CHALLENGE, when this request + // returns, the probing is guaranteed to have been received by the server, and + // the server's response to probing is guaranteed to have been received by the + // client. + EXPECT_EQ(kBarResponseBody, client_->SendSynchronousRequest("/bar")); + + server_thread_->Pause(); + QuicConnection* server_connection = GetServerConnection(); + if (server_connection != nullptr) { + EXPECT_EQ(1u, + server_connection->GetStats().num_connectivity_probing_received); + } else { + ADD_FAILURE() << "Missing server connection"; + } + server_thread_->Resume(); +} + +TEST_P(EndToEndPacketReorderingTest, PathValidationFailure) { + ASSERT_TRUE(Initialize()); + if (!version_.HasIetfQuicFrames() || + !client_->client()->session()->connection()->use_path_validator()) { + return; + } + + client_.reset(CreateQuicClient(nullptr)); + // Finish one request to make sure handshake established. + EXPECT_EQ(kFooResponseBody, client_->SendSynchronousRequest("/foo")); + + // Wait for the connection to become idle, to make sure the packet gets + // delayed is the connectivity probing packet. + client_->WaitForDelayedAcks(); + + QuicSocketAddress old_addr = client_->client()->session()->self_address(); + + // Migrate socket to the new IP address. + QuicIpAddress new_host = TestLoopback(2); + EXPECT_NE(old_addr.host(), new_host); + + // Drop PATH_RESPONSE packets to timeout the path validation. + server_writer_->set_fake_packet_loss_percentage(100); + ASSERT_TRUE(client_->client()->ValidateAndMigrateSocket(new_host)); + while (client_->client()->HasPendingPathValidation()) { + client_->client()->WaitForEvents(); + } + server_thread_->Pause(); + QuicConnection* server_connection = GetServerConnection(); + if (server_connection != nullptr) { + EXPECT_EQ(3u, + server_connection->GetStats().num_connectivity_probing_received); + } else { + ADD_FAILURE() << "Missing server connection"; + } + server_thread_->Resume(); + + EXPECT_EQ(old_addr, client_->client()->session()->self_address()); + server_writer_->set_fake_packet_loss_percentage(0); + EXPECT_EQ(kBarResponseBody, client_->SendSynchronousRequest("/bar")); +} + TEST_P(EndToEndPacketReorderingTest, Buffer0RttRequest) { ASSERT_TRUE(Initialize()); // Finish one request to make sure handshake established.
diff --git a/quic/core/quic_connection.cc b/quic/core/quic_connection.cc index 6e6ded6..b6ea5b1 100644 --- a/quic/core/quic_connection.cc +++ b/quic/core/quic_connection.cc
@@ -30,6 +30,7 @@ #include "net/third_party/quiche/src/quic/core/quic_legacy_version_encapsulator.h" #include "net/third_party/quiche/src/quic/core/quic_packet_creator.h" #include "net/third_party/quiche/src/quic/core/quic_packet_writer.h" +#include "net/third_party/quiche/src/quic/core/quic_path_validator.h" #include "net/third_party/quiche/src/quic/core/quic_types.h" #include "net/third_party/quiche/src/quic/core/quic_utils.h" #include "net/third_party/quiche/src/quic/platform/api/quic_bug_tracker.h" @@ -355,7 +356,8 @@ packet_creator_.let_connection_handle_pings()), use_encryption_level_context_( encrypted_control_frames_ && - GetQuicReloadableFlag(quic_use_encryption_level_context)) { + GetQuicReloadableFlag(quic_use_encryption_level_context)), + path_validator_(alarm_factory_, &arena_, this, random_generator_) { QUIC_BUG_IF(!start_peer_migration_earlier_ && send_path_response_); if (GetQuicReloadableFlag(quic_connection_set_initial_self_address)) { DCHECK(perspective_ == Perspective::IS_CLIENT || @@ -1108,7 +1110,7 @@ } } -void QuicConnection::OnSuccessfulMigrationAfterProbing() { +void QuicConnection::OnSuccessfulMigration() { DCHECK_EQ(perspective_, Perspective::IS_CLIENT); if (IsPathDegrading()) { // If path was previously degrading, and migration is successful after @@ -1597,13 +1599,18 @@ debug_visitor_->OnPathResponseFrame(frame); } MaybeUpdateAckTimeout(); - if (!transmitted_connectivity_probe_payload_ || - *transmitted_connectivity_probe_payload_ != frame.data_buffer) { - // Is not for the probe we sent, ignore it. - return true; + if (use_path_validator_) { + path_validator_.OnPathResponse(frame.data_buffer, + last_packet_destination_address_); + } else { + if (!transmitted_connectivity_probe_payload_ || + *transmitted_connectivity_probe_payload_ != frame.data_buffer) { + // Is not for the probe we sent, ignore it. + return true; + } + // Have received the matching PATH RESPONSE, saved payload no longer valid. + transmitted_connectivity_probe_payload_ = nullptr; } - // Have received the matching PATH RESPONSE, saved payload no longer valid. - transmitted_connectivity_probe_payload_ = nullptr; return true; } @@ -2833,7 +2840,7 @@ QUIC_DVLOG(1) << ENDPOINT << "Sending packet " << packet_number << " : " << (IsRetransmittable(*packet) == HAS_RETRANSMITTABLE_DATA ? "data bearing " - : " ack only ") + : " ack or probing only ") << ", encryption level: " << packet->encryption_level << ", encrypted length:" << encrypted_length << ", fate: " << fate; @@ -2850,6 +2857,8 @@ WriteResult result(WRITE_STATUS_OK, encrypted_length); QuicSocketAddress send_to_address = (send_path_response_) ? packet->peer_address : peer_address(); + // Self address is always the default self address on this code path. + bool send_on_current_path = send_to_address == peer_address(); switch (fate) { case DISCARD: ++stats_.packets_discarded; @@ -3001,17 +3010,23 @@ // In some cases, an MTU probe can cause EMSGSIZE. This indicates that the // MTU discovery is permanently unsuccessful. - if (IsMsgTooBig(result) && is_mtu_discovery) { - // When MSG_TOO_BIG is returned, the system typically knows what the - // actual MTU is, so there is no need to probe further. - // TODO(wub): Reduce max packet size to a safe default, or the actual MTU. - QUIC_DVLOG(1) << ENDPOINT - << " MTU probe packet too big, size:" << encrypted_length - << ", long_term_mtu_:" << long_term_mtu_; - mtu_discoverer_.Disable(); - mtu_discovery_alarm_->Cancel(); - // The write failed, but the writer is not blocked, so return true. - return true; + if (IsMsgTooBig(result)) { + if (is_mtu_discovery) { + // When MSG_TOO_BIG is returned, the system typically knows what the + // actual MTU is, so there is no need to probe further. + // TODO(wub): Reduce max packet size to a safe default, or the actual MTU. + QUIC_DVLOG(1) << ENDPOINT + << " MTU probe packet too big, size:" << encrypted_length + << ", long_term_mtu_:" << long_term_mtu_; + mtu_discoverer_.Disable(); + mtu_discovery_alarm_->Cancel(); + // The write failed, but the writer is not blocked, so return true. + return true; + } + if (use_path_validator_ && !send_on_current_path) { + // Only handle MSG_TOO_BIG as error on current path. + return true; + } } if (IsWriteError(result.status)) { @@ -3072,14 +3087,13 @@ } // Do not measure rtt of this packet if it's not sent on current path. - const bool measure_rtt = send_to_address == peer_address(); - QUIC_DLOG_IF(INFO, !measure_rtt) + QUIC_DLOG_IF(INFO, !send_on_current_path) << ENDPOINT << " Sent packet " << packet->packet_number << " on a different path with remote address " << send_to_address << " while current path has peer address " << peer_address(); const bool in_flight = sent_packet_manager_.OnPacketSent( packet, packet_send_time, packet->transmission_type, - IsRetransmittable(*packet), measure_rtt); + IsRetransmittable(*packet), /*measure_rtt=*/send_on_current_path); QUIC_BUG_IF(default_enable_5rto_blackhole_detection_ && blackhole_detector_.IsDetectionInProgress() && !sent_packet_manager_.HasInFlightPackets()) @@ -4034,6 +4048,7 @@ // Cancel the alarms so they don't trigger any action now that the // connection is closed. CancelAllAlarms(); + path_validator_.CancelPathValidation(); } void QuicConnection::CancelAllAlarms() { @@ -5439,17 +5454,21 @@ return sent_packet_manager_.GetRetransmissionTime(); } -void QuicConnection::SendPathChallenge(const QuicPathFrameBuffer& data_buffer, +bool QuicConnection::SendPathChallenge(const QuicPathFrameBuffer& data_buffer, const QuicSocketAddress& self_address, const QuicSocketAddress& peer_address, QuicPacketWriter* writer) { if (writer == writer_) { - // It's on current path, add the PATH_CHALLENGE the same way as other - // frames. - QuicPacketCreator::ScopedPeerAddressContext context(&packet_creator_, - peer_address); - packet_creator_.AddPathChallengeFrame(data_buffer); - return; + { + // It's on current path, add the PATH_CHALLENGE the same way as other + // frames. + QuicPacketCreator::ScopedPeerAddressContext context(&packet_creator_, + peer_address); + // This may cause connection to be closed. + packet_creator_.AddPathChallengeFrame(data_buffer); + } + // Return outside of the scope so that the flush result can be reflected. + return connected_; } std::unique_ptr<SerializedPacket> probing_packet = packet_creator_.SerializePathChallengeConnectivityProbingPacket( @@ -5457,6 +5476,24 @@ DCHECK_EQ(IsRetransmittable(*probing_packet), NO_RETRANSMITTABLE_DATA); WritePacketUsingWriter(std::move(probing_packet), writer, self_address, peer_address, /*measure_rtt=*/false); + return true; +} + +QuicTime QuicConnection::GetRetryTimeout( + const QuicSocketAddress& peer_address_to_use, + QuicPacketWriter* writer_to_use) const { + if (writer_to_use == writer_ && peer_address_to_use == peer_address()) { + return clock_->ApproximateNow() + sent_packet_manager_.GetPtoDelay(); + } + return clock_->ApproximateNow() + + QuicTime::Delta::FromMilliseconds(3 * kInitialRttMs); +} + +void QuicConnection::ValidatePath( + std::unique_ptr<QuicPathValidationContext> context, + std::unique_ptr<QuicPathValidator::ResultDelegate> result_delegate) { + path_validator_.StartValidingPath(std::move(context), + std::move(result_delegate)); } bool QuicConnection::SendPathResponse(const QuicPathFrameBuffer& data_buffer, @@ -5481,5 +5518,22 @@ SendControlFrame(QuicFrame(QuicPingFrame())); } +bool QuicConnection::HasPendingPathValidation() const { + return path_validator_.HasPendingPathValidation(); +} + +void QuicConnection::MigratePath(const QuicSocketAddress& self_address, + const QuicSocketAddress& peer_address, + QuicPacketWriter* writer, + bool owns_writer) { + if (!connected_) { + return; + } + SetSelfAddress(self_address); + UpdatePeerAddress(peer_address); + SetQuicPacketWriter(writer, owns_writer); + OnSuccessfulMigration(); +} + #undef ENDPOINT // undef for jumbo builds } // namespace quic
diff --git a/quic/core/quic_connection.h b/quic/core/quic_connection.h index b19796a..599099d 100644 --- a/quic/core/quic_connection.h +++ b/quic/core/quic_connection.h
@@ -47,6 +47,7 @@ #include "net/third_party/quiche/src/quic/core/quic_packet_creator.h" #include "net/third_party/quiche/src/quic/core/quic_packet_writer.h" #include "net/third_party/quiche/src/quic/core/quic_packets.h" +#include "net/third_party/quiche/src/quic/core/quic_path_validator.h" #include "net/third_party/quiche/src/quic/core/quic_sent_packet_manager.h" #include "net/third_party/quiche/src/quic/core/quic_time.h" #include "net/third_party/quiche/src/quic/core/quic_types.h" @@ -432,7 +433,8 @@ public QuicPacketCreator::DelegateInterface, public QuicSentPacketManager::NetworkChangeVisitor, public QuicNetworkBlackholeDetector::Delegate, - public QuicIdleNetworkDetector::Delegate { + public QuicIdleNetworkDetector::Delegate, + public QuicPathValidator::SendDelegate { public: // Constructs a new QuicConnection for |connection_id| and // |initial_peer_address| using |writer| to write packets. |owns_writer| @@ -1078,7 +1080,7 @@ void OnSuccessfulVersionNegotiation(); // Called when self migration succeeds after probing. - void OnSuccessfulMigrationAfterProbing(); + void OnSuccessfulMigration(); // Called for QUIC+TLS versions when we send transport parameters. void OnTransportParametersSent( @@ -1104,21 +1106,37 @@ bool send_path_response() const { return send_path_response_; } + bool use_path_validator() const { return use_path_validator_; } + // If now is close to idle timeout, returns true and sends a connectivity // probing packet to test the connection for liveness. Otherwise, returns // false. bool MaybeTestLiveness(); + // QuicPathValidator::SendDelegate // Send PATH_CHALLENGE using the given path information. If |writer| is the // default writer, PATH_CHALLENGE can be bundled with other frames, and the // containing packet can be buffered if the writer is blocked. Otherwise, // PATH_CHALLENGE will be written in an individual packet and it will be // dropped if write fails. |data_buffer| will be populated with the payload // for future validation. - void SendPathChallenge(const QuicPathFrameBuffer& data_buffer, + // Return false if the connection is closed thus the caller will not continue + // the validation, otherwise return true. + bool SendPathChallenge(const QuicPathFrameBuffer& data_buffer, const QuicSocketAddress& self_address, const QuicSocketAddress& peer_address, - QuicPacketWriter* writer); + QuicPacketWriter* writer) override; + // If |writer| is the default writer and |peer_address| is the same as + // peer_address(), return the PTO of this connection. Otherwise, return 3 * + // kInitialRtt. + QuicTime GetRetryTimeout(const QuicSocketAddress& peer_address_to_use, + QuicPacketWriter* writer_to_use) const override; + + // Start vaildating the path defined by |context| asynchronously and call the + // |result_delegate| after validation finishes. + void ValidatePath( + std::unique_ptr<QuicPathValidationContext> context, + std::unique_ptr<QuicPathValidator::ResultDelegate> result_delegate); bool can_receive_ack_frequency_frame() const { return can_receive_ack_frequency_frame_; @@ -1138,6 +1156,13 @@ return use_encryption_level_context_; } + bool HasPendingPathValidation() const; + + void MigratePath(const QuicSocketAddress& self_address, + const QuicSocketAddress& peer_address, + QuicPacketWriter* writer, + bool owns_writer); + protected: // Calls cancel() on all the alarms owned by this connection. void CancelAllAlarms(); @@ -1912,6 +1937,11 @@ // --gfe2_reloadable_flag_quic_start_peer_migration_earlier. bool send_path_response_ = start_peer_migration_earlier_ && GetQuicReloadableFlag(quic_send_path_response); + + bool use_path_validator_ = + send_path_response_ && + GetQuicReloadableFlag(quic_pass_path_response_to_validator); + // True if AckFrequencyFrame is supported. bool can_receive_ack_frequency_frame_ = false; @@ -1941,6 +1971,8 @@ const bool encrypted_control_frames_; const bool use_encryption_level_context_; + + QuicPathValidator path_validator_; }; } // namespace quic
diff --git a/quic/core/quic_connection_test.cc b/quic/core/quic_connection_test.cc index 5f8af1c..93169ea 100644 --- a/quic/core/quic_connection_test.cc +++ b/quic/core/quic_connection_test.cc
@@ -24,6 +24,7 @@ #include "net/third_party/quiche/src/quic/core/quic_constants.h" #include "net/third_party/quiche/src/quic/core/quic_error_codes.h" #include "net/third_party/quiche/src/quic/core/quic_packets.h" +#include "net/third_party/quiche/src/quic/core/quic_path_validator.h" #include "net/third_party/quiche/src/quic/core/quic_simple_buffer_allocator.h" #include "net/third_party/quiche/src/quic/core/quic_types.h" #include "net/third_party/quiche/src/quic/core/quic_utils.h" @@ -40,6 +41,7 @@ #include "net/third_party/quiche/src/quic/test_tools/quic_connection_peer.h" #include "net/third_party/quiche/src/quic/test_tools/quic_framer_peer.h" #include "net/third_party/quiche/src/quic/test_tools/quic_packet_creator_peer.h" +#include "net/third_party/quiche/src/quic/test_tools/quic_path_validator_peer.h" #include "net/third_party/quiche/src/quic/test_tools/quic_sent_packet_manager_peer.h" #include "net/third_party/quiche/src/quic/test_tools/quic_test_utils.h" #include "net/third_party/quiche/src/quic/test_tools/simple_data_producer.h" @@ -1389,6 +1391,24 @@ // Prevent packets from being coalesced. EXPECT_CALL(visitor_, GetHandshakeState()) .WillRepeatedly(Return(HANDSHAKE_CONFIRMED)); + // Clear direct_peer_address. + QuicConnectionPeer::SetDirectPeerAddress(&connection_, QuicSocketAddress()); + // Clear effective_peer_address, it is the same as direct_peer_address for + // this test. + QuicConnectionPeer::SetEffectivePeerAddress(&connection_, + QuicSocketAddress()); + EXPECT_FALSE(connection_.effective_peer_address().IsInitialized()); + + if (QuicVersionUsesCryptoFrames(connection_.transport_version())) { + EXPECT_CALL(visitor_, OnCryptoFrame(_)).Times(AnyNumber()); + } else { + EXPECT_CALL(visitor_, OnStreamFrame(_)).Times(AnyNumber()); + } + QuicPacketCreatorPeer::SetPacketNumber(&peer_creator_, 2); + ProcessFramePacketWithAddresses(MakeCryptoFrame(), kSelfAddress, + kPeerAddress, ENCRYPTION_FORWARD_SECURE); + EXPECT_EQ(kPeerAddress, connection_.peer_address()); + EXPECT_EQ(kPeerAddress, connection_.effective_peer_address()); } void TestClientRetryHandling(bool invalid_retry_tag, @@ -1725,24 +1745,6 @@ TEST_P(QuicConnectionTest, ReceivePathProbeWithNoAddressChangeAtServer) { PathProbeTestInit(Perspective::IS_SERVER); - // Clear direct_peer_address. - QuicConnectionPeer::SetDirectPeerAddress(&connection_, QuicSocketAddress()); - // Clear effective_peer_address, it is the same as direct_peer_address for - // this test. - QuicConnectionPeer::SetEffectivePeerAddress(&connection_, - QuicSocketAddress()); - EXPECT_FALSE(connection_.effective_peer_address().IsInitialized()); - - if (QuicVersionUsesCryptoFrames(connection_.transport_version())) { - EXPECT_CALL(visitor_, OnCryptoFrame(_)).Times(AnyNumber()); - } else { - EXPECT_CALL(visitor_, OnStreamFrame(_)).Times(AnyNumber()); - } - ProcessFramePacketWithAddresses(MakeCryptoFrame(), kSelfAddress, kPeerAddress, - ENCRYPTION_INITIAL); - EXPECT_EQ(kPeerAddress, connection_.peer_address()); - EXPECT_EQ(kPeerAddress, connection_.effective_peer_address()); - EXPECT_CALL(visitor_, OnConnectionMigration(PORT_CHANGE)).Times(0); EXPECT_CALL(visitor_, OnPacketReceived(_, _, false)).Times(0); @@ -1840,24 +1842,6 @@ TEST_P(QuicConnectionTest, ReceivePathProbingAtServer) { PathProbeTestInit(Perspective::IS_SERVER); - // Clear direct_peer_address. - QuicConnectionPeer::SetDirectPeerAddress(&connection_, QuicSocketAddress()); - // Clear effective_peer_address, it is the same as direct_peer_address for - // this test. - QuicConnectionPeer::SetEffectivePeerAddress(&connection_, - QuicSocketAddress()); - EXPECT_FALSE(connection_.effective_peer_address().IsInitialized()); - - if (QuicVersionUsesCryptoFrames(connection_.transport_version())) { - EXPECT_CALL(visitor_, OnCryptoFrame(_)).Times(AnyNumber()); - } else { - EXPECT_CALL(visitor_, OnStreamFrame(_)).Times(AnyNumber()); - } - ProcessFramePacketWithAddresses(MakeCryptoFrame(), kSelfAddress, kPeerAddress, - ENCRYPTION_INITIAL); - EXPECT_EQ(kPeerAddress, connection_.peer_address()); - EXPECT_EQ(kPeerAddress, connection_.effective_peer_address()); - EXPECT_CALL(visitor_, OnConnectionMigration(PORT_CHANGE)).Times(0); if (!GetParam().version.HasIetfQuicFrames()) { EXPECT_CALL(visitor_, @@ -1979,25 +1963,6 @@ TEST_P(QuicConnectionTest, ReceiveReorderedPathProbingAtServer) { PathProbeTestInit(Perspective::IS_SERVER); - // Clear direct_peer_address. - QuicConnectionPeer::SetDirectPeerAddress(&connection_, QuicSocketAddress()); - // Clear effective_peer_address, it is the same as direct_peer_address for - // this test. - QuicConnectionPeer::SetEffectivePeerAddress(&connection_, - QuicSocketAddress()); - EXPECT_FALSE(connection_.effective_peer_address().IsInitialized()); - - if (QuicVersionUsesCryptoFrames(connection_.transport_version())) { - EXPECT_CALL(visitor_, OnCryptoFrame(_)).Times(AnyNumber()); - } else { - EXPECT_CALL(visitor_, OnStreamFrame(_)).Times(AnyNumber()); - } - QuicPacketCreatorPeer::SetPacketNumber(&peer_creator_, 5); - ProcessFramePacketWithAddresses(MakeCryptoFrame(), kSelfAddress, kPeerAddress, - ENCRYPTION_INITIAL); - EXPECT_EQ(kPeerAddress, connection_.peer_address()); - EXPECT_EQ(kPeerAddress, connection_.effective_peer_address()); - // Decrease packet number to simulate out-of-order packets. QuicPacketCreatorPeer::SetPacketNumber(&peer_creator_, 4); @@ -2035,24 +2000,6 @@ TEST_P(QuicConnectionTest, MigrateAfterProbingAtServer) { PathProbeTestInit(Perspective::IS_SERVER); - // Clear direct_peer_address. - QuicConnectionPeer::SetDirectPeerAddress(&connection_, QuicSocketAddress()); - // Clear effective_peer_address, it is the same as direct_peer_address for - // this test. - QuicConnectionPeer::SetEffectivePeerAddress(&connection_, - QuicSocketAddress()); - EXPECT_FALSE(connection_.effective_peer_address().IsInitialized()); - - if (QuicVersionUsesCryptoFrames(connection_.transport_version())) { - EXPECT_CALL(visitor_, OnCryptoFrame(_)).Times(AnyNumber()); - } else { - EXPECT_CALL(visitor_, OnStreamFrame(_)).Times(AnyNumber()); - } - ProcessFramePacketWithAddresses(MakeCryptoFrame(), kSelfAddress, kPeerAddress, - ENCRYPTION_INITIAL); - EXPECT_EQ(kPeerAddress, connection_.peer_address()); - EXPECT_EQ(kPeerAddress, connection_.effective_peer_address()); - EXPECT_CALL(visitor_, OnConnectionMigration(PORT_CHANGE)).Times(0); if (!GetParam().version.HasIetfQuicFrames()) { EXPECT_CALL(visitor_, @@ -2090,24 +2037,6 @@ EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_)); PathProbeTestInit(Perspective::IS_CLIENT); - // Clear direct_peer_address. - QuicConnectionPeer::SetDirectPeerAddress(&connection_, QuicSocketAddress()); - // Clear effective_peer_address, it is the same as direct_peer_address for - // this test. - QuicConnectionPeer::SetEffectivePeerAddress(&connection_, - QuicSocketAddress()); - EXPECT_FALSE(connection_.effective_peer_address().IsInitialized()); - - if (QuicVersionUsesCryptoFrames(connection_.transport_version())) { - EXPECT_CALL(visitor_, OnCryptoFrame(_)).Times(AnyNumber()); - } else { - EXPECT_CALL(visitor_, OnStreamFrame(_)).Times(AnyNumber()); - } - ProcessFramePacketWithAddresses(MakeCryptoFrame(), kSelfAddress, kPeerAddress, - ENCRYPTION_INITIAL); - EXPECT_EQ(kPeerAddress, connection_.peer_address()); - EXPECT_EQ(kPeerAddress, connection_.effective_peer_address()); - // Client takes all padded PING packet as speculative connectivity // probing packet, and reports to visitor. EXPECT_CALL(visitor_, OnConnectionMigration(PORT_CHANGE)).Times(0); @@ -2143,24 +2072,6 @@ EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_)); PathProbeTestInit(Perspective::IS_CLIENT); - // Clear direct_peer_address. - QuicConnectionPeer::SetDirectPeerAddress(&connection_, QuicSocketAddress()); - // Clear effective_peer_address, it is the same as direct_peer_address for - // this test. - QuicConnectionPeer::SetEffectivePeerAddress(&connection_, - QuicSocketAddress()); - EXPECT_FALSE(connection_.effective_peer_address().IsInitialized()); - - if (QuicVersionUsesCryptoFrames(connection_.transport_version())) { - EXPECT_CALL(visitor_, OnCryptoFrame(_)).Times(AnyNumber()); - } else { - EXPECT_CALL(visitor_, OnStreamFrame(_)).Times(AnyNumber()); - } - ProcessFramePacketWithAddresses(MakeCryptoFrame(), kSelfAddress, kPeerAddress, - ENCRYPTION_INITIAL); - EXPECT_EQ(kPeerAddress, connection_.peer_address()); - EXPECT_EQ(kPeerAddress, connection_.effective_peer_address()); - // Process a padded PING packet with a different self address on client side // is effectively receiving a connectivity probing. EXPECT_CALL(visitor_, OnConnectionMigration(PORT_CHANGE)).Times(0); @@ -8592,29 +8503,6 @@ EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_)); PathProbeTestInit(Perspective::IS_CLIENT); - // Clear direct_peer_address and effective_peer_address. - QuicConnectionPeer::SetDirectPeerAddress(&connection_, QuicSocketAddress()); - QuicConnectionPeer::SetEffectivePeerAddress(&connection_, - QuicSocketAddress()); - EXPECT_FALSE(connection_.effective_peer_address().IsInitialized()); - - EXPECT_TRUE(connection_.connected()); - EXPECT_CALL(visitor_, ShouldKeepConnectionAlive()) - .WillRepeatedly(Return(true)); - EXPECT_FALSE(connection_.PathDegradingDetectionInProgress()); - EXPECT_FALSE(connection_.IsPathDegrading()); - EXPECT_FALSE(connection_.GetPingAlarm()->IsSet()); - - if (QuicVersionUsesCryptoFrames(connection_.transport_version())) { - EXPECT_CALL(visitor_, OnCryptoFrame(_)).Times(AnyNumber()); - } else { - EXPECT_CALL(visitor_, OnStreamFrame(_)).Times(AnyNumber()); - } - ProcessFramePacketWithAddresses(MakeCryptoFrame(), kSelfAddress, kPeerAddress, - ENCRYPTION_INITIAL); - EXPECT_EQ(kPeerAddress, connection_.peer_address()); - EXPECT_EQ(kPeerAddress, connection_.effective_peer_address()); - // Send data and verify the path degrading detection is set. const char data[] = "data"; size_t data_size = strlen(data); @@ -8672,7 +8560,7 @@ // Verify new path degrading detection is activated. EXPECT_CALL(visitor_, OnForwardProgressMadeAfterPathDegrading()).Times(1); - connection_.OnSuccessfulMigrationAfterProbing(); + connection_.OnSuccessfulMigration(); EXPECT_FALSE(connection_.IsPathDegrading()); EXPECT_TRUE(connection_.PathDegradingDetectionInProgress()); } @@ -11334,44 +11222,96 @@ EXPECT_TRUE(connection_.connected()); } -TEST_P(QuicConnectionTest, SendPathChallenge) { +class TestQuicPathValidationContext : public QuicPathValidationContext { + public: + TestQuicPathValidationContext(const QuicSocketAddress& self_address, + const QuicSocketAddress& peer_address, + + QuicPacketWriter* writer) + : QuicPathValidationContext(self_address, peer_address), + writer_(writer) {} + + QuicPacketWriter* WriterToUse() override { return writer_; } + + private: + QuicPacketWriter* writer_; +}; + +class TestValidationResultDelegate : public QuicPathValidator::ResultDelegate { + public: + TestValidationResultDelegate(const QuicSocketAddress& expected_self_address, + const QuicSocketAddress& expected_peer_address, + bool* success) + : QuicPathValidator::ResultDelegate(), + expected_self_address_(expected_self_address), + expected_peer_address_(expected_peer_address), + success_(success) {} + void OnPathValidationSuccess( + std::unique_ptr<QuicPathValidationContext> context) override { + EXPECT_EQ(expected_self_address_, context->self_address()); + EXPECT_EQ(expected_peer_address_, context->peer_address()); + *success_ = true; + } + + void OnPathValidationFailure( + std::unique_ptr<QuicPathValidationContext> context) override { + EXPECT_EQ(expected_self_address_, context->self_address()); + EXPECT_EQ(expected_peer_address_, context->peer_address()); + *success_ = false; + } + + private: + QuicSocketAddress expected_self_address_; + QuicSocketAddress expected_peer_address_; + bool* success_; +}; + +TEST_P(QuicConnectionTest, PathValidationOnNewSocketSuccess) { if (!VersionHasIetfQuicFrames(connection_.version().transport_version) || - !connection_.send_path_response()) { + !connection_.use_path_validator()) { return; } PathProbeTestInit(Perspective::IS_CLIENT); - const QuicSocketAddress kNewSourceAddress(QuicIpAddress::Any6(), 12345); - EXPECT_NE(kNewSourceAddress, connection_.self_address()); + const QuicSocketAddress kNewSelfAddress(QuicIpAddress::Any4(), 12345); + EXPECT_NE(kNewSelfAddress, connection_.self_address()); TestPacketWriter new_writer(version(), &clock_, Perspective::IS_CLIENT); - QuicPathFrameBuffer payload{{0xde, 0xad, 0xbe, 0xef, 0xba, 0xdc, 0x0f, 0xfe}}; EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)) + .Times(AtLeast(1u)) .WillOnce(Invoke([&]() { EXPECT_EQ(1u, new_writer.packets_write_attempts()); EXPECT_EQ(1u, new_writer.path_challenge_frames().size()); - EXPECT_EQ( - 0, memcmp(payload.data(), - &(new_writer.path_challenge_frames().front().data_buffer), - sizeof(payload))); EXPECT_EQ(1u, new_writer.padding_frames().size()); - EXPECT_EQ(kNewSourceAddress.host(), + EXPECT_EQ(kNewSelfAddress.host(), new_writer.last_write_source_address()); })); - connection_.SendPathChallenge(payload, kNewSourceAddress, - connection_.peer_address(), &new_writer); + bool success = false; + connection_.ValidatePath( + std::make_unique<TestQuicPathValidationContext>( + kNewSelfAddress, connection_.peer_address(), &new_writer), + std::make_unique<TestValidationResultDelegate>( + kNewSelfAddress, connection_.peer_address(), &success)); EXPECT_EQ(0u, writer_->packets_write_attempts()); + + QuicFrames frames; + frames.push_back(QuicFrame(new QuicPathResponseFrame( + 99, new_writer.path_challenge_frames().front().data_buffer))); + ProcessFramesPacketWithAddresses(frames, kNewSelfAddress, kPeerAddress, + ENCRYPTION_FORWARD_SECURE); + EXPECT_TRUE(success); } +// Tests that PATH_CHALLENGE is dropped if it is sent via a blocked alternative +// writer. TEST_P(QuicConnectionTest, SendPathChallengeUsingBlockedNewSocket) { if (!VersionHasIetfQuicFrames(connection_.version().transport_version) || - !connection_.send_path_response()) { + !connection_.use_path_validator()) { return; } PathProbeTestInit(Perspective::IS_CLIENT); - const QuicSocketAddress kNewSourceAddress(QuicIpAddress::Any6(), 12345); - EXPECT_NE(kNewSourceAddress, connection_.self_address()); + const QuicSocketAddress kNewSelfAddress(QuicIpAddress::Any4(), 12345); + EXPECT_NE(kNewSelfAddress, connection_.self_address()); TestPacketWriter new_writer(version(), &clock_, Perspective::IS_CLIENT); new_writer.BlockOnNextWrite(); - QuicPathFrameBuffer payload{{0xde, 0xad, 0xbe, 0xef, 0xba, 0xdc, 0x0f, 0xfe}}; EXPECT_CALL(visitor_, OnWriteBlocked()).Times(0); EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)) .WillOnce(Invoke([&]() { @@ -11379,16 +11319,16 @@ // treated as sent. EXPECT_EQ(1u, new_writer.packets_write_attempts()); EXPECT_EQ(1u, new_writer.path_challenge_frames().size()); - EXPECT_EQ( - 0, memcmp(payload.data(), - &(new_writer.path_challenge_frames().front().data_buffer), - sizeof(payload))); EXPECT_EQ(1u, new_writer.padding_frames().size()); - EXPECT_EQ(kNewSourceAddress.host(), + EXPECT_EQ(kNewSelfAddress.host(), new_writer.last_write_source_address()); })); - connection_.SendPathChallenge(payload, kNewSourceAddress, - connection_.peer_address(), &new_writer); + bool success = false; + connection_.ValidatePath( + std::make_unique<TestQuicPathValidationContext>( + kNewSelfAddress, connection_.peer_address(), &new_writer), + std::make_unique<TestValidationResultDelegate>( + kNewSelfAddress, connection_.peer_address(), &success)); EXPECT_EQ(0u, writer_->packets_write_attempts()); new_writer.SetWritable(); @@ -11398,7 +11338,9 @@ EXPECT_EQ(1u, new_writer.packets_write_attempts()); } -TEST_P(QuicConnectionTest, SendPathChallengeWithDefaultSocketBlocked) { +// Tests that PATH_CHALLENGE is dropped if it is sent via the default writer +// and the writer is blocked. +TEST_P(QuicConnectionTest, SendPathChallengeUsingBlockedDefaultSocket) { if (!VersionHasIetfQuicFrames(connection_.version().transport_version) || !connection_.send_path_response()) { return; @@ -11407,69 +11349,79 @@ if (version().SupportsAntiAmplificationLimit()) { QuicConnectionPeer::SetAddressValidated(&connection_); } - const QuicSocketAddress kNewPeerAddress(QuicIpAddress::Any6(), 12345); + const QuicSocketAddress kNewPeerAddress(QuicIpAddress::Any4(), 12345); writer_->BlockOnNextWrite(); - QuicPathFrameBuffer payload{{0xde, 0xad, 0xbe, 0xef, 0xba, 0xdc, 0x0f, 0xfe}}; // 1st time is after writer returns WRITE_STATUS_BLOCKED. 2nd time is in // ShouldGeneratePacket(); EXPECT_CALL(visitor_, OnWriteBlocked()).Times(2u); // This packet isn't sent actually, instead it is buffered in the connection. EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)) + .Times(AtLeast(1u)) .WillOnce(Invoke([&]() { EXPECT_EQ(1u, writer_->path_challenge_frames().size()); - EXPECT_EQ( - 0, memcmp(payload.data(), - &(writer_->path_challenge_frames().front().data_buffer), - sizeof(payload))); EXPECT_EQ(1u, writer_->padding_frames().size()); EXPECT_EQ(kNewPeerAddress, writer_->last_write_peer_address()); + })) + .WillRepeatedly(Invoke([&]() { + // Only one PATH_CHALLENGE should be sent out. + EXPECT_EQ(0u, writer_->path_challenge_frames().size()); })); - connection_.SendPathChallenge(payload, connection_.self_address(), - kNewPeerAddress, writer_.get()); + bool success = false; + connection_.ValidatePath( + std::make_unique<TestQuicPathValidationContext>( + connection_.self_address(), kNewPeerAddress, writer_.get()), + std::make_unique<TestValidationResultDelegate>( + connection_.self_address(), kNewPeerAddress, &success)); EXPECT_EQ(1u, writer_->packets_write_attempts()); - memset(payload.data(), 0, sizeof(payload)); // Try again with the new socket blocked from the beginning. The 2nd // PATH_CHALLENGE shouldn't be serialized, but be dropped. - connection_.SendPathChallenge(payload, connection_.self_address(), - kNewPeerAddress, writer_.get()); + clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(3 * kInitialRttMs)); + static_cast<test::MockRandom*>(helper_->GetRandomGenerator())->ChangeValue(); + static_cast<TestAlarmFactory::TestAlarm*>( + QuicPathValidatorPeer::retry_timer( + QuicConnectionPeer::path_validator(&connection_))) + ->Fire(); + // No more write attempt should be made. EXPECT_EQ(1u, writer_->packets_write_attempts()); writer_->SetWritable(); // OnCanWrite() should actually write out the 1st PATH_CHALLENGE packet - // buffered earlier, thus incrementing the write counter. + // buffered earlier, thus incrementing the write counter. It may also send + // ACKs to previously received packets. connection_.OnCanWrite(); - EXPECT_EQ(2u, writer_->packets_write_attempts()); + EXPECT_LE(2u, writer_->packets_write_attempts()); } // Tests that write error on the alternate socket should be ignored. TEST_P(QuicConnectionTest, SendPathChallengeFailOnNewSocket) { if (!VersionHasIetfQuicFrames(connection_.version().transport_version) || - !connection_.send_path_response()) { + !connection_.use_path_validator()) { return; } PathProbeTestInit(Perspective::IS_CLIENT); - const QuicSocketAddress kNewSourceAddress(QuicIpAddress::Any6(), 12345); - EXPECT_NE(kNewSourceAddress, connection_.self_address()); + const QuicSocketAddress kNewSelfAddress(QuicIpAddress::Any4(), 12345); + EXPECT_NE(kNewSelfAddress, connection_.self_address()); TestPacketWriter new_writer(version(), &clock_, Perspective::IS_CLIENT); new_writer.SetShouldWriteFail(); - QuicPathFrameBuffer payload{{0xde, 0xad, 0xbe, 0xef, 0xba, 0xdc, 0x0f, 0xfe}}; EXPECT_CALL(visitor_, OnConnectionClosed(_, ConnectionCloseSource::FROM_SELF)) .Times(0); + EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)).Times(0u); - connection_.SendPathChallenge(payload, kNewSourceAddress, - connection_.peer_address(), &new_writer); - // Regardless of the write error, the PATH_CHALLENGE should still be - // treated as sent. + bool success = false; + connection_.ValidatePath( + std::make_unique<TestQuicPathValidationContext>( + kNewSelfAddress, connection_.peer_address(), &new_writer), + std::make_unique<TestValidationResultDelegate>( + kNewSelfAddress, connection_.peer_address(), &success)); EXPECT_EQ(1u, new_writer.packets_write_attempts()); EXPECT_EQ(1u, new_writer.path_challenge_frames().size()); - EXPECT_EQ(0, memcmp(payload.data(), - &(new_writer.path_challenge_frames().front().data_buffer), - sizeof(payload))); EXPECT_EQ(1u, new_writer.padding_frames().size()); - EXPECT_EQ(kNewSourceAddress.host(), new_writer.last_write_source_address()); + EXPECT_EQ(kNewSelfAddress.host(), new_writer.last_write_source_address()); + EXPECT_EQ(0u, writer_->packets_write_attempts()); + // Regardless of the write error, the connection should still be connected. EXPECT_TRUE(connection_.connected()); } @@ -11477,12 +11429,12 @@ // should close the connection. TEST_P(QuicConnectionTest, SendPathChallengeFailOnDefaultPath) { if (!VersionHasIetfQuicFrames(connection_.version().transport_version) || - !connection_.send_path_response()) { + !connection_.use_path_validator()) { return; } PathProbeTestInit(Perspective::IS_CLIENT); + writer_->SetShouldWriteFail(); - QuicPathFrameBuffer payload{{0xde, 0xad, 0xbe, 0xef, 0xba, 0xdc, 0x0f, 0xfe}}; EXPECT_CALL(visitor_, OnConnectionClosed(_, ConnectionCloseSource::FROM_SELF)) .WillOnce( Invoke([](QuicConnectionCloseFrame frame, ConnectionCloseSource) { @@ -11493,17 +11445,83 @@ // Add a flusher to force flush, otherwise the frames will remain in the // packet creator. QuicConnection::ScopedPacketFlusher flusher(&connection_); - connection_.SendPathChallenge(payload, connection_.self_address(), - connection_.peer_address(), writer_.get()); + bool success = false; + connection_.ValidatePath( + std::make_unique<TestQuicPathValidationContext>( + connection_.self_address(), connection_.peer_address(), + writer_.get()), + std::make_unique<TestValidationResultDelegate>( + connection_.self_address(), connection_.peer_address(), &success)); } EXPECT_EQ(1u, writer_->packets_write_attempts()); EXPECT_EQ(1u, writer_->path_challenge_frames().size()); - EXPECT_EQ(0, memcmp(payload.data(), - &(writer_->path_challenge_frames().front().data_buffer), - sizeof(payload))); EXPECT_EQ(1u, writer_->padding_frames().size()); EXPECT_EQ(connection_.peer_address(), writer_->last_write_peer_address()); EXPECT_FALSE(connection_.connected()); + // Closing connection should abandon ongoing path validation. + EXPECT_FALSE(connection_.HasPendingPathValidation()); +} + +TEST_P(QuicConnectionTest, SendPathChallengeFailOnAlternativePeerAddress) { + if (!VersionHasIetfQuicFrames(connection_.version().transport_version) || + !connection_.use_path_validator()) { + return; + } + PathProbeTestInit(Perspective::IS_SERVER); + + writer_->SetShouldWriteFail(); + const QuicSocketAddress kNewPeerAddress(QuicIpAddress::Any4(), 12345); + EXPECT_CALL(visitor_, OnConnectionClosed(_, ConnectionCloseSource::FROM_SELF)) + .WillOnce( + Invoke([](QuicConnectionCloseFrame frame, ConnectionCloseSource) { + EXPECT_EQ(QUIC_PACKET_WRITE_ERROR, frame.quic_error_code); + })); + // Sending PATH_CHALLENGE to trigger a flush write which will fail and close + // the connection. + bool success = false; + connection_.ValidatePath( + std::make_unique<TestQuicPathValidationContext>( + connection_.self_address(), kNewPeerAddress, writer_.get()), + std::make_unique<TestValidationResultDelegate>( + connection_.self_address(), kNewPeerAddress, &success)); + + EXPECT_EQ(1u, writer_->packets_write_attempts()); + EXPECT_FALSE(connection_.HasPendingPathValidation()); + EXPECT_EQ(1u, writer_->path_challenge_frames().size()); + EXPECT_EQ(1u, writer_->padding_frames().size()); + EXPECT_EQ(kNewPeerAddress, writer_->last_write_peer_address()); + EXPECT_FALSE(connection_.connected()); +} + +TEST_P(QuicConnectionTest, + SendPathChallengeFailPacketTooBigOnAlternativePeerAddress) { + if (!VersionHasIetfQuicFrames(connection_.version().transport_version) || + !connection_.use_path_validator()) { + return; + } + PathProbeTestInit(Perspective::IS_SERVER); + + writer_->SetShouldWriteFail(); + writer_->SetWriteError(EMSGSIZE); + const QuicSocketAddress kNewPeerAddress(QuicIpAddress::Any4(), 12345); + EXPECT_CALL(visitor_, OnConnectionClosed(_, ConnectionCloseSource::FROM_SELF)) + .Times(0u); + EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)).Times(0u); + // Sending PATH_CHALLENGE to trigger a flush write which will fail with + // MSG_TOO_BIG. + bool success = false; + connection_.ValidatePath( + std::make_unique<TestQuicPathValidationContext>( + connection_.self_address(), kNewPeerAddress, writer_.get()), + std::make_unique<TestValidationResultDelegate>( + connection_.self_address(), kNewPeerAddress, &success)); + EXPECT_TRUE(connection_.HasPendingPathValidation()); + // Connection shouldn't be closed. + EXPECT_TRUE(connection_.connected()); + EXPECT_EQ(1u, writer_->packets_write_attempts()); + EXPECT_EQ(1u, writer_->path_challenge_frames().size()); + EXPECT_EQ(1u, writer_->padding_frames().size()); + EXPECT_EQ(kNewPeerAddress, writer_->last_write_peer_address()); } // Check that if there are two PATH_CHALLENGE frames in the packet, the latter @@ -11514,24 +11532,6 @@ } PathProbeTestInit(Perspective::IS_SERVER); - // Clear direct_peer_address. - QuicConnectionPeer::SetDirectPeerAddress(&connection_, QuicSocketAddress()); - // Clear effective_peer_address, it is the same as direct_peer_address for - // this test. - QuicConnectionPeer::SetEffectivePeerAddress(&connection_, - QuicSocketAddress()); - EXPECT_FALSE(connection_.effective_peer_address().IsInitialized()); - - if (QuicVersionUsesCryptoFrames(connection_.transport_version())) { - EXPECT_CALL(visitor_, OnCryptoFrame(_)).Times(AnyNumber()); - } else { - EXPECT_CALL(visitor_, OnStreamFrame(_)).Times(AnyNumber()); - } - ProcessFramePacketWithAddresses(MakeCryptoFrame(), kSelfAddress, kPeerAddress, - ENCRYPTION_FORWARD_SECURE); - EXPECT_EQ(kPeerAddress, connection_.peer_address()); - EXPECT_EQ(kPeerAddress, connection_.effective_peer_address()); - QuicPathFrameBuffer path_frame_buffer1{0, 1, 2, 3, 4, 5, 6, 7}; QuicPathFrameBuffer path_frame_buffer2{8, 9, 10, 11, 12, 13, 14, 15}; QuicFrames frames; @@ -11584,24 +11584,6 @@ } PathProbeTestInit(Perspective::IS_SERVER); - // Clear direct_peer_address. - QuicConnectionPeer::SetDirectPeerAddress(&connection_, QuicSocketAddress()); - // Clear effective_peer_address, it is the same as direct_peer_address for - // this test. - QuicConnectionPeer::SetEffectivePeerAddress(&connection_, - QuicSocketAddress()); - EXPECT_FALSE(connection_.effective_peer_address().IsInitialized()); - - if (QuicVersionUsesCryptoFrames(connection_.transport_version())) { - EXPECT_CALL(visitor_, OnCryptoFrame(_)).Times(AnyNumber()); - } else { - EXPECT_CALL(visitor_, OnStreamFrame(_)).Times(AnyNumber()); - } - ProcessFramePacketWithAddresses(MakeCryptoFrame(), kSelfAddress, kPeerAddress, - ENCRYPTION_INITIAL); - EXPECT_EQ(kPeerAddress, connection_.peer_address()); - EXPECT_EQ(kPeerAddress, connection_.effective_peer_address()); - QuicFrames frames; frames.push_back(QuicFrame(frame1_)); QuicPathFrameBuffer path_frame_buffer{0, 1, 2, 3, 4, 5, 6, 7}; @@ -11646,24 +11628,6 @@ } PathProbeTestInit(Perspective::IS_SERVER); - // Clear direct_peer_address. - QuicConnectionPeer::SetDirectPeerAddress(&connection_, QuicSocketAddress()); - // Clear effective_peer_address, it is the same as direct_peer_address for - // this test. - QuicConnectionPeer::SetEffectivePeerAddress(&connection_, - QuicSocketAddress()); - EXPECT_FALSE(connection_.effective_peer_address().IsInitialized()); - - if (QuicVersionUsesCryptoFrames(connection_.transport_version())) { - EXPECT_CALL(visitor_, OnCryptoFrame(_)).Times(AnyNumber()); - } else { - EXPECT_CALL(visitor_, OnStreamFrame(_)).Times(AnyNumber()); - } - ProcessFramePacketWithAddresses(MakeCryptoFrame(), kSelfAddress, kPeerAddress, - ENCRYPTION_INITIAL); - EXPECT_EQ(kPeerAddress, connection_.peer_address()); - EXPECT_EQ(kPeerAddress, connection_.effective_peer_address()); - QuicFrames frames; QuicPathFrameBuffer path_frame_buffer{0, 1, 2, 3, 4, 5, 6, 7}; frames.push_back(QuicFrame(new QuicPathChallengeFrame(0, path_frame_buffer))); @@ -11717,25 +11681,6 @@ } PathProbeTestInit(Perspective::IS_SERVER); - // Clear direct_peer_address. - QuicConnectionPeer::SetDirectPeerAddress(&connection_, QuicSocketAddress()); - // Clear effective_peer_address, it is the same as direct_peer_address for - // this test. - QuicConnectionPeer::SetEffectivePeerAddress(&connection_, - QuicSocketAddress()); - EXPECT_FALSE(connection_.effective_peer_address().IsInitialized()); - - if (QuicVersionUsesCryptoFrames(connection_.transport_version())) { - EXPECT_CALL(visitor_, OnCryptoFrame(_)).Times(AnyNumber()); - } else { - EXPECT_CALL(visitor_, OnStreamFrame(_)).Times(AnyNumber()); - } - QuicPacketCreatorPeer::SetPacketNumber(&peer_creator_, 2); - ProcessFramePacketWithAddresses(MakeCryptoFrame(), kSelfAddress, kPeerAddress, - ENCRYPTION_FORWARD_SECURE); - EXPECT_EQ(kPeerAddress, connection_.peer_address()); - EXPECT_EQ(kPeerAddress, connection_.effective_peer_address()); - QuicFrames frames; frames.push_back(QuicFrame(frame1_)); QuicPathFrameBuffer path_frame_buffer{0, 1, 2, 3, 4, 5, 6, 7}; @@ -11803,25 +11748,6 @@ } PathProbeTestInit(Perspective::IS_SERVER); - // Clear direct_peer_address. - QuicConnectionPeer::SetDirectPeerAddress(&connection_, QuicSocketAddress()); - // Clear effective_peer_address, it is the same as direct_peer_address for - // this test. - QuicConnectionPeer::SetEffectivePeerAddress(&connection_, - QuicSocketAddress()); - EXPECT_FALSE(connection_.effective_peer_address().IsInitialized()); - - if (QuicVersionUsesCryptoFrames(connection_.transport_version())) { - EXPECT_CALL(visitor_, OnCryptoFrame(_)).Times(AnyNumber()); - } else { - EXPECT_CALL(visitor_, OnStreamFrame(_)).Times(AnyNumber()); - } - QuicPacketCreatorPeer::SetPacketNumber(&peer_creator_, 2); - ProcessFramePacketWithAddresses(MakeCryptoFrame(), kSelfAddress, kPeerAddress, - ENCRYPTION_INITIAL); - EXPECT_EQ(kPeerAddress, connection_.peer_address()); - EXPECT_EQ(kPeerAddress, connection_.effective_peer_address()); - QuicFrames frames; QuicPathFrameBuffer path_frame_buffer{0, 1, 2, 3, 4, 5, 6, 7}; frames.push_back(QuicFrame(new QuicPathChallengeFrame(0, path_frame_buffer))); @@ -12945,6 +12871,20 @@ connection_.GetRetransmissionAlarm()->deadline()); } +TEST_P(QuicConnectionTest, MigratePath) { + EXPECT_CALL(visitor_, OnPathDegrading()); + connection_.OnPathDegradingDetected(); + const QuicSocketAddress kNewSelfAddress(QuicIpAddress::Any4(), 12345); + EXPECT_NE(kNewSelfAddress, connection_.self_address()); + TestPacketWriter new_writer(version(), &clock_, Perspective::IS_CLIENT); + EXPECT_CALL(visitor_, OnForwardProgressMadeAfterPathDegrading()); + connection_.MigratePath(kNewSelfAddress, connection_.peer_address(), + &new_writer, /*owns_writer=*/false); + EXPECT_EQ(kNewSelfAddress, connection_.self_address()); + EXPECT_EQ(&new_writer, QuicConnectionPeer::GetWriter(&connection_)); + EXPECT_FALSE(connection_.IsPathDegrading()); +} + } // namespace } // namespace test } // namespace quic
diff --git a/quic/core/quic_flags_list.h b/quic/core/quic_flags_list.h index ed496e1..10c0cb7 100644 --- a/quic/core/quic_flags_list.h +++ b/quic/core/quic_flags_list.h
@@ -63,6 +63,7 @@ QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_key_update_supported, true) QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_let_connection_handle_pings, true) QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_new_priority_update_frame, true) +QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_pass_path_response_to_validator, false) QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_process_undecryptable_packets_after_async_decrypt_callback, true) QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_record_received_min_ack_delay, true) QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_reject_spdy_frames, true)
diff --git a/quic/core/quic_session.cc b/quic/core/quic_session.cc index 91aca8f..44fe0c8 100644 --- a/quic/core/quic_session.cc +++ b/quic/core/quic_session.cc
@@ -2561,5 +2561,22 @@ return connection_->framer().GetEncryptionLevelToSendApplicationData(); } +void QuicSession::ValidatePath( + std::unique_ptr<QuicPathValidationContext> context, + std::unique_ptr<QuicPathValidator::ResultDelegate> result_delegate) { + connection_->ValidatePath(std::move(context), std::move(result_delegate)); +} + +bool QuicSession::HasPendingPathValidation() const { + return connection_->HasPendingPathValidation(); +} + +void QuicSession::MigratePath(const QuicSocketAddress& self_address, + const QuicSocketAddress& peer_address, + QuicPacketWriter* writer, + bool owns_writer) { + connection_->MigratePath(self_address, peer_address, writer, owns_writer); +} + #undef ENDPOINT // undef for jumbo builds } // namespace quic
diff --git a/quic/core/quic_session.h b/quic/core/quic_session.h index a36040e..3c95b2f 100644 --- a/quic/core/quic_session.h +++ b/quic/core/quic_session.h
@@ -26,6 +26,7 @@ #include "net/third_party/quiche/src/quic/core/quic_error_codes.h" #include "net/third_party/quiche/src/quic/core/quic_packet_creator.h" #include "net/third_party/quiche/src/quic/core/quic_packets.h" +#include "net/third_party/quiche/src/quic/core/quic_path_validator.h" #include "net/third_party/quiche/src/quic/core/quic_stream.h" #include "net/third_party/quiche/src/quic/core/quic_stream_frame_data_producer.h" #include "net/third_party/quiche/src/quic/core/quic_types.h" @@ -369,6 +370,77 @@ // connection, or in a write-blocked stream. bool HasDataToWrite() const; + // Initiates a path validation on the path described in the given context, + // asynchronously calls |result_delegate| upon success or failure. + // The initiator should extend QuicPathValidationContext to provide the writer + // and ResultDelegate to react upon the validation result. + // Example implementations of these for path validation for connection + // migration could be: + // class QUIC_EXPORT_PRIVATE PathMigrationContext + // : public QuicPathValidationContext { + // public: + // PathMigrationContext(std::unique_ptr<QuicPacketWriter> writer, + // const QuicSocketAddress& self_address, + // const QuicSocketAddress& peer_address) + // : QuicPathValidationContext(self_address, peer_address), + // alternative_writer_(std::move(writer)) {} + // + // QuicPacketWriter* WriterToUse() override { + // return alternative_writer_.get(); + // } + // + // QuicPacketWriter* ReleaseWriter() { + // return alternative_writer_.release(); + // } + // + // private: + // std::unique_ptr<QuicPacketWriter> alternative_writer_; + // }; + // + // class PathMigrationValidationResultDelegate + // : public QuicPathValidator::ResultDelegate { + // public: + // PathMigrationValidationResultDelegate(QuicConnection* connection) + // : QuicPathValidator::ResultDelegate(), connection_(connection) {} + // + // void OnPathValidationSuccess( + // std::unique_ptr<QuicPathValidationContext> context) override { + // // Do some work to prepare for migration. + // // ... + // + // // Actually migrate to the validated path. + // auto migration_context = std::unique_ptr<PathMigrationContext>( + // static_cast<PathMigrationContext*>(context.release())); + // connection_->MigratePath(migration_context->self_address(), + // migration_context->peer_address(), + // migration_context->ReleaseWriter(), + // /*owns_writer=*/true); + // + // // Post-migration actions + // // ... + // } + // + // void OnPathValidationFailure( + // std::unique_ptr<QuicPathValidationContext> /*context*/) override { + // // Handle validation failure. + // } + // + // private: + // QuicConnection* connection_; + // }; + void ValidatePath( + std::unique_ptr<QuicPathValidationContext> context, + std::unique_ptr<QuicPathValidator::ResultDelegate> result_delegate); + + // Return true if there is a path being validated. + bool HasPendingPathValidation() const; + + // Switch to the path described in |context| without validating the path. + void MigratePath(const QuicSocketAddress& self_address, + const QuicSocketAddress& peer_address, + QuicPacketWriter* writer, + bool owns_writer); + // Returns the largest payload that will fit into a single MESSAGE frame. // Because overhead can vary during a connection, this method should be // checked for every message.
diff --git a/quic/test_tools/quic_connection_peer.cc b/quic/test_tools/quic_connection_peer.cc index 30ed4fd..10bd838 100644 --- a/quic/test_tools/quic_connection_peer.cc +++ b/quic/test_tools/quic_connection_peer.cc
@@ -399,5 +399,18 @@ connection->SendPingAtLevel(connection->encryption_level()); } +// static +void QuicConnectionPeer::SetLastPacketDestinationAddress( + QuicConnection* connection, + const QuicSocketAddress& address) { + connection->last_packet_destination_address_ = address; +} + +// static +QuicPathValidator* QuicConnectionPeer::path_validator( + QuicConnection* connection) { + return &connection->path_validator_; +} + } // namespace test } // namespace quic
diff --git a/quic/test_tools/quic_connection_peer.h b/quic/test_tools/quic_connection_peer.h index 8859a58..1769033 100644 --- a/quic/test_tools/quic_connection_peer.h +++ b/quic/test_tools/quic_connection_peer.h
@@ -164,6 +164,11 @@ static void SetConnectionClose(QuicConnection* connection); static void SendPing(QuicConnection* connection); + + static void SetLastPacketDestinationAddress(QuicConnection* connection, + const QuicSocketAddress& address); + + static QuicPathValidator* path_validator(QuicConnection* connection); }; } // namespace test
diff --git a/quic/test_tools/quic_test_utils.cc b/quic/test_tools/quic_test_utils.cc index 8bf49d9..7901b1c 100644 --- a/quic/test_tools/quic_test_utils.cc +++ b/quic/test_tools/quic_test_utils.cc
@@ -1465,7 +1465,7 @@ } if (ShouldWriteFail()) { - return WriteResult(WRITE_STATUS_ERROR, 0); + return WriteResult(WRITE_STATUS_ERROR, write_error_code_); } last_packet_size_ = packet.length();
diff --git a/quic/test_tools/quic_test_utils.h b/quic/test_tools/quic_test_utils.h index f05859d..f764e0b 100644 --- a/quic/test_tools/quic_test_utils.h +++ b/quic/test_tools/quic_test_utils.h
@@ -723,6 +723,13 @@ SendMessage, (QuicMessageId, QuicMemSliceSpan, bool), (override)); + MOCK_METHOD(bool, + SendPathChallenge, + (const QuicPathFrameBuffer&, + const QuicSocketAddress&, + const QuicSocketAddress&, + QuicPacketWriter*), + (override)); MOCK_METHOD(void, OnError, (QuicFramer*), (override)); void QuicConnection_OnError(QuicFramer* framer) { @@ -766,6 +773,11 @@ const QuicSocketAddress& peer_address) { QuicConnection::SendConnectivityProbingResponsePacket(peer_address); } + + bool ReallyOnPathResponseFrame(const QuicPathResponseFrame& frame) { + return QuicConnection::OnPathResponseFrame(frame); + } + MOCK_METHOD(bool, OnPathResponseFrame, (const QuicPathResponseFrame&), @@ -2028,6 +2040,8 @@ void SetShouldWriteFail() { write_should_fail_ = true; } + void SetWriteError(int error_code) { write_error_code_ = error_code; } + QuicByteCount GetMaxPacketSize( const QuicSocketAddress& /*peer_address*/) const override { return max_packet_size_; @@ -2223,6 +2237,7 @@ // The soruce/peer address passed into WritePacket(). QuicIpAddress last_write_source_address_; QuicSocketAddress last_write_peer_address_; + int write_error_code_{0}; }; // Parses a packet generated by
diff --git a/quic/tools/quic_client_base.cc b/quic/tools/quic_client_base.cc index 5d89797..f4cd859 100644 --- a/quic/tools/quic_client_base.cc +++ b/quic/tools/quic_client_base.cc
@@ -3,9 +3,12 @@ // found in the LICENSE file. #include "net/third_party/quiche/src/quic/tools/quic_client_base.h" +#include <memory> #include "net/third_party/quiche/src/quic/core/crypto/quic_random.h" #include "net/third_party/quiche/src/quic/core/http/spdy_utils.h" +#include "net/third_party/quiche/src/quic/core/quic_packet_writer.h" +#include "net/third_party/quiche/src/quic/core/quic_path_validator.h" #include "net/third_party/quiche/src/quic/core/quic_server_id.h" #include "net/third_party/quiche/src/quic/core/quic_utils.h" #include "net/third_party/quiche/src/quic/platform/api/quic_flags.h" @@ -13,6 +16,60 @@ namespace quic { +// A path context which owns the writer. +class QUIC_EXPORT_PRIVATE PathMigrationContext + : public QuicPathValidationContext { + public: + PathMigrationContext(std::unique_ptr<QuicPacketWriter> writer, + const QuicSocketAddress& self_address, + const QuicSocketAddress& peer_address) + : QuicPathValidationContext(self_address, peer_address), + alternative_writer_(std::move(writer)) {} + + QuicPacketWriter* WriterToUse() override { return alternative_writer_.get(); } + + QuicPacketWriter* ReleaseWriter() { return alternative_writer_.release(); } + + private: + std::unique_ptr<QuicPacketWriter> alternative_writer_; +}; + +// Implements the basic feature of a result delegate for path validation for +// connection migration. If the validation succeeds, migrate to the alternative +// path. Otherwise, stay on the current path. +class QuicClientSocketMigrationValidationResultDelegate + : public QuicPathValidator::ResultDelegate { + public: + QuicClientSocketMigrationValidationResultDelegate(QuicClientBase* client) + : QuicPathValidator::ResultDelegate(), client_(client) {} + + // QuicPathValidator::ResultDelegate + // Overridden to start migration and takes the ownership of the writer in the + // context. + void OnPathValidationSuccess( + std::unique_ptr<QuicPathValidationContext> context) override { + QUIC_DLOG(INFO) << "Successfully validated path from " << *context + << ". Migrate to it now."; + auto migration_context = std::unique_ptr<PathMigrationContext>( + static_cast<PathMigrationContext*>(context.release())); + client_->session()->MigratePath( + migration_context->self_address(), migration_context->peer_address(), + migration_context->WriterToUse(), /*owns_writer=*/false); + DCHECK(migration_context->WriterToUse() != nullptr); + // Hand the ownership of the alternative writer to the client. + client_->set_writer(migration_context->ReleaseWriter()); + } + + void OnPathValidationFailure( + std::unique_ptr<QuicPathValidationContext> context) override { + QUIC_LOG(WARNING) << "Fail to validate path " << *context + << ", stop migrating."; + } + + private: + QuicClientBase* client_; +}; + QuicClientBase::NetworkHelper::~NetworkHelper() = default; QuicClientBase::QuicClientBase( @@ -203,21 +260,55 @@ } network_helper_->CleanUpAllUDPSockets(); + std::unique_ptr<QuicPacketWriter> writer = + CreateWriterForNewNetwork(new_host, port); + if (writer == nullptr) { + return false; + } + session()->MigratePath(network_helper_->GetLatestClientAddress(), + session()->connection()->peer_address(), writer.get(), + false); + set_writer(writer.release()); + return true; +} - set_bind_to_address(new_host); - if (!network_helper_->CreateUDPSocketAndBind(server_address_, - bind_to_address_, port)) { +bool QuicClientBase::ValidateAndMigrateSocket(const QuicIpAddress& new_host) { + DCHECK(VersionHasIetfQuicFrames( + session_->connection()->version().transport_version) && + session_->connection()->use_path_validator()); + if (!connected()) { return false; } - session()->connection()->SetSelfAddress( - network_helper_->GetLatestClientAddress()); + std::unique_ptr<QuicPacketWriter> writer = + CreateWriterForNewNetwork(new_host, local_port_); + if (writer == nullptr) { + return false; + } + // Asynchronously start migration. + session_->ValidatePath( + std::make_unique<PathMigrationContext>( + std::move(writer), network_helper_->GetLatestClientAddress(), + session_->peer_address()), + std::make_unique<QuicClientSocketMigrationValidationResultDelegate>( + this)); + return true; +} + +std::unique_ptr<QuicPacketWriter> QuicClientBase::CreateWriterForNewNetwork( + const QuicIpAddress& new_host, + int port) { + set_bind_to_address(new_host); + if (!network_helper_->CreateUDPSocketAndBind(server_address_, + bind_to_address_, port)) { + return nullptr; + } QuicPacketWriter* writer = network_helper_->CreateQuicPacketWriter(); - set_writer(writer); - session()->connection()->SetQuicPacketWriter(writer, false); - - return true; + QUIC_LOG_IF(WARNING, writer == writer_.get()) + << "The new writer is wrapped in the same wrapper as the old one, thus " + "appearing to have the same address as the old one."; + return std::unique_ptr<QuicPacketWriter>(writer); } bool QuicClientBase::ChangeEphemeralPort() { @@ -351,4 +442,46 @@ return false; } +bool QuicClientBase::HasPendingPathValidation() { + return session()->HasPendingPathValidation(); +} + +class ValidationResultDelegate : public QuicPathValidator::ResultDelegate { + public: + ValidationResultDelegate(QuicClientBase* client) + : QuicPathValidator::ResultDelegate(), client_(client) {} + + void OnPathValidationSuccess( + std::unique_ptr<QuicPathValidationContext> context) override { + QUIC_DLOG(INFO) << "Successfully validated path from " << *context; + client_->AddValidatedPath(std::move(context)); + } + void OnPathValidationFailure( + std::unique_ptr<QuicPathValidationContext> context) override { + QUIC_LOG(WARNING) << "Fail to validate path " << *context + << ", stop migrating."; + } + + private: + QuicClientBase* client_; +}; + +void QuicClientBase::ValidateNewNetwork(const QuicIpAddress& host) { + std::unique_ptr<QuicPacketWriter> writer = + CreateWriterForNewNetwork(host, local_port_); + auto result_delegate = std::make_unique<ValidationResultDelegate>(this); + if (writer == nullptr) { + result_delegate->OnPathValidationFailure( + std::make_unique<PathMigrationContext>( + nullptr, network_helper_->GetLatestClientAddress(), + session_->peer_address())); + return; + } + session()->ValidatePath( + std::make_unique<PathMigrationContext>( + std::move(writer), network_helper_->GetLatestClientAddress(), + session_->peer_address()), + std::move(result_delegate)); +} + } // namespace quic
diff --git a/quic/tools/quic_client_base.h b/quic/tools/quic_client_base.h index ce2444c..7a531e3 100644 --- a/quic/tools/quic_client_base.h +++ b/quic/tools/quic_client_base.h
@@ -8,6 +8,7 @@ #ifndef QUICHE_QUIC_TOOLS_QUIC_CLIENT_BASE_H_ #define QUICHE_QUIC_TOOLS_QUIC_CLIENT_BASE_H_ +#include <memory> #include <string> #include "absl/base/attributes.h" @@ -121,6 +122,11 @@ // Migrate to a new socket (new_host, port) during an active connection. bool MigrateSocketWithSpecifiedPort(const QuicIpAddress& new_host, int port); + // Validate the new socket and migrate to it if the validation succeeds. + // Otherwise stay on the current socket. Return true if the validation has + // started. + bool ValidateAndMigrateSocket(const QuicIpAddress& new_host); + // Open a new socket to change to a new ephemeral port. bool ChangeEphemeralPort(); @@ -244,6 +250,19 @@ client_connection_id_length_ = client_connection_id_length; } + bool HasPendingPathValidation(); + + void ValidateNewNetwork(const QuicIpAddress& host); + + void AddValidatedPath(std::unique_ptr<QuicPathValidationContext> context) { + validated_paths_.push_back(std::move(context)); + } + + const std::vector<std::unique_ptr<QuicPathValidationContext>>& + validated_paths() const { + return validated_paths_; + } + protected: // TODO(rch): Move GetNumSentClientHellosFromSession and // GetNumReceivedServerConfigUpdatesFromSession into a new/better @@ -299,6 +318,10 @@ // version. bool CanReconnectWithDifferentVersion(ParsedQuicVersion* version) const; + std::unique_ptr<QuicPacketWriter> CreateWriterForNewNetwork( + const QuicIpAddress& new_host, + int port); + // |server_id_| is a tuple (hostname, port, is_https) of the server. QuicServerId server_id_; @@ -370,6 +393,9 @@ // GetClientConnectionId creates a random connection ID of this length. // Defaults to 0. uint8_t client_connection_id_length_; + + // Stores validated paths. + std::vector<std::unique_ptr<QuicPathValidationContext>> validated_paths_; }; } // namespace quic
diff --git a/quic/tools/quic_client_epoll_network_helper.cc b/quic/tools/quic_client_epoll_network_helper.cc index 2a9ebba..9cde39c 100644 --- a/quic/tools/quic_client_epoll_network_helper.cc +++ b/quic/tools/quic_client_epoll_network_helper.cc
@@ -129,7 +129,7 @@ void QuicClientEpollNetworkHelper::OnShutdown(QuicEpollServer* /*eps*/, int /*fd*/) {} -void QuicClientEpollNetworkHelper::OnEvent(int /*fd*/, QuicEpollEvent* event) { +void QuicClientEpollNetworkHelper::OnEvent(int fd, QuicEpollEvent* event) { if (event->in_events & EPOLLIN) { QUIC_DVLOG(1) << "Read packets on EPOLLIN"; int times_to_read = max_reads_per_epoll_loop_; @@ -137,9 +137,8 @@ QuicPacketCount packets_dropped = 0; while (client_->connected() && more_to_read && times_to_read > 0) { more_to_read = packet_reader_->ReadAndDispatchPackets( - GetLatestFD(), GetLatestClientAddress().port(), - *client_->helper()->GetClock(), this, - overflow_supported_ ? &packets_dropped : nullptr); + fd, GetLatestClientAddress().port(), *client_->helper()->GetClock(), + this, overflow_supported_ ? &packets_dropped : nullptr); --times_to_read; } if (packets_dropped_ < packets_dropped) {