blob: 2d206c0cc5eeb729b9b7d0bf0a2976bb1eec2ad4 [file] [log] [blame]
// Copyright 2018 The Chromium Authors
// 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/base/span.h"
#include "platform/impl/network_interface.h"
#include "platform/impl/scoped_pipe.h"
#include "platform/impl/socket_address_posix.h"
#include "util/osp_logging.h"
#include "util/std_util.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.
uint8_t ToPrefixLength(std::span<const uint8_t> netmask) {
uint8_t result = 0;
size_t i = 0;
// Ensure all of the leftmost bits are set.
while (i < netmask.size() && 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 < netmask.size() && 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 < netmask.size()) {
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{};
// `ifmr` is both an input and an output of the `ioctl` method, and since
// it is zero-initialized it can safely be copied with strncpy() and will
// always be NUL terminated.
strncpy(ifmr.ifm_name, name.c_str(), 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);
ByteView address_bytes(reinterpret_cast<uint8_t*>(LLADDR(addr_dl)),
addr_dl->sdl_alen);
interface->hardware_address.assign(address_bytes.begin(),
address_bytes.end());
} else if (cur->ifa_addr->sa_family == AF_INET6) { // Ipv6 address.
struct in6_ifreq ifr = {};
// `ifr` is both an input and an output of the `ioctl` method, and since
// it is zero-initialized it can safely be copied with strncpy() and will
// always be NUL terminated.
strncpy(ifr.ifr_name, cur->ifa_name, sizeof(ifr.ifr_name) - 1);
std::copy_n(reinterpret_cast<const uint8_t*>(cur->ifa_addr),
cur->ifa_addr->sa_len,
reinterpret_cast<uint8_t*>(&ifr.ifr_ifru.ifru_addr));
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);
const IPAddress ip = GetIPAddressFromSockAddr(*addr_in6);
std::array<uint8_t, IPAddress::kV6Size> netmask_bytes{};
if (cur->ifa_netmask && cur->ifa_netmask->sa_family == AF_INET6) {
auto* netmask_in6 =
reinterpret_cast<const sockaddr_in6*>(cur->ifa_netmask);
std::copy_n(netmask_in6->sin6_addr.s6_addr, netmask_bytes.size(),
netmask_bytes.begin());
}
interface->addresses.emplace_back(ip, ToPrefixLength(netmask_bytes));
} else if (cur->ifa_addr->sa_family == AF_INET) { // Ipv4 address.
auto* const addr_in = reinterpret_cast<const sockaddr_in*>(cur->ifa_addr);
IPAddress ip = GetIPAddressFromSockAddr(*addr_in);
std::array<uint8_t, IPAddress::kV4Size> netmask_bytes{};
if (cur->ifa_netmask && cur->ifa_netmask->sa_family == AF_INET) {
auto* netmask_in =
reinterpret_cast<const sockaddr_in*>(cur->ifa_netmask);
std::copy_n(
reinterpret_cast<const uint8_t*>(&netmask_in->sin_addr.s_addr),
netmask_bytes.size(), netmask_bytes.begin());
}
interface->addresses.emplace_back(ip, ToPrefixLength(netmask_bytes));
}
}
return results;
}
} // namespace
std::vector<InterfaceInfo> GetNetworkInterfaces() {
std::vector<InterfaceInfo> results;
ifaddrs* interfaces;
if (getifaddrs(&interfaces) == 0) {
results = ProcessInterfacesList(interfaces);
freeifaddrs(interfaces);
}
return results;
}
} // namespace openscreen