// Copyright 2018 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 #include #include #include #include #include #include #include // net/if.h must be included before this. #include #include #include #include #include "platform/api/network_interface.h" #include "platform/base/ip_address.h" #include "platform/impl/network_interface.h" #include "platform/impl/scoped_pipe.h" #include "util/osp_logging.h" namespace openscreen { namespace { // Assuming |netmask| consists of 0 to N*8 leftmost bits set followed by all // unset bits, return the number of leftmost bits set. This also sanity-checks // that there are no "holes" in the bit pattern, returning 0 if that check // fails. template uint8_t ToPrefixLength(const uint8_t (&netmask)[N]) { uint8_t result = 0; size_t i = 0; // Ensure all of the leftmost bits are set. while (i < N && netmask[i] == UINT8_C(0xff)) { result += 8; ++i; } // Check the intermediate byte, the first that is not 0xFF, // e.g. 0b11100000 or 0x00 if (i < N && netmask[i] != UINT8_C(0x00)) { uint8_t last_byte = netmask[i]; // Check the left most bit, bitshifting as we go. while (last_byte & UINT8_C(0x80)) { ++result; last_byte <<= 1; } OSP_CHECK(last_byte == UINT8_C(0x00)); ++i; } // Ensure the rest of the bytes are zeroed out. while (i < N) { OSP_CHECK(netmask[i] == UINT8_C(0x00)); ++i; } return result; } std::vector ProcessInterfacesList(ifaddrs* interfaces) { // Socket used for querying interface media types. const ScopedFd ioctl_socket(socket(AF_INET6, SOCK_DGRAM, 0)); // Walk the |interfaces| linked list, creating the hierarchical structure. std::vector results; for (ifaddrs* cur = interfaces; cur; cur = cur->ifa_next) { // Skip: 1) interfaces that are down, 2) interfaces with no address // configured. if (!(IFF_RUNNING & cur->ifa_flags) || !cur->ifa_addr) { continue; } // Look-up the InterfaceInfo entry by name. Auto-create a new one if none by // the current name exists in |results|. const std::string name = cur->ifa_name; const auto it = std::find_if( results.begin(), results.end(), [&name](const InterfaceInfo& info) { return info.name == name; }); InterfaceInfo* interface; if (it == results.end()) { InterfaceInfo::Type type = InterfaceInfo::Type::kOther; // Query for the interface media type and status. If not valid/active, // skip further processing. Note that "active" here means the media is // connected to the interface, which is different than the interface being // up/down. ifmediareq ifmr; memset(&ifmr, 0, sizeof(ifmr)); // Note: Because of the memset(), memcpy() can be used to copy the // ifmr.ifm_name string, and it will always be NUL terminated. memcpy(ifmr.ifm_name, name.data(), std::min(name.size(), sizeof(ifmr.ifm_name) - 1)); if (ioctl(ioctl_socket.get(), SIOCGIFMEDIA, &ifmr) >= 0) { if (!((ifmr.ifm_status & IFM_AVALID) && (ifmr.ifm_status & IFM_ACTIVE))) { continue; // Skip this interface since it's not valid or active. } if (ifmr.ifm_current & IFM_IEEE80211) { type = InterfaceInfo::Type::kWifi; } else if (ifmr.ifm_current & IFM_ETHER) { type = InterfaceInfo::Type::kEthernet; } } else if (cur->ifa_flags & IFF_LOOPBACK) { type = InterfaceInfo::Type::kLoopback; } else { continue; } // Start with an unknown hardware ethernet address, which should be // updated as the linked list is walked. const uint8_t kUnknownHardwareAddress[6] = {0, 0, 0, 0, 0, 0}; results.emplace_back(if_nametoindex(cur->ifa_name), kUnknownHardwareAddress, name, type, // IPSubnets to be filled-in later. std::vector()); interface = &(results.back()); } else { interface = &(*it); } // Add another address to the list of addresses for the current interface. if (cur->ifa_addr->sa_family == AF_LINK) { // Hardware ethernet address. auto* const addr_dl = reinterpret_cast(cur->ifa_addr); const caddr_t lladdr = LLADDR(addr_dl); static_assert(sizeof(lladdr) >= sizeof(interface->hardware_address), "Platform defines too-small link addresses?"); memcpy(&interface->hardware_address[0], &lladdr[0], sizeof(interface->hardware_address)); } else if (cur->ifa_addr->sa_family == AF_INET6) { // Ipv6 address. struct in6_ifreq ifr = {}; // Reject network interfaces that have a deprecated flag set. strncpy(ifr.ifr_name, cur->ifa_name, sizeof(ifr.ifr_name) - 1); memcpy(&ifr.ifr_ifru.ifru_addr, cur->ifa_addr, cur->ifa_addr->sa_len); if (ioctl(ioctl_socket.get(), SIOCGIFAFLAG_IN6, &ifr) != 0 || ifr.ifr_ifru.ifru_flags & IN6_IFF_DEPRECATED) { continue; } auto* const addr_in6 = reinterpret_cast(cur->ifa_addr); uint8_t tmp[sizeof(addr_in6->sin6_addr.s6_addr)]; memcpy(tmp, &(addr_in6->sin6_addr.s6_addr), sizeof(tmp)); const IPAddress ip(IPAddress::Version::kV6, tmp); memset(tmp, 0, sizeof(tmp)); if (cur->ifa_netmask && cur->ifa_netmask->sa_family == AF_INET6) { memcpy(tmp, &(reinterpret_cast(cur->ifa_netmask) ->sin6_addr.s6_addr), sizeof(tmp)); } interface->addresses.emplace_back(ip, ToPrefixLength(tmp)); } else if (cur->ifa_addr->sa_family == AF_INET) { // Ipv4 address. auto* const addr_in = reinterpret_cast(cur->ifa_addr); uint8_t tmp[sizeof(addr_in->sin_addr.s_addr)]; memcpy(tmp, &(addr_in->sin_addr.s_addr), sizeof(tmp)); IPAddress ip(IPAddress::Version::kV4, tmp); memset(tmp, 0, sizeof(tmp)); if (cur->ifa_netmask && cur->ifa_netmask->sa_family == AF_INET) { memcpy(tmp, &(reinterpret_cast(cur->ifa_netmask) ->sin_addr.s_addr), sizeof(tmp)); } interface->addresses.emplace_back(ip, ToPrefixLength(tmp)); } } return results; } } // namespace std::vector GetAllInterfaces() { std::vector results; ifaddrs* interfaces; if (getifaddrs(&interfaces) == 0) { results = ProcessInterfacesList(interfaces); freeifaddrs(interfaces); } return results; } } // namespace openscreen