191 lines
6.8 KiB
C++
191 lines
6.8 KiB
C++
// 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 <net/if.h>
|
|
#include <net/if_dl.h>
|
|
#include <net/if_media.h>
|
|
#include <netinet/in.h>
|
|
#include <netinet/in_var.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/types.h>
|
|
|
|
// net/if.h must be included before this.
|
|
#include <ifaddrs.h>
|
|
|
|
#include <algorithm>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#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 <size_t N>
|
|
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<InterfaceInfo> 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<InterfaceInfo> 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<IPSubnet>());
|
|
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<const sockaddr_dl*>(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<const sockaddr_in6*>(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<const sockaddr_in6*>(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<const sockaddr_in*>(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<const sockaddr_in*>(cur->ifa_netmask)
|
|
->sin_addr.s_addr),
|
|
sizeof(tmp));
|
|
}
|
|
interface->addresses.emplace_back(ip, ToPrefixLength(tmp));
|
|
}
|
|
}
|
|
|
|
return results;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
std::vector<InterfaceInfo> GetAllInterfaces() {
|
|
std::vector<InterfaceInfo> results;
|
|
ifaddrs* interfaces;
|
|
if (getifaddrs(&interfaces) == 0) {
|
|
results = ProcessInterfacesList(interfaces);
|
|
freeifaddrs(interfaces);
|
|
}
|
|
return results;
|
|
}
|
|
|
|
} // namespace openscreen
|