| // 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 <cstring> | 
 |  | 
 | #include "absl/strings/string_view.h" | 
 | #include "quic/platform/api/quic_bug_tracker.h" | 
 | #include "quic/platform/api/quic_ip_address_family.h" | 
 | #include "quic/platform/api/quic_logging.h" | 
 | #include "quic/qbone/platform/icmp_packet.h" | 
 | #include "quic/qbone/platform/internet_checksum.h" | 
 | #include "quic/qbone/platform/tcp_packet.h" | 
 | #include "common/quiche_endian.h" | 
 |  | 
 | namespace { | 
 |  | 
 | constexpr size_t kIPv6AddressSize = 16; | 
 | constexpr size_t kIPv6MinPacketSize = 1280; | 
 | constexpr size_t kIcmpTtl = 64; | 
 | constexpr size_t kICMPv6DestinationUnreachableDueToSourcePolicy = 5; | 
 |  | 
 | }  // namespace | 
 |  | 
 | namespace quic { | 
 |  | 
 | const QuicIpAddress QbonePacketProcessor::kInvalidIpAddress = | 
 |     QuicIpAddress::Any6(); | 
 |  | 
 | QbonePacketProcessor::QbonePacketProcessor(QuicIpAddress self_ip, | 
 |                                            QuicIpAddress client_ip, | 
 |                                            size_t client_ip_subnet_length, | 
 |                                            OutputInterface* output, | 
 |                                            StatsInterface* stats) | 
 |     : client_ip_(client_ip), | 
 |       output_(output), | 
 |       stats_(stats), | 
 |       filter_(new Filter) { | 
 |   memcpy(self_ip_.s6_addr, self_ip.ToPackedString().data(), kIPv6AddressSize); | 
 |   QUICHE_DCHECK_LE(client_ip_subnet_length, kIPv6AddressSize * 8); | 
 |   client_ip_subnet_length_ = client_ip_subnet_length; | 
 |  | 
 |   QUICHE_DCHECK(IpAddressFamily::IP_V6 == self_ip.address_family()); | 
 |   QUICHE_DCHECK(IpAddressFamily::IP_V6 == client_ip.address_family()); | 
 |   QUICHE_DCHECK(self_ip != kInvalidIpAddress); | 
 | } | 
 |  | 
 | QbonePacketProcessor::OutputInterface::~OutputInterface() {} | 
 | QbonePacketProcessor::StatsInterface::~StatsInterface() {} | 
 | QbonePacketProcessor::Filter::~Filter() {} | 
 |  | 
 | QbonePacketProcessor::ProcessingResult | 
 | QbonePacketProcessor::Filter::FilterPacket(Direction direction, | 
 |                                            absl::string_view full_packet, | 
 |                                            absl::string_view payload, | 
 |                                            icmp6_hdr* icmp_header, | 
 |                                            OutputInterface* output) { | 
 |   return ProcessingResult::OK; | 
 | } | 
 |  | 
 | void QbonePacketProcessor::ProcessPacket(std::string* packet, | 
 |                                          Direction direction) { | 
 |   if (QUIC_PREDICT_FALSE(!IsValid())) { | 
 |     QUIC_BUG(quic_bug_11024_1) | 
 |         << "QuicPacketProcessor is invoked in an invalid state."; | 
 |     stats_->OnPacketDroppedSilently(direction); | 
 |     return; | 
 |   } | 
 |  | 
 |   uint8_t transport_protocol; | 
 |   char* transport_data; | 
 |   icmp6_hdr icmp_header; | 
 |   memset(&icmp_header, 0, sizeof(icmp_header)); | 
 |   ProcessingResult result = ProcessIPv6HeaderAndFilter( | 
 |       packet, direction, &transport_protocol, &transport_data, &icmp_header); | 
 |  | 
 |   switch (result) { | 
 |     case ProcessingResult::OK: | 
 |       switch (direction) { | 
 |         case Direction::FROM_OFF_NETWORK: | 
 |           output_->SendPacketToNetwork(*packet); | 
 |           break; | 
 |         case Direction::FROM_NETWORK: | 
 |           output_->SendPacketToClient(*packet); | 
 |           break; | 
 |       } | 
 |       stats_->OnPacketForwarded(direction); | 
 |       break; | 
 |     case ProcessingResult::SILENT_DROP: | 
 |       stats_->OnPacketDroppedSilently(direction); | 
 |       break; | 
 |     case ProcessingResult::DEFER: | 
 |       stats_->OnPacketDeferred(direction); | 
 |       break; | 
 |     case ProcessingResult::ICMP: | 
 |       SendIcmpResponse(&icmp_header, *packet, direction); | 
 |       stats_->OnPacketDroppedWithIcmp(direction); | 
 |       break; | 
 |     case ProcessingResult::ICMP_AND_TCP_RESET: | 
 |       SendIcmpResponse(&icmp_header, *packet, direction); | 
 |       stats_->OnPacketDroppedWithIcmp(direction); | 
 |       SendTcpReset(*packet, direction); | 
 |       stats_->OnPacketDroppedWithTcpReset(direction); | 
 |       break; | 
 |     case ProcessingResult::TCP_RESET: | 
 |       SendTcpReset(*packet, direction); | 
 |       stats_->OnPacketDroppedWithTcpReset(direction); | 
 |       break; | 
 |   } | 
 | } | 
 |  | 
 | QbonePacketProcessor::ProcessingResult | 
 | QbonePacketProcessor::ProcessIPv6HeaderAndFilter(std::string* packet, | 
 |                                                  Direction direction, | 
 |                                                  uint8_t* transport_protocol, | 
 |                                                  char** transport_data, | 
 |                                                  icmp6_hdr* icmp_header) { | 
 |   ProcessingResult result = ProcessIPv6Header( | 
 |       packet, direction, transport_protocol, transport_data, icmp_header); | 
 |  | 
 |   if (result == ProcessingResult::OK) { | 
 |     char* packet_data = &*packet->begin(); | 
 |     size_t header_size = *transport_data - packet_data; | 
 |     // Sanity-check the bounds. | 
 |     if (packet_data >= *transport_data || header_size > packet->size() || | 
 |         header_size < kIPv6HeaderSize) { | 
 |       QUIC_BUG(quic_bug_11024_2) | 
 |           << "Invalid pointers encountered in " | 
 |              "QbonePacketProcessor::ProcessPacket.  Dropping the packet"; | 
 |       return ProcessingResult::SILENT_DROP; | 
 |     } | 
 |  | 
 |     result = filter_->FilterPacket( | 
 |         direction, *packet, | 
 |         absl::string_view(*transport_data, packet->size() - header_size), | 
 |         icmp_header, output_); | 
 |   } | 
 |  | 
 |   // Do not send ICMP error messages in response to ICMP errors. | 
 |   if (result == ProcessingResult::ICMP) { | 
 |     const uint8_t* header = reinterpret_cast<const uint8_t*>(packet->data()); | 
 |  | 
 |     constexpr size_t kIPv6NextHeaderOffset = 6; | 
 |     constexpr size_t kIcmpMessageTypeOffset = kIPv6HeaderSize + 0; | 
 |     constexpr size_t kIcmpMessageTypeMaxError = 127; | 
 |     if ( | 
 |         // Check size. | 
 |         packet->size() >= (kIPv6HeaderSize + kICMPv6HeaderSize) && | 
 |         // Check that the packet is in fact ICMP. | 
 |         header[kIPv6NextHeaderOffset] == IPPROTO_ICMPV6 && | 
 |         // Check that ICMP message type is an error. | 
 |         header[kIcmpMessageTypeOffset] < kIcmpMessageTypeMaxError) { | 
 |       result = ProcessingResult::SILENT_DROP; | 
 |     } | 
 |   } | 
 |  | 
 |   return result; | 
 | } | 
 |  | 
 | QbonePacketProcessor::ProcessingResult QbonePacketProcessor::ProcessIPv6Header( | 
 |     std::string* packet, | 
 |     Direction direction, | 
 |     uint8_t* transport_protocol, | 
 |     char** transport_data, | 
 |     icmp6_hdr* icmp_header) { | 
 |   // Check if the packet is big enough to have IPv6 header. | 
 |   if (packet->size() < kIPv6HeaderSize) { | 
 |     QUIC_DVLOG(1) << "Dropped malformed packet: IPv6 header too short"; | 
 |     return ProcessingResult::SILENT_DROP; | 
 |   } | 
 |  | 
 |   // Check version field. | 
 |   ip6_hdr* header = reinterpret_cast<ip6_hdr*>(&*packet->begin()); | 
 |   if (header->ip6_vfc >> 4 != 6) { | 
 |     QUIC_DVLOG(1) << "Dropped malformed packet: IP version is not IPv6"; | 
 |     return ProcessingResult::SILENT_DROP; | 
 |   } | 
 |  | 
 |   // Check payload size. | 
 |   const size_t declared_payload_size = | 
 |       quiche::QuicheEndian::NetToHost16(header->ip6_plen); | 
 |   const size_t actual_payload_size = packet->size() - kIPv6HeaderSize; | 
 |   if (declared_payload_size != actual_payload_size) { | 
 |     QUIC_DVLOG(1) | 
 |         << "Dropped malformed packet: incorrect packet length specified"; | 
 |     return ProcessingResult::SILENT_DROP; | 
 |   } | 
 |  | 
 |   // Check that the address of the client is in the packet. | 
 |   QuicIpAddress address_to_check; | 
 |   uint8_t address_reject_code; | 
 |   bool ip_parse_result; | 
 |   switch (direction) { | 
 |     case Direction::FROM_OFF_NETWORK: | 
 |       // Expect the source IP to match the client. | 
 |       ip_parse_result = address_to_check.FromPackedString( | 
 |           reinterpret_cast<const char*>(&header->ip6_src), | 
 |           sizeof(header->ip6_src)); | 
 |       address_reject_code = kICMPv6DestinationUnreachableDueToSourcePolicy; | 
 |       break; | 
 |     case Direction::FROM_NETWORK: | 
 |       // Expect the destination IP to match the client. | 
 |       ip_parse_result = address_to_check.FromPackedString( | 
 |           reinterpret_cast<const char*>(&header->ip6_dst), | 
 |           sizeof(header->ip6_src)); | 
 |       address_reject_code = ICMP6_DST_UNREACH_NOROUTE; | 
 |       break; | 
 |   } | 
 |   QUICHE_DCHECK(ip_parse_result); | 
 |   if (!client_ip_.InSameSubnet(address_to_check, client_ip_subnet_length_)) { | 
 |     QUIC_DVLOG(1) | 
 |         << "Dropped packet: source/destination address is not client's"; | 
 |     icmp_header->icmp6_type = ICMP6_DST_UNREACH; | 
 |     icmp_header->icmp6_code = address_reject_code; | 
 |     return ProcessingResult::ICMP; | 
 |   } | 
 |  | 
 |   // Check and decrement TTL. | 
 |   if (header->ip6_hops <= 1) { | 
 |     icmp_header->icmp6_type = ICMP6_TIME_EXCEEDED; | 
 |     icmp_header->icmp6_code = ICMP6_TIME_EXCEED_TRANSIT; | 
 |     return ProcessingResult::ICMP; | 
 |   } | 
 |   header->ip6_hops--; | 
 |  | 
 |   // Check and extract IP headers. | 
 |   switch (header->ip6_nxt) { | 
 |     case IPPROTO_TCP: | 
 |     case IPPROTO_UDP: | 
 |     case IPPROTO_ICMPV6: | 
 |       *transport_protocol = header->ip6_nxt; | 
 |       *transport_data = (&*packet->begin()) + kIPv6HeaderSize; | 
 |       break; | 
 |     default: | 
 |       icmp_header->icmp6_type = ICMP6_PARAM_PROB; | 
 |       icmp_header->icmp6_code = ICMP6_PARAMPROB_NEXTHEADER; | 
 |       return ProcessingResult::ICMP; | 
 |   } | 
 |  | 
 |   return ProcessingResult::OK; | 
 | } | 
 |  | 
 | void QbonePacketProcessor::SendIcmpResponse(icmp6_hdr* icmp_header, | 
 |                                             absl::string_view original_packet, | 
 |                                             Direction original_direction) { | 
 |   in6_addr dst; | 
 |   // TODO(b/70339814): ensure this is actually a unicast address. | 
 |   memcpy(dst.s6_addr, &original_packet[8], kIPv6AddressSize); | 
 |  | 
 |   CreateIcmpPacket(self_ip_, dst, *icmp_header, original_packet, | 
 |                    [this, original_direction](absl::string_view packet) { | 
 |                      SendResponse(original_direction, packet); | 
 |                    }); | 
 | } | 
 |  | 
 | void QbonePacketProcessor::SendTcpReset(absl::string_view original_packet, | 
 |                                         Direction original_direction) { | 
 |   CreateTcpResetPacket(original_packet, | 
 |                        [this, original_direction](absl::string_view packet) { | 
 |                          SendResponse(original_direction, packet); | 
 |                        }); | 
 | } | 
 |  | 
 | void QbonePacketProcessor::SendResponse(Direction original_direction, | 
 |                                         absl::string_view packet) { | 
 |   switch (original_direction) { | 
 |     case Direction::FROM_OFF_NETWORK: | 
 |       output_->SendPacketToClient(packet); | 
 |       break; | 
 |     case Direction::FROM_NETWORK: | 
 |       output_->SendPacketToNetwork(packet); | 
 |       break; | 
 |   } | 
 | } | 
 |  | 
 | }  // namespace quic |