| // Copyright (c) 2012 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 "shill/device_info.h" |
| |
| #include <arpa/inet.h> |
| #include <fcntl.h> |
| #include <linux/if_tun.h> |
| #include <linux/netlink.h> |
| #include <linux/rtnetlink.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/bind.h> |
| #include <base/file_util.h> |
| #include <base/memory/scoped_ptr.h> |
| #include <base/stl_util.h> |
| #include <base/string_number_conversions.h> |
| #include <base/string_util.h> |
| #include <base/stringprintf.h> |
| |
| #include "shill/control_interface.h" |
| #include "shill/device.h" |
| #include "shill/device_stub.h" |
| #include "shill/ethernet.h" |
| #include "shill/logging.h" |
| #include "shill/manager.h" |
| #include "shill/netlink_attribute.h" |
| #include "shill/netlink_manager.h" |
| #include "shill/nl80211_message.h" |
| #include "shill/routing_table.h" |
| #include "shill/rtnl_handler.h" |
| #include "shill/rtnl_listener.h" |
| #include "shill/rtnl_message.h" |
| #include "shill/service.h" |
| #include "shill/sockets.h" |
| #include "shill/virtio_ethernet.h" |
| #include "shill/vpn_provider.h" |
| #include "shill/wifi.h" |
| |
| using base::Bind; |
| using base::FilePath; |
| using base::StringPrintf; |
| using base::Unretained; |
| using std::map; |
| using std::set; |
| using std::string; |
| using std::vector; |
| |
| namespace shill { |
| |
| // static |
| const char DeviceInfo::kModemPseudoDeviceNamePrefix[] = "pseudomodem"; |
| const char DeviceInfo::kEthernetPseudoDeviceNamePrefix[] = "pseudoethernet"; |
| const char DeviceInfo::kDeviceInfoRoot[] = "/sys/class/net"; |
| const char DeviceInfo::kDriverCdcEther[] = "cdc_ether"; |
| const char DeviceInfo::kDriverCdcNcm[] = "cdc_ncm"; |
| const char DeviceInfo::kDriverGdmWiMax[] = "gdm_wimax"; |
| const char DeviceInfo::kDriverVirtioNet[] = "virtio_net"; |
| const char DeviceInfo::kInterfaceUevent[] = "uevent"; |
| const char DeviceInfo::kInterfaceUeventWifiSignature[] = "DEVTYPE=wlan\n"; |
| const char DeviceInfo::kInterfaceDevice[] = "device"; |
| const char DeviceInfo::kInterfaceDriver[] = "device/driver"; |
| const char DeviceInfo::kInterfaceTunFlags[] = "tun_flags"; |
| const char DeviceInfo::kInterfaceType[] = "type"; |
| const char *DeviceInfo::kModemDrivers[] = { |
| "gobi", |
| "QCUSBNet2k", |
| "GobiNet", |
| "cdc_mbim", |
| "qmi_wwan" |
| }; |
| const char DeviceInfo::kTunDeviceName[] = "/dev/net/tun"; |
| const int DeviceInfo::kDelayedDeviceCreationSeconds = 5; |
| const int DeviceInfo::kRequestLinkStatisticsIntervalMilliseconds = 20000; |
| |
| DeviceInfo::DeviceInfo(ControlInterface *control_interface, |
| EventDispatcher *dispatcher, |
| Metrics *metrics, |
| Manager *manager) |
| : control_interface_(control_interface), |
| dispatcher_(dispatcher), |
| metrics_(metrics), |
| manager_(manager), |
| link_callback_(Bind(&DeviceInfo::LinkMsgHandler, Unretained(this))), |
| address_callback_(Bind(&DeviceInfo::AddressMsgHandler, Unretained(this))), |
| link_listener_(NULL), |
| address_listener_(NULL), |
| device_info_root_(kDeviceInfoRoot), |
| routing_table_(RoutingTable::GetInstance()), |
| rtnl_handler_(RTNLHandler::GetInstance()), |
| netlink_manager_(NetlinkManager::GetInstance()), |
| sockets_(new Sockets()) { |
| } |
| |
| DeviceInfo::~DeviceInfo() {} |
| |
| void DeviceInfo::AddDeviceToBlackList(const string &device_name) { |
| black_list_.insert(device_name); |
| } |
| |
| bool DeviceInfo::IsDeviceBlackListed(const string &device_name) { |
| return ContainsKey(black_list_, device_name); |
| } |
| |
| void DeviceInfo::Start() { |
| link_listener_.reset( |
| new RTNLListener(RTNLHandler::kRequestLink, link_callback_)); |
| address_listener_.reset( |
| new RTNLListener(RTNLHandler::kRequestAddr, address_callback_)); |
| rtnl_handler_->RequestDump(RTNLHandler::kRequestLink | |
| RTNLHandler::kRequestAddr); |
| request_link_statistics_callback_.Reset( |
| Bind(&DeviceInfo::RequestLinkStatistics, AsWeakPtr())); |
| dispatcher_->PostDelayedTask(request_link_statistics_callback_.callback(), |
| kRequestLinkStatisticsIntervalMilliseconds); |
| } |
| |
| void DeviceInfo::Stop() { |
| link_listener_.reset(); |
| address_listener_.reset(); |
| infos_.clear(); |
| request_link_statistics_callback_.Cancel(); |
| delayed_devices_callback_.Cancel(); |
| delayed_devices_.clear(); |
| } |
| |
| vector<string> DeviceInfo::GetUninitializedTechnologies() const { |
| set<string> unique_technologies; |
| set<Technology::Identifier> initialized_technologies; |
| for (map<int, Info>::const_iterator it = infos_.begin(); it != infos_.end(); |
| ++it) { |
| Technology::Identifier technology = it->second.technology; |
| if (it->second.device) { |
| // If there is more than one device for a technology and at least |
| // one of them has been initialized, make sure that it doesn't get |
| // listed as uninitialized. |
| initialized_technologies.insert(technology); |
| unique_technologies.erase(Technology::NameFromIdentifier(technology)); |
| continue; |
| } |
| if (Technology::IsPrimaryConnectivityTechnology(technology) && |
| !ContainsKey(initialized_technologies, technology)) |
| unique_technologies.insert(Technology::NameFromIdentifier(technology)); |
| } |
| return vector<string>(unique_technologies.begin(), unique_technologies.end()); |
| } |
| |
| void DeviceInfo::RegisterDevice(const DeviceRefPtr &device) { |
| SLOG(Device, 2) << __func__ << "(" << device->link_name() << ", " |
| << device->interface_index() << ")"; |
| delayed_devices_.erase(device->interface_index()); |
| CHECK(!GetDevice(device->interface_index()).get()); |
| infos_[device->interface_index()].device = device; |
| if (metrics_->IsDeviceRegistered(device->interface_index(), |
| device->technology())) { |
| metrics_->NotifyDeviceInitialized(device->interface_index()); |
| } else { |
| metrics_->RegisterDevice(device->interface_index(), device->technology()); |
| } |
| if (Technology::IsPrimaryConnectivityTechnology(device->technology())) { |
| manager_->RegisterDevice(device); |
| } |
| } |
| |
| void DeviceInfo::DeregisterDevice(const DeviceRefPtr &device) { |
| int interface_index = device->interface_index(); |
| |
| SLOG(Device, 2) << __func__ << "(" << device->link_name() << ", " |
| << interface_index << ")"; |
| CHECK((device->technology() == Technology::kCellular) || |
| (device->technology() == Technology::kWiMax)); |
| |
| // Release reference to the device |
| map<int, Info>::iterator iter = infos_.find(interface_index); |
| if (iter != infos_.end()) { |
| SLOG(Device, 2) << "Removing device from info for index: " |
| << interface_index; |
| manager_->DeregisterDevice(device); |
| // Release the reference to the device, but maintain the mapping |
| // for the index. That will be cleaned up by an RTNL message. |
| iter->second.device = NULL; |
| } |
| metrics_->DeregisterDevice(device->interface_index()); |
| } |
| |
| FilePath DeviceInfo::GetDeviceInfoPath(const string &iface_name, |
| const string &path_name) { |
| return device_info_root_.Append(iface_name).Append(path_name); |
| } |
| |
| bool DeviceInfo::GetDeviceInfoContents(const string &iface_name, |
| const string &path_name, |
| string *contents_out) { |
| return file_util::ReadFileToString(GetDeviceInfoPath(iface_name, path_name), |
| contents_out); |
| } |
| bool DeviceInfo::GetDeviceInfoSymbolicLink(const string &iface_name, |
| const string &path_name, |
| FilePath *path_out) { |
| return file_util::ReadSymbolicLink(GetDeviceInfoPath(iface_name, path_name), |
| path_out); |
| } |
| |
| Technology::Identifier DeviceInfo::GetDeviceTechnology( |
| const string &iface_name) { |
| string type_string; |
| int arp_type = ARPHRD_VOID; |
| if (GetDeviceInfoContents(iface_name, kInterfaceType, &type_string) && |
| TrimString(type_string, "\n", &type_string) && |
| !base::StringToInt(type_string, &arp_type)) { |
| arp_type = ARPHRD_VOID; |
| } |
| |
| string contents; |
| if (!GetDeviceInfoContents(iface_name, kInterfaceUevent, &contents)) { |
| LOG(INFO) << StringPrintf("%s: device %s has no uevent file", |
| __func__, iface_name.c_str()); |
| return Technology::kUnknown; |
| } |
| |
| // If the "uevent" file contains the string "DEVTYPE=wlan\n" at the |
| // start of the file or after a newline, we can safely assume this |
| // is a wifi device. |
| if (contents.find(kInterfaceUeventWifiSignature) != string::npos) { |
| SLOG(Device, 2) |
| << StringPrintf("%s: device %s has wifi signature in uevent file", |
| __func__, iface_name.c_str()); |
| if (arp_type == ARPHRD_IEEE80211_RADIOTAP) { |
| SLOG(Device, 2) << StringPrintf("%s: wifi device %s is in monitor mode", |
| __func__, iface_name.c_str()); |
| return Technology::kWiFiMonitor; |
| } |
| return Technology::kWifi; |
| } |
| |
| // Special case for pseudo modems which are used for testing |
| if (iface_name.find(kModemPseudoDeviceNamePrefix) == 0) { |
| SLOG(Device, 2) << StringPrintf( |
| "%s: device %s is a pseudo modem for testing", |
| __func__, iface_name.c_str()); |
| return Technology::kCellular; |
| } |
| |
| // Special case for pseudo ethernet devices which are used for testing. |
| if (iface_name.find(kEthernetPseudoDeviceNamePrefix) == 0) { |
| SLOG(Device, 2) << StringPrintf( |
| "%s: device %s is a virtual ethernet device for testing", |
| __func__, iface_name.c_str()); |
| return Technology::kEthernet; |
| } |
| |
| FilePath driver_path; |
| if (!GetDeviceInfoSymbolicLink(iface_name, kInterfaceDriver, &driver_path)) { |
| SLOG(Device, 2) << StringPrintf("%s: device %s has no device symlink", |
| __func__, iface_name.c_str()); |
| if (arp_type == ARPHRD_LOOPBACK) { |
| SLOG(Device, 2) << StringPrintf("%s: device %s is a loopback device", |
| __func__, iface_name.c_str()); |
| return Technology::kLoopback; |
| } |
| if (arp_type == ARPHRD_PPP) { |
| SLOG(Device, 2) << StringPrintf("%s: device %s is a ppp device", |
| __func__, iface_name.c_str()); |
| return Technology::kPPP; |
| } |
| string tun_flags_str; |
| int tun_flags = 0; |
| if (GetDeviceInfoContents(iface_name, kInterfaceTunFlags, &tun_flags_str) && |
| TrimString(tun_flags_str, "\n", &tun_flags_str) && |
| base::HexStringToInt(tun_flags_str, &tun_flags) && |
| (tun_flags & IFF_TUN)) { |
| SLOG(Device, 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()); |
| // See if driver for this interface is in a list of known modem driver names. |
| for (size_t modem_idx = 0; modem_idx < arraysize(kModemDrivers); |
| ++modem_idx) { |
| if (driver_name == kModemDrivers[modem_idx]) { |
| SLOG(Device, 2) |
| << StringPrintf("%s: device %s is matched with modem driver %s", |
| __func__, iface_name.c_str(), driver_name.c_str()); |
| return Technology::kCellular; |
| } |
| } |
| |
| if (driver_name == kDriverGdmWiMax) { |
| SLOG(Device, 2) << StringPrintf("%s: device %s is a WiMAX device", |
| __func__, iface_name.c_str()); |
| return Technology::kWiMax; |
| } |
| |
| // For cdc_ether / cdc_ncm devices, make sure it's a modem because this driver |
| // can be used for other ethernet devices. |
| if (driver_name == kDriverCdcEther || driver_name == kDriverCdcNcm) { |
| if (IsCdcEthernetModemDevice(iface_name)) { |
| LOG(INFO) << StringPrintf("%s: device %s is a %s modem device", __func__, |
| iface_name.c_str(), driver_name.c_str()); |
| return Technology::kCellular; |
| } |
| SLOG(Device, 2) << StringPrintf("%s: device %s is a %s device", __func__, |
| iface_name.c_str(), driver_name.c_str()); |
| return Technology::kCDCEthernet; |
| } |
| |
| // Special case for the virtio driver, used when run under KVM. See also |
| // the comment in VirtioEthernet::Start. |
| if (driver_name == kDriverVirtioNet) { |
| SLOG(Device, 2) << StringPrintf("%s: device %s is virtio ethernet", |
| __func__, iface_name.c_str()); |
| return Technology::kVirtioEthernet; |
| } |
| |
| SLOG(Device, 2) << StringPrintf("%s: device %s, with driver %s, " |
| "is defaulted to type ethernet", |
| __func__, iface_name.c_str(), |
| driver_name.c_str()); |
| return Technology::kEthernet; |
| } |
| |
| bool DeviceInfo::IsCdcEthernetModemDevice(const std::string &iface_name) { |
| // A cdc_ether / cdc_ncm device is a modem device if it also exposes tty |
| // interfaces. To determine this, we look for the existence of the tty |
| // interface in the USB device sysfs tree. |
| // |
| // A typical sysfs dir hierarchy for a cdc_ether / cdc_ncm modem USB device is |
| // as follows: |
| // |
| // /sys/devices/pci0000:00/0000:00:1d.7/usb1/1-2 |
| // 1-2:1.0 |
| // tty |
| // ttyACM0 |
| // 1-2:1.1 |
| // net |
| // usb0 |
| // 1-2:1.2 |
| // tty |
| // ttyACM1 |
| // ... |
| // |
| // /sys/class/net/usb0/device symlinks to |
| // /sys/devices/pci0000:00/0000:00:1d.7/usb1/1-2/1-2:1.1 |
| // |
| // Note that some modem devices have the tty directory one level deeper |
| // (eg. E362), so the device tree for the tty interface is: |
| // /sys/devices/pci0000:00/0000:00:1d.7/usb/1-2/1-2:1.0/ttyUSB0/tty/ttyUSB0 |
| |
| FilePath device_file = GetDeviceInfoPath(iface_name, kInterfaceDevice); |
| FilePath device_path; |
| if (!file_util::ReadSymbolicLink(device_file, &device_path)) { |
| SLOG(Device, 2) << StringPrintf("%s: device %s has no device symlink", |
| __func__, iface_name.c_str()); |
| return false; |
| } |
| if (!device_path.IsAbsolute()) { |
| device_path = device_file.DirName().Append(device_path); |
| file_util::AbsolutePath(&device_path); |
| } |
| |
| // Look for tty interface by enumerating all directories under the parent |
| // USB device and see if there's a subdirectory "tty" inside. In other |
| // words, using the example dir hierarchy above, find |
| // /sys/devices/pci0000:00/0000:00:1d.7/usb1/1-2/.../tty. |
| // If this exists, then this is a modem device. |
| return HasSubdir(device_path.DirName(), FilePath("tty")); |
| } |
| |
| // static |
| bool DeviceInfo::HasSubdir(const FilePath &base_dir, const FilePath &subdir) { |
| file_util::FileEnumerator::FileType type = |
| static_cast<file_util::FileEnumerator::FileType>( |
| file_util::FileEnumerator::DIRECTORIES | |
| file_util::FileEnumerator::SHOW_SYM_LINKS); |
| file_util::FileEnumerator dir_enum(base_dir, true, type); |
| for (FilePath curr_dir = dir_enum.Next(); !curr_dir.empty(); |
| curr_dir = dir_enum.Next()) { |
| if (curr_dir.BaseName() == subdir) |
| return true; |
| } |
| return false; |
| } |
| |
| DeviceRefPtr DeviceInfo::CreateDevice(const string &link_name, |
| const string &address, |
| int interface_index, |
| Technology::Identifier technology) { |
| DeviceRefPtr device; |
| delayed_devices_.erase(interface_index); |
| infos_[interface_index].technology = technology; |
| |
| switch (technology) { |
| case Technology::kCellular: |
| #if defined(DISABLE_CELLULAR) |
| LOG(WARNING) << "Cellular support is not implemented. " |
| << "Ignore cellular device " << link_name << " at index " |
| << interface_index << "."; |
| return NULL; |
| #else |
| // Cellular devices are managed by ModemInfo. |
| SLOG(Device, 2) << "Cellular link " << link_name |
| << " at index " << interface_index |
| << " -- notifying ModemInfo."; |
| |
| // The MAC address provided by RTNL is not reliable for Gobi 2K modems. |
| // Clear it here, and it will be fetched from the kernel in |
| // GetMACAddress(). |
| infos_[interface_index].mac_address.Clear(); |
| manager_->modem_info()->OnDeviceInfoAvailable(link_name); |
| break; |
| #endif // DISABLE_CELLULAR |
| case Technology::kEthernet: |
| device = new Ethernet(control_interface_, dispatcher_, metrics_, |
| manager_, link_name, address, interface_index); |
| device->EnableIPv6Privacy(); |
| break; |
| case Technology::kVirtioEthernet: |
| device = new VirtioEthernet(control_interface_, dispatcher_, metrics_, |
| manager_, link_name, address, |
| interface_index); |
| device->EnableIPv6Privacy(); |
| break; |
| case Technology::kWifi: |
| // Defer creating this device until we get information about the |
| // type of WiFi interface. |
| GetWiFiInterfaceInfo(interface_index); |
| break; |
| case Technology::kWiMax: |
| #if defined(DISABLE_WIMAX) |
| LOG(WARNING) << "WiMax support is not implemented. Ignore WiMax link " |
| << link_name << " at index " << interface_index << "."; |
| return NULL; |
| #else |
| // WiMax devices are managed by WiMaxProvider. |
| SLOG(Device, 2) << "WiMax link " << link_name |
| << " at index " << interface_index |
| << " -- notifying WiMaxProvider."; |
| // The MAC address provided by RTNL may not be the final value as the |
| // WiMAX device may change the address after initialization. Clear it |
| // here, and it will be fetched from the kernel when |
| // WiMaxProvider::CreateDevice() is called after the WiMAX device DBus |
| // object is created by the WiMAX manager daemon. |
| infos_[interface_index].mac_address.Clear(); |
| manager_->wimax_provider()->OnDeviceInfoAvailable(link_name); |
| break; |
| #endif // DISABLE_WIMAX |
| case Technology::kPPP: |
| case Technology::kTunnel: |
| // Tunnel and PPP devices are managed by the VPN code (PPP for |
| // l2tpipsec). Notify the VPN Provider of the interface's presence. |
| // Since CreateDevice is only called once in the lifetime of an |
| // interface index, this notification will only occur the first |
| // time the device is seen. |
| SLOG(Device, 2) << "Tunnel / PPP link " << link_name |
| << " at index " << interface_index |
| << " -- notifying VPNProvider."; |
| if (!manager_->vpn_provider()->OnDeviceInfoAvailable(link_name, |
| interface_index) && |
| technology == Technology::kTunnel) { |
| // If VPN does not know anything about this tunnel, it is probably |
| // left over from a previous instance and should not exist. |
| SLOG(Device, 2) << "Tunnel link is unused. Deleting."; |
| DeleteInterface(interface_index); |
| } |
| break; |
| case Technology::kLoopback: |
| // Loopback devices are largely ignored, but we should make sure the |
| // link is enabled. |
| SLOG(Device, 2) << "Bringing up loopback device " << link_name |
| << " at index " << interface_index; |
| rtnl_handler_->SetInterfaceFlags(interface_index, IFF_UP, IFF_UP); |
| return NULL; |
| case Technology::kCDCEthernet: |
| // CDCEthernet devices are of indeterminate type when they are |
| // initially created. Some time later, tty devices may or may |
| // not appear under the same USB device root, which will identify |
| // it as a modem. Alternatively, ModemManager may discover the |
| // device and create and register a Cellular device. In either |
| // case, we should delay creating a Device until we can make a |
| // better determination of what type this Device should be. |
| LOG(INFO) << "Delaying creation of device for " << link_name |
| << " at index " << interface_index; |
| DelayDeviceCreation(interface_index); |
| return NULL; |
| default: |
| // We will not manage this device in shill. Do not create a device |
| // object or do anything to change its state. We create a stub object |
| // which is useful for testing. |
| return new DeviceStub(control_interface_, dispatcher_, metrics_, |
| manager_, link_name, address, interface_index, |
| technology); |
| } |
| |
| // Reset the routing table and addresses. |
| routing_table_->FlushRoutes(interface_index); |
| FlushAddresses(interface_index); |
| |
| manager_->UpdateUninitializedTechnologies(); |
| |
| return device; |
| } |
| |
| void DeviceInfo::AddLinkMsgHandler(const RTNLMessage &msg) { |
| DCHECK(msg.type() == RTNLMessage::kTypeLink && |
| msg.mode() == RTNLMessage::kModeAdd); |
| int dev_index = msg.interface_index(); |
| Technology::Identifier technology = Technology::kUnknown; |
| unsigned int flags = msg.link_status().flags; |
| unsigned int change = msg.link_status().change; |
| bool new_device = |
| !ContainsKey(infos_, dev_index) || infos_[dev_index].has_addresses_only; |
| SLOG(Device, 2) << __func__ << "(index=" << dev_index |
| << std::showbase << std::hex |
| << ", flags=" << flags << ", change=" << change << ")" |
| << std::dec << std::noshowbase |
| << ", new_device=" << new_device; |
| infos_[dev_index].has_addresses_only = false; |
| infos_[dev_index].flags = flags; |
| |
| RetrieveLinkStatistics(dev_index, msg); |
| |
| DeviceRefPtr device = GetDevice(dev_index); |
| if (new_device) { |
| CHECK(!device); |
| if (!msg.HasAttribute(IFLA_IFNAME)) { |
| LOG(ERROR) << "Add Link message does not have IFLA_IFNAME!"; |
| return; |
| } |
| ByteString b(msg.GetAttribute(IFLA_IFNAME)); |
| string link_name(reinterpret_cast<const char*>(b.GetConstData())); |
| SLOG(Device, 2) << "add link index " << dev_index << " name " << link_name; |
| infos_[dev_index].name = link_name; |
| indices_[link_name] = dev_index; |
| |
| if (!link_name.empty()) { |
| if (ContainsKey(black_list_, link_name)) { |
| technology = Technology::kBlacklisted; |
| } else { |
| technology = GetDeviceTechnology(link_name); |
| } |
| } |
| string address; |
| if (msg.HasAttribute(IFLA_ADDRESS)) { |
| infos_[dev_index].mac_address = msg.GetAttribute(IFLA_ADDRESS); |
| address = StringToLowerASCII(infos_[dev_index].mac_address.HexEncode()); |
| SLOG(Device, 2) << "link index " << dev_index << " address " |
| << infos_[dev_index].mac_address.HexEncode(); |
| } else if (technology != Technology::kTunnel && |
| technology != Technology::kPPP) { |
| LOG(ERROR) << "Add Link message for link '" << link_name |
| << "' does not have IFLA_ADDRESS!"; |
| return; |
| } |
| metrics_->RegisterDevice(dev_index, technology); |
| device = CreateDevice(link_name, address, dev_index, technology); |
| if (device) { |
| RegisterDevice(device); |
| } |
| } |
| if (device) { |
| device->LinkEvent(flags, change); |
| } |
| } |
| |
| void DeviceInfo::DelLinkMsgHandler(const RTNLMessage &msg) { |
| SLOG(Device, 2) << __func__ << "(index=" << msg.interface_index() << ")"; |
| |
| DCHECK(msg.type() == RTNLMessage::kTypeLink && |
| msg.mode() == RTNLMessage::kModeDelete); |
| SLOG(Device, 2) << __func__ << "(index=" << msg.interface_index() |
| << std::showbase << std::hex |
| << ", flags=" << msg.link_status().flags |
| << ", change=" << msg.link_status().change << ")"; |
| RemoveInfo(msg.interface_index()); |
| } |
| |
| DeviceRefPtr DeviceInfo::GetDevice(int interface_index) const { |
| const Info *info = GetInfo(interface_index); |
| return info ? info->device : NULL; |
| } |
| |
| int DeviceInfo::GetIndex(const string &interface_name) const { |
| map<string, int>::const_iterator it = indices_.find(interface_name); |
| return it == indices_.end() ? -1 : it->second; |
| } |
| |
| bool DeviceInfo::GetMACAddress(int interface_index, ByteString *address) const { |
| const Info *info = GetInfo(interface_index); |
| if (!info) { |
| return false; |
| } |
| // |mac_address| from RTNL is not used for some devices, in which case it will |
| // be empty here. |
| if (!info->mac_address.IsEmpty()) { |
| *address = info->mac_address; |
| return true; |
| } |
| |
| // Ask the kernel for the MAC address. |
| *address = GetMACAddressFromKernel(interface_index); |
| return !address->IsEmpty(); |
| } |
| |
| ByteString DeviceInfo::GetMACAddressFromKernel(int interface_index) const { |
| const Info *info = GetInfo(interface_index); |
| if (!info) { |
| return ByteString(); |
| } |
| |
| const int fd = sockets_->Socket(PF_INET, SOCK_DGRAM, 0); |
| if (fd < 0) { |
| LOG(ERROR) << __func__ << ": Unable to open socket: " << fd; |
| return ByteString(); |
| } |
| |
| ScopedSocketCloser socket_closer(sockets_.get(), fd); |
| struct ifreq ifr; |
| memset(&ifr, 0, sizeof(ifr)); |
| ifr.ifr_ifindex = interface_index; |
| strcpy(ifr.ifr_ifrn.ifrn_name, info->name.c_str()); |
| int err = sockets_->Ioctl(fd, SIOCGIFHWADDR, &ifr); |
| if (err < 0) { |
| LOG(ERROR) << __func__ << ": Unable to read MAC address: " << errno; |
| return ByteString(); |
| } |
| |
| return ByteString(ifr.ifr_hwaddr.sa_data, IFHWADDRLEN); |
| } |
| |
| bool DeviceInfo::GetMACAddressOfPeer(int interface_index, |
| const IPAddress &peer, |
| ByteString *mac_address) const { |
| const Info *info = GetInfo(interface_index); |
| if (!info || !peer.IsValid()) { |
| return false; |
| } |
| |
| if (peer.family() != IPAddress::kFamilyIPv4) { |
| NOTIMPLEMENTED() << ": only implemented for IPv4"; |
| return false; |
| } |
| |
| const int fd = sockets_->Socket(PF_INET, SOCK_DGRAM, 0); |
| if (fd < 0) { |
| LOG(ERROR) << __func__ << ": Unable to open socket: " << fd; |
| return false; |
| } |
| |
| ScopedSocketCloser socket_closer(sockets_.get(), fd); |
| struct arpreq areq; |
| memset(&areq, 0, sizeof(areq)); |
| |
| strncpy(areq.arp_dev, info->name.c_str(), sizeof(areq.arp_dev) - 1); |
| areq.arp_dev[sizeof(areq.arp_dev) - 1] = '\0'; |
| |
| struct sockaddr_in *protocol_address = |
| reinterpret_cast<struct sockaddr_in *>(&areq.arp_pa); |
| protocol_address->sin_family = AF_INET; |
| CHECK_EQ(sizeof(protocol_address->sin_addr.s_addr), peer.GetLength()); |
| memcpy(&protocol_address->sin_addr.s_addr, peer.address().GetConstData(), |
| sizeof(protocol_address->sin_addr.s_addr)); |
| |
| struct sockaddr_in *hardware_address = |
| reinterpret_cast<struct sockaddr_in *>(&areq.arp_ha); |
| hardware_address->sin_family = ARPHRD_ETHER; |
| |
| int err = sockets_->Ioctl(fd, SIOCGARP, &areq); |
| if (err < 0) { |
| LOG(ERROR) << __func__ << ": Unable to perform ARP lookup: " << errno; |
| return false; |
| } |
| |
| ByteString peer_address(areq.arp_ha.sa_data, IFHWADDRLEN); |
| |
| if (peer_address.IsZero()) { |
| LOG(INFO) << __func__ << ": ARP lookup is still in progress"; |
| return false; |
| } |
| |
| CHECK(mac_address); |
| *mac_address = peer_address; |
| return true; |
| } |
| |
| bool DeviceInfo::GetAddresses(int interface_index, |
| vector<AddressData> *addresses) const { |
| const Info *info = GetInfo(interface_index); |
| if (!info) { |
| return false; |
| } |
| *addresses = info->ip_addresses; |
| return true; |
| } |
| |
| void DeviceInfo::FlushAddresses(int interface_index) const { |
| SLOG(Device, 2) << __func__ << "(" << interface_index << ")"; |
| const Info *info = GetInfo(interface_index); |
| if (!info) { |
| return; |
| } |
| const vector<AddressData> &addresses = info->ip_addresses; |
| vector<AddressData>::const_iterator iter; |
| for (iter = addresses.begin(); iter != addresses.end(); ++iter) { |
| if (iter->address.family() == IPAddress::kFamilyIPv4 || |
| (iter->scope == RT_SCOPE_UNIVERSE && |
| (iter->flags & ~IFA_F_TEMPORARY) == 0)) { |
| SLOG(Device, 2) << __func__ << ": removing ip address " |
| << iter->address.ToString() |
| << " from interface " << interface_index; |
| rtnl_handler_->RemoveInterfaceAddress(interface_index, iter->address); |
| } |
| } |
| } |
| |
| bool DeviceInfo::HasOtherAddress( |
| int interface_index, const IPAddress &this_address) const { |
| SLOG(Device, 3) << __func__ << "(" << interface_index << ")"; |
| const Info *info = GetInfo(interface_index); |
| if (!info) { |
| return false; |
| } |
| bool has_other_address = false; |
| bool has_this_address = false; |
| for (const auto &local_address : info->ip_addresses) { |
| if (local_address.address.family() != this_address.family()) { |
| continue; |
| } |
| if (local_address.address.address().Equals(this_address.address())) { |
| has_this_address = true; |
| } else if (this_address.family() == IPAddress::kFamilyIPv4) { |
| has_other_address = true; |
| } else if ((local_address.scope == RT_SCOPE_UNIVERSE && |
| (local_address.flags & IFA_F_TEMPORARY) == 0)) { |
| has_other_address = true; |
| } |
| } |
| return has_other_address && !has_this_address; |
| } |
| |
| bool DeviceInfo::HasDirectConnectivityTo( |
| int interface_index, const IPAddress &address) const { |
| SLOG(Device, 3) << __func__ << "(" << interface_index << ")"; |
| const Info *info = GetInfo(interface_index); |
| if (!info) { |
| return false; |
| } |
| |
| for (const auto &local_address : info->ip_addresses) { |
| if (local_address.address.family() == address.family() && |
| local_address.address.CanReachAddress(address)) { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| bool DeviceInfo::GetFlags(int interface_index, unsigned int *flags) const { |
| const Info *info = GetInfo(interface_index); |
| if (!info) { |
| return false; |
| } |
| *flags = info->flags; |
| return true; |
| } |
| |
| bool DeviceInfo::GetByteCounts(int interface_index, |
| uint64 *rx_bytes, |
| uint64 *tx_bytes) const { |
| const Info *info = GetInfo(interface_index); |
| if (!info) { |
| return false; |
| } |
| *rx_bytes = info->rx_bytes; |
| *tx_bytes = info->tx_bytes; |
| return true; |
| } |
| |
| bool DeviceInfo::CreateTunnelInterface(string *interface_name) const { |
| 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) const { |
| 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()) { |
| return NULL; |
| } |
| return &iter->second; |
| } |
| |
| void DeviceInfo::RemoveInfo(int interface_index) { |
| map<int, Info>::iterator iter = infos_.find(interface_index); |
| if (iter != infos_.end()) { |
| SLOG(Device, 2) << "Removing info for device index: " << interface_index; |
| if (iter->second.device.get()) { |
| manager_->DeregisterDevice(iter->second.device); |
| } |
| indices_.erase(iter->second.name); |
| infos_.erase(iter); |
| delayed_devices_.erase(interface_index); |
| } else { |
| SLOG(Device, 2) << __func__ << ": Unknown device index: " |
| << interface_index; |
| } |
| } |
| |
| void DeviceInfo::LinkMsgHandler(const RTNLMessage &msg) { |
| DCHECK(msg.type() == RTNLMessage::kTypeLink); |
| if (msg.mode() == RTNLMessage::kModeAdd) { |
| AddLinkMsgHandler(msg); |
| } else if (msg.mode() == RTNLMessage::kModeDelete) { |
| DelLinkMsgHandler(msg); |
| } else { |
| NOTREACHED(); |
| } |
| } |
| |
| void DeviceInfo::AddressMsgHandler(const RTNLMessage &msg) { |
| SLOG(Device, 2) << __func__; |
| DCHECK(msg.type() == RTNLMessage::kTypeAddress); |
| int interface_index = msg.interface_index(); |
| if (!ContainsKey(infos_, interface_index)) { |
| SLOG(Device, 2) << "Got advance address information for unknown index " |
| << interface_index; |
| infos_[interface_index].has_addresses_only = true; |
| } |
| const RTNLMessage::AddressStatus &status = msg.address_status(); |
| IPAddress address(msg.family(), |
| msg.HasAttribute(IFA_LOCAL) ? |
| msg.GetAttribute(IFA_LOCAL) : msg.GetAttribute(IFA_ADDRESS), |
| status.prefix_len); |
| |
| SLOG_IF(Device, 2, msg.HasAttribute(IFA_LOCAL)) |
| << "Found local address attribute for interface " << interface_index; |
| |
| vector<AddressData> &address_list = infos_[interface_index].ip_addresses; |
| vector<AddressData>::iterator iter; |
| for (iter = address_list.begin(); iter != address_list.end(); ++iter) { |
| if (address.Equals(iter->address)) { |
| break; |
| } |
| } |
| if (iter != address_list.end()) { |
| if (msg.mode() == RTNLMessage::kModeDelete) { |
| SLOG(Device, 2) << "Delete address for interface " << interface_index; |
| address_list.erase(iter); |
| } else { |
| iter->flags = status.flags; |
| iter->scope = status.scope; |
| } |
| } else if (msg.mode() == RTNLMessage::kModeAdd) { |
| address_list.push_back(AddressData(address, status.flags, status.scope)); |
| SLOG(Device, 2) << "Add address " << address.ToString() |
| << " for interface " << interface_index; |
| } |
| } |
| |
| void DeviceInfo::DelayDeviceCreation(int interface_index) { |
| delayed_devices_.insert(interface_index); |
| delayed_devices_callback_.Reset( |
| Bind(&DeviceInfo::DelayedDeviceCreationTask, AsWeakPtr())); |
| dispatcher_->PostDelayedTask(delayed_devices_callback_.callback(), |
| kDelayedDeviceCreationSeconds * 1000); |
| } |
| |
| // Re-evaluate the technology type for each delayed device. |
| void DeviceInfo::DelayedDeviceCreationTask() { |
| while (!delayed_devices_.empty()) { |
| set<int>::iterator it = delayed_devices_.begin(); |
| int dev_index = *it; |
| delayed_devices_.erase(it); |
| |
| DCHECK(ContainsKey(infos_, dev_index)); |
| DCHECK(!GetDevice(dev_index)); |
| |
| const string &link_name = infos_[dev_index].name; |
| Technology::Identifier technology = GetDeviceTechnology(link_name); |
| |
| if (technology == Technology::kCDCEthernet) { |
| LOG(INFO) << "In " << __func__ << ": device " << link_name |
| << " is now assumed to be regular Ethernet."; |
| technology = Technology::kEthernet; |
| } else if (technology != Technology::kCellular) { |
| LOG(WARNING) << "In " << __func__ << ": device " << link_name |
| << " is unexpected technology " |
| << Technology::NameFromIdentifier(technology); |
| } |
| string address = |
| StringToLowerASCII(infos_[dev_index].mac_address.HexEncode()); |
| DCHECK(!address.empty()); |
| |
| DeviceRefPtr device = CreateDevice(link_name, address, dev_index, |
| technology); |
| if (device) { |
| RegisterDevice(device); |
| } |
| } |
| } |
| |
| void DeviceInfo::RetrieveLinkStatistics(int interface_index, |
| const RTNLMessage &msg) { |
| if (!msg.HasAttribute(IFLA_STATS64)) { |
| return; |
| } |
| ByteString stats_bytes(msg.GetAttribute(IFLA_STATS64)); |
| struct rtnl_link_stats64 stats; |
| if (stats_bytes.GetLength() < sizeof(stats)) { |
| LOG(WARNING) << "Link statistics size is too small: " |
| << stats_bytes.GetLength() << " < " << sizeof(stats); |
| return; |
| } |
| |
| memcpy(&stats, stats_bytes.GetConstData(), sizeof(stats)); |
| SLOG(Device, 2) << "Link statistics for " |
| << " interface index " << interface_index << ": " |
| << "receive: " << stats.rx_bytes << "; " |
| << "transmit: " << stats.tx_bytes << "."; |
| infos_[interface_index].rx_bytes = stats.rx_bytes; |
| infos_[interface_index].tx_bytes = stats.tx_bytes; |
| } |
| |
| void DeviceInfo::RequestLinkStatistics() { |
| rtnl_handler_->RequestDump(RTNLHandler::kRequestLink); |
| dispatcher_->PostDelayedTask(request_link_statistics_callback_.callback(), |
| kRequestLinkStatisticsIntervalMilliseconds); |
| } |
| |
| void DeviceInfo::GetWiFiInterfaceInfo(int interface_index) { |
| GetInterfaceMessage msg; |
| if (!msg.attributes()->SetU32AttributeValue(NL80211_ATTR_IFINDEX, |
| interface_index)) { |
| LOG(ERROR) << "Unable to set interface index attribute for " |
| "GetInterface message. Interface type cannot be " |
| "determined!"; |
| return; |
| } |
| netlink_manager_->SendNl80211Message( |
| &msg, |
| Bind(&DeviceInfo::OnWiFiInterfaceInfoReceived, AsWeakPtr()), |
| Bind(&NetlinkManager::OnNetlinkMessageError)); |
| } |
| |
| void DeviceInfo::OnWiFiInterfaceInfoReceived(const Nl80211Message &msg) { |
| if (msg.command() != NL80211_CMD_NEW_INTERFACE) { |
| LOG(ERROR) << "Message is not a new interface response"; |
| return; |
| } |
| |
| uint32_t interface_index; |
| if (!msg.const_attributes()->GetU32AttributeValue(NL80211_ATTR_IFINDEX, |
| &interface_index)) { |
| LOG(ERROR) << "Message contains no interface index"; |
| return; |
| } |
| uint32_t interface_type; |
| if (!msg.const_attributes()->GetU32AttributeValue(NL80211_ATTR_IFTYPE, |
| &interface_type)) { |
| LOG(ERROR) << "Message contains no interface type"; |
| return; |
| } |
| const Info *info = GetInfo(interface_index); |
| if (!info) { |
| LOG(ERROR) << "Could not find device info for interface index " |
| << interface_index; |
| return; |
| } |
| if (info->device) { |
| LOG(ERROR) << "Device already created for interface index " |
| << interface_index; |
| return; |
| } |
| if (interface_type != NL80211_IFTYPE_STATION) { |
| LOG(INFO) << "Ignoring WiFi device " |
| << info->name |
| << " at interface index " |
| << interface_index |
| << " since it is not in station mode."; |
| return; |
| } |
| LOG(INFO) << "Creating WiFi device for station mode interface " |
| << info->name |
| << " at interface index " |
| << interface_index; |
| string address = StringToLowerASCII(info->mac_address.HexEncode()); |
| DeviceRefPtr device = |
| new WiFi(control_interface_, dispatcher_, metrics_, manager_, |
| info->name, address, interface_index); |
| device->EnableIPv6Privacy(); |
| RegisterDevice(device); |
| } |
| |
| } // namespace shill |