|  | // 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 "net/third_party/quiche/src/quic/qbone/qbone_packet_processor.h" | 
|  |  | 
|  | #include <cstring> | 
|  |  | 
|  | #include "net/third_party/quiche/src/quic/platform/api/quic_bug_tracker.h" | 
|  | #include "net/third_party/quiche/src/quic/platform/api/quic_logging.h" | 
|  | #include "net/third_party/quiche/src/quic/qbone/platform/icmp_packet.h" | 
|  | #include "net/third_party/quiche/src/quic/qbone/platform/internet_checksum.h" | 
|  | #include "net/third_party/quiche/src/quic/qbone/platform/tcp_packet.h" | 
|  | #include "net/third_party/quiche/src/common/platform/api/quiche_endian.h" | 
|  | #include "net/third_party/quiche/src/common/platform/api/quiche_string_piece.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); | 
|  | DCHECK_LE(client_ip_subnet_length, kIPv6AddressSize * 8); | 
|  | client_ip_subnet_length_ = client_ip_subnet_length; | 
|  |  | 
|  | DCHECK(IpAddressFamily::IP_V6 == self_ip.address_family()); | 
|  | DCHECK(IpAddressFamily::IP_V6 == client_ip.address_family()); | 
|  | DCHECK(self_ip != kInvalidIpAddress); | 
|  | } | 
|  |  | 
|  | QbonePacketProcessor::OutputInterface::~OutputInterface() {} | 
|  | QbonePacketProcessor::StatsInterface::~StatsInterface() {} | 
|  | QbonePacketProcessor::Filter::~Filter() {} | 
|  |  | 
|  | QbonePacketProcessor::ProcessingResult | 
|  | QbonePacketProcessor::Filter::FilterPacket( | 
|  | Direction direction, | 
|  | quiche::QuicheStringPiece full_packet, | 
|  | quiche::QuicheStringPiece 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 << "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; | 
|  | } | 
|  | } | 
|  |  | 
|  | 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 << "Invalid pointers encountered in " | 
|  | "QbonePacketProcessor::ProcessPacket.  Dropping the packet"; | 
|  | return ProcessingResult::SILENT_DROP; | 
|  | } | 
|  |  | 
|  | result = filter_->FilterPacket( | 
|  | direction, *packet, | 
|  | quiche::QuicheStringPiece(*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; | 
|  | } | 
|  | 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, | 
|  | quiche::QuicheStringPiece 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](quiche::QuicheStringPiece packet) { | 
|  | SendResponse(original_direction, packet); | 
|  | }); | 
|  | } | 
|  |  | 
|  | void QbonePacketProcessor::SendTcpReset( | 
|  | quiche::QuicheStringPiece original_packet, | 
|  | Direction original_direction) { | 
|  | CreateTcpResetPacket(original_packet, [this, original_direction]( | 
|  | quiche::QuicheStringPiece packet) { | 
|  | SendResponse(original_direction, packet); | 
|  | }); | 
|  | } | 
|  |  | 
|  | void QbonePacketProcessor::SendResponse(Direction original_direction, | 
|  | quiche::QuicheStringPiece packet) { | 
|  | switch (original_direction) { | 
|  | case Direction::FROM_OFF_NETWORK: | 
|  | output_->SendPacketToClient(packet); | 
|  | break; | 
|  | case Direction::FROM_NETWORK: | 
|  | output_->SendPacketToNetwork(packet); | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | }  // namespace quic |