shill: vpn: Add support for creating a tunnel interface
Use the ioctl inteface for creating a TUN device (no RTNL interface
for this appears available). Also create a means for removing
interfaces, using the RTNL interface.
BUG=chromium-os:26841
TEST=New unit test for RTNL interface, manual testing for ioctl
interface and for TUN interface detection and handling.
Change-Id: If70eeeecd5d2a2e3c348b56c297c0f3dd4226b8f
Reviewed-on: https://gerrit.chromium.org/gerrit/17127
Commit-Ready: Paul Stewart <pstew@chromium.org>
Reviewed-by: Paul Stewart <pstew@chromium.org>
Tested-by: Paul Stewart <pstew@chromium.org>
diff --git a/device_info.cc b/device_info.cc
index 3419146..75f4734 100644
--- a/device_info.cc
+++ b/device_info.cc
@@ -2,18 +2,22 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-#include <time.h>
+#include "shill/device_info.h"
-#include <unistd.h>
-#include <string.h>
-#include <sys/socket.h>
#include <arpa/inet.h>
-#include <netinet/ether.h>
-#include <net/if.h>
-#include <net/if_arp.h>
+#include <fcntl.h>
+#include <linux/if_tun.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
-#include <fcntl.h>
+#include <net/if.h>
+#include <net/if_arp.h>
+#include <netinet/ether.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <time.h>
+#include <unistd.h>
+
#include <string>
#include <base/callback_old.h>
@@ -27,7 +31,6 @@
#include "shill/control_interface.h"
#include "shill/device.h"
-#include "shill/device_info.h"
#include "shill/device_stub.h"
#include "shill/ethernet.h"
#include "shill/manager.h"
@@ -50,6 +53,8 @@
// static
const char DeviceInfo::kInterfaceDriver[] = "/sys/class/net/%s/device/driver";
// static
+const char DeviceInfo::kInterfaceTunFlags[] = "/sys/class/net/%s/tun_flags";
+// static
const char DeviceInfo::kInterfaceType[] = "/sys/class/net/%s/type";
// static
const char *DeviceInfo::kModemDrivers[] = {
@@ -59,6 +64,8 @@
"cdc_ether",
NULL
};
+// static
+const char DeviceInfo::kTunDeviceName[] = "/dev/net/tun";
DeviceInfo::DeviceInfo(ControlInterface *control_interface,
EventDispatcher *dispatcher,
@@ -162,7 +169,19 @@
if (!file_util::ReadSymbolicLink(driver_file, &driver_path)) {
VLOG(2) << StringPrintf("%s: device %s has no device symlink",
__func__, iface_name.c_str());
- return Technology::kUnknown;
+ FilePath tun_flags_file(StringPrintf(kInterfaceTunFlags,
+ iface_name.c_str()));
+ string tun_flags_string;
+ int tun_flags = 0;
+ if (file_util::ReadFileToString(tun_flags_file, &tun_flags_string) &&
+ TrimString(tun_flags_string, "\n", &tun_flags_string) &&
+ base::HexStringToInt(tun_flags_string, &tun_flags) &&
+ (tun_flags & IFF_TUN)) {
+ VLOG(2) << StringPrintf("%s: device %s is tun device",
+ __func__, iface_name.c_str());
+ return Technology::kTunnel;
+ }
+ return Technology::kUnknown;
}
string driver_name(driver_path.BaseName().value());
@@ -200,14 +219,6 @@
DeviceRefPtr device = GetDevice(dev_index);
if (!device.get()) {
- if (msg.HasAttribute(IFLA_ADDRESS)) {
- infos_[dev_index].mac_address = msg.GetAttribute(IFLA_ADDRESS);
- VLOG(2) << "link index " << dev_index << " address "
- << infos_[dev_index].mac_address.HexEncode();
- } else {
- LOG(ERROR) << "Add Link message does not have IFLA_ADDRESS!";
- return;
- }
if (!msg.HasAttribute(IFLA_IFNAME)) {
LOG(ERROR) << "Add Link message does not have IFLA_IFNAME!";
return;
@@ -223,8 +234,16 @@
technology = GetDeviceTechnology(link_name);
}
}
- string address =
- StringToLowerASCII(infos_[dev_index].mac_address.HexEncode());
+ string address;
+ if (msg.HasAttribute(IFLA_ADDRESS)) {
+ infos_[dev_index].mac_address = msg.GetAttribute(IFLA_ADDRESS);
+ address = StringToLowerASCII(infos_[dev_index].mac_address.HexEncode());
+ VLOG(2) << "link index " << dev_index << " address "
+ << infos_[dev_index].mac_address.HexEncode();
+ } else if (technology != Technology::kTunnel) {
+ LOG(ERROR) << "Add Link message does not have IFLA_ADDRESS!";
+ return;
+ }
switch (technology) {
case Technology::kCellular:
// Cellular devices are managed by ModemInfo.
@@ -242,6 +261,12 @@
link_name, address, dev_index);
device->EnableIPv6Privacy();
break;
+ case Technology::kTunnel:
+ // Tunnel devices are managed by the VPN code.
+ VLOG(2) << "Tunnel link " << link_name << " at index " << dev_index
+ << " -- notifying VPNProvider.";
+ // TODO(pstew): Notify VPNProvider once that method exists.
+ return;
default:
device = new DeviceStub(control_interface_, dispatcher_, metrics_,
manager_, link_name, address, dev_index,
@@ -317,6 +342,36 @@
return true;
}
+bool DeviceInfo::CreateTunnelInterface(string *interface_name) {
+ int fd = HANDLE_EINTR(open(kTunDeviceName, O_RDWR));
+ if (fd < 0) {
+ PLOG(ERROR) << "failed to open " << kTunDeviceName;
+ return false;
+ }
+ file_util::ScopedFD scoped_fd(&fd);
+
+ struct ifreq ifr;
+ memset(&ifr, 0, sizeof(ifr));
+ ifr.ifr_flags = IFF_TUN | IFF_NO_PI;
+ if (HANDLE_EINTR(ioctl(fd, TUNSETIFF, &ifr))) {
+ PLOG(ERROR) << "failed to create tunnel interface";
+ return false;
+ }
+
+ if (HANDLE_EINTR(ioctl(fd, TUNSETPERSIST, 1))) {
+ PLOG(ERROR) << "failed to set tunnel interface to be persistent";
+ return false;
+ }
+
+ *interface_name = string(ifr.ifr_name);
+
+ return true;
+}
+
+bool DeviceInfo::DeleteInterface(int interface_index) {
+ return rtnl_handler_->RemoveInterface(interface_index);
+}
+
const DeviceInfo::Info *DeviceInfo::GetInfo(int interface_index) const {
map<int, Info>::const_iterator iter = infos_.find(interface_index);
if (iter == infos_.end()) {
diff --git a/device_info.h b/device_info.h
index f080fd0..3911a75 100644
--- a/device_info.h
+++ b/device_info.h
@@ -19,6 +19,7 @@
#include "shill/device.h"
#include "shill/ip_address.h"
#include "shill/rtnl_listener.h"
+#include "shill/technology.h"
namespace shill {
@@ -67,6 +68,8 @@
virtual bool GetAddresses(int interface_index,
std::vector<AddressData> *addresses) const;
virtual void FlushAddresses(int interface_index) const;
+ virtual bool CreateTunnelInterface(std::string *interface_name);
+ virtual bool DeleteInterface(int interface_index);
private:
friend class DeviceInfoTest;
@@ -84,9 +87,10 @@
static const char kInterfaceUevent[];
static const char kInterfaceUeventWifiSignature[];
static const char kInterfaceDriver[];
+ static const char kInterfaceTunFlags[];
static const char kInterfaceType[];
static const char *kModemDrivers[];
- static const char kInterfaceIPv6Privacy[];
+ static const char kTunDeviceName[];
static Technology::Identifier GetDeviceTechnology(
const std::string &face_name);
diff --git a/rtnl_handler.cc b/rtnl_handler.cc
index fde52d0..66639f5 100644
--- a/rtnl_handler.cc
+++ b/rtnl_handler.cc
@@ -310,6 +310,18 @@
IPAddress(local.family()));
}
+bool RTNLHandler::RemoveInterface(int interface_index) {
+ RTNLMessage msg(
+ RTNLMessage::kTypeLink,
+ RTNLMessage::kModeDelete,
+ NLM_F_REQUEST,
+ 0,
+ 0,
+ interface_index,
+ IPAddress::kFamilyUnknown);
+ return SendMessage(&msg);
+}
+
int RTNLHandler::GetInterfaceIndex(const string &interface_name) {
if (interface_name.empty()) {
LOG(ERROR) << "Empty interface name -- unable to obtain index.";
diff --git a/rtnl_handler.h b/rtnl_handler.h
index b945840..6b88f0b 100644
--- a/rtnl_handler.h
+++ b/rtnl_handler.h
@@ -79,6 +79,9 @@
virtual bool RemoveInterfaceAddress(int interface_index,
const IPAddress &local);
+ // Remove a network interface from the kernel.
+ virtual bool RemoveInterface(int interface_index);
+
// Request that various tables (link, address, routing) tables be
// exhaustively dumped via RTNL. As results arrive from the kernel
// they will be broadcast to all listeners. The possible values
diff --git a/rtnl_message.cc b/rtnl_message.cc
index 96e3764..bf9e8d0 100644
--- a/rtnl_message.cc
+++ b/rtnl_message.cc
@@ -265,6 +265,7 @@
hdr->hdr.nlmsg_type = (mode_ == kModeAdd) ? RTM_NEWLINK : RTM_DELLINK;
hdr->hdr.nlmsg_len = NLMSG_LENGTH(sizeof(hdr->ifi));
hdr->ifi.ifi_family = family_;
+ hdr->ifi.ifi_index = interface_index_;
hdr->ifi.ifi_type = link_status_.type;
hdr->ifi.ifi_flags = link_status_.flags;
hdr->ifi.ifi_change = link_status_.change;
diff --git a/rtnl_message_unittest.cc b/rtnl_message_unittest.cc
index 1f8d55f..461458f 100644
--- a/rtnl_message_unittest.cc
+++ b/rtnl_message_unittest.cc
@@ -592,7 +592,7 @@
ByteString(kAddNeighborMessage, sizeof(kAddNeighborMessage))));
}
-TEST_F(RTNLMessageTest, Encode) {
+TEST_F(RTNLMessageTest, EncodeRouteAdd) {
RTNLMessage msg(RTNLMessage::kTypeRoute,
RTNLMessage::kModeAdd,
0, 1, 2, 0,
@@ -628,4 +628,33 @@
13);
}
+TEST_F(RTNLMessageTest, EncodeLinkDel) {
+ const int kInterfaceIndex = 0x1234;
+ RTNLMessage pmsg(RTNLMessage::kTypeLink,
+ RTNLMessage::kModeDelete,
+ NLM_F_REQUEST,
+ 0,
+ 0,
+ kInterfaceIndex,
+ IPAddress::kFamilyUnknown);
+
+
+ RTNLMessage msg;
+ EXPECT_TRUE(msg.Decode(pmsg.Encode()));
+
+ EXPECT_EQ(RTNLMessage::kTypeLink, msg.type());
+ EXPECT_EQ(RTNLMessage::kModeDelete, msg.mode());
+ EXPECT_EQ(kInterfaceIndex, msg.interface_index());
+
+ RTNLMessage::LinkStatus status = msg.link_status();
+ EXPECT_EQ(0, status.flags);
+ EXPECT_EQ(0, status.change);
+
+ EXPECT_FALSE(msg.HasAttribute(IFLA_ADDRESS));
+ EXPECT_FALSE(msg.HasAttribute(IFLA_IFNAME));
+ EXPECT_FALSE(msg.HasAttribute(IFLA_MTU));
+ EXPECT_FALSE(msg.HasAttribute(IFLA_QDISC));
+ EXPECT_FALSE(msg.HasAttribute(IFLA_OPERSTATE));
+}
+
} // namespace shill
diff --git a/technology.cc b/technology.cc
index 36c50af..887dc1a 100644
--- a/technology.cc
+++ b/technology.cc
@@ -20,6 +20,7 @@
using std::string;
using std::vector;
+const char Technology::kTunnelName[] = "Tunnel";
const char Technology::kUnknownName[] = "Unknown";
// static
@@ -32,6 +33,8 @@
return kCellular;
} else if (name == flimflam::kTypeVPN) {
return kVPN;
+ } else if (name == kTunnelName) {
+ return kTunnel;
} else {
return kUnknown;
}
@@ -47,6 +50,8 @@
return flimflam::kTypeCellular;
} else if (id == kVPN) {
return flimflam::kTypeVPN;
+ } else if (id == kTunnel) {
+ return kTunnelName;
} else {
return kUnknownName;
}
diff --git a/technology.h b/technology.h
index 160a68a..ec3a301 100644
--- a/technology.h
+++ b/technology.h
@@ -20,6 +20,7 @@
kWiFiMonitor,
kCellular,
kVPN,
+ kTunnel,
kBlacklisted,
kUnknown,
};
@@ -38,6 +39,7 @@
Error *error);
private:
+ static const char kTunnelName[];
static const char kUnknownName[];
};