| // 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. | 
 |  | 
 | #include "quic/core/quic_path_validator.h" | 
 |  | 
 | #include <memory> | 
 |  | 
 | #include "quic/core/frames/quic_path_challenge_frame.h" | 
 | #include "quic/core/quic_constants.h" | 
 | #include "quic/core/quic_types.h" | 
 | #include "quic/platform/api/quic_ip_address.h" | 
 | #include "quic/platform/api/quic_socket_address.h" | 
 | #include "quic/platform/api/quic_test.h" | 
 | #include "quic/test_tools/mock_clock.h" | 
 | #include "quic/test_tools/mock_random.h" | 
 | #include "quic/test_tools/quic_path_validator_peer.h" | 
 | #include "quic/test_tools/quic_test_utils.h" | 
 | #include "quic/test_tools/quic_transport_test_tools.h" | 
 |  | 
 | using testing::_; | 
 | using testing::Invoke; | 
 | using testing::Return; | 
 |  | 
 | namespace quic { | 
 | namespace test { | 
 |  | 
 | class MockSendDelegate : public QuicPathValidator::SendDelegate { | 
 |  public: | 
 |   // Send a PATH_CHALLENGE frame using given path information and populate | 
 |   // |data_buffer| with the frame payload. Return true if the validator should | 
 |   // move forward in validation, i.e. arm the retry timer. | 
 |   MOCK_METHOD(bool, | 
 |               SendPathChallenge, | 
 |               (const QuicPathFrameBuffer&, | 
 |                const QuicSocketAddress&, | 
 |                const QuicSocketAddress&, | 
 |                const QuicSocketAddress&, | 
 |                QuicPacketWriter*), | 
 |               (override)); | 
 |  | 
 |   MOCK_METHOD(QuicTime, | 
 |               GetRetryTimeout, | 
 |               (const QuicSocketAddress&, QuicPacketWriter*), | 
 |               (const, override)); | 
 | }; | 
 |  | 
 | class QuicPathValidatorTest : public QuicTest { | 
 |  public: | 
 |   QuicPathValidatorTest() | 
 |       : path_validator_(&alarm_factory_, &arena_, &send_delegate_, &random_, | 
 |                         /*context=*/nullptr), | 
 |         context_(new MockQuicPathValidationContext( | 
 |             self_address_, peer_address_, effective_peer_address_, &writer_)), | 
 |         result_delegate_( | 
 |             new testing::StrictMock<MockQuicPathValidationResultDelegate>()) { | 
 |     clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(1)); | 
 |     ON_CALL(send_delegate_, GetRetryTimeout(_, _)) | 
 |         .WillByDefault( | 
 |             Return(clock_.ApproximateNow() + | 
 |                    3 * QuicTime::Delta::FromMilliseconds(kInitialRttMs))); | 
 |   } | 
 |  | 
 |  protected: | 
 |   quic::test::MockAlarmFactory alarm_factory_; | 
 |   MockSendDelegate send_delegate_; | 
 |   MockRandom random_; | 
 |   MockClock clock_; | 
 |   QuicConnectionArena arena_; | 
 |   QuicPathValidator path_validator_; | 
 |   QuicSocketAddress self_address_{QuicIpAddress::Any4(), 443}; | 
 |   QuicSocketAddress peer_address_{QuicIpAddress::Loopback4(), 443}; | 
 |   QuicSocketAddress effective_peer_address_{QuicIpAddress::Loopback4(), 12345}; | 
 |   MockPacketWriter writer_; | 
 |   MockQuicPathValidationContext* context_; | 
 |   MockQuicPathValidationResultDelegate* result_delegate_; | 
 | }; | 
 |  | 
 | TEST_F(QuicPathValidatorTest, PathValidationSuccessOnFirstRound) { | 
 |   QuicPathFrameBuffer challenge_data; | 
 |   EXPECT_CALL(send_delegate_, | 
 |               SendPathChallenge(_, self_address_, peer_address_, | 
 |                                 effective_peer_address_, &writer_)) | 
 |       .WillOnce(Invoke([&](const QuicPathFrameBuffer& payload, | 
 |                            const QuicSocketAddress&, const QuicSocketAddress&, | 
 |                            const QuicSocketAddress&, QuicPacketWriter*) { | 
 |         memcpy(challenge_data.data(), payload.data(), payload.size()); | 
 |         return true; | 
 |       })); | 
 |   EXPECT_CALL(send_delegate_, GetRetryTimeout(peer_address_, &writer_)); | 
 |   path_validator_.StartPathValidation( | 
 |       std::unique_ptr<QuicPathValidationContext>(context_), | 
 |       std::unique_ptr<MockQuicPathValidationResultDelegate>(result_delegate_)); | 
 |   EXPECT_TRUE(path_validator_.HasPendingPathValidation()); | 
 |   EXPECT_TRUE(path_validator_.IsValidatingPeerAddress(effective_peer_address_)); | 
 |   EXPECT_CALL(*result_delegate_, OnPathValidationSuccess(_)) | 
 |       .WillOnce(Invoke([=](std::unique_ptr<QuicPathValidationContext> context) { | 
 |         EXPECT_EQ(context.get(), context_); | 
 |       })); | 
 |   path_validator_.OnPathResponse(challenge_data, self_address_); | 
 |   EXPECT_FALSE(path_validator_.HasPendingPathValidation()); | 
 | } | 
 |  | 
 | TEST_F(QuicPathValidatorTest, RespondWithDifferentSelfAddress) { | 
 |   QuicPathFrameBuffer challenge_data; | 
 |   EXPECT_CALL(send_delegate_, | 
 |               SendPathChallenge(_, self_address_, peer_address_, | 
 |                                 effective_peer_address_, &writer_)) | 
 |       .WillOnce(Invoke([&](const QuicPathFrameBuffer payload, | 
 |                            const QuicSocketAddress&, const QuicSocketAddress&, | 
 |                            const QuicSocketAddress&, QuicPacketWriter*) { | 
 |         memcpy(challenge_data.data(), payload.data(), payload.size()); | 
 |         return true; | 
 |       })); | 
 |   EXPECT_CALL(send_delegate_, GetRetryTimeout(peer_address_, &writer_)); | 
 |   path_validator_.StartPathValidation( | 
 |       std::unique_ptr<QuicPathValidationContext>(context_), | 
 |       std::unique_ptr<MockQuicPathValidationResultDelegate>(result_delegate_)); | 
 |  | 
 |   // Reception of a PATH_RESPONSE on a different self address should be ignored. | 
 |   const QuicSocketAddress kAlternativeSelfAddress(QuicIpAddress::Any6(), 54321); | 
 |   EXPECT_NE(kAlternativeSelfAddress, self_address_); | 
 |   path_validator_.OnPathResponse(challenge_data, kAlternativeSelfAddress); | 
 |  | 
 |   EXPECT_CALL(*result_delegate_, OnPathValidationSuccess(_)) | 
 |       .WillOnce(Invoke([=](std::unique_ptr<QuicPathValidationContext> context) { | 
 |         EXPECT_EQ(context->self_address(), self_address_); | 
 |       })); | 
 |   path_validator_.OnPathResponse(challenge_data, self_address_); | 
 | } | 
 |  | 
 | TEST_F(QuicPathValidatorTest, RespondAfter1stRetry) { | 
 |   QuicPathFrameBuffer challenge_data; | 
 |   EXPECT_CALL(send_delegate_, | 
 |               SendPathChallenge(_, self_address_, peer_address_, | 
 |                                 effective_peer_address_, &writer_)) | 
 |       .WillOnce(Invoke([&](const QuicPathFrameBuffer& payload, | 
 |                            const QuicSocketAddress&, const QuicSocketAddress&, | 
 |                            const QuicSocketAddress&, QuicPacketWriter*) { | 
 |         // Store up the 1st PATH_CHALLANGE payload. | 
 |         memcpy(challenge_data.data(), payload.data(), payload.size()); | 
 |         return true; | 
 |       })) | 
 |       .WillOnce(Invoke([&](const QuicPathFrameBuffer& payload, | 
 |                            const QuicSocketAddress&, const QuicSocketAddress&, | 
 |                            const QuicSocketAddress&, QuicPacketWriter*) { | 
 |         EXPECT_NE(payload, challenge_data); | 
 |         return true; | 
 |       })); | 
 |   EXPECT_CALL(send_delegate_, GetRetryTimeout(peer_address_, &writer_)) | 
 |       .Times(2u); | 
 |   path_validator_.StartPathValidation( | 
 |       std::unique_ptr<QuicPathValidationContext>(context_), | 
 |       std::unique_ptr<MockQuicPathValidationResultDelegate>(result_delegate_)); | 
 |  | 
 |   clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(3 * kInitialRttMs)); | 
 |   random_.ChangeValue(); | 
 |   alarm_factory_.FireAlarm( | 
 |       QuicPathValidatorPeer::retry_timer(&path_validator_)); | 
 |  | 
 |   EXPECT_CALL(*result_delegate_, OnPathValidationSuccess(_)); | 
 |   // Respond to the 1st PATH_CHALLENGE should complete the validation. | 
 |   path_validator_.OnPathResponse(challenge_data, self_address_); | 
 |   EXPECT_FALSE(path_validator_.HasPendingPathValidation()); | 
 | } | 
 |  | 
 | TEST_F(QuicPathValidatorTest, RespondToRetryChallenge) { | 
 |   QuicPathFrameBuffer challenge_data; | 
 |   EXPECT_CALL(send_delegate_, | 
 |               SendPathChallenge(_, self_address_, peer_address_, | 
 |                                 effective_peer_address_, &writer_)) | 
 |       .WillOnce(Invoke([&](const QuicPathFrameBuffer& payload, | 
 |                            const QuicSocketAddress&, const QuicSocketAddress&, | 
 |                            const QuicSocketAddress&, QuicPacketWriter*) { | 
 |         memcpy(challenge_data.data(), payload.data(), payload.size()); | 
 |         return true; | 
 |       })) | 
 |       .WillOnce(Invoke([&](const QuicPathFrameBuffer& payload, | 
 |                            const QuicSocketAddress&, const QuicSocketAddress&, | 
 |                            const QuicSocketAddress&, QuicPacketWriter*) { | 
 |         EXPECT_NE(challenge_data, payload); | 
 |         memcpy(challenge_data.data(), payload.data(), payload.size()); | 
 |         return true; | 
 |       })); | 
 |   EXPECT_CALL(send_delegate_, GetRetryTimeout(peer_address_, &writer_)) | 
 |       .Times(2u); | 
 |   path_validator_.StartPathValidation( | 
 |       std::unique_ptr<QuicPathValidationContext>(context_), | 
 |       std::unique_ptr<MockQuicPathValidationResultDelegate>(result_delegate_)); | 
 |  | 
 |   clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(3 * kInitialRttMs)); | 
 |   random_.ChangeValue(); | 
 |   alarm_factory_.FireAlarm( | 
 |       QuicPathValidatorPeer::retry_timer(&path_validator_)); | 
 |  | 
 |   // Respond to the 2nd PATH_CHALLENGE should complete the validation. | 
 |   EXPECT_CALL(*result_delegate_, OnPathValidationSuccess(_)); | 
 |   path_validator_.OnPathResponse(challenge_data, self_address_); | 
 |   EXPECT_FALSE(path_validator_.HasPendingPathValidation()); | 
 | } | 
 |  | 
 | TEST_F(QuicPathValidatorTest, ValidationTimeOut) { | 
 |   EXPECT_CALL(send_delegate_, | 
 |               SendPathChallenge(_, self_address_, peer_address_, | 
 |                                 effective_peer_address_, &writer_)) | 
 |       .Times(3u) | 
 |       .WillRepeatedly(Return(true)); | 
 |   EXPECT_CALL(send_delegate_, GetRetryTimeout(peer_address_, &writer_)) | 
 |       .Times(3u); | 
 |   path_validator_.StartPathValidation( | 
 |       std::unique_ptr<QuicPathValidationContext>(context_), | 
 |       std::unique_ptr<MockQuicPathValidationResultDelegate>(result_delegate_)); | 
 |  | 
 |   QuicPathFrameBuffer challenge_data; | 
 |   memset(challenge_data.data(), 'a', challenge_data.size()); | 
 |   // Reception of a PATH_RESPONSE with different payload should be ignored. | 
 |   path_validator_.OnPathResponse(challenge_data, self_address_); | 
 |  | 
 |   // Retry 3 times. The 3rd time should fail the validation. | 
 |   EXPECT_CALL(*result_delegate_, OnPathValidationFailure(_)) | 
 |       .WillOnce(Invoke([=](std::unique_ptr<QuicPathValidationContext> context) { | 
 |         EXPECT_EQ(context_, context.get()); | 
 |       })); | 
 |   for (size_t i = 0; i <= QuicPathValidator::kMaxRetryTimes; ++i) { | 
 |     clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(3 * kInitialRttMs)); | 
 |     alarm_factory_.FireAlarm( | 
 |         QuicPathValidatorPeer::retry_timer(&path_validator_)); | 
 |   } | 
 | } | 
 |  | 
 | TEST_F(QuicPathValidatorTest, SendPathChallengeError) { | 
 |   EXPECT_CALL(send_delegate_, | 
 |               SendPathChallenge(_, self_address_, peer_address_, | 
 |                                 effective_peer_address_, &writer_)) | 
 |       .WillOnce(Invoke([&](const QuicPathFrameBuffer&, const QuicSocketAddress&, | 
 |                            const QuicSocketAddress&, const QuicSocketAddress&, | 
 |                            QuicPacketWriter*) { | 
 |         // Abandon this validation in the call stack shouldn't cause crash and | 
 |         // should cancel the alarm. | 
 |         path_validator_.CancelPathValidation(); | 
 |         return false; | 
 |       })); | 
 |   EXPECT_CALL(send_delegate_, GetRetryTimeout(peer_address_, &writer_)) | 
 |       .Times(0u); | 
 |   EXPECT_CALL(*result_delegate_, OnPathValidationFailure(_)); | 
 |   path_validator_.StartPathValidation( | 
 |       std::unique_ptr<QuicPathValidationContext>(context_), | 
 |       std::unique_ptr<MockQuicPathValidationResultDelegate>(result_delegate_)); | 
 |   EXPECT_FALSE(path_validator_.HasPendingPathValidation()); | 
 |   EXPECT_FALSE(QuicPathValidatorPeer::retry_timer(&path_validator_)->IsSet()); | 
 | } | 
 |  | 
 | }  // namespace test | 
 | }  // namespace quic |