|  | // 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 "absl/cleanup/cleanup.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(); | 
|  | } | 
|  | CloseDevice(); | 
|  | } | 
|  |  | 
|  | 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_) { | 
|  | 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 TunDevice::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 TunDevice::GetFileDescriptor() const { | 
|  | return file_descriptor_; | 
|  | } | 
|  |  | 
|  | bool TunDevice::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_TUN | IFF_MULTI_QUEUE | IFF_NO_PI; | 
|  |  | 
|  | // 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 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))) { | 
|  | CloseDevice(); | 
|  | 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::CloseDevice() { | 
|  | if (file_descriptor_ != kInvalidFd) { | 
|  | kernel_.close(file_descriptor_); | 
|  | file_descriptor_ = kInvalidFd; | 
|  | } | 
|  | } | 
|  |  | 
|  | }  // namespace quic |