blob: 519ef43f69c041a1241a1a9aff3fc989e91ae110 [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 "quiche/quic/qbone/qbone_packet_processor.h"
#include <utility>
#include "absl/strings/string_view.h"
#include "quiche/quic/platform/api/quic_test.h"
#include "quiche/quic/qbone/qbone_packet_processor_test_tools.h"
#include "quiche/common/quiche_text_utils.h"
namespace quic::test {
namespace {
using Direction = QbonePacketProcessor::Direction;
using ProcessingResult = QbonePacketProcessor::ProcessingResult;
using OutputInterface = QbonePacketProcessor::OutputInterface;
using ::testing::_;
using ::testing::Eq;
using ::testing::Invoke;
using ::testing::Return;
using ::testing::WithArgs;
// 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,
};
static const char kReferenceEchoRequestData[] = {
// IPv6 with zero TOS and flow label.
0x60, 0x00, 0x00, 0x00,
// Payload size is 64 bytes.
0x00, 64,
// Next header is ICMP
58,
// TTL is 127.
127,
// 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 fe80::71:626f:6e6f
0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x71, 0x62, 0x6f, 0x6e, 0x6f,
// ICMP Type ping request
128,
// ICMP Code 0
0,
// Checksum is not actually checked in any of the tests, so we leave it as
// zero
0x00, 0x00,
// ICMP Identifier (0xcafe to be memorable)
0xca, 0xfe,
// Sequence number
0x00, 0x01,
// Data, starting with unix timeval then 0x10..0x37
0x67, 0x37, 0x8a, 0x63, 0x00, 0x00, 0x00, 0x00,
0x96, 0x58, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27,
0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
};
static const char kReferenceEchoReplyData[] = {
// IPv6 with zero TOS and flow label.
0x60, 0x00, 0x00, 0x00,
// Payload size is 64 bytes.
0x00, 64,
// Next header is ICMP
58,
// TTL is 255.
255,
// IP address of the sender is fd00:4:0:1::1
0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04,
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,
// ICMP Type ping reply
129,
// ICMP Code 0
0,
// Checksum
0x66, 0xb6,
// ICMP Identifier (0xcafe to be memorable)
0xca, 0xfe,
// Sequence number
0x00, 0x01,
// Data, starting with unix timeval then 0x10..0x37
0x67, 0x37, 0x8a, 0x63, 0x00, 0x00, 0x00, 0x00,
0x96, 0x58, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27,
0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
};
// clang-format on
static const absl::string_view kReferenceClientPacket(
kReferenceClientPacketData, ABSL_ARRAYSIZE(kReferenceClientPacketData));
static const absl::string_view kReferenceNetworkPacket(
kReferenceNetworkPacketData, ABSL_ARRAYSIZE(kReferenceNetworkPacketData));
static const absl::string_view kReferenceClientSubnetPacket(
kReferenceClientSubnetPacketData,
ABSL_ARRAYSIZE(kReferenceClientSubnetPacketData));
static const absl::string_view kReferenceEchoRequest(
kReferenceEchoRequestData, ABSL_ARRAYSIZE(kReferenceEchoRequestData));
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());
}
TEST_F(QbonePacketProcessorTest, Icmp6EchoResponseHasRightPayload) {
auto filter = std::make_unique<MockPacketFilter>();
EXPECT_CALL(*filter, FilterPacket(_, _, _, _, _))
.WillOnce(WithArgs<2, 3>(
Invoke([](absl::string_view payload, icmp6_hdr* icmp_header) {
icmp_header->icmp6_type = ICMP6_ECHO_REPLY;
icmp_header->icmp6_code = 0;
auto* request_header =
reinterpret_cast<const icmp6_hdr*>(payload.data());
icmp_header->icmp6_id = request_header->icmp6_id;
icmp_header->icmp6_seq = request_header->icmp6_seq;
return ProcessingResult::ICMP;
})));
processor_->set_filter(std::move(filter));
EXPECT_CALL(stats_, OnPacketDroppedWithIcmp(Direction::FROM_OFF_NETWORK));
EXPECT_CALL(output_, SendPacketToClient(_))
.WillOnce(Invoke([](absl::string_view packet) {
// Explicit conversion because otherwise it is treated as a null
// terminated string.
absl::string_view expected = absl::string_view(
kReferenceEchoReplyData, sizeof(kReferenceEchoReplyData));
EXPECT_THAT(packet, Eq(expected));
QUIC_LOG(INFO) << "ICMP response:\n"
<< quiche::QuicheTextUtils::HexDump(packet);
}));
SendPacketFromClient(kReferenceEchoRequest);
}
} // namespace
} // namespace quic::test