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);
diff --git a/test/meson.build b/test/meson.build
index 62ca28b..651f542 100644
--- a/test/meson.build
+++ b/test/meson.build
@@ -51,6 +51,7 @@
   'netlink',
   'network_manager',
   #'rtnetlink',
+  'system_queries',
   'types',
   'util',
   'vlan_interface',
diff --git a/test/mock_network_manager.hpp b/test/mock_network_manager.hpp
index 69a3263..637ce69 100644
--- a/test/mock_network_manager.hpp
+++ b/test/mock_network_manager.hpp
@@ -25,20 +25,18 @@
 
     void createInterfaces() override
     {
-        // clear all the interfaces first
         interfaces.clear();
-        auto interfaceStrList = system::getInterfaces();
-        for (auto& interface : interfaceStrList)
+        for (auto& interface : system::getInterfaces())
         {
-            // normal ethernet interface
-            config::Parser config(config::pathForIntfConf(confDir, interface));
+            config::Parser config(
+                config::pathForIntfConf(confDir, *interface.name));
             auto intf = std::make_unique<MockEthernetInterface>(
-                bus, *this, getInterfaceInfo(interface), objectPath, config);
+                bus, *this, interface, objectPath, config);
             intf->createIPAddressObjects();
             intf->createStaticNeighborObjects();
             intf->loadNameServers(config);
             this->interfaces.emplace(
-                std::make_pair(std::move(interface), std::move(intf)));
+                std::make_pair(std::move(*interface.name), std::move(intf)));
         }
     }
 
diff --git a/test/mock_syscall.cpp b/test/mock_syscall.cpp
index e5f97be..e481afd 100644
--- a/test/mock_syscall.cpp
+++ b/test/mock_syscall.cpp
@@ -1,8 +1,8 @@
+#include "system_queries.hpp"
 #include "util.hpp"
 
 #include <arpa/inet.h>
 #include <dlfcn.h>
-#include <ifaddrs.h>
 #include <linux/netlink.h>
 #include <linux/rtnetlink.h>
 #include <net/ethernet.h>
@@ -24,46 +24,15 @@
 #include <string_view>
 #include <vector>
 
-#define MAX_IFADDRS 5
-
-int debugging = false;
-
-/* Data for mocking getifaddrs */
-struct ifaddr_storage
-{
-    struct ifaddrs ifaddr;
-    struct sockaddr_storage addr;
-    struct sockaddr_storage mask;
-    struct sockaddr_storage bcast;
-} mock_ifaddr_storage[MAX_IFADDRS];
-
-struct ifaddrs* mock_ifaddrs = nullptr;
-
-int ifaddr_count = 0;
-
-/* Stub library functions */
-void freeifaddrs(ifaddrs* /*ifp*/)
-{
-    return;
-}
-
 std::map<int, std::queue<std::string>> mock_rtnetlinks;
 
-struct MockInfo
-{
-    unsigned idx;
-    unsigned flags;
-    std::optional<ether_addr> mac;
-    std::optional<unsigned> mtu;
-};
+using phosphor::network::system::InterfaceInfo;
 
-std::map<std::string, MockInfo> mock_if;
+std::map<std::string, InterfaceInfo> mock_if;
 std::map<int, std::string> mock_if_indextoname;
 
 void mock_clear()
 {
-    mock_ifaddrs = nullptr;
-    ifaddr_count = 0;
     mock_rtnetlinks.clear();
     mock_if.clear();
     mock_if_indextoname.clear();
@@ -78,40 +47,12 @@
     }
 
     mock_if.emplace(
-        name, MockInfo{.idx = idx, .flags = flags, .mac = mac, .mtu = mtu});
+        name,
+        InterfaceInfo{
+            .idx = idx, .flags = flags, .name = name, .mac = mac, .mtu = mtu});
     mock_if_indextoname.emplace(idx, name);
 }
 
-void mock_addIP(const char* name, const char* addr, const char* mask)
-{
-    struct ifaddrs* ifaddr = &mock_ifaddr_storage[ifaddr_count].ifaddr;
-
-    struct sockaddr_in* in =
-        reinterpret_cast<sockaddr_in*>(&mock_ifaddr_storage[ifaddr_count].addr);
-    struct sockaddr_in* mask_in =
-        reinterpret_cast<sockaddr_in*>(&mock_ifaddr_storage[ifaddr_count].mask);
-
-    in->sin_family = AF_INET;
-    in->sin_port = 0;
-    in->sin_addr.s_addr = inet_addr(addr);
-
-    mask_in->sin_family = AF_INET;
-    mask_in->sin_port = 0;
-    mask_in->sin_addr.s_addr = inet_addr(mask);
-
-    ifaddr->ifa_next = nullptr;
-    ifaddr->ifa_name = const_cast<char*>(name);
-    ifaddr->ifa_flags = 0;
-    ifaddr->ifa_addr = reinterpret_cast<struct sockaddr*>(in);
-    ifaddr->ifa_netmask = reinterpret_cast<struct sockaddr*>(mask_in);
-    ifaddr->ifa_data = nullptr;
-
-    if (ifaddr_count > 0)
-        mock_ifaddr_storage[ifaddr_count - 1].ifaddr.ifa_next = ifaddr;
-    ifaddr_count++;
-    mock_ifaddrs = &mock_ifaddr_storage[0].ifaddr;
-}
-
 void validateMsgHdr(const struct msghdr* msg)
 {
     if (msg->msg_namelen != sizeof(sockaddr_nl))
@@ -132,36 +73,70 @@
     }
 }
 
+void appendRTAttr(std::string& msgBuf, unsigned short type,
+                  std::string_view data)
+{
+    const auto rta_begin = msgBuf.size();
+    msgBuf.append(RTA_SPACE(data.size()), '\0');
+    auto& rta = *reinterpret_cast<rtattr*>(msgBuf.data() + rta_begin);
+    rta.rta_len = RTA_LENGTH(data.size());
+    rta.rta_type = type;
+    std::copy(data.begin(), data.end(),
+              msgBuf.data() + rta_begin + RTA_LENGTH(0));
+}
+
 ssize_t sendmsg_link_dump(std::queue<std::string>& msgs, std::string_view in)
 {
-    const ssize_t ret = in.size();
-    const auto& hdrin = stdplus::raw::copyFrom<nlmsghdr>(in);
-    if (hdrin.nlmsg_type != RTM_GETLINK)
+    if (const auto& hdrin = *reinterpret_cast<const nlmsghdr*>(in.data());
+        hdrin.nlmsg_type != RTM_GETLINK)
     {
         return 0;
     }
 
+    std::string msgBuf;
+    msgBuf.reserve(8192);
     for (const auto& [name, i] : mock_if)
     {
-        ifinfomsg info{};
-        info.ifi_index = i.idx;
-        info.ifi_flags = i.flags;
-        nlmsghdr hdr{};
-        hdr.nlmsg_len = NLMSG_LENGTH(sizeof(info));
+        if (msgBuf.size() > 4096)
+        {
+            msgs.emplace(std::move(msgBuf));
+        }
+        const auto nlbegin = msgBuf.size();
+        msgBuf.append(NLMSG_SPACE(sizeof(ifinfomsg)), '\0');
+        {
+            auto& info = *reinterpret_cast<ifinfomsg*>(msgBuf.data() + nlbegin +
+                                                       NLMSG_HDRLEN);
+            info.ifi_index = i.idx;
+            info.ifi_flags = i.flags;
+        }
+        if (i.name)
+        {
+            appendRTAttr(msgBuf, IFLA_IFNAME, {name.data(), name.size() + 1});
+        }
+        if (i.mac)
+        {
+            appendRTAttr(msgBuf, IFLA_ADDRESS,
+                         stdplus::raw::asView<char>(*i.mac));
+        }
+        if (i.mtu)
+        {
+            appendRTAttr(msgBuf, IFLA_MTU, stdplus::raw::asView<char>(*i.mtu));
+        }
+        auto& hdr = *reinterpret_cast<nlmsghdr*>(msgBuf.data() + nlbegin);
+        hdr.nlmsg_len = msgBuf.size() - nlbegin;
         hdr.nlmsg_type = RTM_NEWLINK;
         hdr.nlmsg_flags = NLM_F_MULTI;
-        auto& out = msgs.emplace(hdr.nlmsg_len, '\0');
-        memcpy(out.data(), &hdr, sizeof(hdr));
-        memcpy(NLMSG_DATA(out.data()), &info, sizeof(info));
+        msgBuf.resize(NLMSG_ALIGN(msgBuf.size()), '\0');
     }
-
-    nlmsghdr hdr{};
+    const auto nlbegin = msgBuf.size();
+    msgBuf.append(NLMSG_SPACE(0), '\0');
+    auto& hdr = *reinterpret_cast<nlmsghdr*>(msgBuf.data() + nlbegin);
     hdr.nlmsg_len = NLMSG_LENGTH(0);
     hdr.nlmsg_type = NLMSG_DONE;
     hdr.nlmsg_flags = NLM_F_MULTI;
-    auto& out = msgs.emplace(hdr.nlmsg_len, '\0');
-    memcpy(out.data(), &hdr, sizeof(hdr));
-    return ret;
+
+    msgs.emplace(std::move(msgBuf));
+    return in.size();
 }
 
 ssize_t sendmsg_ack(std::queue<std::string>& msgs, std::string_view in)
@@ -178,23 +153,6 @@
 
 extern "C" {
 
-int getifaddrs(ifaddrs** ifap)
-{
-    *ifap = mock_ifaddrs;
-    return 0;
-}
-
-unsigned if_nametoindex(const char* ifname)
-{
-    auto it = mock_if.find(ifname);
-    if (it == mock_if.end())
-    {
-        errno = ENXIO;
-        return 0;
-    }
-    return it->second.idx;
-}
-
 char* if_indextoname(unsigned ifindex, char* ifname)
 {
     auto it = mock_if_indextoname.find(ifindex);
@@ -214,24 +172,7 @@
     va_end(vl);
 
     auto req = reinterpret_cast<ifreq*>(data);
-    if (request == SIOCGIFHWADDR)
-    {
-        auto it = mock_if.find(req->ifr_name);
-        if (it == mock_if.end())
-        {
-            errno = ENXIO;
-            return -1;
-        }
-        if (!it->second.mac)
-        {
-            errno = EOPNOTSUPP;
-            return -1;
-        }
-        std::memcpy(req->ifr_hwaddr.sa_data, &*it->second.mac,
-                    sizeof(*it->second.mac));
-        return 0;
-    }
-    else if (request == SIOCGIFFLAGS)
+    if (request == SIOCGIFFLAGS)
     {
         auto it = mock_if.find(req->ifr_name);
         if (it == mock_if.end())
diff --git a/test/mock_syscall.hpp b/test/mock_syscall.hpp
index 8d51e0f..39c3986 100644
--- a/test/mock_syscall.hpp
+++ b/test/mock_syscall.hpp
@@ -8,16 +8,6 @@
  */
 void mock_clear();
 
-/** @brief Adds the given interface and addr info
- *         into the ifaddr list.
- *  @param[in] name - Interface name.
- *  @param[in] addr - IP address.
- *  @param[in] mask - subnet mask.
- *  @param[in] flags - Interface flags.
- */
-
-void mock_addIP(const char* name, const char* addr, const char* mask);
-
 /** @brief Adds an address string to index mapping and MAC mapping
  *
  *  @param[in] name - Interface name
diff --git a/test/test_ethernet_interface.cpp b/test/test_ethernet_interface.cpp
index ace2b1d..e65d03c 100644
--- a/test/test_ethernet_interface.cpp
+++ b/test/test_ethernet_interface.cpp
@@ -2,6 +2,7 @@
 #include "ipaddress.hpp"
 #include "mock_network_manager.hpp"
 #include "mock_syscall.hpp"
+#include "system_queries.hpp"
 #include "util.hpp"
 
 #include <arpa/inet.h>
@@ -45,7 +46,8 @@
     {
         mock_clear();
         mock_addIF("test0", /*idx=*/1);
-        return {bus, manager, getInterfaceInfo("test0"),
+        return {bus, manager,
+                system::InterfaceInfo{.idx = 1, .flags = 0, .name = "test0"},
                 "/xyz/openbmc_test/network"sv, config::Parser()};
     }
 
@@ -113,7 +115,12 @@
     constexpr unsigned mtu = 150;
 
     mock_addIF("test1", idx, IFF_RUNNING, mac, mtu);
-    MockEthernetInterface intf(bus, manager, getInterfaceInfo("test1"),
+    MockEthernetInterface intf(bus, manager,
+                               system::InterfaceInfo{.idx = idx,
+                                                     .flags = IFF_RUNNING,
+                                                     .name = "test1",
+                                                     .mac = mac,
+                                                     .mtu = mtu},
                                "/xyz/openbmc_test/network"sv, config::Parser());
 
     EXPECT_EQ(mtu, intf.mtu());
diff --git a/test/test_network_manager.cpp b/test/test_network_manager.cpp
index a34a5e7..5298719 100644
--- a/test/test_network_manager.cpp
+++ b/test/test_network_manager.cpp
@@ -50,7 +50,6 @@
 
     // Adds the following ip in the getifaddrs list.
     mock_addIF("igb1", /*idx=*/2);
-    mock_addIP("igb1", "192.0.2.3", "255.255.255.128");
 
     // Now create the interfaces which will call the mocked getifaddrs
     // which returns the above interface detail.
@@ -64,9 +63,7 @@
     mock_clear();
 
     mock_addIF("igb0", /*idx=*/1);
-    mock_addIP("igb0", "192.0.2.2", "255.255.255.128");
     mock_addIF("igb1", /*idx=*/2);
-    mock_addIP("igb1", "192.0.2.3", "255.255.255.128");
 
     createInterfaces();
     EXPECT_THAT(manager.getInterfaces(),
diff --git a/test/test_system_queries.cpp b/test/test_system_queries.cpp
new file mode 100644
index 0000000..d615a87
--- /dev/null
+++ b/test/test_system_queries.cpp
@@ -0,0 +1,115 @@
+#include "system_queries.hpp"
+
+#include <linux/rtnetlink.h>
+#include <net/if.h>
+
+#include <stdplus/raw.hpp>
+
+#include <gtest/gtest.h>
+
+using std::literals::string_view_literals::operator""sv;
+
+namespace phosphor::network::system
+{
+namespace detail
+{
+
+TEST(ParseInterface, NotLinkType)
+{
+    nlmsghdr hdr{};
+    hdr.nlmsg_type = RTM_NEWADDR;
+
+    EXPECT_THROW(parseInterface(hdr, ""), std::runtime_error);
+}
+
+TEST(ParseInterface, SmallMsg)
+{
+    nlmsghdr hdr{};
+    hdr.nlmsg_type = RTM_NEWLINK;
+    auto data = "1"sv;
+
+    EXPECT_THROW(parseInterface(hdr, data), std::runtime_error);
+}
+
+TEST(ParseInterface, NoAttrs)
+{
+    nlmsghdr hdr{};
+    hdr.nlmsg_type = RTM_NEWLINK;
+    struct
+    {
+        ifinfomsg hdr __attribute__((aligned(NLMSG_ALIGNTO)));
+    } msg;
+    msg.hdr.ifi_index = 1;
+    msg.hdr.ifi_flags = 2;
+    auto data = stdplus::raw::asView<char>(msg);
+
+    auto info = parseInterface(hdr, data);
+    auto expected = InterfaceInfo{.idx = 1, .flags = 2};
+    EXPECT_EQ(info, expected);
+}
+
+TEST(ParseInterface, AllAttrs)
+{
+    nlmsghdr hdr{};
+    hdr.nlmsg_type = RTM_NEWLINK;
+    struct
+    {
+        ifinfomsg hdr __attribute__((aligned(NLMSG_ALIGNTO)));
+        rtattr addr_hdr __attribute__((aligned((RTA_ALIGNTO))));
+        char addr[6]
+            __attribute__((aligned((RTA_ALIGNTO)))) = {0, 1, 2, 3, 4, 5};
+        rtattr name_hdr __attribute__((aligned((RTA_ALIGNTO))));
+        char name[5] __attribute__((aligned((RTA_ALIGNTO)))) = "eth0";
+        rtattr mtu_hdr __attribute__((aligned((RTA_ALIGNTO))));
+        unsigned mtu __attribute__((aligned((RTA_ALIGNTO)))) = 50;
+    } msg;
+    msg.hdr.ifi_index = 1;
+    msg.hdr.ifi_flags = 2;
+    msg.addr_hdr.rta_type = IFLA_ADDRESS;
+    msg.addr_hdr.rta_len = RTA_LENGTH(sizeof(msg.addr));
+    msg.name_hdr.rta_type = IFLA_IFNAME;
+    msg.name_hdr.rta_len = RTA_LENGTH(sizeof(msg.name));
+    msg.mtu_hdr.rta_type = IFLA_MTU;
+    msg.mtu_hdr.rta_len = RTA_LENGTH(sizeof(msg.mtu));
+    auto data = stdplus::raw::asView<char>(msg);
+
+    auto info = parseInterface(hdr, data);
+    auto expected = InterfaceInfo{.idx = 1,
+                                  .flags = 2,
+                                  .name = "eth0",
+                                  .mac = ether_addr{0, 1, 2, 3, 4, 5},
+                                  .mtu = 50};
+    EXPECT_EQ(info, expected);
+}
+
+TEST(ValidateNewInterface, Loopback)
+{
+    InterfaceInfo info;
+    info.flags = IFF_LOOPBACK | IFF_RUNNING;
+    EXPECT_FALSE(validateNewInterface(info));
+}
+
+TEST(ValidateNewInterface, NoName)
+{
+    EXPECT_THROW(validateNewInterface(InterfaceInfo{}), std::invalid_argument);
+}
+
+TEST(ValidateNewInterface, IgnoredInterface)
+{
+    InterfaceInfo info;
+    setenv("IGNORED_INTERFACES", "ign", true);
+    info.name = "ign";
+    info.flags = IFF_RUNNING;
+    EXPECT_FALSE(validateNewInterface(info));
+}
+
+TEST(ValidateNewInterface, Valid)
+{
+    InterfaceInfo info;
+    info.name = "eth0";
+    info.flags = 0;
+    EXPECT_TRUE(validateNewInterface(info));
+}
+
+} // namespace detail
+} // namespace phosphor::network::system
diff --git a/test/test_vlan_interface.cpp b/test/test_vlan_interface.cpp
index 499ab34..c17bf97 100644
--- a/test/test_vlan_interface.cpp
+++ b/test/test_vlan_interface.cpp
@@ -2,6 +2,7 @@
 #include "ipaddress.hpp"
 #include "mock_network_manager.hpp"
 #include "mock_syscall.hpp"
+#include "system_queries.hpp"
 #include "vlan_interface.hpp"
 
 #include <arpa/inet.h>
@@ -44,7 +45,7 @@
         mock_addIF("test0", /*idx=*/1);
         return {bus,
                 manager,
-                getInterfaceInfo("test0"),
+                system::InterfaceInfo{.idx = 1, .flags = 0, .name = "test0"},
                 "/xyz/openbmc_test/network"sv,
                 config::Parser(),
                 /*emitSignal=*/false,