blob: e1fef11d69df784fa9afcc9d5bfdbbd317c3eaeb [file] [log] [blame]
// Copyright 2018 The Chromium OS 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 "arc/network/device_manager.h"
#include <linux/rtnetlink.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <utility>
#include <base/strings/string_util.h>
#include <base/strings/stringprintf.h>
#include <shill/net/rtnl_handler.h>
namespace arc_networkd {
namespace {
constexpr const char kArcDevicePrefix[] = "arc";
constexpr const char kVpnInterfaceHostPattern[] = "tun";
constexpr const char kVpnInterfaceGuestPrefix[] = "cros_";
constexpr std::array<const char*, 2> kEthernetInterfacePrefixes{{"eth", "usb"}};
constexpr std::array<const char*, 2> kWifiInterfacePrefixes{{"wlan", "mlan"}};
// Global compile time switch for the method configuring Ipv6 address for ARC.
// When set to true, arc-networkd will try to generate an address and set onto
// ARC interface (legacy method); when set to false, NDProxy is enabled.
constexpr const bool kFindIpv6RoutesLegacy = true;
bool IsArcDevice(const std::string& ifname) {
return base::StartsWith(ifname, kArcDevicePrefix,
base::CompareCase::INSENSITIVE_ASCII);
}
bool IsHostVpnInterface(const std::string& ifname) {
return base::StartsWith(ifname, kVpnInterfaceHostPattern,
base::CompareCase::INSENSITIVE_ASCII);
}
bool IsEthernetInterface(const std::string& ifname) {
for (const auto& prefix : kEthernetInterfacePrefixes) {
if (base::StartsWith(ifname, prefix,
base::CompareCase::INSENSITIVE_ASCII)) {
return true;
}
}
return false;
}
bool IsWifiInterface(const std::string& ifname) {
for (const auto& prefix : kWifiInterfacePrefixes) {
if (base::StartsWith(ifname, prefix,
base::CompareCase::INSENSITIVE_ASCII)) {
return true;
}
}
return false;
}
bool IsMulticastInterface(const std::string& ifname) {
if (ifname.empty()) {
return false;
}
int fd = socket(AF_INET, SOCK_DGRAM, 0);
if (fd < 0) {
// If IPv4 fails, try to open a socket using IPv6.
fd = socket(AF_INET6, SOCK_DGRAM, 0);
if (fd < 0) {
LOG(ERROR) << "Unable to create socket";
return false;
}
}
struct ifreq ifr;
memset(&ifr, 0, sizeof(ifr));
strncpy(ifr.ifr_name, ifname.c_str(), IFNAMSIZ);
if (ioctl(fd, SIOCGIFFLAGS, &ifr) < 0) {
PLOG(ERROR) << "SIOCGIFFLAGS failed for " << ifname;
close(fd);
return false;
}
close(fd);
return (ifr.ifr_flags & IFF_MULTICAST);
}
} // namespace
DeviceManager::DeviceManager(std::unique_ptr<ShillClient> shill_client,
AddressManager* addr_mgr,
Datapath* datapath,
bool is_arc_legacy,
HelperProcess* mcast_proxy,
HelperProcess* nd_proxy)
: shill_client_(std::move(shill_client)),
addr_mgr_(addr_mgr),
datapath_(datapath),
mcast_proxy_(mcast_proxy),
nd_proxy_(nd_proxy),
is_arc_legacy_(is_arc_legacy) {
DCHECK(shill_client_);
DCHECK(addr_mgr_);
DCHECK(datapath_);
CHECK(mcast_proxy_);
link_listener_ = std::make_unique<shill::RTNLListener>(
shill::RTNLHandler::kRequestLink,
Bind(&DeviceManager::LinkMsgHandler, weak_factory_.GetWeakPtr()));
shill::RTNLHandler::GetInstance()->Start(RTMGRP_LINK);
shill_client_->RegisterDefaultInterfaceChangedHandler(base::Bind(
&DeviceManager::OnDefaultInterfaceChanged, weak_factory_.GetWeakPtr()));
shill_client_->RegisterDevicesChangedHandler(
base::Bind(&DeviceManager::OnDevicesChanged, weak_factory_.GetWeakPtr()));
shill_client_->ScanDevices(
base::Bind(&DeviceManager::OnDevicesChanged, weak_factory_.GetWeakPtr()));
if (nd_proxy) {
nd_proxy->RegisterDeviceMessageHandler(
base::Bind(&DeviceManager::OnDeviceMessageFromNDProxy,
weak_factory_.GetWeakPtr()));
}
}
DeviceManager::~DeviceManager() {
shill_client_->UnregisterDevicesChangedHandler();
shill_client_->UnregisterDefaultInterfaceChangedHandler();
}
void DeviceManager::RegisterDeviceAddedHandler(const DeviceHandler& handler) {
add_handlers_.emplace_back(handler);
}
void DeviceManager::RegisterDeviceRemovedHandler(const DeviceHandler& handler) {
rm_handlers_.emplace_back(handler);
}
void DeviceManager::RegisterDeviceIPv6AddressFoundHandler(
const DeviceHandler& handler) {
ipv6_handlers_.emplace_back(handler);
}
void DeviceManager::RegisterDefaultInterfaceChangedHandler(
const NameHandler& handler) {
default_iface_handlers_.emplace_back(handler);
}
void DeviceManager::ProcessDevices(const DeviceHandler& handler) {
for (const auto& d : devices_) {
handler.Run(d.second.get());
}
}
void DeviceManager::OnGuestStart(GuestMessage::GuestType guest) {
for (auto& d : devices_) {
d.second->OnGuestStart(guest);
}
}
void DeviceManager::OnGuestStop(GuestMessage::GuestType guest) {
for (auto& d : devices_) {
d.second->OnGuestStop(guest);
}
}
void DeviceManager::OnDeviceMessageFromNDProxy(const DeviceMessage& msg) {
const std::string& dev_ifname = msg.dev_ifname();
LOG_IF(DFATAL, dev_ifname.empty())
<< "Received DeviceMessage w/ empty dev_ifname";
if (!datapath_->AddIPv6HostRoute(dev_ifname, msg.guest_ip6addr(), 128)) {
LOG(WARNING) << "Failed to setup the IPv6 route for interface "
<< dev_ifname;
}
}
bool DeviceManager::Add(const std::string& name) {
if (name.empty() || Exists(name) ||
(is_arc_legacy_ && name != kAndroidLegacyDevice))
return false;
auto device = MakeDevice(name);
if (!device)
return false;
LOG(INFO) << "Adding device " << *device;
if (device->options().ipv6_enabled) {
if (!datapath_->AddIPv6Forwarding(device->ifname(),
device->config().host_ifname())) {
LOG(ERROR) << "Failed to setup iptables forwarding rule for IPv6 from "
<< device->ifname() << " to "
<< device->config().host_ifname();
}
if (device->options().find_ipv6_routes_legacy) {
device->RegisterIPv6SetupHandler(base::Bind(
&DeviceManager::OnIPv6AddressFound, weak_factory_.GetWeakPtr()));
}
}
for (auto& h : add_handlers_) {
h.Run(device.get());
}
devices_.emplace(name, std::move(device));
return true;
}
bool DeviceManager::Remove(const std::string& name) {
auto it = devices_.find(name);
if (it == devices_.end())
return false;
LOG(INFO) << "Removing device " << name;
if (it->second->options().ipv6_enabled) {
datapath_->RemoveIPv6Forwarding(it->second->ifname(),
it->second->config().host_ifname());
}
if (it->second->options().fwd_multicast) {
DeviceMessage msg;
msg.set_dev_ifname(it->second->ifname());
msg.set_teardown(true);
IpHelperMessage ipm;
*ipm.mutable_device_message() = msg;
mcast_proxy_->SendMessage(ipm);
}
for (auto& h : rm_handlers_) {
h.Run(it->second.get());
}
devices_.erase(it);
return true;
}
Device* DeviceManager::FindByHostInterface(const std::string& ifname) const {
// As long as the device list is small, this linear search is fine.
for (auto& d : devices_) {
if (d.second->config().host_ifname() == ifname)
return d.second.get();
}
return nullptr;
}
Device* DeviceManager::FindByGuestInterface(const std::string& ifname) const {
// As long as the device list is small, this linear search is fine.
for (auto& d : devices_) {
if (d.second->config().guest_ifname() == ifname)
return d.second.get();
}
return nullptr;
}
bool DeviceManager::Exists(const std::string& name) const {
return devices_.find(name) != devices_.end();
}
const std::string& DeviceManager::DefaultInterface() const {
return default_ifname_;
}
void DeviceManager::LinkMsgHandler(const shill::RTNLMessage& msg) {
if (!msg.HasAttribute(IFLA_IFNAME)) {
LOG(ERROR) << "Link event message does not have IFLA_IFNAME";
return;
}
// Only consider virtual interfaces that were created for guests; for now this
// only includes those prefixed with 'arc'.
shill::ByteString b(msg.GetAttribute(IFLA_IFNAME));
std::string ifname(reinterpret_cast<const char*>(
b.GetSubstring(0, IFNAMSIZ).GetConstData()));
if (!IsArcDevice(ifname))
return;
bool link_up = msg.link_status().flags & IFF_UP;
Device* device = FindByHostInterface(ifname);
if (!device || !device->HostLinkUp(link_up))
return;
if (!link_up) {
LOG(INFO) << ifname << " is now down";
device->Disable();
if ((nd_proxy_ && device->options().ipv6_enabled &&
!device->options().find_ipv6_routes_legacy) ||
device->options().fwd_multicast) {
DeviceMessage msg;
msg.set_dev_ifname(device->ifname());
msg.set_br_ifname(device->config().host_ifname());
msg.set_teardown(true);
IpHelperMessage ipm;
*ipm.mutable_device_message() = msg;
if (nd_proxy_ && device->options().ipv6_enabled &&
!device->options().find_ipv6_routes_legacy)
nd_proxy_->SendMessage(ipm);
if (device->options().fwd_multicast)
mcast_proxy_->SendMessage(ipm);
}
return;
}
// The link is now up.
LOG(INFO) << ifname << " is now up";
if (device->IsLegacyAndroid())
device->Enable(default_ifname_);
else if (!device->IsAndroid())
device->Enable(device->config().guest_ifname());
if ((nd_proxy_ != nullptr && device->options().ipv6_enabled &&
!device->options().find_ipv6_routes_legacy) ||
device->options().fwd_multicast) {
DeviceMessage msg;
msg.set_dev_ifname(device->ifname());
msg.set_guest_ip4addr(device->config().guest_ipv4_addr());
msg.set_br_ifname(device->config().host_ifname());
IpHelperMessage ipm;
*ipm.mutable_device_message() = msg;
if (nd_proxy_ != nullptr && device->options().ipv6_enabled &&
!device->options().find_ipv6_routes_legacy)
nd_proxy_->SendMessage(ipm);
if (device->options().fwd_multicast)
mcast_proxy_->SendMessage(ipm);
}
}
std::unique_ptr<Device> DeviceManager::MakeDevice(
const std::string& name) const {
DCHECK(!name.empty());
Device::Options opts;
std::string host_ifname, guest_ifname;
AddressManager::Guest guest = AddressManager::Guest::ARC;
if (name == kAndroidLegacyDevice) {
host_ifname = "arcbr0";
guest_ifname = "arc0";
opts.ipv6_enabled = true;
opts.find_ipv6_routes_legacy = kFindIpv6RoutesLegacy;
opts.fwd_multicast = true;
} else {
if (name == kAndroidDevice) {
host_ifname = "arcbr0";
opts.fwd_multicast = false;
} else {
guest = AddressManager::Guest::ARC_NET;
host_ifname = base::StringPrintf("arc_%s", name.c_str());
opts.fwd_multicast = IsMulticastInterface(name);
}
guest_ifname = name;
// Android VPNs and native VPNs use the same "tun%d" name pattern for VPN
// tun interfaces. To distinguish between both and avoid name collisions
// native VPN interfaces are not exposed with their exact names inside the
// ARC network namespace. This additional naming convention is not known to
// Chrome and ARC has to fix names in ArcNetworkBridge.java when receiving
// NetworkConfiguration mojo objects from Chrome.
if (IsHostVpnInterface(guest_ifname)) {
guest_ifname = kVpnInterfaceGuestPrefix + guest_ifname;
}
// TODO(crbug/726815) Also enable |ipv6_enabled| for cellular networks
// once IPv6 is enabled on cellular networks in shill.
opts.ipv6_enabled =
IsEthernetInterface(guest_ifname) || IsWifiInterface(guest_ifname);
opts.find_ipv6_routes_legacy = kFindIpv6RoutesLegacy;
}
auto ipv4_subnet = addr_mgr_->AllocateIPv4Subnet(guest);
if (!ipv4_subnet) {
LOG(ERROR) << "Subnet already in use or unavailable. Cannot make device: "
<< name;
return nullptr;
}
auto host_ipv4_addr = ipv4_subnet->AllocateAtOffset(0);
if (!host_ipv4_addr) {
LOG(ERROR)
<< "Bridge address already in use or unavailable. Cannot make device: "
<< name;
return nullptr;
}
auto guest_ipv4_addr = ipv4_subnet->AllocateAtOffset(1);
if (!guest_ipv4_addr) {
LOG(ERROR)
<< "ARC address already in use or unavailable. Cannot make device: "
<< name;
return nullptr;
}
auto config = std::make_unique<Device::Config>(
host_ifname, guest_ifname, addr_mgr_->GenerateMacAddress(),
std::move(ipv4_subnet), std::move(host_ipv4_addr),
std::move(guest_ipv4_addr));
return std::make_unique<Device>(name, std::move(config), opts);
}
void DeviceManager::OnDefaultInterfaceChanged(const std::string& ifname) {
if (ifname == default_ifname_)
return;
LOG(INFO) << "Default interface changed from [" << default_ifname_ << "] to ["
<< ifname << "]";
default_ifname_ = ifname;
for (const auto& h : default_iface_handlers_) {
h.Run(default_ifname_);
}
}
void DeviceManager::OnDevicesChanged(const std::set<std::string>& devices) {
std::vector<std::string> removed;
for (const auto& d : devices_) {
const std::string& name = d.first;
if (name != kAndroidDevice && name != kAndroidLegacyDevice &&
devices.find(name) == devices.end())
removed.emplace_back(name);
}
for (const std::string& name : removed)
Remove(name);
for (const std::string& name : devices)
Add(name);
}
void DeviceManager::OnIPv6AddressFound(Device* device) {
for (const auto& h : ipv6_handlers_) {
h.Run(device);
}
}
} // namespace arc_networkd