blob: 3381cd2b3f4e607128571381b47882f1b1d24445 [file] [log] [blame]
// 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/bonnet/tun_device_packet_exchanger.h"
#include <netinet/icmp6.h>
#include <netinet/ip6.h>
#include <utility>
#include "absl/strings/str_cat.h"
#include "quic/qbone/platform/icmp_packet.h"
#include "quic/qbone/platform/netlink_interface.h"
#include "quic/qbone/qbone_constants.h"
namespace quic {
TunDevicePacketExchanger::TunDevicePacketExchanger(
size_t mtu, KernelInterface* kernel, NetlinkInterface* netlink,
QbonePacketExchanger::Visitor* visitor, size_t max_pending_packets,
bool is_tap, StatsInterface* stats, absl::string_view ifname)
: QbonePacketExchanger(visitor, max_pending_packets),
mtu_(mtu),
kernel_(kernel),
netlink_(netlink),
ifname_(ifname),
is_tap_(is_tap),
stats_(stats) {
if (is_tap_) {
mtu_ += ETH_HLEN;
}
}
bool TunDevicePacketExchanger::WritePacket(const char* packet, size_t size,
bool* blocked, std::string* error) {
*blocked = false;
if (fd_ < 0) {
*error = absl::StrCat("Invalid file descriptor of the TUN device: ", fd_);
stats_->OnWriteError(error);
return false;
}
auto buffer = std::make_unique<QuicData>(packet, size);
if (is_tap_) {
buffer = ApplyL2Headers(*buffer);
}
int result = kernel_->write(fd_, buffer->data(), buffer->length());
if (result == -1) {
if (errno == EWOULDBLOCK || errno == EAGAIN) {
// The tunnel is blocked. Note that this does not mean the receive buffer
// of a TCP connection is filled. This simply means the TUN device itself
// is blocked on handing packets to the rest part of the kernel.
*error = absl::StrCat("Write to the TUN device was blocked: ", errno);
*blocked = true;
stats_->OnWriteError(error);
}
return false;
}
stats_->OnPacketWritten(result);
return true;
}
std::unique_ptr<QuicData> TunDevicePacketExchanger::ReadPacket(
bool* blocked, std::string* error) {
*blocked = false;
if (fd_ < 0) {
*error = absl::StrCat("Invalid file descriptor of the TUN device: ", fd_);
stats_->OnReadError(error);
return nullptr;
}
// Reading on a TUN device returns a packet at a time. If the packet is longer
// than the buffer, it's truncated.
auto read_buffer = std::make_unique<char[]>(mtu_);
int result = kernel_->read(fd_, read_buffer.get(), mtu_);
// 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.
if (result <= 0) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
*error = absl::StrCat("Read from the TUN device was blocked: ", errno);
*blocked = true;
stats_->OnReadError(error);
}
return nullptr;
}
auto buffer = std::make_unique<QuicData>(read_buffer.release(), result, true);
if (is_tap_) {
buffer = ConsumeL2Headers(*buffer);
}
if (buffer) {
stats_->OnPacketRead(buffer->length());
}
return buffer;
}
void TunDevicePacketExchanger::set_file_descriptor(int fd) { fd_ = fd; }
const TunDevicePacketExchanger::StatsInterface*
TunDevicePacketExchanger::stats_interface() const {
return stats_;
}
std::unique_ptr<QuicData> TunDevicePacketExchanger::ApplyL2Headers(
const QuicData& l3_packet) {
if (is_tap_ && !mac_initialized_) {
NetlinkInterface::LinkInfo link_info{};
if (netlink_->GetLinkInfo(ifname_, &link_info)) {
memcpy(tap_mac_, link_info.hardware_address, ETH_ALEN);
mac_initialized_ = true;
} else {
QUIC_LOG_EVERY_N_SEC(ERROR, 30)
<< "Unable to get link info for: " << ifname_;
}
}
const auto l2_packet_size = l3_packet.length() + ETH_HLEN;
auto l2_buffer = std::make_unique<char[]>(l2_packet_size);
// Populate the Ethernet header
auto* hdr = reinterpret_cast<ethhdr*>(l2_buffer.get());
// Set src & dst to my own address
memcpy(hdr->h_dest, tap_mac_, ETH_ALEN);
memcpy(hdr->h_source, tap_mac_, ETH_ALEN);
// Assume ipv6 for now
// TODO(b/195113643): Support additional protocols.
hdr->h_proto = absl::ghtons(ETH_P_IPV6);
// Copy the l3 packet into buffer, just after the ethernet header.
memcpy(l2_buffer.get() + ETH_HLEN, l3_packet.data(), l3_packet.length());
return std::make_unique<QuicData>(l2_buffer.release(), l2_packet_size, true);
}
std::unique_ptr<QuicData> TunDevicePacketExchanger::ConsumeL2Headers(
const QuicData& l2_packet) {
if (l2_packet.length() < ETH_HLEN) {
// Packet is too short for ethernet headers. Drop it.
return nullptr;
}
auto* hdr = reinterpret_cast<const ethhdr*>(l2_packet.data());
if (hdr->h_proto != absl::ghtons(ETH_P_IPV6)) {
return nullptr;
}
constexpr auto kIp6PrefixLen = ETH_HLEN + sizeof(ip6_hdr);
constexpr auto kIcmp6PrefixLen = kIp6PrefixLen + sizeof(icmp6_hdr);
if (l2_packet.length() < kIp6PrefixLen) {
// Packet is too short to be ipv6. Drop it.
return nullptr;
}
auto* ip_hdr = reinterpret_cast<const ip6_hdr*>(l2_packet.data() + ETH_HLEN);
const bool is_icmp = ip_hdr->ip6_ctlun.ip6_un1.ip6_un1_nxt == IPPROTO_ICMPV6;
bool is_neighbor_solicit = false;
if (is_icmp) {
if (l2_packet.length() < kIcmp6PrefixLen) {
// Packet is too short to be icmp6. Drop it.
return nullptr;
}
is_neighbor_solicit =
reinterpret_cast<const icmp6_hdr*>(l2_packet.data() + kIp6PrefixLen)
->icmp6_type == ND_NEIGHBOR_SOLICIT;
}
if (is_neighbor_solicit) {
// If we've received a neighbor solicitation, craft an advertisement to
// respond with and write it back to the local interface.
auto* icmp6_payload = l2_packet.data() + kIcmp6PrefixLen;
QuicIpAddress target_address(
*reinterpret_cast<const in6_addr*>(icmp6_payload));
if (target_address != *QboneConstants::GatewayAddress()) {
// Only respond to solicitations for our gateway address
return nullptr;
}
// 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, 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], tap_mac_, 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) {
bool blocked;
std::string error;
WritePacket(packet.data(), packet.size(), &blocked,
&error);
});
// Do not forward the neighbor solicitation through the tunnel since it's
// link-local.
return nullptr;
}
// If this isn't a Neighbor Solicitation, remove the L2 headers and forward
// it as though it were an L3 packet.
const auto l3_packet_size = l2_packet.length() - ETH_HLEN;
auto shift_buffer = std::make_unique<char[]>(l3_packet_size);
memcpy(shift_buffer.get(), l2_packet.data() + ETH_HLEN, l3_packet_size);
return std::make_unique<QuicData>(shift_buffer.release(), l3_packet_size,
true);
}
} // namespace quic