diff --git a/collector/lib/HostHeuristics.cpp b/collector/lib/HostHeuristics.cpp index 7cc6d33127..560a4ad2b1 100644 --- a/collector/lib/HostHeuristics.cpp +++ b/collector/lib/HostHeuristics.cpp @@ -1,6 +1,7 @@ #include "HostHeuristics.h" #include "Logging.h" +#include "NetworkConnection.h" // for enumerating local addresses namespace collector { @@ -93,11 +94,38 @@ class CPUHeuristic : public Heuristic { } }; +class NetworkInterfaceHeuristic : public Heuristic { + public: + // Log local network interface addresses and warn if any are public. + void Process(HostInfo& host, const CollectorConfig& config, HostConfig* hconfig) const { + auto interfaces = GetLocalInterfaceAddresses(); + + for (const auto& iface : interfaces) { + const auto& addr = iface.address(); + + // Skip loopback addresses + if (addr.IsLocal()) { + continue; + } + + CLOG(INFO) << "Local interface address: " << iface; + + // Warn if the address is not private (i.e., it is public) + if (addr.IsPublic()) { + CLOG(WARNING) + << "Interface address " << addr << " is public (not in private IP ranges). " + << "Network flows will not work unless you set 'ROX_NON_AGGREGATED_NETWORKS'"; + } + } + } +}; + const std::unique_ptr g_host_heuristics[] = { std::unique_ptr(new CollectionHeuristic), std::unique_ptr(new DockerDesktopHeuristic), std::unique_ptr(new PowerHeuristic), std::unique_ptr(new CPUHeuristic), + std::unique_ptr(new NetworkInterfaceHeuristic), }; } // namespace diff --git a/collector/lib/NetworkConnection.cpp b/collector/lib/NetworkConnection.cpp index 426b7cca5a..3004c6b1bc 100644 --- a/collector/lib/NetworkConnection.cpp +++ b/collector/lib/NetworkConnection.cpp @@ -1,5 +1,6 @@ #include "NetworkConnection.h" +#include #include #include @@ -135,4 +136,72 @@ std::optional IPNet::parse(const std::string& ipnet_string) { return IPNet(address.value(), prefix_length, prefix_length != 0); } +size_t netmask_to_prefix_length(const struct sockaddr* netmask) { + if (!netmask) { + return 0; + } + + size_t prefix_length = 0; + + if (netmask->sa_family == AF_INET) { + const auto* sin = reinterpret_cast(netmask); + uint32_t mask = ntohl(sin->sin_addr.s_addr); + for (int i = 31; i >= 0; --i) { + if (mask & (1U << i)) { + prefix_length++; + } else { + break; + } + } + } else if (netmask->sa_family == AF_INET6) { + const auto* sin6 = reinterpret_cast(netmask); + const uint8_t* addr_bytes = sin6->sin6_addr.s6_addr; + for (size_t i = 0; i < 16; ++i) { + uint8_t byte = addr_bytes[i]; + for (int j = 7; j >= 0; --j) { + if (byte & (1U << j)) { + prefix_length++; + } else { + return prefix_length; + } + } + } + } + + return prefix_length; +} + +std::vector GetLocalInterfaceAddresses() { + std::vector result; + struct ifaddrs* ifaddr = nullptr; + + if (getifaddrs(&ifaddr) == -1) { + return result; + } + + for (struct ifaddrs* ifa = ifaddr; ifa != nullptr; ifa = ifa->ifa_next) { + if (!ifa->ifa_addr) { + continue; + } + + Address::Family family = get_family(*reinterpret_cast(ifa->ifa_addr)); + if (family == Address::Family::UNKNOWN) { + continue; + } + + const uint8_t* raw_address = get_raw_address(*reinterpret_cast(ifa->ifa_addr)); + if (!raw_address) { + continue; + } + + Address addr(family, reinterpret_cast&>(*raw_address)); + size_t prefix_length = netmask_to_prefix_length(ifa->ifa_netmask); + + result.emplace_back(addr, prefix_length, true); + } + + freeifaddrs(ifaddr); + return result; +} + } // namespace collector diff --git a/collector/lib/NetworkConnection.h b/collector/lib/NetworkConnection.h index 3d458ab33a..4807186279 100644 --- a/collector/lib/NetworkConnection.h +++ b/collector/lib/NetworkConnection.h @@ -488,4 +488,12 @@ static inline const std::vector& PrivateNetworks() { return *ret; } +// GetLocalInterfaceAddresses returns a list of IPNet objects representing +// the local system's network interface addresses with their netmasks. +std::vector GetLocalInterfaceAddresses(); + +// netmask_to_prefix_length converts a sockaddr netmask to a prefix length (e.g., /24). +// Returns 0 if the netmask is null or has an unsupported address family. +size_t netmask_to_prefix_length(const struct sockaddr* netmask); + } // namespace collector diff --git a/collector/test/NetworkConnectionTest.cpp b/collector/test/NetworkConnectionTest.cpp index db04d073f6..fa5ee0e138 100644 --- a/collector/test/NetworkConnectionTest.cpp +++ b/collector/test/NetworkConnectionTest.cpp @@ -1,5 +1,9 @@ #include +#include +#include +#include + #include "NetworkConnection.h" #include "Utility.h" #include "gmock/gmock.h" @@ -176,6 +180,75 @@ TEST(TestIPNet, TestIsCanonicalExternalIp) { } } +TEST(TestNetmaskToPrefixLength, IPv4Netmasks) { + // Test /0 - 0.0.0.0 + struct sockaddr_in netmask_0 = {.sin_family = AF_INET, .sin_addr = {.s_addr = 0x00000000}}; + EXPECT_EQ(netmask_to_prefix_length(reinterpret_cast(&netmask_0)), 0); + + // Test /8 - 255.0.0.0 + struct sockaddr_in netmask_8 = {.sin_family = AF_INET, .sin_addr = {.s_addr = htonl(0xff000000)}}; + EXPECT_EQ(netmask_to_prefix_length(reinterpret_cast(&netmask_8)), 8); + + // Test /12 - 255.240.0.0 + struct sockaddr_in netmask_12 = {.sin_family = AF_INET, .sin_addr = {.s_addr = htonl(0xfff00000)}}; + EXPECT_EQ(netmask_to_prefix_length(reinterpret_cast(&netmask_12)), 12); + + // Test /16 - 255.255.0.0 + struct sockaddr_in netmask_16 = {.sin_family = AF_INET, .sin_addr = {.s_addr = htonl(0xffff0000)}}; + EXPECT_EQ(netmask_to_prefix_length(reinterpret_cast(&netmask_16)), 16); + + // Test /24 - 255.255.255.0 + struct sockaddr_in netmask_24 = {.sin_family = AF_INET, .sin_addr = {.s_addr = htonl(0xffffff00)}}; + EXPECT_EQ(netmask_to_prefix_length(reinterpret_cast(&netmask_24)), 24); + + // Test /28 - 255.255.255.240 + struct sockaddr_in netmask_28 = {.sin_family = AF_INET, .sin_addr = {.s_addr = htonl(0xfffffff0)}}; + EXPECT_EQ(netmask_to_prefix_length(reinterpret_cast(&netmask_28)), 28); + + // Test /32 - 255.255.255.255 + struct sockaddr_in netmask_32 = {.sin_family = AF_INET, .sin_addr = {.s_addr = htonl(0xffffffff)}}; + EXPECT_EQ(netmask_to_prefix_length(reinterpret_cast(&netmask_32)), 32); +} + +TEST(TestNetmaskToPrefixLength, IPv6Netmasks) { + // Test /0 - all zeros + struct sockaddr_in6 netmask_0 = {.sin6_family = AF_INET6, .sin6_addr = {{0}}}; + EXPECT_EQ(netmask_to_prefix_length(reinterpret_cast(&netmask_0)), 0); + + // Test /8 - ff00:: + struct sockaddr_in6 netmask_8 = {.sin6_family = AF_INET6, .sin6_addr = {{0xff, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}}; + EXPECT_EQ(netmask_to_prefix_length(reinterpret_cast(&netmask_8)), 8); + + // Test /48 - ffff:ffff:ffff:: + struct sockaddr_in6 netmask_48 = {.sin6_family = AF_INET6, .sin6_addr = {{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}}; + EXPECT_EQ(netmask_to_prefix_length(reinterpret_cast(&netmask_48)), 48); + + // Test /52 - ffff:ffff:ffff:f000:: + struct sockaddr_in6 netmask_52 = {.sin6_family = AF_INET6, .sin6_addr = {{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}}; + EXPECT_EQ(netmask_to_prefix_length(reinterpret_cast(&netmask_52)), 52); + + // Test /64 - ffff:ffff:ffff:ffff:: + struct sockaddr_in6 netmask_64 = {.sin6_family = AF_INET6, .sin6_addr = {{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0, 0, 0, 0, 0, 0, 0, 0}}}; + EXPECT_EQ(netmask_to_prefix_length(reinterpret_cast(&netmask_64)), 64); + + // Test /96 - ffff:ffff:ffff:ffff:ffff:ffff:: + struct sockaddr_in6 netmask_96 = {.sin6_family = AF_INET6, .sin6_addr = {{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0, 0, 0, 0}}}; + EXPECT_EQ(netmask_to_prefix_length(reinterpret_cast(&netmask_96)), 96); + + // Test /128 - ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff + struct sockaddr_in6 netmask_128 = {.sin6_family = AF_INET6, .sin6_addr = {{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}}}; + EXPECT_EQ(netmask_to_prefix_length(reinterpret_cast(&netmask_128)), 128); +} + +TEST(TestNetmaskToPrefixLength, EdgeCases) { + // Null pointer + EXPECT_EQ(netmask_to_prefix_length(nullptr), 0); + + // Unsupported address family + struct sockaddr unsupported = {.sa_family = AF_UNIX}; + EXPECT_EQ(netmask_to_prefix_length(&unsupported), 0); +} + } // namespace } // namespace collector