blob: d79400c04188a882271494796af99406db718220 [file] [log] [blame]
// Copyright (c) 2019 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/qbone/qbone_packet_processor.h"
#include <utility>
#include "absl/strings/string_view.h"
#include "quic/platform/api/quic_test.h"
#include "quic/qbone/qbone_packet_processor_test_tools.h"
namespace quic {
namespace {
using Direction = QbonePacketProcessor::Direction;
using ProcessingResult = QbonePacketProcessor::ProcessingResult;
using OutputInterface = QbonePacketProcessor::OutputInterface;
using ::testing::_;
using ::testing::Return;
// clang-format off
static const char kReferenceClientPacketData[] = {
// IPv6 with zero TOS and flow label.
0x60, 0x00, 0x00, 0x00,
// Payload size is 8 bytes.
0x00, 0x08,
// Next header is UDP
17,
// TTL is 50.
50,
// IP address of the sender is fd00:0:0:1::1
0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
// IP address of the receiver is fd00:0:0:5::1
0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
// Source port 12345
0x30, 0x39,
// Destination port 443
0x01, 0xbb,
// UDP content length is zero
0x00, 0x00,
// Checksum is not actually checked in any of the tests, so we leave it as
// zero
0x00, 0x00,
};
static const char kReferenceNetworkPacketData[] = {
// IPv6 with zero TOS and flow label.
0x60, 0x00, 0x00, 0x00,
// Payload size is 8 bytes.
0x00, 0x08,
// Next header is UDP
17,
// TTL is 50.
50,
// IP address of the sender is fd00:0:0:5::1
0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
// IP address of the receiver is fd00:0:0:1::1
0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
// Source port 443
0x01, 0xbb,
// Destination port 12345
0x30, 0x39,
// UDP content length is zero
0x00, 0x00,
// Checksum is not actually checked in any of the tests, so we leave it as
// zero
0x00, 0x00,
};
static const char kReferenceClientSubnetPacketData[] = {
// IPv6 with zero TOS and flow label.
0x60, 0x00, 0x00, 0x00,
// Payload size is 8 bytes.
0x00, 0x08,
// Next header is UDP
17,
// TTL is 50.
50,
// IP address of the sender is fd00:0:0:2::1, which is within the /62 of the
// client.
0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
// IP address of the receiver is fd00:0:0:5::1
0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
// Source port 12345
0x30, 0x39,
// Destination port 443
0x01, 0xbb,
// UDP content length is zero
0x00, 0x00,
// Checksum is not actually checked in any of the tests, so we leave it as
// zero
0x00, 0x00,
};
// clang-format on
static const absl::string_view kReferenceClientPacket(
kReferenceClientPacketData,
arraysize(kReferenceClientPacketData));
static const absl::string_view kReferenceNetworkPacket(
kReferenceNetworkPacketData,
arraysize(kReferenceNetworkPacketData));
static const absl::string_view kReferenceClientSubnetPacket(
kReferenceClientSubnetPacketData,
arraysize(kReferenceClientSubnetPacketData));
MATCHER_P(IsIcmpMessage,
icmp_type,
"Checks whether the argument is an ICMP message of supplied type") {
if (arg.size() < kTotalICMPv6HeaderSize) {
return false;
}
return arg[40] == icmp_type;
}
class MockPacketFilter : public QbonePacketProcessor::Filter {
public:
MOCK_METHOD(ProcessingResult,
FilterPacket,
(Direction,
absl::string_view,
absl::string_view,
icmp6_hdr*,
OutputInterface*),
(override));
};
class QbonePacketProcessorTest : public QuicTest {
protected:
QbonePacketProcessorTest() {
QUICHE_CHECK(client_ip_.FromString("fd00:0:0:1::1"));
QUICHE_CHECK(self_ip_.FromString("fd00:0:0:4::1"));
QUICHE_CHECK(network_ip_.FromString("fd00:0:0:5::1"));
processor_ = std::make_unique<QbonePacketProcessor>(
self_ip_, client_ip_, /*client_ip_subnet_length=*/62, &output_,
&stats_);
}
void SendPacketFromClient(absl::string_view packet) {
std::string packet_buffer(packet.data(), packet.size());
processor_->ProcessPacket(&packet_buffer, Direction::FROM_OFF_NETWORK);
}
void SendPacketFromNetwork(absl::string_view packet) {
std::string packet_buffer(packet.data(), packet.size());
processor_->ProcessPacket(&packet_buffer, Direction::FROM_NETWORK);
}
QuicIpAddress client_ip_;
QuicIpAddress self_ip_;
QuicIpAddress network_ip_;
std::unique_ptr<QbonePacketProcessor> processor_;
testing::StrictMock<MockPacketProcessorOutput> output_;
testing::StrictMock<MockPacketProcessorStats> stats_;
};
TEST_F(QbonePacketProcessorTest, EmptyPacket) {
EXPECT_CALL(stats_, OnPacketDroppedSilently(Direction::FROM_OFF_NETWORK));
SendPacketFromClient("");
EXPECT_CALL(stats_, OnPacketDroppedSilently(Direction::FROM_NETWORK));
SendPacketFromNetwork("");
}
TEST_F(QbonePacketProcessorTest, RandomGarbage) {
EXPECT_CALL(stats_, OnPacketDroppedSilently(Direction::FROM_OFF_NETWORK));
SendPacketFromClient(std::string(1280, 'a'));
EXPECT_CALL(stats_, OnPacketDroppedSilently(Direction::FROM_NETWORK));
SendPacketFromNetwork(std::string(1280, 'a'));
}
TEST_F(QbonePacketProcessorTest, RandomGarbageWithCorrectLengthFields) {
std::string packet(40, 'a');
packet[4] = 0;
packet[5] = 0;
EXPECT_CALL(stats_, OnPacketDroppedWithIcmp(Direction::FROM_OFF_NETWORK));
EXPECT_CALL(output_, SendPacketToClient(IsIcmpMessage(ICMP6_DST_UNREACH)));
SendPacketFromClient(packet);
}
TEST_F(QbonePacketProcessorTest, GoodPacketFromClient) {
EXPECT_CALL(stats_, OnPacketForwarded(Direction::FROM_OFF_NETWORK));
EXPECT_CALL(output_, SendPacketToNetwork(_));
SendPacketFromClient(kReferenceClientPacket);
}
TEST_F(QbonePacketProcessorTest, GoodPacketFromClientSubnet) {
EXPECT_CALL(stats_, OnPacketForwarded(Direction::FROM_OFF_NETWORK));
EXPECT_CALL(output_, SendPacketToNetwork(_));
SendPacketFromClient(kReferenceClientSubnetPacket);
}
TEST_F(QbonePacketProcessorTest, GoodPacketFromNetwork) {
EXPECT_CALL(stats_, OnPacketForwarded(Direction::FROM_NETWORK));
EXPECT_CALL(output_, SendPacketToClient(_));
SendPacketFromNetwork(kReferenceNetworkPacket);
}
TEST_F(QbonePacketProcessorTest, GoodPacketFromNetworkWrongDirection) {
EXPECT_CALL(stats_, OnPacketDroppedWithIcmp(Direction::FROM_OFF_NETWORK));
EXPECT_CALL(output_, SendPacketToClient(IsIcmpMessage(ICMP6_DST_UNREACH)));
SendPacketFromClient(kReferenceNetworkPacket);
}
TEST_F(QbonePacketProcessorTest, TtlExpired) {
std::string packet(kReferenceNetworkPacket);
packet[7] = 1;
EXPECT_CALL(stats_, OnPacketDroppedWithIcmp(Direction::FROM_NETWORK));
EXPECT_CALL(output_, SendPacketToNetwork(IsIcmpMessage(ICMP6_TIME_EXCEEDED)));
SendPacketFromNetwork(packet);
}
TEST_F(QbonePacketProcessorTest, UnknownProtocol) {
std::string packet(kReferenceNetworkPacket);
packet[6] = IPPROTO_SCTP;
EXPECT_CALL(stats_, OnPacketDroppedWithIcmp(Direction::FROM_NETWORK));
EXPECT_CALL(output_, SendPacketToNetwork(IsIcmpMessage(ICMP6_PARAM_PROB)));
SendPacketFromNetwork(packet);
}
TEST_F(QbonePacketProcessorTest, FilterFromClient) {
auto filter = std::make_unique<MockPacketFilter>();
EXPECT_CALL(*filter, FilterPacket(_, _, _, _, _))
.WillRepeatedly(Return(ProcessingResult::SILENT_DROP));
processor_->set_filter(std::move(filter));
EXPECT_CALL(stats_, OnPacketDroppedSilently(Direction::FROM_OFF_NETWORK));
SendPacketFromClient(kReferenceClientPacket);
}
class TestFilter : public QbonePacketProcessor::Filter {
public:
TestFilter(QuicIpAddress client_ip, QuicIpAddress network_ip)
: client_ip_(client_ip), network_ip_(network_ip) {}
ProcessingResult FilterPacket(Direction direction,
absl::string_view full_packet,
absl::string_view payload,
icmp6_hdr* icmp_header,
OutputInterface* output) override {
EXPECT_EQ(kIPv6HeaderSize, full_packet.size() - payload.size());
EXPECT_EQ(IPPROTO_UDP, TransportProtocolFromHeader(full_packet));
EXPECT_EQ(client_ip_, SourceIpFromHeader(full_packet));
EXPECT_EQ(network_ip_, DestinationIpFromHeader(full_packet));
called_++;
return ProcessingResult::SILENT_DROP;
}
int called() const { return called_; }
private:
int called_ = 0;
QuicIpAddress client_ip_;
QuicIpAddress network_ip_;
};
// Verify that the parameters are passed correctly into the filter, and that the
// helper functions of the filter class work.
TEST_F(QbonePacketProcessorTest, FilterHelperFunctions) {
auto filter_owned = std::make_unique<TestFilter>(client_ip_, network_ip_);
TestFilter* filter = filter_owned.get();
processor_->set_filter(std::move(filter_owned));
EXPECT_CALL(stats_, OnPacketDroppedSilently(Direction::FROM_OFF_NETWORK));
SendPacketFromClient(kReferenceClientPacket);
ASSERT_EQ(1, filter->called());
}
} // namespace
} // namespace quic