| // 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 |