| // 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/bonnet/tun_device_packet_exchanger.h" |
| |
| #include <netinet/icmp6.h> |
| #include <netinet/ip6.h> |
| #include <sys/uio.h> |
| |
| #include <algorithm> |
| #include <cerrno> |
| #include <cstddef> |
| #include <cstring> |
| #include <memory> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "absl/base/macros.h" |
| #include "absl/base/nullability.h" |
| #include "absl/status/status.h" |
| #include "absl/status/statusor.h" |
| #include "absl/strings/str_cat.h" |
| #include "absl/strings/string_view.h" |
| #include "absl/time/clock.h" |
| #include "absl/time/time.h" |
| #include "absl/types/span.h" |
| #include "quiche/quic/platform/api/quic_bug_tracker.h" |
| #include "quiche/quic/platform/api/quic_ip_address.h" |
| #include "quiche/quic/platform/api/quic_logging.h" |
| #include "quiche/quic/qbone/platform/icmp_packet.h" |
| #include "quiche/quic/qbone/platform/kernel_interface.h" |
| #include "quiche/quic/qbone/platform/netlink_interface.h" |
| #include "quiche/quic/qbone/qbone_client_interface.h" |
| #include "quiche/quic/qbone/qbone_constants.h" |
| #include "quiche/common/platform/api/quiche_logging.h" |
| #include "quiche/common/quiche_endian.h" |
| |
| namespace quic { |
| |
| using ::quiche::QuicheEndian; |
| |
| TunDevicePacketExchanger::TunDevicePacketExchanger( |
| size_t mtu, KernelInterface* kernel, NetlinkInterface* netlink, |
| Visitor* absl_nonnull visitor, bool is_tap, absl::string_view ifname) |
| : kernel_(kernel), |
| netlink_(netlink), |
| visitor_(*visitor), |
| ifname_(ifname), |
| // Reading on a TUN device returns a packet at a time. If the packet is |
| // longer than the buffer, it's truncated. |
| read_buffer_(mtu), |
| is_tap_(is_tap) {} |
| |
| TunDevicePacketExchanger::~TunDevicePacketExchanger() { |
| QUIC_BUG_IF(bonnet_tun_device_packet_exchanger_not_stopped, |
| read_fd_ >= 0 || write_fd_ >= 0); |
| } |
| |
| void TunDevicePacketExchanger::Start(int read_fd, int write_fd) { |
| // Allow idempotent Start() calls with the same file descriptors, but |
| // otherwise it's a bug to try starting an already started exchanger. |
| QUIC_BUG_IF(qbone_tun_device_packet_exchanger_already_started, |
| (read_fd_ >= 0 || write_fd_ >= 0) && |
| (read_fd_ != read_fd || write_fd_ != write_fd)); |
| |
| QUIC_BUG_IF(qbone_tun_device_packet_exchanger_invalid_read_fd, read_fd < 0); |
| QUIC_BUG_IF(qbone_tun_device_packet_exchanger_invalid_write_fd, write_fd < 0); |
| |
| read_fd_ = read_fd; |
| write_fd_ = write_fd; |
| } |
| |
| void TunDevicePacketExchanger::Stop() { |
| // This implementation does not employ any worker threads, so there cannot be |
| // any pending operations to wait for completion. |
| read_fd_ = -1; |
| write_fd_ = -1; |
| } |
| |
| int TunDevicePacketExchanger::OnReadFromNetworkReady( |
| int max_packets_to_read, QboneClientInterface* qbone_client) { |
| if (read_fd_ < 0) { |
| QUIC_BUG(qbone_tun_device_packet_exchanger_read_with_invalid_fd) |
| << "Invalid file descriptor of the TUN device: " << read_fd_; |
| return 0; |
| } |
| |
| int packets_read = 0; |
| for (int i = 0; i < max_packets_to_read; ++i) { |
| // Should be at least one packet available to read if this is called, so |
| // fully process any blocked errors on the first read. After that, blocked |
| // errors are just the signal that there are no more packets to read. |
| bool exchange_blocked_error = packets_read == 0; |
| |
| if (ReadAndExchangeSinglePacket(qbone_client, exchange_blocked_error)) { |
| packets_read++; |
| } else { |
| break; |
| } |
| } |
| |
| return packets_read; |
| } |
| |
| void TunDevicePacketExchanger::WritePacketToNetwork(const char* packet, |
| size_t size) { |
| if (write_fd_ < 0) { |
| QUIC_BUG(qbone_tun_device_packet_exchanger_write_with_invalid_fd) |
| << "Invalid file descriptor of the TUN device: " << write_fd_; |
| return; |
| } |
| |
| if (is_tap_ && !eth_hdr_initialized_) { |
| InitializeEthHdr(); |
| } |
| struct iovec iov[2]; |
| iov[0].iov_base = is_tap_ ? ð_hdr_ : nullptr; |
| iov[0].iov_len = is_tap_ ? ETH_HLEN : 0; |
| iov[1].iov_base = const_cast<char*>(packet); |
| iov[1].iov_len = size; |
| |
| absl::Status status = absl::OkStatus(); |
| absl::Time start = absl::Now(); |
| int result = kernel_->writev(write_fd_, iov, ABSL_ARRAYSIZE(iov)); |
| if (result < 0) { |
| status = absl::ErrnoToStatus(errno, "Write to the TUN device failed."); |
| } |
| absl::Duration latency = std::max(absl::Now() - start, absl::ZeroDuration()); |
| |
| if (!status.ok()) { |
| QUIC_LOG_EVERY_N_SEC(ERROR, 60) << "Packet write failed: " << status; |
| visitor_.OnWrite(std::move(status)); |
| return; |
| } |
| |
| visitor_.OnWrite(std::vector<WriteResult>{WriteResult{ |
| .packet = |
| absl::MakeSpan(reinterpret_cast<const std::byte*>(packet), size), |
| .latency = latency}}); |
| } |
| |
| bool TunDevicePacketExchanger::ReadAndExchangeSinglePacket( |
| QboneClientInterface* qbone_client, bool exchange_blocked_error) { |
| QUICHE_DCHECK_GE(read_fd_, 0); |
| |
| // TODO(ericorth): Consider allocating these buffers once and reusing rather |
| // than repeating for each packet. |
| ethhdr eth_header; |
| struct iovec iov[2]; |
| |
| iov[0].iov_base = is_tap_ ? ð_header : nullptr; |
| iov[0].iov_len = is_tap_ ? ETH_HLEN : 0; |
| iov[1].iov_base = read_buffer_.data(); |
| iov[1].iov_len = read_buffer_.size(); |
| |
| absl::Status status = absl::OkStatus(); |
| absl::Time start = absl::Now(); |
| int result = kernel_->readv(read_fd_, iov, ABSL_ARRAYSIZE(iov)); |
| int saved_errno = errno; |
| absl::Duration latency = std::max(absl::Now() - start, absl::ZeroDuration()); |
| |
| if (result < 0) { |
| if ((saved_errno == EAGAIN || saved_errno == EWOULDBLOCK) && |
| !exchange_blocked_error) { |
| // No more packets available to read. |
| return false; |
| } else { |
| status = |
| absl::ErrnoToStatus(saved_errno, "Read from the TUN device failed."); |
| } |
| } else if (result == 0) { |
| // Note that 0 means end of file, but we're talking about a TUN device - |
| // there is no end of file. Therefore 0 also indicates error. |
| status = absl::InternalError( |
| "Read from the TUN device returned unexpected 0 (EOF)."); |
| } |
| |
| if (!status.ok()) { |
| QUIC_LOG_EVERY_N_SEC(ERROR, 60) << "Packet read failed: " << status; |
| visitor_.OnRead(std::move(status)); |
| return false; // Assume no more packets after any socket read error. |
| } |
| |
| int l3_packet_size = is_tap_ ? result - ETH_HLEN : result; |
| if (l3_packet_size <= 0 || l3_packet_size > read_buffer_.size()) { |
| absl::Status error = |
| absl::InternalError(absl::StrCat("Invalid packet size.")); |
| QUIC_LOG_EVERY_N_SEC(ERROR, 60) << "Packet read failed: " << error; |
| visitor_.OnRead(std::move(error)); |
| return true; // Invalid packet does not mean there are no more packets. |
| } |
| absl::Span<const std::byte> l3_packet = |
| absl::MakeSpan(read_buffer_.data(), l3_packet_size); |
| |
| if (is_tap_) { |
| switch (ValidateL2Headers(eth_header, l3_packet)) { |
| case L2ValidationResult::kInvalid: { |
| absl::Status error = absl::InvalidArgumentError("Invalid L2 headers."); |
| visitor_.OnRead(std::move(error)); |
| return true; // Invalid packet does not mean there are no more packets. |
| } |
| case L2ValidationResult::kValidLinkLocal: |
| return true; |
| case L2ValidationResult::kValidNormal: |
| // Packet is valid and should be forwarded to the tunnel. Fall through |
| // to normal processing. |
| break; |
| } |
| } |
| |
| visitor_.OnRead(std::vector<ReadResult>{ |
| ReadResult{.packet = l3_packet, .latency = latency}}); |
| qbone_client->ProcessPacketFromNetwork(absl::string_view( |
| reinterpret_cast<const char*>(l3_packet.data()), l3_packet.size())); |
| return true; |
| } |
| |
| void TunDevicePacketExchanger::InitializeEthHdr() { |
| if (!eth_hdr_initialized_) { |
| NetlinkInterface::LinkInfo link_info{}; |
| if (netlink_->GetLinkInfo(ifname_, &link_info)) { |
| // Set src & dst to my own address |
| memcpy(ð_hdr_.h_dest, link_info.hardware_address, ETH_ALEN); |
| memcpy(ð_hdr_.h_source, link_info.hardware_address, ETH_ALEN); |
| // Assume ipv6 for now |
| // TODO(b/195113643): Support additional protocols. |
| eth_hdr_.h_proto = QuicheEndian::HostToNet16(ETH_P_IPV6); |
| eth_hdr_initialized_ = true; |
| } else { |
| QUIC_LOG_EVERY_N_SEC(ERROR, 30) |
| << "Unable to get link info for: " << ifname_; |
| } |
| } |
| } |
| |
| TunDevicePacketExchanger::L2ValidationResult |
| TunDevicePacketExchanger::ValidateL2Headers( |
| const ethhdr& eth_header, absl::Span<const std::byte> packet) { |
| if (eth_header.h_proto != QuicheEndian::HostToNet16(ETH_P_IPV6)) { |
| return L2ValidationResult::kInvalid; |
| } |
| constexpr auto kIp6PrefixLen = sizeof(ip6_hdr); |
| constexpr auto kIcmp6PrefixLen = kIp6PrefixLen + sizeof(icmp6_hdr); |
| if (packet.length() < kIp6PrefixLen) { |
| // Packet is too short to be ipv6. Drop it. |
| return L2ValidationResult::kInvalid; |
| } |
| auto* ip_hdr = reinterpret_cast<const ip6_hdr*>(packet.data()); |
| const bool is_icmp = ip_hdr->ip6_ctlun.ip6_un1.ip6_un1_nxt == IPPROTO_ICMPV6; |
| |
| bool is_neighbor_solicit = false; |
| if (is_icmp) { |
| if (packet.length() < kIcmp6PrefixLen) { |
| // Packet is too short to be icmp6. Drop it. |
| return L2ValidationResult::kInvalid; |
| } |
| is_neighbor_solicit = |
| reinterpret_cast<const icmp6_hdr*>(packet.subspan(kIp6PrefixLen).data()) |
| ->icmp6_type == ND_NEIGHBOR_SOLICIT; |
| } |
| |
| if (is_neighbor_solicit) { |
| // We need the local interface MAC address to respond. |
| if (!eth_hdr_initialized_) { |
| InitializeEthHdr(); |
| } |
| // If we've received a neighbor solicitation, craft an advertisement to |
| // respond with and write it back to the local interface. |
| absl::Span<const std::byte> icmp6_payload = packet.subspan(kIcmp6PrefixLen); |
| |
| if (icmp6_payload.size() < sizeof(in6_addr)) { |
| // Packet is too short to contain a valid ICMPv6 payload. Drop it. |
| return L2ValidationResult::kInvalid; |
| } |
| |
| QuicIpAddress target_address( |
| *reinterpret_cast<const in6_addr*>(icmp6_payload.data())); |
| if (target_address != *QboneConstants::GatewayAddress()) { |
| // Only respond to solicitations for our gateway address |
| return L2ValidationResult::kValidLinkLocal; |
| } |
| |
| // Neighbor Advertisement crafted per: |
| // https://datatracker.ietf.org/doc/html/rfc4861#section-4.4 |
| // |
| // Using the Target link-layer address option defined at: |
| // https://datatracker.ietf.org/doc/html/rfc4861#section-4.6.1 |
| constexpr size_t kIcmpv6OptionSize = 8; |
| const int payload_size = sizeof(in6_addr) + kIcmpv6OptionSize; |
| auto payload = std::make_unique<char[]>(payload_size); |
| // Place the solicited IPv6 address at the beginning of the response payload |
| memcpy(payload.get(), icmp6_payload.data(), sizeof(in6_addr)); |
| // Setup the Target link-layer address option: |
| // 0 1 2 3 |
| // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 |
| // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| // | Type | Length | Link-Layer Address ... |
| // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| int pos = sizeof(in6_addr); |
| payload[pos++] = ND_OPT_TARGET_LINKADDR; // Type |
| payload[pos++] = 1; // Length in units of 8 octets |
| memcpy(&payload[pos], eth_hdr_.h_source, |
| ETH_ALEN); // This interfaces' MAC address |
| |
| // Populate the ICMPv6 header |
| icmp6_hdr response_hdr{}; |
| response_hdr.icmp6_type = ND_NEIGHBOR_ADVERT; |
| // Set the solicited bit to true |
| response_hdr.icmp6_dataun.icmp6_un_data8[0] = 64; |
| // Craft the full ICMPv6 packet and then ship it off to WritePacket |
| // to have it frame it with L2 headers and send it back to the requesting |
| // neighbor. |
| CreateIcmpPacket(ip_hdr->ip6_src, ip_hdr->ip6_src, response_hdr, |
| absl::string_view(payload.get(), payload_size), |
| [this](absl::string_view packet) { |
| WritePacketToNetwork(packet.data(), packet.size()); |
| }); |
| return L2ValidationResult::kValidLinkLocal; |
| } |
| |
| return L2ValidationResult::kValidNormal; |
| } |
| |
| } // namespace quic |