blob: 812fa7074ce49e920152706f1e03454f044945ea [file] [log] [blame]
// Copyright 2020 The Fuchsia 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 <arpa/inet.h>
#include <errno.h>
#include <ifaddrs.h>
// <net/if.h> doesn't contain the full list of interface flags on Linux.
#if defined(__linux__)
#include <linux/if.h>
#else
#include <net/if.h>
#endif
#include <algorithm>
#include <string>
#include <tuple>
#include <unordered_set>
#include <vector>
#include <gtest/gtest.h>
namespace {
uint8_t count_prefix(const uint8_t* mask, size_t len) {
uint8_t l = 0;
for (size_t i = 0; i < len; i++) {
auto m = mask[i];
for (size_t j = 0; j < sizeof(mask); j++) {
if (m) {
l++;
m <<= 1;
} else {
return l;
}
}
}
return l;
}
TEST(GetIfAddrsTest, GetIfAddrsTest) {
const uint32_t unsupported_flags = IFF_BROADCAST | IFF_DEBUG | IFF_POINTOPOINT | IFF_NOTRAILERS |
IFF_NOARP | IFF_ALLMULTI | IFF_MASTER | IFF_SLAVE |
IFF_MULTICAST | IFF_PORTSEL | IFF_AUTOMEDIA | IFF_DYNAMIC |
IFF_LOWER_UP | IFF_DORMANT | IFF_ECHO;
// Fields of this tuple are: interface_name, address, prefix_length, scope_id, flags.
using InterfaceAddress = std::tuple<std::string, std::string, uint8_t, uint32_t, uint32_t>;
std::vector<InterfaceAddress> want_ifaddrs{
std::make_tuple("lo", "127.0.0.1", 8, 0, IFF_LOOPBACK | IFF_UP | IFF_RUNNING),
std::make_tuple("lo", "::1", 128, 0, IFF_LOOPBACK | IFF_UP | IFF_RUNNING),
};
const size_t lo_addrs_count = want_ifaddrs.size();
#if defined(__Fuchsia__)
want_ifaddrs.push_back(std::make_tuple("ep1", "192.168.0.1", 20, 0, IFF_UP | IFF_RUNNING));
want_ifaddrs.push_back(std::make_tuple("ep2", "192.168.0.2", 15, 0, IFF_UP | IFF_RUNNING));
want_ifaddrs.push_back(std::make_tuple("ep3", "fe80::1", 64, 4, IFF_UP | IFF_RUNNING));
want_ifaddrs.push_back(std::make_tuple("ep4", "1234::5:6:7:8", 120, 0, IFF_UP | IFF_RUNNING));
#endif
struct ifaddrs* ifaddr;
ASSERT_EQ(getifaddrs(&ifaddr), 0) << strerror(errno);
std::vector<InterfaceAddress> got_ifaddrs;
constexpr size_t link_local_addrs_per_external_interface = 1;
const size_t want_unknown_link_local_addrs =
link_local_addrs_per_external_interface * (want_ifaddrs.size() - lo_addrs_count);
size_t got_unknown_link_local_addrs = 0;
for (auto it = ifaddr; it != nullptr; it = it->ifa_next) {
const auto if_name = std::string(it->ifa_name);
#if defined(__linux__)
// Only loopback is consistent on host environments.
if (if_name != "lo") {
continue;
}
#endif
switch (it->ifa_addr->sa_family) {
case AF_INET: {
struct sockaddr_in* addr_in = reinterpret_cast<sockaddr_in*>(it->ifa_addr);
char sin_addr_buf[INET_ADDRSTRLEN];
const char* sin_addr =
inet_ntop(AF_INET, &addr_in->sin_addr, sin_addr_buf, INET_ADDRSTRLEN);
const sockaddr_in* netmask = reinterpret_cast<sockaddr_in*>(it->ifa_netmask);
const uint8_t prefix_len =
count_prefix(reinterpret_cast<const uint8_t*>(&netmask->sin_addr.s_addr), 4);
got_ifaddrs.push_back(std::make_tuple(if_name, std::string(sin_addr), prefix_len, 0,
it->ifa_flags & ~unsupported_flags));
break;
}
case AF_INET6: {
struct sockaddr_in6* addr_in6 = reinterpret_cast<sockaddr_in6*>(it->ifa_addr);
char sin6_addr_buf[INET6_ADDRSTRLEN];
const char* sin6_addr =
inet_ntop(AF_INET6, &(addr_in6->sin6_addr), sin6_addr_buf, INET6_ADDRSTRLEN);
const std::string sin6_addr_str = std::string(sin6_addr);
const bool is_known_addr = std::any_of(want_ifaddrs.begin(), want_ifaddrs.end(),
[sin6_addr_str](const InterfaceAddress& ifaddr) {
return std::get<1>(ifaddr) == sin6_addr_str;
});
// Skip and count auto-generated link-local addresses.
if (IN6_IS_ADDR_LINKLOCAL(addr_in6->sin6_addr.s6_addr) && !is_known_addr) {
got_unknown_link_local_addrs++;
continue;
}
const sockaddr_in6* netmask = reinterpret_cast<sockaddr_in6*>(it->ifa_netmask);
const uint8_t prefix_len = count_prefix(netmask->sin6_addr.s6_addr, 16);
got_ifaddrs.push_back(std::make_tuple(if_name, sin6_addr_str, prefix_len,
addr_in6->sin6_scope_id,
it->ifa_flags & ~unsupported_flags));
break;
}
case AF_PACKET:
// Ignore AF_PACKET addresses because raw sockets are not supported on Fuchsia.
continue;
default:
GTEST_FAIL() << "unexpected address family " << it->ifa_addr->sa_family;
}
}
EXPECT_EQ(got_ifaddrs, want_ifaddrs);
EXPECT_EQ(got_unknown_link_local_addrs, want_unknown_link_local_addrs);
freeifaddrs(ifaddr);
}
} // namespace