summaryrefslogtreecommitdiff
path: root/chromium/net/third_party/quiche/src/quic/qbone/bonnet/tun_device.cc
blob: 65197cdd4d902b1fa6bc41fb6a67b49b32b0413b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
// 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