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/dhcp_configuration.cpp b/src/dhcp_configuration.cpp
index 33b2cbd..1822c35 100644
--- a/src/dhcp_configuration.cpp
+++ b/src/dhcp_configuration.cpp
@@ -27,11 +27,11 @@
 {
     config::Parser conf;
     {
-        auto interfaceStrList = system::getInterfaces();
-        if (!interfaceStrList.empty())
+        auto interfaces = system::getInterfaces();
+        if (!interfaces.empty())
         {
             conf.setFile(config::pathForIntfConf(manager.getConfDir(),
-                                                 *interfaceStrList.begin()));
+                                                 *interfaces[0].name));
         }
     }
 
diff --git a/src/ethernet_interface.cpp b/src/ethernet_interface.cpp
index 09fd9dd..021c126 100644
--- a/src/ethernet_interface.cpp
+++ b/src/ethernet_interface.cpp
@@ -12,6 +12,7 @@
 #include <linux/if_addr.h>
 #include <linux/neighbour.h>
 #include <linux/rtnetlink.h>
+#include <net/if.h>
 
 #include <algorithm>
 #include <filesystem>
@@ -64,15 +65,6 @@
     return fallback;
 }
 
-InterfaceInfo getInterfaceInfo(stdplus::zstring_view ifname)
-{
-    return InterfaceInfo{.running = system::intfIsRunning(ifname),
-                         .index = system::intfIndex(ifname),
-                         .name = std::string(ifname),
-                         .mac = system::getMAC(ifname),
-                         .mtu = system::getMTU(ifname)};
-}
-
 static std::string makeObjPath(std::string_view root, std::string_view intf)
 {
     auto ret = fmt::format(FMT_COMPILE("{}/{}"), root, intf);
@@ -81,18 +73,18 @@
 }
 
 EthernetInterface::EthernetInterface(sdbusplus::bus_t& bus, Manager& manager,
-                                     const InterfaceInfo& info,
+                                     const system::InterfaceInfo& info,
                                      std::string_view objRoot,
                                      const config::Parser& config,
                                      bool emitSignal,
                                      std::optional<bool> enabled) :
-    EthernetInterface(bus, manager, info, makeObjPath(objRoot, info.name),
+    EthernetInterface(bus, manager, info, makeObjPath(objRoot, *info.name),
                       config, emitSignal, enabled)
 {
 }
 
 EthernetInterface::EthernetInterface(sdbusplus::bus_t& bus, Manager& manager,
-                                     const InterfaceInfo& info,
+                                     const system::InterfaceInfo& info,
                                      std::string&& objPath,
                                      const config::Parser& config,
                                      bool emitSignal,
@@ -100,9 +92,9 @@
     Ifaces(bus, objPath.c_str(),
            emitSignal ? Ifaces::action::defer_emit
                       : Ifaces::action::emit_no_signals),
-    bus(bus), manager(manager), objPath(std::move(objPath)), ifIdx(info.index)
+    bus(bus), manager(manager), objPath(std::move(objPath)), ifIdx(info.idx)
 {
-    interfaceName(info.name);
+    interfaceName(*info.name);
     auto dhcpVal = getDHCPValue(config);
     EthernetInterfaceIntf::dhcp4(dhcpVal.v4);
     EthernetInterfaceIntf::dhcp6(dhcpVal.v6);
@@ -115,7 +107,7 @@
 
     for (const auto& gateway : gatewayList)
     {
-        if (gateway.first == info.name)
+        if (gateway.first == *info.name)
         {
             defaultGateway = gateway.second;
             break;
@@ -124,7 +116,7 @@
 
     for (const auto& gateway6 : gateway6List)
     {
-        if (gateway6.first == info.name)
+        if (gateway6.first == *info.name)
         {
             defaultGateway6 = gateway6.second;
             break;
@@ -138,8 +130,8 @@
 
     if (ifIdx > 0)
     {
-        auto ethInfo = ignoreError("GetEthInfo", info.name, {}, [&] {
-            return system::getEthInfo(info.name);
+        auto ethInfo = ignoreError("GetEthInfo", *info.name, {}, [&] {
+            return system::getEthInfo(*info.name);
         });
         EthernetInterfaceIntf::autoNeg(ethInfo.autoneg);
         EthernetInterfaceIntf::speed(ethInfo.speed);
@@ -154,9 +146,9 @@
     }
 }
 
-void EthernetInterface::updateInfo(const InterfaceInfo& info)
+void EthernetInterface::updateInfo(const system::InterfaceInfo& info)
 {
-    EthernetInterfaceIntf::linkUp(info.running);
+    EthernetInterfaceIntf::linkUp(info.flags & IFF_RUNNING);
     if (info.mac)
     {
         MacAddressIntf::macAddress(std::to_string(*info.mac));
@@ -775,14 +767,13 @@
     return fmt::format(FMT_COMPILE("{}.{}"), interfaceName(), id);
 }
 
-void EthernetInterface::loadVLAN(std::string_view objRoot, uint16_t id)
+void EthernetInterface::loadVLAN(std::string_view objRoot, uint16_t id,
+                                 system::InterfaceInfo&& info)
 {
-    auto vlanInterfaceName = vlanIntfName(id);
     config::Parser config(
-        config::pathForIntfConf(manager.getConfDir(), vlanInterfaceName));
+        config::pathForIntfConf(manager.getConfDir(), *info.name));
     auto vlanIntf = std::make_unique<phosphor::network::VlanInterface>(
-        bus, manager, getInterfaceInfo(vlanInterfaceName), objRoot, config, id,
-        *this);
+        bus, manager, info, objRoot, config, id, *this);
 
     // Fetch the ip address from the system
     // and create the dbus object.
@@ -791,8 +782,7 @@
     vlanIntf->loadNameServers(config);
     vlanIntf->loadNTPServers(config);
 
-    this->vlanInterfaces.emplace(std::move(vlanInterfaceName),
-                                 std::move(vlanIntf));
+    this->vlanInterfaces.emplace(std::move(*info.name), std::move(vlanIntf));
 }
 
 ObjectPath EthernetInterface::createVLAN(uint16_t id)
@@ -808,8 +798,19 @@
     }
 
     auto objRoot = std::string_view(objPath).substr(0, objPath.rfind('/'));
-    auto info = getInterfaceInfo(interfaceName());
-    info.name = vlanInterfaceName;
+    auto macStr = MacAddressIntf::macAddress();
+    std::optional<ether_addr> mac;
+    if (!macStr.empty())
+    {
+        mac.emplace(mac_address::fromString(macStr));
+    }
+    auto info = system::InterfaceInfo{
+        .idx = 0, // TODO: Query the correct value after creation
+        .flags = 0,
+        .name = vlanInterfaceName,
+        .mac = std::move(mac),
+        .mtu = mtu(),
+    };
 
     // Pass the parents nicEnabled property, so that the child
     // VLAN interface can inherit.
@@ -821,7 +822,8 @@
     // write the device file for the vlan interface.
     vlanIntf->writeDeviceFile();
 
-    this->vlanInterfaces.emplace(vlanInterfaceName, std::move(vlanIntf));
+    this->vlanInterfaces.emplace(std::move(vlanInterfaceName),
+                                 std::move(vlanIntf));
 
     writeConfigurationFile();
     manager.reloadConfigs();
diff --git a/src/ethernet_interface.hpp b/src/ethernet_interface.hpp
index 3480a57..4db338e 100644
--- a/src/ethernet_interface.hpp
+++ b/src/ethernet_interface.hpp
@@ -5,8 +5,6 @@
 #include "xyz/openbmc_project/Network/IP/Create/server.hpp"
 #include "xyz/openbmc_project/Network/Neighbor/CreateStatic/server.hpp"
 
-#include <netinet/ether.h>
-
 #include <optional>
 #include <sdbusplus/bus.hpp>
 #include <sdbusplus/server/object.hpp>
@@ -49,23 +47,11 @@
 {
 class Parser;
 }
-
-/** @class InterfaceInfo
- *  @brief Information about interfaces from the kernel
- */
-struct InterfaceInfo
+namespace system
 {
-    bool running;
-    unsigned index;
-    std::string name;
-    std::optional<ether_addr> mac;
-    std::optional<unsigned> mtu;
+struct InterfaceInfo;
 };
 
-/** @brief Returns an InterfaceInfo for the given string name
- */
-InterfaceInfo getInterfaceInfo(stdplus::zstring_view ifname);
-
 /** @class EthernetInterface
  *  @brief OpenBMC Ethernet Interface implementation.
  *  @details A concrete implementation for the
@@ -92,12 +78,13 @@
      *  @param[in] enabled - Override the lookup of nicEnabled
      */
     EthernetInterface(sdbusplus::bus_t& bus, Manager& manager,
-                      const InterfaceInfo& info, std::string_view objRoot,
-                      const config::Parser& config, bool emitSignal = true,
+                      const system::InterfaceInfo& info,
+                      std::string_view objRoot, const config::Parser& config,
+                      bool emitSignal = true,
                       std::optional<bool> enabled = std::nullopt);
 
     /** @brief Updates the interface information based on new InterfaceInfo */
-    void updateInfo(const InterfaceInfo& info);
+    void updateInfo(const system::InterfaceInfo& info);
 
     /** @brief Function used to load the ntpservers
      */
@@ -221,7 +208,8 @@
      *         and creates the ip address dbus objects.
      *  @param[in] vlanID- VLAN identifier.
      */
-    void loadVLAN(std::string_view objRoot, uint16_t vlanID);
+    void loadVLAN(std::string_view objRoot, uint16_t vlanID,
+                  system::InterfaceInfo&& info);
 
     /** @brief write the network conf file with the in-memory objects.
      */
@@ -308,7 +296,7 @@
 
   private:
     EthernetInterface(sdbusplus::bus_t& bus, Manager& manager,
-                      const InterfaceInfo& info, std::string&& objPath,
+                      const system::InterfaceInfo& info, std::string&& objPath,
                       const config::Parser& config, bool emitSignal,
                       std::optional<bool> enabled);
 
diff --git a/src/network_manager.cpp b/src/network_manager.cpp
index 9a9067e..6763b4b 100644
--- a/src/network_manager.cpp
+++ b/src/network_manager.cpp
@@ -48,25 +48,26 @@
     auto isCreated = false;
     try
     {
-        auto interfaceStrList = system::getInterfaces();
-        for (const auto& interface : interfaceStrList)
+        for (const auto& interface : system::getInterfaces())
         {
             // if the interface has '.' in the name, it means that this is a
             // VLAN - don't create the network file.
-            if (interface.find(".") != std::string::npos)
+            if (interface.name->find(".") != std::string::npos)
             {
                 continue;
             }
 
-            fs::path filePath = config::pathForIntfConf(confDir, interface);
+            fs::path filePath =
+                config::pathForIntfConf(confDir, *interface.name);
 
             // create the interface specific network file
             // if not existing.
             if (!fs::is_regular_file(filePath))
             {
-                bmc::writeDHCPDefault(filePath, interface);
-                log<level::INFO>("Created the default network file.",
-                                 entry("INTERFACE=%s", interface.c_str()));
+                bmc::writeDHCPDefault(filePath, *interface.name);
+                log<level::INFO>(
+                    "Created the default network file.",
+                    entry("INTERFACE=%s", interface.name->c_str()));
                 isCreated = true;
             }
         }
@@ -98,19 +99,17 @@
 {
     // clear all the interfaces first
     interfaces.clear();
-    auto interfaceStrList = system::getInterfaces();
-    for (auto& interface : interfaceStrList)
+    for (auto& interface : system::getInterfaces())
     {
-        auto index = interface.find(".");
-
         // interface can be of vlan type or normal ethernet interface.
         // vlan interface looks like "interface.vlanid",so here by looking
         // at the interface name we decide that we need
         // to create the vlaninterface or normal physical interface.
-        if (index != std::string::npos)
+        if (const auto index = interface.name->find(".");
+            index != std::string::npos)
         {
             // it is vlan interface
-            auto sv = std::string_view(interface);
+            auto sv = std::string_view(*interface.name);
             auto interfaceName = sv.substr(0, index);
             auto vlanStr = sv.substr(index + 1);
             uint16_t vlanId;
@@ -125,25 +124,23 @@
             if (it == interfaces.end())
             {
                 auto msg = fmt::format("Missing interface({}) for VLAN({}): {}",
-                                       interfaceName, vlanId, interface);
+                                       interfaceName, vlanId, *interface.name);
                 log<level::ERR>(msg.c_str());
                 continue;
             }
-            it->second->loadVLAN(objectPath, vlanId);
+            it->second->loadVLAN(objectPath, vlanId, std::move(interface));
             continue;
         }
-        // normal ethernet interface
-        config::Parser config(config::pathForIntfConf(confDir, interface));
 
+        config::Parser config(
+            config::pathForIntfConf(confDir, *interface.name));
         auto intf = std::make_unique<phosphor::network::EthernetInterface>(
-            bus, *this, getInterfaceInfo(interface), objectPath, config);
-
+            bus, *this, interface, objectPath, config);
         intf->createIPAddressObjects();
         intf->createStaticNeighborObjects();
         intf->loadNameServers(config);
         intf->loadNTPServers(config);
-
-        this->interfaces.emplace(std::move(interface), std::move(intf));
+        this->interfaces.emplace(std::move(*interface.name), std::move(intf));
     }
 }
 
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;
 }
 
diff --git a/src/system_queries.hpp b/src/system_queries.hpp
index 3182cd6..3bbb021 100644
--- a/src/system_queries.hpp
+++ b/src/system_queries.hpp
@@ -1,17 +1,20 @@
 #pragma once
 #include "types.hpp"
 
-#include <netinet/ether.h>
+#include <net/ethernet.h>
 
 #include <cstdint>
 #include <optional>
 #include <stdplus/zstring.hpp>
 #include <stdplus/zstring_view.hpp>
+#include <string>
 #include <string_view>
+#include <vector>
+
+struct nlmsghdr;
 
 namespace phosphor::network::system
 {
-
 struct EthInfo
 {
     bool autoneg;
@@ -21,19 +24,39 @@
 
 bool intfIsRunning(std::string_view ifname);
 
-unsigned intfIndex(stdplus::const_zstring ifname);
-
-std::optional<ether_addr> getMAC(stdplus::zstring_view ifname);
-
 std::optional<unsigned> getMTU(stdplus::zstring_view ifname);
 
 void setMTU(std::string_view ifname, unsigned mtu);
 
 void setNICUp(std::string_view ifname, bool up);
 
+/** @class InterfaceInfo
+ *  @brief Information about interfaces from the kernel
+ */
+struct InterfaceInfo
+{
+    unsigned idx;
+    unsigned flags;
+    std::optional<std::string> name = std::nullopt;
+    std::optional<ether_addr> mac = std::nullopt;
+    std::optional<unsigned> mtu = std::nullopt;
+
+    constexpr bool operator==(const InterfaceInfo& rhs) const noexcept
+    {
+        return idx == rhs.idx && flags == rhs.flags && name == rhs.name &&
+               mac == rhs.mac && mtu == rhs.mtu;
+    }
+};
+
+namespace detail
+{
+InterfaceInfo parseInterface(const nlmsghdr& hdr, std::string_view msg);
+bool validateNewInterface(const InterfaceInfo& info);
+} // namespace detail
+
 /** @brief Get all the interfaces from the system.
  *  @returns list of interface names.
  */
-string_uset getInterfaces();
+std::vector<InterfaceInfo> getInterfaces();
 
 } // namespace phosphor::network::system
diff --git a/src/util.cpp b/src/util.cpp
index 111c090..e65c17d 100644
--- a/src/util.cpp
+++ b/src/util.cpp
@@ -10,7 +10,6 @@
 #include <fmt/format.h>
 #include <sys/wait.h>
 
-#include <algorithm>
 #include <cctype>
 #include <charconv>
 #include <fstream>
diff --git a/src/util.hpp b/src/util.hpp
index 46353a4..19eb108 100644
--- a/src/util.hpp
+++ b/src/util.hpp
@@ -1,7 +1,7 @@
 #pragma once
 #include "types.hpp"
 
-#include <netinet/ether.h>
+#include <net/ethernet.h>
 #include <netinet/in.h>
 #include <unistd.h>
 
diff --git a/src/vlan_interface.cpp b/src/vlan_interface.cpp
index 0d7879c..cb086c7 100644
--- a/src/vlan_interface.cpp
+++ b/src/vlan_interface.cpp
@@ -3,6 +3,7 @@
 #include "config_parser.hpp"
 #include "ethernet_interface.hpp"
 #include "network_manager.hpp"
+#include "system_queries.hpp"
 
 #include <phosphor-logging/elog-errors.hpp>
 #include <phosphor-logging/log.hpp>
@@ -18,7 +19,7 @@
 using namespace sdbusplus::xyz::openbmc_project::Common::Error;
 
 VlanInterface::VlanInterface(sdbusplus::bus_t& bus, Manager& manager,
-                             const InterfaceInfo& info,
+                             const system::InterfaceInfo& info,
                              std::string_view objRoot,
                              const config::Parser& config, uint16_t vlanID,
                              EthernetInterface& parent, bool emitSignal,
diff --git a/src/vlan_interface.hpp b/src/vlan_interface.hpp
index 9d27e30..7cdb61d 100644
--- a/src/vlan_interface.hpp
+++ b/src/vlan_interface.hpp
@@ -16,6 +16,10 @@
 
 class EthernetInterface;
 class Manager;
+namespace system
+{
+struct InterfaceInfo;
+}
 
 using DeleteIface = sdbusplus::xyz::openbmc_project::Object::server::Delete;
 using VlanIface = sdbusplus::xyz::openbmc_project::Network::server::VLAN;
@@ -51,7 +55,7 @@
      *  This constructor is called during loading the VLAN Interface
      */
     VlanInterface(sdbusplus::bus_t& bus, Manager& manager,
-                  const InterfaceInfo& info, std::string_view objRoot,
+                  const system::InterfaceInfo& info, std::string_view objRoot,
                   const config::Parser& config, uint16_t vlanID,
                   EthernetInterface& parent, bool emitSignal = true,
                   std::optional<bool> enabled = std::nullopt);