wub | f975eac | 2019-08-19 19:41:01 -0700 | [diff] [blame] | 1 | // Copyright (c) 2019 The Chromium Authors. All rights reserved. |
| 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | |
QUICHE team | 5be974e | 2020-12-29 18:35:24 -0500 | [diff] [blame] | 5 | #include "quic/qbone/qbone_packet_processor.h" |
wub | f975eac | 2019-08-19 19:41:01 -0700 | [diff] [blame] | 6 | |
| 7 | #include <cstring> |
| 8 | |
vasilvv | 1436e34 | 2020-10-09 12:31:16 -0700 | [diff] [blame] | 9 | #include "absl/strings/string_view.h" |
QUICHE team | 5be974e | 2020-12-29 18:35:24 -0500 | [diff] [blame] | 10 | #include "quic/platform/api/quic_bug_tracker.h" |
| 11 | #include "quic/platform/api/quic_logging.h" |
| 12 | #include "quic/qbone/platform/icmp_packet.h" |
| 13 | #include "quic/qbone/platform/internet_checksum.h" |
| 14 | #include "quic/qbone/platform/tcp_packet.h" |
| 15 | #include "common/quiche_endian.h" |
wub | f975eac | 2019-08-19 19:41:01 -0700 | [diff] [blame] | 16 | |
| 17 | namespace { |
| 18 | |
| 19 | constexpr size_t kIPv6AddressSize = 16; |
| 20 | constexpr size_t kIPv6MinPacketSize = 1280; |
| 21 | constexpr size_t kIcmpTtl = 64; |
| 22 | constexpr size_t kICMPv6DestinationUnreachableDueToSourcePolicy = 5; |
| 23 | |
| 24 | } // namespace |
| 25 | |
| 26 | namespace quic { |
| 27 | |
| 28 | const QuicIpAddress QbonePacketProcessor::kInvalidIpAddress = |
| 29 | QuicIpAddress::Any6(); |
| 30 | |
| 31 | QbonePacketProcessor::QbonePacketProcessor(QuicIpAddress self_ip, |
| 32 | QuicIpAddress client_ip, |
| 33 | size_t client_ip_subnet_length, |
| 34 | OutputInterface* output, |
| 35 | StatsInterface* stats) |
| 36 | : client_ip_(client_ip), |
| 37 | output_(output), |
| 38 | stats_(stats), |
| 39 | filter_(new Filter) { |
| 40 | memcpy(self_ip_.s6_addr, self_ip.ToPackedString().data(), kIPv6AddressSize); |
| 41 | DCHECK_LE(client_ip_subnet_length, kIPv6AddressSize * 8); |
| 42 | client_ip_subnet_length_ = client_ip_subnet_length; |
| 43 | |
| 44 | DCHECK(IpAddressFamily::IP_V6 == self_ip.address_family()); |
| 45 | DCHECK(IpAddressFamily::IP_V6 == client_ip.address_family()); |
| 46 | DCHECK(self_ip != kInvalidIpAddress); |
| 47 | } |
| 48 | |
| 49 | QbonePacketProcessor::OutputInterface::~OutputInterface() {} |
| 50 | QbonePacketProcessor::StatsInterface::~StatsInterface() {} |
| 51 | QbonePacketProcessor::Filter::~Filter() {} |
| 52 | |
| 53 | QbonePacketProcessor::ProcessingResult |
vasilvv | 1436e34 | 2020-10-09 12:31:16 -0700 | [diff] [blame] | 54 | QbonePacketProcessor::Filter::FilterPacket(Direction direction, |
| 55 | absl::string_view full_packet, |
| 56 | absl::string_view payload, |
| 57 | icmp6_hdr* icmp_header, |
| 58 | OutputInterface* output) { |
wub | f975eac | 2019-08-19 19:41:01 -0700 | [diff] [blame] | 59 | return ProcessingResult::OK; |
| 60 | } |
| 61 | |
QUICHE team | b80d7c3 | 2020-02-23 23:44:20 -0800 | [diff] [blame] | 62 | void QbonePacketProcessor::ProcessPacket(std::string* packet, |
| 63 | Direction direction) { |
wub | f975eac | 2019-08-19 19:41:01 -0700 | [diff] [blame] | 64 | if (QUIC_PREDICT_FALSE(!IsValid())) { |
| 65 | QUIC_BUG << "QuicPacketProcessor is invoked in an invalid state."; |
| 66 | stats_->OnPacketDroppedSilently(direction); |
| 67 | return; |
| 68 | } |
| 69 | |
| 70 | uint8_t transport_protocol; |
| 71 | char* transport_data; |
| 72 | icmp6_hdr icmp_header; |
| 73 | memset(&icmp_header, 0, sizeof(icmp_header)); |
| 74 | ProcessingResult result = ProcessIPv6HeaderAndFilter( |
| 75 | packet, direction, &transport_protocol, &transport_data, &icmp_header); |
| 76 | |
| 77 | switch (result) { |
| 78 | case ProcessingResult::OK: |
| 79 | switch (direction) { |
QUICHE team | 234c877 | 2019-12-06 14:14:12 -0800 | [diff] [blame] | 80 | case Direction::FROM_OFF_NETWORK: |
wub | f975eac | 2019-08-19 19:41:01 -0700 | [diff] [blame] | 81 | output_->SendPacketToNetwork(*packet); |
| 82 | break; |
| 83 | case Direction::FROM_NETWORK: |
| 84 | output_->SendPacketToClient(*packet); |
| 85 | break; |
| 86 | } |
| 87 | stats_->OnPacketForwarded(direction); |
| 88 | break; |
| 89 | case ProcessingResult::SILENT_DROP: |
| 90 | stats_->OnPacketDroppedSilently(direction); |
| 91 | break; |
| 92 | case ProcessingResult::DEFER: |
| 93 | stats_->OnPacketDeferred(direction); |
| 94 | break; |
| 95 | case ProcessingResult::ICMP: |
| 96 | SendIcmpResponse(&icmp_header, *packet, direction); |
| 97 | stats_->OnPacketDroppedWithIcmp(direction); |
| 98 | break; |
| 99 | case ProcessingResult::ICMP_AND_TCP_RESET: |
| 100 | SendIcmpResponse(&icmp_header, *packet, direction); |
| 101 | stats_->OnPacketDroppedWithIcmp(direction); |
| 102 | SendTcpReset(*packet, direction); |
| 103 | stats_->OnPacketDroppedWithTcpReset(direction); |
| 104 | break; |
| 105 | } |
| 106 | } |
| 107 | |
| 108 | QbonePacketProcessor::ProcessingResult |
QUICHE team | b80d7c3 | 2020-02-23 23:44:20 -0800 | [diff] [blame] | 109 | QbonePacketProcessor::ProcessIPv6HeaderAndFilter(std::string* packet, |
wub | f975eac | 2019-08-19 19:41:01 -0700 | [diff] [blame] | 110 | Direction direction, |
| 111 | uint8_t* transport_protocol, |
| 112 | char** transport_data, |
| 113 | icmp6_hdr* icmp_header) { |
| 114 | ProcessingResult result = ProcessIPv6Header( |
| 115 | packet, direction, transport_protocol, transport_data, icmp_header); |
| 116 | |
| 117 | if (result == ProcessingResult::OK) { |
| 118 | char* packet_data = &*packet->begin(); |
| 119 | size_t header_size = *transport_data - packet_data; |
| 120 | // Sanity-check the bounds. |
| 121 | if (packet_data >= *transport_data || header_size > packet->size() || |
| 122 | header_size < kIPv6HeaderSize) { |
| 123 | QUIC_BUG << "Invalid pointers encountered in " |
| 124 | "QbonePacketProcessor::ProcessPacket. Dropping the packet"; |
| 125 | return ProcessingResult::SILENT_DROP; |
| 126 | } |
| 127 | |
| 128 | result = filter_->FilterPacket( |
| 129 | direction, *packet, |
vasilvv | 1436e34 | 2020-10-09 12:31:16 -0700 | [diff] [blame] | 130 | absl::string_view(*transport_data, packet->size() - header_size), |
wub | f975eac | 2019-08-19 19:41:01 -0700 | [diff] [blame] | 131 | icmp_header, output_); |
| 132 | } |
| 133 | |
| 134 | // Do not send ICMP error messages in response to ICMP errors. |
| 135 | if (result == ProcessingResult::ICMP) { |
| 136 | const uint8_t* header = reinterpret_cast<const uint8_t*>(packet->data()); |
| 137 | |
| 138 | constexpr size_t kIPv6NextHeaderOffset = 6; |
| 139 | constexpr size_t kIcmpMessageTypeOffset = kIPv6HeaderSize + 0; |
| 140 | constexpr size_t kIcmpMessageTypeMaxError = 127; |
| 141 | if ( |
| 142 | // Check size. |
| 143 | packet->size() >= (kIPv6HeaderSize + kICMPv6HeaderSize) && |
| 144 | // Check that the packet is in fact ICMP. |
| 145 | header[kIPv6NextHeaderOffset] == IPPROTO_ICMPV6 && |
| 146 | // Check that ICMP message type is an error. |
| 147 | header[kIcmpMessageTypeOffset] < kIcmpMessageTypeMaxError) { |
| 148 | result = ProcessingResult::SILENT_DROP; |
| 149 | } |
| 150 | } |
| 151 | |
| 152 | return result; |
| 153 | } |
| 154 | |
| 155 | QbonePacketProcessor::ProcessingResult QbonePacketProcessor::ProcessIPv6Header( |
QUICHE team | b80d7c3 | 2020-02-23 23:44:20 -0800 | [diff] [blame] | 156 | std::string* packet, |
wub | f975eac | 2019-08-19 19:41:01 -0700 | [diff] [blame] | 157 | Direction direction, |
| 158 | uint8_t* transport_protocol, |
| 159 | char** transport_data, |
| 160 | icmp6_hdr* icmp_header) { |
| 161 | // Check if the packet is big enough to have IPv6 header. |
| 162 | if (packet->size() < kIPv6HeaderSize) { |
| 163 | QUIC_DVLOG(1) << "Dropped malformed packet: IPv6 header too short"; |
| 164 | return ProcessingResult::SILENT_DROP; |
| 165 | } |
| 166 | |
| 167 | // Check version field. |
| 168 | ip6_hdr* header = reinterpret_cast<ip6_hdr*>(&*packet->begin()); |
| 169 | if (header->ip6_vfc >> 4 != 6) { |
| 170 | QUIC_DVLOG(1) << "Dropped malformed packet: IP version is not IPv6"; |
| 171 | return ProcessingResult::SILENT_DROP; |
| 172 | } |
| 173 | |
| 174 | // Check payload size. |
| 175 | const size_t declared_payload_size = |
QUICHE team | 173c48f | 2019-11-19 16:34:44 -0800 | [diff] [blame] | 176 | quiche::QuicheEndian::NetToHost16(header->ip6_plen); |
wub | f975eac | 2019-08-19 19:41:01 -0700 | [diff] [blame] | 177 | const size_t actual_payload_size = packet->size() - kIPv6HeaderSize; |
| 178 | if (declared_payload_size != actual_payload_size) { |
| 179 | QUIC_DVLOG(1) |
| 180 | << "Dropped malformed packet: incorrect packet length specified"; |
| 181 | return ProcessingResult::SILENT_DROP; |
| 182 | } |
| 183 | |
| 184 | // Check that the address of the client is in the packet. |
| 185 | QuicIpAddress address_to_check; |
| 186 | uint8_t address_reject_code; |
| 187 | bool ip_parse_result; |
| 188 | switch (direction) { |
QUICHE team | 234c877 | 2019-12-06 14:14:12 -0800 | [diff] [blame] | 189 | case Direction::FROM_OFF_NETWORK: |
wub | f975eac | 2019-08-19 19:41:01 -0700 | [diff] [blame] | 190 | // Expect the source IP to match the client. |
| 191 | ip_parse_result = address_to_check.FromPackedString( |
| 192 | reinterpret_cast<const char*>(&header->ip6_src), |
| 193 | sizeof(header->ip6_src)); |
| 194 | address_reject_code = kICMPv6DestinationUnreachableDueToSourcePolicy; |
| 195 | break; |
| 196 | case Direction::FROM_NETWORK: |
| 197 | // Expect the destination IP to match the client. |
| 198 | ip_parse_result = address_to_check.FromPackedString( |
| 199 | reinterpret_cast<const char*>(&header->ip6_dst), |
| 200 | sizeof(header->ip6_src)); |
| 201 | address_reject_code = ICMP6_DST_UNREACH_NOROUTE; |
| 202 | break; |
| 203 | } |
| 204 | DCHECK(ip_parse_result); |
| 205 | if (!client_ip_.InSameSubnet(address_to_check, client_ip_subnet_length_)) { |
| 206 | QUIC_DVLOG(1) |
| 207 | << "Dropped packet: source/destination address is not client's"; |
| 208 | icmp_header->icmp6_type = ICMP6_DST_UNREACH; |
| 209 | icmp_header->icmp6_code = address_reject_code; |
| 210 | return ProcessingResult::ICMP; |
| 211 | } |
| 212 | |
| 213 | // Check and decrement TTL. |
| 214 | if (header->ip6_hops <= 1) { |
| 215 | icmp_header->icmp6_type = ICMP6_TIME_EXCEEDED; |
| 216 | icmp_header->icmp6_code = ICMP6_TIME_EXCEED_TRANSIT; |
| 217 | return ProcessingResult::ICMP; |
| 218 | } |
| 219 | header->ip6_hops--; |
| 220 | |
| 221 | // Check and extract IP headers. |
| 222 | switch (header->ip6_nxt) { |
| 223 | case IPPROTO_TCP: |
| 224 | case IPPROTO_UDP: |
| 225 | case IPPROTO_ICMPV6: |
| 226 | *transport_protocol = header->ip6_nxt; |
| 227 | *transport_data = (&*packet->begin()) + kIPv6HeaderSize; |
| 228 | break; |
| 229 | default: |
| 230 | icmp_header->icmp6_type = ICMP6_PARAM_PROB; |
| 231 | icmp_header->icmp6_code = ICMP6_PARAMPROB_NEXTHEADER; |
| 232 | return ProcessingResult::ICMP; |
| 233 | } |
| 234 | |
| 235 | return ProcessingResult::OK; |
| 236 | } |
| 237 | |
vasilvv | 1436e34 | 2020-10-09 12:31:16 -0700 | [diff] [blame] | 238 | void QbonePacketProcessor::SendIcmpResponse(icmp6_hdr* icmp_header, |
| 239 | absl::string_view original_packet, |
| 240 | Direction original_direction) { |
wub | f975eac | 2019-08-19 19:41:01 -0700 | [diff] [blame] | 241 | in6_addr dst; |
| 242 | // TODO(b/70339814): ensure this is actually a unicast address. |
| 243 | memcpy(dst.s6_addr, &original_packet[8], kIPv6AddressSize); |
| 244 | |
vasilvv | 1436e34 | 2020-10-09 12:31:16 -0700 | [diff] [blame] | 245 | CreateIcmpPacket(self_ip_, dst, *icmp_header, original_packet, |
| 246 | [this, original_direction](absl::string_view packet) { |
| 247 | SendResponse(original_direction, packet); |
| 248 | }); |
wub | f975eac | 2019-08-19 19:41:01 -0700 | [diff] [blame] | 249 | } |
| 250 | |
vasilvv | 1436e34 | 2020-10-09 12:31:16 -0700 | [diff] [blame] | 251 | void QbonePacketProcessor::SendTcpReset(absl::string_view original_packet, |
| 252 | Direction original_direction) { |
| 253 | CreateTcpResetPacket(original_packet, |
| 254 | [this, original_direction](absl::string_view packet) { |
| 255 | SendResponse(original_direction, packet); |
| 256 | }); |
wub | f975eac | 2019-08-19 19:41:01 -0700 | [diff] [blame] | 257 | } |
| 258 | |
| 259 | void QbonePacketProcessor::SendResponse(Direction original_direction, |
vasilvv | 1436e34 | 2020-10-09 12:31:16 -0700 | [diff] [blame] | 260 | absl::string_view packet) { |
wub | f975eac | 2019-08-19 19:41:01 -0700 | [diff] [blame] | 261 | switch (original_direction) { |
QUICHE team | 234c877 | 2019-12-06 14:14:12 -0800 | [diff] [blame] | 262 | case Direction::FROM_OFF_NETWORK: |
wub | f975eac | 2019-08-19 19:41:01 -0700 | [diff] [blame] | 263 | output_->SendPacketToClient(packet); |
| 264 | break; |
| 265 | case Direction::FROM_NETWORK: |
| 266 | output_->SendPacketToNetwork(packet); |
| 267 | break; |
| 268 | } |
| 269 | } |
| 270 | |
| 271 | } // namespace quic |