system_queries: Get Interface Info from netlink

This reduces all of the interface information probing down to a single
netlink request for all the label + mac information needed to build an
ethernet interface.

If we ever plumb the ethernet stats into dbus, we can get that
information with this same netlink code.

Change-Id: Ied8a73639dcd74bcfecda392905638573ab7970f
Signed-off-by: William A. Kennington III <wak@google.com>
diff --git a/src/system_queries.cpp b/src/system_queries.cpp
index 0aa245e..aa8e46b 100644
--- a/src/system_queries.cpp
+++ b/src/system_queries.cpp
@@ -1,9 +1,9 @@
 #include "system_queries.hpp"
 
+#include "netlink.hpp"
 #include "util.hpp"
 
 #include <fmt/format.h>
-#include <ifaddrs.h>
 #include <linux/ethtool.h>
 #include <linux/sockios.h>
 #include <net/if.h>
@@ -104,25 +104,6 @@
     return executeIFReq(ifname, SIOCGIFFLAGS).ifr_flags & IFF_RUNNING;
 }
 
-unsigned intfIndex(stdplus::const_zstring ifname)
-{
-    unsigned idx = if_nametoindex(ifname.c_str());
-    if (idx == 0)
-    {
-        auto msg = fmt::format("if_nametoindex({})", ifname);
-        throw std::system_error(errno, std::generic_category(), msg);
-    }
-    return idx;
-}
-
-std::optional<ether_addr> getMAC(stdplus::zstring_view ifname)
-{
-    return optionalIFReq(
-        ifname, SIOCGIFHWADDR, "IFHWADDR", [](const ifreq& ifr) {
-            return stdplus::raw::refFrom<ether_addr>(ifr.ifr_hwaddr.sa_data);
-        });
-}
-
 std::optional<unsigned> getMTU(stdplus::zstring_view ifname)
 {
     return optionalIFReq(ifname, SIOCGIFMTU, "GMTU",
@@ -144,21 +125,70 @@
     getIFSock().ioctl(SIOCSIFFLAGS, &ifr);
 }
 
-string_uset getInterfaces()
+InterfaceInfo detail::parseInterface(const nlmsghdr& hdr, std::string_view msg)
 {
-    string_uset ret;
-    struct ifaddrs* root;
-    CHECK_ERRNO(getifaddrs(&root), "getifaddrs");
-    const auto& ignored = internal::getIgnoredInterfaces();
-    for (auto it = root; it != nullptr; it = it->ifa_next)
+    if (hdr.nlmsg_type != RTM_NEWLINK)
     {
-        if (!(it->ifa_flags & IFF_LOOPBACK) &&
-            ignored.find(it->ifa_name) == ignored.end())
+        throw std::runtime_error("Not an interface msg");
+    }
+    auto ifinfo = stdplus::raw::extract<ifinfomsg>(msg);
+    InterfaceInfo ret;
+    ret.flags = ifinfo.ifi_flags;
+    ret.idx = ifinfo.ifi_index;
+    while (!msg.empty())
+    {
+        auto [hdr, data] = netlink::extractRtAttr(msg);
+        if (hdr.rta_type == IFLA_IFNAME)
         {
-            ret.emplace(it->ifa_name);
+            ret.name.emplace(data.begin(), data.end() - 1);
+        }
+        else if (hdr.rta_type == IFLA_ADDRESS)
+        {
+            if (data.size() != sizeof(ether_addr))
+            {
+                // Some interfaces have IP addresses for their LLADDR
+                continue;
+            }
+            ret.mac.emplace(stdplus::raw::copyFrom<ether_addr>(data));
+        }
+        else if (hdr.rta_type == IFLA_MTU)
+        {
+            ret.mtu.emplace(stdplus::raw::copyFrom<unsigned>(data));
         }
     }
-    freeifaddrs(root);
+    return ret;
+}
+
+bool detail::validateNewInterface(const InterfaceInfo& info)
+{
+    if (info.flags & IFF_LOOPBACK)
+    {
+        return false;
+    }
+    if (!info.name)
+    {
+        throw std::invalid_argument("Interface Dump missing name");
+    }
+    const auto& ignored = internal::getIgnoredInterfaces();
+    if (ignored.find(*info.name) != ignored.end())
+    {
+        return false;
+    }
+    return true;
+}
+
+std::vector<InterfaceInfo> getInterfaces()
+{
+    std::vector<InterfaceInfo> ret;
+    auto cb = [&](const nlmsghdr& hdr, std::string_view msg) {
+        auto info = detail::parseInterface(hdr, msg);
+        if (detail::validateNewInterface(info))
+        {
+            ret.emplace_back(std::move(info));
+        }
+    };
+    ifinfomsg msg{};
+    netlink::performRequest(NETLINK_ROUTE, RTM_GETLINK, NLM_F_DUMP, msg, cb);
     return ret;
 }