blob: 3ca52e8d6d67bf433f079d667b3be20abebe905b [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.h"
#include <fcntl.h>
#include <linux/if_tun.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include "quic/platform/api/quic_bug_tracker.h"
#include "quic/platform/api/quic_logging.h"
#include "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;
TunDevice::TunDevice(const std::string& interface_name,
int mtu,
bool persist,
bool setup_tun,
KernelInterface* kernel)
: interface_name_(interface_name),
mtu_(mtu),
persist_(persist),
setup_tun_(setup_tun),
file_descriptor_(kInvalidFd),
kernel_(*kernel) {}
TunDevice::~TunDevice() {
if (!persist_) {
Down();
}
CleanUpFileDescriptor();
}
bool TunDevice::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 TunDevice::Up() {
if (setup_tun_ && !is_interface_up_) {
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;
is_interface_up_ =
NetdeviceIoctl(SIOCSIFFLAGS, reinterpret_cast<void*>(&if_request));
return is_interface_up_;
} else {
return true;
}
}
// TODO(pengg): might be better to use netlink socket, once we have a library to
// use
bool TunDevice::Down() {
if (setup_tun_ && is_interface_up_) {
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;
is_interface_up_ =
!NetdeviceIoctl(SIOCSIFFLAGS, reinterpret_cast<void*>(&if_request));
return !is_interface_up_;
} else {
return true;
}
}
int TunDevice::GetFileDescriptor() const {
return file_descriptor_;
}
bool TunDevice::OpenDevice() {
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_TUN | IFF_MULTI_QUEUE | IFF_NO_PI;
// TODO(pengg): port MakeCleanup to quic/platform? This makes the call to
// CleanUpFileDescriptor nicer and less error-prone.
// 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.
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;
CleanUpFileDescriptor();
return false;
}
file_descriptor_ = fd;
if (!CheckFeatures(fd)) {
CleanUpFileDescriptor();
return false;
}
if (kernel_.ioctl(fd, TUNSETIFF, reinterpret_cast<void*>(&if_request)) != 0) {
QUIC_PLOG(WARNING) << "Failed to TUNSETIFF on fd(" << fd << ")";
CleanUpFileDescriptor();
return false;
}
if (kernel_.ioctl(
fd, TUNSETPERSIST,
persist_ ? reinterpret_cast<void*>(&if_request) : nullptr) != 0) {
QUIC_PLOG(WARNING) << "Failed to TUNSETPERSIST on fd(" << fd << ")";
CleanUpFileDescriptor();
return false;
}
return true;
}
// TODO(pengg): might be better to use netlink socket, once we have a library to
// use
bool TunDevice::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))) {
CleanUpFileDescriptor();
return false;
}
return true;
}
bool TunDevice::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 TunDevice::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 TunDevice::CleanUpFileDescriptor() {
if (file_descriptor_ != kInvalidFd) {
kernel_.close(file_descriptor_);
file_descriptor_ = kInvalidFd;
}
}
} // namespace quic