ipaddress: Parse from netlink

This improves on the old code for enumerating IP addresses by allowing
the application of filtering rules prior to listing out the IPs. The
netlink interface provides the information in a more direct form with
less superfluous enumeration of data about the address.

This will be required to determine deprecated / dynamic addresses from
static ones with IPv6.

Change-Id: I8ff2408b58921a82fd556d8ed08c203171c88035
Signed-off-by: William A. Kennington III <wak@google.com>
diff --git a/src/ethernet_interface.cpp b/src/ethernet_interface.cpp
index 8700d05..a1d9b83 100644
--- a/src/ethernet_interface.cpp
+++ b/src/ethernet_interface.cpp
@@ -3,8 +3,10 @@
 #include "ethernet_interface.hpp"
 
 #include "config_parser.hpp"
+#include "ipaddress.hpp"
 #include "neighbor.hpp"
 #include "network_manager.hpp"
+#include "types.hpp"
 #include "vlan_interface.hpp"
 
 #include <arpa/inet.h>
@@ -125,17 +127,18 @@
     }
 }
 
-static IP::Protocol convertFamily(int family)
+static IP::Protocol getProtocol(const InAddrAny& addr)
 {
-    switch (family)
+    if (std::holds_alternative<in_addr>(addr))
     {
-        case AF_INET:
-            return IP::Protocol::IPv4;
-        case AF_INET6:
-            return IP::Protocol::IPv6;
+        return IP::Protocol::IPv4;
+    }
+    else if (std::holds_alternative<in6_addr>(addr))
+    {
+        return IP::Protocol::IPv6;
     }
 
-    throw std::invalid_argument("Bad address family");
+    throw std::runtime_error("Invalid addr type");
 }
 
 void EthernetInterface::disableDHCP(IP::Protocol protocol)
@@ -191,17 +194,19 @@
 {
     addrs.clear();
 
-    auto addrs = getInterfaceAddrs()[interfaceName()];
-
-    for (auto& addr : addrs)
+    AddressFilter filter;
+    filter.interface = ifIndex();
+    auto currentAddrs = getCurrentAddresses(filter);
+    for (const auto& addr : currentAddrs)
     {
-        IP::Protocol addressType = convertFamily(addr.addrType);
+        auto address = toString(addr.address);
+        IP::Protocol addressType = getProtocol(addr.address);
         IP::AddressOrigin origin = IP::AddressOrigin::Static;
         if (dhcpIsEnabled(addressType))
         {
             origin = IP::AddressOrigin::DHCP;
         }
-        if (isLinkLocalIP(addr.ipaddress))
+        if (addr.scope == RT_SCOPE_LINK)
         {
             origin = IP::AddressOrigin::LinkLocal;
         }
@@ -209,13 +214,12 @@
         std::string gateway = "";
 
         std::string ipAddressObjectPath = generateObjectPath(
-            addressType, addr.ipaddress, addr.prefix, gateway, origin);
+            addressType, address, addr.prefix, gateway, origin);
 
         this->addrs.insert_or_assign(
-            addr.ipaddress,
-            std::make_shared<phosphor::network::IPAddress>(
-                bus, ipAddressObjectPath.c_str(), *this, addressType,
-                addr.ipaddress, origin, addr.prefix, gateway));
+            address, std::make_shared<phosphor::network::IPAddress>(
+                         bus, ipAddressObjectPath.c_str(), *this, addressType,
+                         address, origin, addr.prefix, gateway));
     }
 }
 
diff --git a/src/ipaddress.cpp b/src/ipaddress.cpp
index ee87d6c..becb5b3 100644
--- a/src/ipaddress.cpp
+++ b/src/ipaddress.cpp
@@ -3,16 +3,38 @@
 #include "ipaddress.hpp"
 
 #include "ethernet_interface.hpp"
+#include "netlink.hpp"
 #include "util.hpp"
 
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+
 #include <phosphor-logging/elog-errors.hpp>
 #include <phosphor-logging/log.hpp>
+#include <stdexcept>
+#include <stdplus/raw.hpp>
+#include <string>
+#include <string_view>
+#include <vector>
 #include <xyz/openbmc_project/Common/error.hpp>
+
 namespace phosphor
 {
 namespace network
 {
 
+std::vector<AddressInfo> getCurrentAddresses(const AddressFilter& filter)
+{
+    std::vector<AddressInfo> addresses;
+    auto cb = [&filter, &addresses](const nlmsghdr& hdr, std::string_view msg) {
+        detail::parseAddress(filter, hdr, msg, addresses);
+    };
+    ifaddrmsg msg{};
+    msg.ifa_index = filter.interface;
+    netlink::performRequest(NETLINK_ROUTE, RTM_GETADDR, NLM_F_DUMP, msg, cb);
+    return addresses;
+}
+
 using namespace phosphor::logging;
 using namespace sdbusplus::xyz::openbmc_project::Common::Error;
 using NotAllowed = sdbusplus::xyz::openbmc_project::Common::Error::NotAllowed;
@@ -69,5 +91,56 @@
     parent.deleteObject(address());
 }
 
+namespace detail
+{
+
+void parseAddress(const AddressFilter& filter, const nlmsghdr& hdr,
+                  std::string_view msg, std::vector<AddressInfo>& addresses)
+{
+    if (hdr.nlmsg_type != RTM_NEWADDR)
+    {
+        throw std::runtime_error("Not an address msg");
+    }
+    auto ifaddr = stdplus::raw::extract<ifaddrmsg>(msg);
+
+    // Filter out addresses we don't care about
+    unsigned ifindex = ifaddr.ifa_index;
+    if (filter.interface != 0 && filter.interface != ifindex)
+    {
+        return;
+    }
+    if (filter.scope && *filter.scope != ifaddr.ifa_scope)
+    {
+        return;
+    }
+
+    // Build the info about the address we found
+    AddressInfo address;
+    address.interface = ifindex;
+    address.prefix = ifaddr.ifa_prefixlen;
+    address.flags = ifaddr.ifa_flags;
+    address.scope = ifaddr.ifa_scope;
+    bool set_addr = false;
+    while (!msg.empty())
+    {
+        auto [hdr, data] = netlink::extractRtAttr(msg);
+        if (hdr.rta_type == IFA_ADDRESS)
+        {
+            address.address = addrFromBuf(ifaddr.ifa_family, data);
+            set_addr = true;
+        }
+        else if (hdr.rta_type == IFA_FLAGS)
+        {
+            address.flags = stdplus::raw::extract<uint32_t>(data);
+        }
+    }
+    if (!set_addr)
+    {
+        throw std::runtime_error("Missing address");
+    }
+    addresses.push_back(std::move(address));
+}
+
+} // namespace detail
 } // namespace network
 } // namespace phosphor
diff --git a/src/ipaddress.hpp b/src/ipaddress.hpp
index b78b677..b46d64e 100644
--- a/src/ipaddress.hpp
+++ b/src/ipaddress.hpp
@@ -1,8 +1,15 @@
 #pragma once
+#include "types.hpp"
 
+#include <linux/netlink.h>
+
+#include <cstdint>
+#include <optional>
 #include <sdbusplus/bus.hpp>
 #include <sdbusplus/server/object.hpp>
 #include <string>
+#include <string_view>
+#include <vector>
 #include <xyz/openbmc_project/Network/IP/server.hpp>
 #include <xyz/openbmc_project/Object/Delete/server.hpp>
 
@@ -19,6 +26,30 @@
 
 class EthernetInterface;
 
+/* @class AddressFilter
+ */
+struct AddressFilter
+{
+    unsigned interface = 0;
+    std::optional<uint8_t> scope;
+};
+
+/** @class AddressInfo
+ *  @brief Information about a addresses from the kernel
+ */
+struct AddressInfo
+{
+    unsigned interface;
+    InAddrAny address;
+    uint8_t prefix;
+    uint8_t scope;
+    uint32_t flags;
+};
+
+/** @brief Returns a list of the current system neighbor table
+ */
+std::vector<AddressInfo> getCurrentAddresses(const AddressFilter& filter);
+
 /** @class IPAddress
  *  @brief OpenBMC IPAddress implementation.
  *  @details A concrete implementation for the
@@ -71,5 +102,12 @@
     EthernetInterface& parent;
 };
 
+namespace detail
+{
+
+void parseAddress(const AddressFilter& filter, const nlmsghdr& hdr,
+                  std::string_view msg, std::vector<AddressInfo>& addresses);
+
+} // namespace detail
 } // namespace network
 } // namespace phosphor
diff --git a/src/types.hpp b/src/types.hpp
index a4173c1..2e27298 100644
--- a/src/types.hpp
+++ b/src/types.hpp
@@ -1,24 +1,18 @@
 #pragma once
 
-#include "ipaddress.hpp"
-
 #include <ifaddrs.h>
 #include <netinet/in.h>
 #include <systemd/sd-event.h>
 
-#include <array>
 #include <chrono>
 #include <cstddef>
 #include <functional>
-#include <list>
-#include <map>
 #include <memory>
 #include <sdeventplus/clock.hpp>
 #include <sdeventplus/utility/timer.hpp>
 #include <set>
 #include <string>
 #include <variant>
-#include <vector>
 
 namespace phosphor
 {
@@ -48,13 +42,6 @@
 
 using IntfName = std::string;
 
-struct AddrInfo
-{
-    uint8_t addrType;
-    std::string ipaddress;
-    uint16_t prefix;
-};
-
 using Addr_t = ifaddrs*;
 
 struct AddrDeleter
@@ -83,8 +70,6 @@
 // Byte representations for common address types in network byte order
 using InAddrAny = std::variant<struct in_addr, struct in6_addr>;
 
-using AddrList = std::list<AddrInfo>;
-using IntfAddrMap = std::map<IntfName, AddrList>;
 using InterfaceList = std::set<IntfName>;
 
 using Timer = sdeventplus::utility::Timer<sdeventplus::ClockId::Monotonic>;
diff --git a/src/util.cpp b/src/util.cpp
index 3bd3806..803d26e 100644
--- a/src/util.cpp
+++ b/src/util.cpp
@@ -145,43 +145,6 @@
 
 } // namespace internal
 
-uint8_t toCidr(int addressFamily, const std::string& subnetMask)
-{
-    uint32_t subnet[sizeof(in6_addr) / sizeof(uint32_t)];
-    if (inet_pton(addressFamily, subnetMask.c_str(), &subnet) != 1)
-    {
-        log<level::ERR>("inet_pton failed:",
-                        entry("SUBNETMASK=%s", subnetMask.c_str()));
-        return 0;
-    }
-
-    static_assert(sizeof(in6_addr) % sizeof(uint32_t) == 0);
-    static_assert(sizeof(in_addr) % sizeof(uint32_t) == 0);
-    auto i = (addressFamily == AF_INET ? sizeof(in_addr) : sizeof(in6_addr)) /
-             sizeof(uint32_t);
-    while (i > 0)
-    {
-        if (subnet[--i] != 0)
-        {
-            auto v = be32toh(subnet[i]);
-            static_assert(sizeof(unsigned) == sizeof(uint32_t));
-            auto trailing = __builtin_ctz(v);
-            auto ret = (i + 1) * 32 - trailing;
-            bool valid = ~v == 0 || 32 == trailing + __builtin_clz(~v);
-            while (i > 0 && (valid = (~subnet[--i] == 0) && valid))
-                ;
-            if (!valid)
-            {
-                log<level::ERR>("Invalid netmask",
-                                entry("SUBNETMASK=%s", subnetMask.c_str()));
-                return 0;
-            }
-            return ret;
-        }
-    }
-    return 0;
-}
-
 std::string toMask(int addressFamily, uint8_t prefix)
 {
     if (addressFamily == AF_INET6)
@@ -272,11 +235,6 @@
     throw std::runtime_error("Invalid addr type");
 }
 
-bool isLinkLocalIP(const std::string& address)
-{
-    return address.find(IPV4_PREFIX) == 0 || address.find(IPV6_PREFIX) == 0;
-}
-
 bool isValidIP(int addressFamily, const std::string& address)
 {
     unsigned char buf[sizeof(struct in6_addr)];
@@ -307,81 +265,6 @@
     return true;
 }
 
-IntfAddrMap getInterfaceAddrs()
-{
-    IntfAddrMap intfMap{};
-    struct ifaddrs* ifaddr = nullptr;
-
-    // attempt to fill struct with ifaddrs
-    if (getifaddrs(&ifaddr) == -1)
-    {
-        auto error = errno;
-        log<level::ERR>("Error occurred during the getifaddrs call",
-                        entry("ERRNO=%s", strerror(error)));
-        elog<InternalFailure>();
-    }
-
-    AddrPtr ifaddrPtr(ifaddr);
-    ifaddr = nullptr;
-
-    std::string intfName{};
-
-    for (ifaddrs* ifa = ifaddrPtr.get(); ifa != nullptr; ifa = ifa->ifa_next)
-    {
-        // walk interfaces
-        if (ifa->ifa_addr == nullptr)
-        {
-            continue;
-        }
-
-        // get only INET interfaces not ipv6
-        if (ifa->ifa_addr->sa_family == AF_INET ||
-            ifa->ifa_addr->sa_family == AF_INET6)
-        {
-            // if loopback, or not running ignore
-            if ((ifa->ifa_flags & IFF_LOOPBACK) ||
-                !(ifa->ifa_flags & IFF_RUNNING))
-            {
-                continue;
-            }
-            intfName = ifa->ifa_name;
-            AddrInfo info{};
-            char ip[INET6_ADDRSTRLEN] = {0};
-            char subnetMask[INET6_ADDRSTRLEN] = {0};
-
-            if (ifa->ifa_addr->sa_family == AF_INET)
-            {
-
-                inet_ntop(ifa->ifa_addr->sa_family,
-                          &(((struct sockaddr_in*)(ifa->ifa_addr))->sin_addr),
-                          ip, sizeof(ip));
-
-                inet_ntop(
-                    ifa->ifa_addr->sa_family,
-                    &(((struct sockaddr_in*)(ifa->ifa_netmask))->sin_addr),
-                    subnetMask, sizeof(subnetMask));
-            }
-            else
-            {
-                inet_ntop(ifa->ifa_addr->sa_family,
-                          &(((struct sockaddr_in6*)(ifa->ifa_addr))->sin6_addr),
-                          ip, sizeof(ip));
-
-                inet_ntop(
-                    ifa->ifa_addr->sa_family,
-                    &(((struct sockaddr_in6*)(ifa->ifa_netmask))->sin6_addr),
-                    subnetMask, sizeof(subnetMask));
-            }
-
-            info.addrType = ifa->ifa_addr->sa_family;
-            info.ipaddress = ip;
-            info.prefix = toCidr(info.addrType, std::string(subnetMask));
-            intfMap[intfName].push_back(info);
-        }
-    }
-    return intfMap;
-}
-
 InterfaceList getInterfaces()
 {
     InterfaceList interfaces{};
diff --git a/src/util.hpp b/src/util.hpp
index fd00b31..43c231f 100644
--- a/src/util.hpp
+++ b/src/util.hpp
@@ -26,8 +26,6 @@
 constexpr auto IPV4_MIN_PREFIX_LENGTH = 1;
 constexpr auto IPV4_MAX_PREFIX_LENGTH = 32;
 constexpr auto IPV6_MAX_PREFIX_LENGTH = 128;
-constexpr auto IPV4_PREFIX = "169.254";
-constexpr auto IPV6_PREFIX = "fe80";
 
 namespace mac_address
 {
@@ -78,13 +76,6 @@
 constexpr auto networkdService = "systemd-networkd.service";
 constexpr auto timeSynchdService = "systemd-timesyncd.service";
 
-/* @brief converts the given subnet into prefix notation.
- * @param[in] addressFamily - IP address family(AF_INET/AF_INET6).
- * @param[in] mask - Subnet Mask.
- * @returns prefix.
- */
-uint8_t toCidr(int addressFamily, const std::string& mask);
-
 /* @brief converts a sockaddr for the specified address family into
  *        a type_safe InAddrAny.
  * @param[in] addressFamily - The address family of the buf
@@ -100,19 +91,6 @@
 std::string toString(const struct in_addr& addr);
 std::string toString(const struct in6_addr& addr);
 
-/* @brief converts the prefix into subnetmask.
- * @param[in] addressFamily - IP address family(AF_INET/AF_INET6).
- * @param[in] prefix - prefix length.
- * @returns subnet mask.
- */
-std::string toMask(int addressFamily, uint8_t prefix);
-
-/* @brief checks that the given ip address is link local or not.
- * @param[in] address - IP address.
- * @returns true if it is linklocal otherwise false.
- */
-bool isLinkLocalIP(const std::string& address);
-
 /* @brief checks that the given ip address valid or not.
  * @param[in] addressFamily - IP address family(AF_INET/AF_INET6).
  * @param[in] address - IP address.
@@ -127,12 +105,6 @@
  */
 bool isValidPrefix(int addressFamily, uint8_t prefixLength);
 
-/** @brief Gets the map of interface and the associated
- *         address.
- *  @returns map of interface and the address.
- */
-IntfAddrMap getInterfaceAddrs();
-
 /** @brief Get all the interfaces from the system.
  *  @returns list of interface names.
  */