blob: 62044b17ceff8439f4725f975602f6e3965e36b4 [file] [log] [blame] [edit]
// 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 "quiche/quic/core/quic_path_validator.h"
#include <memory>
#include "quiche/quic/core/frames/quic_path_challenge_frame.h"
#include "quiche/quic/core/quic_constants.h"
#include "quiche/quic/core/quic_types.h"
#include "quiche/quic/platform/api/quic_ip_address.h"
#include "quiche/quic/platform/api/quic_socket_address.h"
#include "quiche/quic/platform/api/quic_test.h"
#include "quiche/quic/test_tools/mock_clock.h"
#include "quiche/quic/test_tools/mock_random.h"
#include "quiche/quic/test_tools/quic_path_validator_peer.h"
#include "quiche/quic/test_tools/quic_test_utils.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_,
&clock_,
/*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_));
const QuicTime expected_start_time = clock_.Now();
path_validator_.StartPathValidation(
std::unique_ptr<QuicPathValidationContext>(context_),
std::unique_ptr<MockQuicPathValidationResultDelegate>(result_delegate_),
PathValidationReason::kMultiPort);
EXPECT_TRUE(path_validator_.HasPendingPathValidation());
EXPECT_EQ(PathValidationReason::kMultiPort,
path_validator_.GetPathValidationReason());
EXPECT_TRUE(path_validator_.IsValidatingPeerAddress(effective_peer_address_));
EXPECT_CALL(*result_delegate_, OnPathValidationSuccess(_, _))
.WillOnce(
Invoke([=, this](std::unique_ptr<QuicPathValidationContext> context,
QuicTime start_time) {
EXPECT_EQ(context.get(), context_);
EXPECT_EQ(start_time, expected_start_time);
}));
clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(kInitialRttMs));
path_validator_.OnPathResponse(challenge_data, self_address_);
EXPECT_FALSE(path_validator_.HasPendingPathValidation());
EXPECT_EQ(PathValidationReason::kReasonUnknown,
path_validator_.GetPathValidationReason());
}
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_));
const QuicTime expected_start_time = clock_.Now();
path_validator_.StartPathValidation(
std::unique_ptr<QuicPathValidationContext>(context_),
std::unique_ptr<MockQuicPathValidationResultDelegate>(result_delegate_),
PathValidationReason::kMultiPort);
// Reception of a PATH_RESPONSE on a different self address should be ignored.
const QuicSocketAddress kAlternativeSelfAddress(QuicIpAddress::Any6(), 54321);
EXPECT_NE(kAlternativeSelfAddress, self_address_);
clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(kInitialRttMs));
path_validator_.OnPathResponse(challenge_data, kAlternativeSelfAddress);
EXPECT_CALL(*result_delegate_, OnPathValidationSuccess(_, _))
.WillOnce(
Invoke([=, this](std::unique_ptr<QuicPathValidationContext> context,
QuicTime start_time) {
EXPECT_EQ(context->self_address(), self_address_);
EXPECT_EQ(start_time, expected_start_time);
}));
clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(kInitialRttMs));
path_validator_.OnPathResponse(challenge_data, self_address_);
EXPECT_EQ(PathValidationReason::kReasonUnknown,
path_validator_.GetPathValidationReason());
}
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);
const QuicTime start_time = clock_.Now();
path_validator_.StartPathValidation(
std::unique_ptr<QuicPathValidationContext>(context_),
std::unique_ptr<MockQuicPathValidationResultDelegate>(result_delegate_),
PathValidationReason::kMultiPort);
clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(3 * kInitialRttMs));
random_.ChangeValue();
alarm_factory_.FireAlarm(
QuicPathValidatorPeer::retry_timer(&path_validator_));
EXPECT_CALL(*result_delegate_, OnPathValidationSuccess(_, start_time));
// 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_),
PathValidationReason::kMultiPort);
clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(3 * kInitialRttMs));
const QuicTime start_time = clock_.Now();
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(_, start_time));
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_),
PathValidationReason::kMultiPort);
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([=, this](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_));
}
EXPECT_EQ(PathValidationReason::kReasonUnknown,
path_validator_.GetPathValidationReason());
}
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_),
PathValidationReason::kMultiPort);
EXPECT_FALSE(path_validator_.HasPendingPathValidation());
EXPECT_FALSE(QuicPathValidatorPeer::retry_timer(&path_validator_)->IsSet());
EXPECT_EQ(PathValidationReason::kReasonUnknown,
path_validator_.GetPathValidationReason());
}
} // namespace test
} // namespace quic