| // 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 "net/third_party/quiche/src/quic/core/quic_path_validator.h" | |
| #include <memory> | |
| #include "net/third_party/quiche/src/quic/core/frames/quic_path_challenge_frame.h" | |
| #include "net/third_party/quiche/src/quic/core/quic_constants.h" | |
| #include "net/third_party/quiche/src/quic/core/quic_types.h" | |
| #include "net/third_party/quiche/src/quic/platform/api/quic_ip_address.h" | |
| #include "net/third_party/quiche/src/quic/platform/api/quic_socket_address.h" | |
| #include "net/third_party/quiche/src/quic/platform/api/quic_test.h" | |
| #include "net/third_party/quiche/src/quic/test_tools/mock_clock.h" | |
| #include "net/third_party/quiche/src/quic/test_tools/mock_random.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_test_utils.h" | |
| #include "net/third_party/quiche/src/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&, | |
| 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_(new MockQuicPathValidationContext(self_address_, | |
| peer_address_, | |
| &writer_)), | |
| result_delegate_(new 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}; | |
| MockPacketWriter writer_; | |
| MockQuicPathValidationContext* context_; | |
| MockQuicPathValidationResultDelegate* result_delegate_; | |
| }; | |
| TEST_F(QuicPathValidatorTest, PathValidationSuccessOnFirstRound) { | |
| QuicPathFrameBuffer challenge_data; | |
| EXPECT_CALL(send_delegate_, | |
| SendPathChallenge(_, self_address_, peer_address_, &writer_)) | |
| .WillOnce(Invoke([&](const QuicPathFrameBuffer& payload, | |
| 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_.StartValidingPath( | |
| std::unique_ptr<QuicPathValidationContext>(context_), | |
| std::unique_ptr<MockQuicPathValidationResultDelegate>(result_delegate_)); | |
| EXPECT_TRUE(path_validator_.HasPendingPathValidation()); | |
| 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_, &writer_)) | |
| .WillOnce(Invoke([&](const QuicPathFrameBuffer payload, | |
| 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_.StartValidingPath( | |
| 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_, &writer_)) | |
| .WillOnce(Invoke([&](const QuicPathFrameBuffer& payload, | |
| 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&, | |
| QuicPacketWriter*) { | |
| EXPECT_NE(payload, challenge_data); | |
| return true; | |
| })); | |
| EXPECT_CALL(send_delegate_, GetRetryTimeout(peer_address_, &writer_)) | |
| .Times(2u); | |
| path_validator_.StartValidingPath( | |
| 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_, &writer_)) | |
| .WillOnce(Invoke([&](const QuicPathFrameBuffer& payload, | |
| const QuicSocketAddress&, const QuicSocketAddress&, | |
| QuicPacketWriter*) { | |
| memcpy(challenge_data.data(), payload.data(), payload.size()); | |
| return true; | |
| })) | |
| .WillOnce(Invoke([&](const QuicPathFrameBuffer& payload, | |
| 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_.StartValidingPath( | |
| 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_, &writer_)) | |
| .Times(3u) | |
| .WillRepeatedly(Return(true)); | |
| EXPECT_CALL(send_delegate_, GetRetryTimeout(peer_address_, &writer_)) | |
| .Times(3u); | |
| path_validator_.StartValidingPath( | |
| 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_, &writer_)) | |
| .WillOnce(Invoke([&](const QuicPathFrameBuffer&, 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); | |
| path_validator_.StartValidingPath( | |
| 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 |