1 // Copyright (c) 2019 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "quiche/quic/qbone/bonnet/tun_device.h"
6
7 #include <fcntl.h>
8 #include <linux/if_tun.h>
9 #include <net/if.h>
10 #include <sys/ioctl.h>
11 #include <sys/socket.h>
12
13 #include "absl/cleanup/cleanup.h"
14 #include "quiche/quic/platform/api/quic_bug_tracker.h"
15 #include "quiche/quic/platform/api/quic_logging.h"
16 #include "quiche/quic/qbone/platform/kernel_interface.h"
17
18 ABSL_FLAG(std::string, qbone_client_tun_device_path, "/dev/net/tun",
19 "The path to the QBONE client's TUN device.");
20
21 namespace quic {
22
23 const int kInvalidFd = -1;
24
TunTapDevice(const std::string & interface_name,int mtu,bool persist,bool setup_tun,bool is_tap,KernelInterface * kernel)25 TunTapDevice::TunTapDevice(const std::string& interface_name, int mtu,
26 bool persist, bool setup_tun, bool is_tap,
27 KernelInterface* kernel)
28 : interface_name_(interface_name),
29 mtu_(mtu),
30 persist_(persist),
31 setup_tun_(setup_tun),
32 is_tap_(is_tap),
33 file_descriptor_(kInvalidFd),
34 kernel_(*kernel) {}
35
~TunTapDevice()36 TunTapDevice::~TunTapDevice() {
37 if (!persist_) {
38 Down();
39 }
40 CloseDevice();
41 }
42
Init()43 bool TunTapDevice::Init() {
44 if (interface_name_.empty() || interface_name_.size() >= IFNAMSIZ) {
45 QUIC_BUG(quic_bug_10995_1)
46 << "interface_name must be nonempty and shorter than " << IFNAMSIZ;
47 return false;
48 }
49
50 if (!OpenDevice()) {
51 return false;
52 }
53
54 if (!ConfigureInterface()) {
55 return false;
56 }
57
58 return true;
59 }
60
61 // TODO(pengg): might be better to use netlink socket, once we have a library to
62 // use
Up()63 bool TunTapDevice::Up() {
64 if (!setup_tun_) {
65 return true;
66 }
67 struct ifreq if_request;
68 memset(&if_request, 0, sizeof(if_request));
69 // copy does not zero-terminate the result string, but we've memset the
70 // entire struct.
71 interface_name_.copy(if_request.ifr_name, IFNAMSIZ);
72 if_request.ifr_flags = IFF_UP;
73
74 return NetdeviceIoctl(SIOCSIFFLAGS, reinterpret_cast<void*>(&if_request));
75 }
76
77 // TODO(pengg): might be better to use netlink socket, once we have a library to
78 // use
Down()79 bool TunTapDevice::Down() {
80 if (!setup_tun_) {
81 return true;
82 }
83 struct ifreq if_request;
84 memset(&if_request, 0, sizeof(if_request));
85 // copy does not zero-terminate the result string, but we've memset the
86 // entire struct.
87 interface_name_.copy(if_request.ifr_name, IFNAMSIZ);
88 if_request.ifr_flags = 0;
89
90 return NetdeviceIoctl(SIOCSIFFLAGS, reinterpret_cast<void*>(&if_request));
91 }
92
GetFileDescriptor() const93 int TunTapDevice::GetFileDescriptor() const { return file_descriptor_; }
94
OpenDevice()95 bool TunTapDevice::OpenDevice() {
96 if (file_descriptor_ != kInvalidFd) {
97 CloseDevice();
98 }
99
100 struct ifreq if_request;
101 memset(&if_request, 0, sizeof(if_request));
102 // copy does not zero-terminate the result string, but we've memset the entire
103 // struct.
104 interface_name_.copy(if_request.ifr_name, IFNAMSIZ);
105
106 // Always set IFF_MULTI_QUEUE since a persistent device does not allow this
107 // flag to be flipped when re-opening it. The only way to flip this flag is to
108 // destroy the device and create a new one, but that deletes any existing
109 // routing associated with the interface, which makes the meaning of the
110 // 'persist' bit ambiguous.
111 if_request.ifr_flags = IFF_MULTI_QUEUE | IFF_NO_PI;
112 if (is_tap_) {
113 if_request.ifr_flags |= IFF_TAP;
114 } else {
115 if_request.ifr_flags |= IFF_TUN;
116 }
117
118 // When the device is running with IFF_MULTI_QUEUE set, each call to open will
119 // create a queue which can be used to read/write packets from/to the device.
120 bool successfully_opened = false;
121 auto cleanup = absl::MakeCleanup([this, &successfully_opened]() {
122 if (!successfully_opened) {
123 CloseDevice();
124 }
125 });
126
127 const std::string tun_device_path =
128 absl::GetFlag(FLAGS_qbone_client_tun_device_path);
129 int fd = kernel_.open(tun_device_path.c_str(), O_RDWR);
130 if (fd < 0) {
131 QUIC_PLOG(WARNING) << "Failed to open " << tun_device_path;
132 return successfully_opened;
133 }
134 file_descriptor_ = fd;
135 if (!CheckFeatures(fd)) {
136 return successfully_opened;
137 }
138
139 if (kernel_.ioctl(fd, TUNSETIFF, reinterpret_cast<void*>(&if_request)) != 0) {
140 QUIC_PLOG(WARNING) << "Failed to TUNSETIFF on fd(" << fd << ")";
141 return successfully_opened;
142 }
143
144 if (kernel_.ioctl(
145 fd, TUNSETPERSIST,
146 persist_ ? reinterpret_cast<void*>(&if_request) : nullptr) != 0) {
147 QUIC_PLOG(WARNING) << "Failed to TUNSETPERSIST on fd(" << fd << ")";
148 return successfully_opened;
149 }
150
151 successfully_opened = true;
152 return successfully_opened;
153 }
154
155 // TODO(pengg): might be better to use netlink socket, once we have a library to
156 // use
ConfigureInterface()157 bool TunTapDevice::ConfigureInterface() {
158 if (!setup_tun_) {
159 return true;
160 }
161
162 struct ifreq if_request;
163 memset(&if_request, 0, sizeof(if_request));
164 // copy does not zero-terminate the result string, but we've memset the entire
165 // struct.
166 interface_name_.copy(if_request.ifr_name, IFNAMSIZ);
167 if_request.ifr_mtu = mtu_;
168
169 if (!NetdeviceIoctl(SIOCSIFMTU, reinterpret_cast<void*>(&if_request))) {
170 CloseDevice();
171 return false;
172 }
173
174 return true;
175 }
176
CheckFeatures(int tun_device_fd)177 bool TunTapDevice::CheckFeatures(int tun_device_fd) {
178 unsigned int actual_features;
179 if (kernel_.ioctl(tun_device_fd, TUNGETFEATURES, &actual_features) != 0) {
180 QUIC_PLOG(WARNING) << "Failed to TUNGETFEATURES";
181 return false;
182 }
183 unsigned int required_features = IFF_TUN | IFF_NO_PI;
184 if ((required_features & actual_features) != required_features) {
185 QUIC_LOG(WARNING)
186 << "Required feature does not exist. required_features: 0x" << std::hex
187 << required_features << " vs actual_features: 0x" << std::hex
188 << actual_features;
189 return false;
190 }
191 return true;
192 }
193
NetdeviceIoctl(int request,void * argp)194 bool TunTapDevice::NetdeviceIoctl(int request, void* argp) {
195 int fd = kernel_.socket(AF_INET6, SOCK_DGRAM, 0);
196 if (fd < 0) {
197 QUIC_PLOG(WARNING) << "Failed to create AF_INET6 socket.";
198 return false;
199 }
200
201 if (kernel_.ioctl(fd, request, argp) != 0) {
202 QUIC_PLOG(WARNING) << "Failed ioctl request: " << request;
203 kernel_.close(fd);
204 return false;
205 }
206 kernel_.close(fd);
207 return true;
208 }
209
CloseDevice()210 void TunTapDevice::CloseDevice() {
211 if (file_descriptor_ != kInvalidFd) {
212 kernel_.close(file_descriptor_);
213 file_descriptor_ = kInvalidFd;
214 }
215 }
216
217 } // namespace quic
218