blob: 9b11d99e8c6deee8f96486a21bbfb26e219654d9 [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 "quiche/quic/qbone/bonnet/tun_device.h"
#include <fcntl.h>
#include <linux/if_tun.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include "absl/cleanup/cleanup.h"
#include "quiche/quic/platform/api/quic_bug_tracker.h"
#include "quiche/quic/platform/api/quic_logging.h"
#include "quiche/quic/qbone/platform/kernel_interface.h"
ABSL_FLAG(std::string, qbone_client_tun_device_path, "/dev/net/tun",
"The path to the QBONE client's TUN device.");
namespace quic {
const int kInvalidFd = -1;
TunTapDevice::TunTapDevice(const std::string& interface_name, int mtu,
bool persist, bool setup_tun, bool is_tap,
KernelInterface* kernel)
: interface_name_(interface_name),
mtu_(mtu),
persist_(persist),
setup_tun_(setup_tun),
is_tap_(is_tap),
file_descriptor_(kInvalidFd),
kernel_(*kernel) {}
TunTapDevice::~TunTapDevice() {
if (!persist_) {
Down();
}
CloseDevice();
}
bool TunTapDevice::Init() {
if (interface_name_.empty() || interface_name_.size() >= IFNAMSIZ) {
QUIC_BUG(quic_bug_10995_1)
<< "interface_name must be nonempty and shorter than " << IFNAMSIZ;
return false;
}
if (!OpenDevice()) {
return false;
}
if (!ConfigureInterface()) {
return false;
}
return true;
}
// TODO(pengg): might be better to use netlink socket, once we have a library to
// use
bool TunTapDevice::Up() {
if (!setup_tun_) {
return true;
}
struct ifreq if_request;
memset(&if_request, 0, sizeof(if_request));
// copy does not zero-terminate the result string, but we've memset the
// entire struct.
interface_name_.copy(if_request.ifr_name, IFNAMSIZ);
if_request.ifr_flags = IFF_UP;
return NetdeviceIoctl(SIOCSIFFLAGS, reinterpret_cast<void*>(&if_request));
}
// TODO(pengg): might be better to use netlink socket, once we have a library to
// use
bool TunTapDevice::Down() {
if (!setup_tun_) {
return true;
}
struct ifreq if_request;
memset(&if_request, 0, sizeof(if_request));
// copy does not zero-terminate the result string, but we've memset the
// entire struct.
interface_name_.copy(if_request.ifr_name, IFNAMSIZ);
if_request.ifr_flags = 0;
return NetdeviceIoctl(SIOCSIFFLAGS, reinterpret_cast<void*>(&if_request));
}
int TunTapDevice::GetFileDescriptor() const { return file_descriptor_; }
bool TunTapDevice::OpenDevice() {
if (file_descriptor_ != kInvalidFd) {
CloseDevice();
}
struct ifreq if_request;
memset(&if_request, 0, sizeof(if_request));
// copy does not zero-terminate the result string, but we've memset the entire
// struct.
interface_name_.copy(if_request.ifr_name, IFNAMSIZ);
// Always set IFF_MULTI_QUEUE since a persistent device does not allow this
// flag to be flipped when re-opening it. The only way to flip this flag is to
// destroy the device and create a new one, but that deletes any existing
// routing associated with the interface, which makes the meaning of the
// 'persist' bit ambiguous.
if_request.ifr_flags = IFF_MULTI_QUEUE | IFF_NO_PI;
if (is_tap_) {
if_request.ifr_flags |= IFF_TAP;
} else {
if_request.ifr_flags |= IFF_TUN;
}
// When the device is running with IFF_MULTI_QUEUE set, each call to open will
// create a queue which can be used to read/write packets from/to the device.
bool successfully_opened = false;
auto cleanup = absl::MakeCleanup([this, &successfully_opened]() {
if (!successfully_opened) {
CloseDevice();
}
});
const std::string tun_device_path =
absl::GetFlag(FLAGS_qbone_client_tun_device_path);
int fd = kernel_.open(tun_device_path.c_str(), O_RDWR);
if (fd < 0) {
QUIC_PLOG(WARNING) << "Failed to open " << tun_device_path;
return successfully_opened;
}
file_descriptor_ = fd;
if (!CheckFeatures(fd)) {
return successfully_opened;
}
if (kernel_.ioctl(fd, TUNSETIFF, reinterpret_cast<void*>(&if_request)) != 0) {
QUIC_PLOG(WARNING) << "Failed to TUNSETIFF on fd(" << fd << ")";
return successfully_opened;
}
if (kernel_.ioctl(
fd, TUNSETPERSIST,
persist_ ? reinterpret_cast<void*>(&if_request) : nullptr) != 0) {
QUIC_PLOG(WARNING) << "Failed to TUNSETPERSIST on fd(" << fd << ")";
return successfully_opened;
}
successfully_opened = true;
return successfully_opened;
}
// TODO(pengg): might be better to use netlink socket, once we have a library to
// use
bool TunTapDevice::ConfigureInterface() {
if (!setup_tun_) {
return true;
}
struct ifreq if_request;
memset(&if_request, 0, sizeof(if_request));
// copy does not zero-terminate the result string, but we've memset the entire
// struct.
interface_name_.copy(if_request.ifr_name, IFNAMSIZ);
if_request.ifr_mtu = mtu_;
if (!NetdeviceIoctl(SIOCSIFMTU, reinterpret_cast<void*>(&if_request))) {
CloseDevice();
return false;
}
return true;
}
bool TunTapDevice::CheckFeatures(int tun_device_fd) {
unsigned int actual_features;
if (kernel_.ioctl(tun_device_fd, TUNGETFEATURES, &actual_features) != 0) {
QUIC_PLOG(WARNING) << "Failed to TUNGETFEATURES";
return false;
}
unsigned int required_features = IFF_TUN | IFF_NO_PI;
if ((required_features & actual_features) != required_features) {
QUIC_LOG(WARNING)
<< "Required feature does not exist. required_features: 0x" << std::hex
<< required_features << " vs actual_features: 0x" << std::hex
<< actual_features;
return false;
}
return true;
}
bool TunTapDevice::NetdeviceIoctl(int request, void* argp) {
int fd = kernel_.socket(AF_INET6, SOCK_DGRAM, 0);
if (fd < 0) {
QUIC_PLOG(WARNING) << "Failed to create AF_INET6 socket.";
return false;
}
if (kernel_.ioctl(fd, request, argp) != 0) {
QUIC_PLOG(WARNING) << "Failed ioctl request: " << request;
kernel_.close(fd);
return false;
}
kernel_.close(fd);
return true;
}
void TunTapDevice::CloseDevice() {
if (file_descriptor_ != kInvalidFd) {
kernel_.close(file_descriptor_);
file_descriptor_ = kInvalidFd;
}
}
} // namespace quic