rtnetlink: Migrate neighbor functions

Change-Id: I697f3d70f327f5d1d5e104dc7e4a2af528bf2b6e
Signed-off-by: William A. Kennington III <wak@google.com>
diff --git a/src/ethernet_interface.cpp b/src/ethernet_interface.cpp
index b3179f4..4664655 100644
--- a/src/ethernet_interface.cpp
+++ b/src/ethernet_interface.cpp
@@ -216,21 +216,16 @@
 void EthernetInterface::createStaticNeighborObjects()
 {
     staticNeighbors.clear();
-
-    NeighborFilter filter;
-    filter.interface = ifIdx;
-    filter.state = NUD_PERMANENT;
-    auto neighbors = getCurrentNeighbors(filter);
-    for (const auto& neighbor : neighbors)
+    for (const auto& neighbor : system::getNeighbors({.ifidx = ifIdx}))
     {
-        if (!neighbor.mac)
+        if (!neighbor.mac || (neighbor.state & NUD_PERMANENT) == 0)
         {
             continue;
         }
         staticNeighbors.emplace(
-            neighbor.address,
+            neighbor.addr,
             std::make_unique<Neighbor>(bus, std::string_view(objPath), *this,
-                                       neighbor.address, *neighbor.mac,
+                                       neighbor.addr, *neighbor.mac,
                                        Neighbor::State::Permanent));
     }
 }
diff --git a/src/neighbor.cpp b/src/neighbor.cpp
index c9c1fc9..0f5140e 100644
--- a/src/neighbor.cpp
+++ b/src/neighbor.cpp
@@ -1,92 +1,17 @@
 #include "neighbor.hpp"
 
 #include "ethernet_interface.hpp"
-#include "netlink.hpp"
 #include "network_manager.hpp"
-#include "util.hpp"
-
-#include <linux/neighbour.h>
-#include <linux/netlink.h>
-#include <linux/rtnetlink.h>
-#include <net/if.h>
-#include <sys/socket.h>
-#include <sys/types.h>
 
 #include <phosphor-logging/elog-errors.hpp>
 #include <phosphor-logging/elog.hpp>
-#include <stdexcept>
-#include <stdplus/raw.hpp>
 #include <string>
-#include <string_view>
-#include <utility>
-#include <vector>
 #include <xyz/openbmc_project/Common/error.hpp>
 
 namespace phosphor
 {
 namespace network
 {
-namespace detail
-{
-
-void parseNeighbor(const NeighborFilter& filter, const nlmsghdr& hdr,
-                   std::string_view msg, std::vector<NeighborInfo>& neighbors)
-{
-    if (hdr.nlmsg_type != RTM_NEWNEIGH)
-    {
-        throw std::runtime_error("Not a neighbor msg");
-    }
-    const auto& ndm = netlink::extractRtData<ndmsg>(msg);
-
-    // Filter out neighbors we don't care about
-    unsigned ifindex = ndm.ndm_ifindex;
-    if (filter.interface != 0 && filter.interface != ifindex)
-    {
-        return;
-    }
-    if ((ndm.ndm_state & filter.state) == 0)
-    {
-        return;
-    }
-
-    // Build the neighbor info for our valid neighbor
-    NeighborInfo neighbor;
-    neighbor.interface = ifindex;
-    neighbor.state = ndm.ndm_state;
-    bool set_addr = false;
-    while (!msg.empty())
-    {
-        auto [hdr, data] = netlink::extractRtAttr(msg);
-        if (hdr.rta_type == NDA_LLADDR)
-        {
-            neighbor.mac = stdplus::raw::copyFrom<ether_addr>(data);
-        }
-        else if (hdr.rta_type == NDA_DST)
-        {
-            neighbor.address = addrFromBuf(ndm.ndm_family, data);
-            set_addr = true;
-        }
-    }
-    if (!set_addr)
-    {
-        throw std::runtime_error("Missing address");
-    }
-    neighbors.push_back(std::move(neighbor));
-}
-
-} // namespace detail
-
-std::vector<NeighborInfo> getCurrentNeighbors(const NeighborFilter& filter)
-{
-    std::vector<NeighborInfo> neighbors;
-    auto cb = [&filter, &neighbors](const nlmsghdr& hdr, std::string_view msg) {
-        detail::parseNeighbor(filter, hdr, msg, neighbors);
-    };
-    ndmsg msg{};
-    msg.ndm_ifindex = filter.interface;
-    netlink::performRequest(NETLINK_ROUTE, RTM_GETNEIGH, NLM_F_DUMP, msg, cb);
-    return neighbors;
-}
 
 static auto makeObjPath(std::string_view root, InAddrAny addr)
 {
diff --git a/src/neighbor.hpp b/src/neighbor.hpp
index 4cd3682..37f8d17 100644
--- a/src/neighbor.hpp
+++ b/src/neighbor.hpp
@@ -1,16 +1,10 @@
 #pragma once
 #include "types.hpp"
 
-#include <linux/netlink.h>
-#include <net/ethernet.h>
-
-#include <cstdint>
-#include <optional>
 #include <sdbusplus/bus.hpp>
 #include <sdbusplus/message/native_types.hpp>
 #include <sdbusplus/server/object.hpp>
 #include <string_view>
-#include <vector>
 #include <xyz/openbmc_project/Network/Neighbor/server.hpp>
 #include <xyz/openbmc_project/Object/Delete/server.hpp>
 
@@ -26,34 +20,6 @@
 
 class EthernetInterface;
 
-/* @class NeighborFilter
- */
-struct NeighborFilter
-{
-    unsigned interface;
-    uint16_t state;
-
-    /* @brief Creates an empty filter */
-    NeighborFilter() : interface(0), state(~UINT16_C(0))
-    {
-    }
-};
-
-/** @class NeighborInfo
- *  @brief Information about a neighbor from the kernel
- */
-struct NeighborInfo
-{
-    unsigned interface;
-    InAddrAny address;
-    std::optional<ether_addr> mac;
-    uint16_t state;
-};
-
-/** @brief Returns a list of the current system neighbor table
- */
-std::vector<NeighborInfo> getCurrentNeighbors(const NeighborFilter& filter);
-
 /** @class Neighbor
  *  @brief OpenBMC network neighbor implementation.
  *  @details A concrete implementation for the
@@ -111,13 +77,5 @@
              State state);
 };
 
-namespace detail
-{
-
-void parseNeighbor(const NeighborFilter& filter, const nlmsghdr& hdr,
-                   std::string_view msg, std::vector<NeighborInfo>& neighbors);
-
-} // namespace detail
-
 } // namespace network
 } // namespace phosphor
diff --git a/src/rtnetlink.cpp b/src/rtnetlink.cpp
index 5d6e660..6b456b7 100644
--- a/src/rtnetlink.cpp
+++ b/src/rtnetlink.cpp
@@ -81,4 +81,32 @@
     return ret;
 }
 
+NeighborInfo neighFromRtm(std::string_view msg)
+{
+    const auto& ndm = netlink::extractRtData<ndmsg>(msg);
+
+    NeighborInfo ret;
+    ret.ifidx = ndm.ndm_ifindex;
+    ret.state = ndm.ndm_state;
+    bool set_addr = false;
+    while (!msg.empty())
+    {
+        auto [hdr, data] = netlink::extractRtAttr(msg);
+        if (hdr.rta_type == NDA_LLADDR)
+        {
+            ret.mac = stdplus::raw::copyFrom<ether_addr>(data);
+        }
+        else if (hdr.rta_type == NDA_DST)
+        {
+            ret.addr = addrFromBuf(ndm.ndm_family, data);
+            set_addr = true;
+        }
+    }
+    if (!set_addr)
+    {
+        throw std::runtime_error("Missing address");
+    }
+    return ret;
+}
+
 } // namespace phosphor::network::netlink
diff --git a/src/rtnetlink.hpp b/src/rtnetlink.hpp
index 48f3820..2862143 100644
--- a/src/rtnetlink.hpp
+++ b/src/rtnetlink.hpp
@@ -13,4 +13,6 @@
 
 AddressInfo addrFromRtm(std::string_view msg);
 
+NeighborInfo neighFromRtm(std::string_view msg);
+
 } // namespace phosphor::network::netlink
diff --git a/src/system_queries.cpp b/src/system_queries.cpp
index 5aba1d2..35dfa10 100644
--- a/src/system_queries.cpp
+++ b/src/system_queries.cpp
@@ -235,6 +235,16 @@
     return true;
 }
 
+bool detail::validateNewNeigh(const NeighborInfo& info,
+                              const NeighborFilter& filter) noexcept
+{
+    if (filter.ifidx != 0 && filter.ifidx != info.ifidx)
+    {
+        return false;
+    }
+    return true;
+}
+
 std::vector<InterfaceInfo> getInterfaces()
 {
     std::vector<InterfaceInfo> ret;
@@ -266,4 +276,20 @@
     return ret;
 }
 
+std::vector<NeighborInfo> getNeighbors(const NeighborFilter& filter)
+{
+    std::vector<NeighborInfo> ret;
+    auto cb = [&](const nlmsghdr&, std::string_view msg) {
+        auto info = netlink::neighFromRtm(msg);
+        if (detail::validateNewNeigh(info, filter))
+        {
+            ret.push_back(std::move(info));
+        }
+    };
+    ndmsg msg{};
+    msg.ndm_ifindex = filter.ifidx;
+    netlink::performRequest(NETLINK_ROUTE, RTM_GETNEIGH, NLM_F_DUMP, msg, cb);
+    return ret;
+}
+
 } // namespace phosphor::network::system
diff --git a/src/system_queries.hpp b/src/system_queries.hpp
index 97a5faa..615c282 100644
--- a/src/system_queries.hpp
+++ b/src/system_queries.hpp
@@ -58,12 +58,19 @@
     unsigned ifidx = 0;
 };
 
+struct NeighborFilter
+{
+    unsigned ifidx = 0;
+};
+
 namespace detail
 {
 InterfaceInfo parseInterface(const nlmsghdr& hdr, std::string_view msg);
 bool validateNewInterface(const InterfaceInfo& info);
 bool validateNewAddr(const AddressInfo& info,
                      const AddressFilter& filter) noexcept;
+bool validateNewNeigh(const NeighborInfo& info,
+                      const NeighborFilter& filter) noexcept;
 } // namespace detail
 
 /** @brief Get all the interfaces from the system.
@@ -76,4 +83,8 @@
  */
 std::vector<AddressInfo> getAddresses(const AddressFilter& filter);
 
+/** @brief Returns a list of system neighbor table
+ */
+std::vector<NeighborInfo> getNeighbors(const NeighborFilter& filter);
+
 } // namespace phosphor::network::system
diff --git a/src/types.hpp b/src/types.hpp
index 8755068..bdb431d 100644
--- a/src/types.hpp
+++ b/src/types.hpp
@@ -86,6 +86,17 @@
     uint32_t flags;
 };
 
+/** @class NeighborInfo
+ *  @brief Information about a neighbor from the kernel
+ */
+struct NeighborInfo
+{
+    unsigned ifidx;
+    InAddrAny addr;
+    std::optional<ether_addr> mac;
+    uint16_t state;
+};
+
 struct string_hash : public std::hash<std::string_view>
 {
     using is_transparent = void;
diff --git a/test/meson.build b/test/meson.build
index 6a46c1d..22bbfe3 100644
--- a/test/meson.build
+++ b/test/meson.build
@@ -46,7 +46,6 @@
 tests = [
   'config_parser',
   'ethernet_interface',
-  'neighbor',
   'netlink',
   'network_manager',
   'rtnetlink',
diff --git a/test/test_neighbor.cpp b/test/test_neighbor.cpp
deleted file mode 100644
index 3cdb209..0000000
--- a/test/test_neighbor.cpp
+++ /dev/null
@@ -1,239 +0,0 @@
-#include "neighbor.hpp"
-#include "util.hpp"
-
-#include <arpa/inet.h>
-#include <linux/netlink.h>
-#include <linux/rtnetlink.h>
-#include <net/ethernet.h>
-
-#include <cstring>
-#include <stdexcept>
-#include <string>
-#include <vector>
-
-#include <gtest/gtest.h>
-
-namespace phosphor
-{
-namespace network
-{
-namespace detail
-{
-
-TEST(ParseNeighbor, NotNeighborType)
-{
-    nlmsghdr hdr{};
-    hdr.nlmsg_type = RTM_NEWLINK;
-    NeighborFilter filter;
-
-    std::vector<NeighborInfo> neighbors;
-    EXPECT_THROW(parseNeighbor(filter, hdr, "", neighbors), std::runtime_error);
-    EXPECT_EQ(0, neighbors.size());
-}
-
-TEST(ParseNeighbor, SmallMsg)
-{
-    nlmsghdr hdr{};
-    hdr.nlmsg_type = RTM_NEWNEIGH;
-    std::string data = "1";
-    NeighborFilter filter;
-
-    std::vector<NeighborInfo> neighbors;
-    EXPECT_THROW(parseNeighbor(filter, hdr, data, neighbors),
-                 std::runtime_error);
-    EXPECT_EQ(0, neighbors.size());
-}
-
-TEST(ParseNeighbor, NoAttrs)
-{
-    nlmsghdr hdr{};
-    hdr.nlmsg_type = RTM_NEWNEIGH;
-    ndmsg msg{};
-    msg.ndm_ifindex = 1;
-    msg.ndm_state = NUD_REACHABLE;
-    std::string data;
-    data.append(reinterpret_cast<char*>(&msg), sizeof(msg));
-    NeighborFilter filter;
-
-    std::vector<NeighborInfo> neighbors;
-    EXPECT_THROW(parseNeighbor(filter, hdr, data, neighbors),
-                 std::runtime_error);
-    EXPECT_EQ(0, neighbors.size());
-}
-
-TEST(ParseNeighbor, NoAddress)
-{
-    nlmsghdr hdr{};
-    hdr.nlmsg_type = RTM_NEWNEIGH;
-    ndmsg msg{};
-    msg.ndm_ifindex = 1;
-    msg.ndm_state = NUD_REACHABLE;
-    ether_addr mac = {{0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa}};
-    rtattr lladdr{};
-    constexpr size_t len = RTA_LENGTH(sizeof(mac));
-    lladdr.rta_len = len;
-    lladdr.rta_type = NDA_LLADDR;
-    char lladdrbuf[RTA_ALIGN(len)];
-    std::memset(lladdrbuf, '\0', sizeof(lladdrbuf));
-    std::memcpy(lladdrbuf, &lladdr, sizeof(lladdr));
-    std::memcpy(RTA_DATA(lladdrbuf), &mac, sizeof(mac));
-    std::string data;
-    data.append(reinterpret_cast<char*>(&msg), sizeof(msg));
-    data.append(reinterpret_cast<char*>(&lladdrbuf), sizeof(lladdrbuf));
-    NeighborFilter filter;
-
-    std::vector<NeighborInfo> neighbors;
-    EXPECT_THROW(parseNeighbor(filter, hdr, data, neighbors),
-                 std::runtime_error);
-    EXPECT_EQ(0, neighbors.size());
-}
-
-TEST(ParseNeighbor, NoMAC)
-{
-    nlmsghdr hdr{};
-    hdr.nlmsg_type = RTM_NEWNEIGH;
-    ndmsg msg{};
-    msg.ndm_family = AF_INET;
-    msg.ndm_state = NUD_PERMANENT;
-    msg.ndm_ifindex = 1;
-    in_addr addr;
-    ASSERT_EQ(1, inet_pton(msg.ndm_family, "192.168.10.1", &addr));
-    rtattr dst{};
-    constexpr size_t len = RTA_LENGTH(sizeof(addr));
-    dst.rta_len = len;
-    dst.rta_type = NDA_DST;
-    char dstbuf[RTA_ALIGN(len)];
-    std::memset(dstbuf, '\0', sizeof(dstbuf));
-    std::memcpy(dstbuf, &dst, sizeof(dst));
-    std::memcpy(RTA_DATA(dstbuf), &addr, sizeof(addr));
-    std::string data;
-    data.append(reinterpret_cast<char*>(&msg), sizeof(msg));
-    data.append(reinterpret_cast<char*>(&dstbuf), sizeof(dstbuf));
-    NeighborFilter filter;
-
-    std::vector<NeighborInfo> neighbors;
-    parseNeighbor(filter, hdr, data, neighbors);
-    EXPECT_EQ(1, neighbors.size());
-    EXPECT_EQ(msg.ndm_ifindex, neighbors[0].interface);
-    EXPECT_EQ(msg.ndm_state, neighbors[0].state);
-    EXPECT_FALSE(neighbors[0].mac);
-    EXPECT_EQ(addr, neighbors[0].address);
-}
-
-TEST(ParseNeighbor, FilterInterface)
-{
-    nlmsghdr hdr{};
-    hdr.nlmsg_type = RTM_NEWNEIGH;
-    ndmsg msg{};
-    msg.ndm_family = AF_INET;
-    msg.ndm_state = NUD_PERMANENT;
-    msg.ndm_ifindex = 2;
-    in_addr addr;
-    ASSERT_EQ(1, inet_pton(msg.ndm_family, "192.168.10.1", &addr));
-    rtattr dst{};
-    constexpr size_t len = RTA_LENGTH(sizeof(addr));
-    dst.rta_len = len;
-    dst.rta_type = NDA_DST;
-    char dstbuf[RTA_ALIGN(len)];
-    std::memset(dstbuf, '\0', sizeof(dstbuf));
-    std::memcpy(dstbuf, &dst, sizeof(dst));
-    std::memcpy(RTA_DATA(dstbuf), &addr, sizeof(addr));
-    std::string data;
-    data.append(reinterpret_cast<char*>(&msg), sizeof(msg));
-    data.append(reinterpret_cast<char*>(&dstbuf), sizeof(dstbuf));
-    NeighborFilter filter;
-
-    std::vector<NeighborInfo> neighbors;
-    filter.interface = 1;
-    parseNeighbor(filter, hdr, data, neighbors);
-    EXPECT_EQ(0, neighbors.size());
-    filter.interface = 2;
-    parseNeighbor(filter, hdr, data, neighbors);
-    EXPECT_EQ(1, neighbors.size());
-    EXPECT_EQ(msg.ndm_ifindex, neighbors[0].interface);
-    EXPECT_EQ(msg.ndm_state, neighbors[0].state);
-    EXPECT_FALSE(neighbors[0].mac);
-    EXPECT_EQ(addr, neighbors[0].address);
-}
-
-TEST(ParseNeighbor, FilterState)
-{
-    nlmsghdr hdr{};
-    hdr.nlmsg_type = RTM_NEWNEIGH;
-    ndmsg msg{};
-    msg.ndm_family = AF_INET;
-    msg.ndm_state = NUD_PERMANENT;
-    msg.ndm_ifindex = 2;
-    in_addr addr;
-    ASSERT_EQ(1, inet_pton(msg.ndm_family, "192.168.10.1", &addr));
-    rtattr dst{};
-    constexpr size_t len = RTA_LENGTH(sizeof(addr));
-    dst.rta_len = len;
-    dst.rta_type = NDA_DST;
-    char dstbuf[RTA_ALIGN(len)];
-    std::memset(dstbuf, '\0', sizeof(dstbuf));
-    std::memcpy(dstbuf, &dst, sizeof(dst));
-    std::memcpy(RTA_DATA(dstbuf), &addr, sizeof(addr));
-    std::string data;
-    data.append(reinterpret_cast<char*>(&msg), sizeof(msg));
-    data.append(reinterpret_cast<char*>(&dstbuf), sizeof(dstbuf));
-    NeighborFilter filter;
-
-    std::vector<NeighborInfo> neighbors;
-    filter.state = NUD_NOARP;
-    parseNeighbor(filter, hdr, data, neighbors);
-    EXPECT_EQ(0, neighbors.size());
-    filter.state = NUD_PERMANENT | NUD_NOARP;
-    parseNeighbor(filter, hdr, data, neighbors);
-    EXPECT_EQ(1, neighbors.size());
-    EXPECT_EQ(msg.ndm_ifindex, neighbors[0].interface);
-    EXPECT_EQ(msg.ndm_state, neighbors[0].state);
-    EXPECT_FALSE(neighbors[0].mac);
-    EXPECT_EQ(addr, neighbors[0].address);
-}
-
-TEST(ParseNeighbor, Full)
-{
-    nlmsghdr hdr{};
-    hdr.nlmsg_type = RTM_NEWNEIGH;
-    ndmsg msg{};
-    msg.ndm_family = AF_INET6;
-    msg.ndm_state = NUD_NOARP;
-    msg.ndm_ifindex = 1;
-    ether_addr mac = {{0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa}};
-    rtattr lladdr{};
-    constexpr size_t lladdr_len = RTA_LENGTH(sizeof(mac));
-    lladdr.rta_len = lladdr_len;
-    lladdr.rta_type = NDA_LLADDR;
-    char lladdrbuf[RTA_ALIGN(lladdr_len)];
-    std::memset(lladdrbuf, '\0', sizeof(lladdrbuf));
-    std::memcpy(lladdrbuf, &lladdr, sizeof(lladdr));
-    std::memcpy(RTA_DATA(lladdrbuf), &mac, sizeof(mac));
-    in6_addr addr;
-    ASSERT_EQ(1, inet_pton(msg.ndm_family, "fd00::1", &addr));
-    rtattr dst{};
-    constexpr size_t dst_len = RTA_LENGTH(sizeof(addr));
-    dst.rta_len = dst_len;
-    dst.rta_type = NDA_DST;
-    char dstbuf[RTA_ALIGN(dst_len)];
-    std::memset(dstbuf, '\0', sizeof(dstbuf));
-    std::memcpy(dstbuf, &dst, sizeof(dst));
-    std::memcpy(RTA_DATA(dstbuf), &addr, sizeof(addr));
-    std::string data;
-    data.append(reinterpret_cast<char*>(&msg), sizeof(msg));
-    data.append(reinterpret_cast<char*>(&lladdrbuf), sizeof(lladdrbuf));
-    data.append(reinterpret_cast<char*>(&dstbuf), sizeof(dstbuf));
-    NeighborFilter filter;
-
-    std::vector<NeighborInfo> neighbors;
-    parseNeighbor(filter, hdr, data, neighbors);
-    EXPECT_EQ(1, neighbors.size());
-    EXPECT_EQ(msg.ndm_ifindex, neighbors[0].interface);
-    EXPECT_EQ(msg.ndm_state, neighbors[0].state);
-    EXPECT_EQ(mac, neighbors[0].mac);
-    EXPECT_EQ(addr, neighbors[0].address);
-}
-
-} // namespace detail
-} // namespace network
-} // namespace phosphor
diff --git a/test/test_rtnetlink.cpp b/test/test_rtnetlink.cpp
index 9518cdc..8bf2a1e 100644
--- a/test/test_rtnetlink.cpp
+++ b/test/test_rtnetlink.cpp
@@ -63,4 +63,54 @@
     EXPECT_EQ(0xff00ff00, ret.flags);
 }
 
+TEST(NeighFromRtm, MissingAddr)
+{
+    struct
+    {
+        alignas(NLMSG_ALIGNTO) ndmsg ndm = {};
+    } msg;
+    EXPECT_THROW(neighFromRtm(stdplus::raw::asView<char>(msg)),
+                 std::runtime_error);
+}
+
+TEST(NeighFromRtm, NoMac)
+{
+    struct
+    {
+        alignas(NLMSG_ALIGNTO) ndmsg ndm;
+        alignas(NLMSG_ALIGNTO) rtattr addr_hdr;
+        alignas(NLMSG_ALIGNTO) uint8_t addr[4] = {192, 168, 1, 20};
+    } msg;
+    msg.ndm.ndm_family = AF_INET;
+    msg.ndm.ndm_state = 4;
+    msg.addr_hdr.rta_type = NDA_DST;
+    msg.addr_hdr.rta_len = RTA_LENGTH(sizeof(msg.addr));
+
+    auto ret = neighFromRtm(stdplus::raw::asView<char>(msg));
+    EXPECT_EQ(msg.ndm.ndm_state, ret.state);
+    EXPECT_EQ((in_addr{hton(0xc0a80114)}), ret.addr);
+    EXPECT_FALSE(ret.mac);
+}
+
+TEST(NeighFromRtm, Full)
+{
+    struct
+    {
+        alignas(NLMSG_ALIGNTO) ndmsg ndm;
+        alignas(NLMSG_ALIGNTO) rtattr addr_hdr;
+        alignas(NLMSG_ALIGNTO) uint8_t addr[4] = {192, 168, 1, 20};
+        alignas(NLMSG_ALIGNTO) rtattr mac_hdr;
+        alignas(NLMSG_ALIGNTO) uint8_t mac[6] = {1, 2, 3, 4, 5, 6};
+    } msg;
+    msg.ndm.ndm_family = AF_INET;
+    msg.addr_hdr.rta_type = NDA_DST;
+    msg.addr_hdr.rta_len = RTA_LENGTH(sizeof(msg.addr));
+    msg.mac_hdr.rta_type = NDA_LLADDR;
+    msg.mac_hdr.rta_len = RTA_LENGTH(sizeof(msg.mac));
+
+    auto ret = neighFromRtm(stdplus::raw::asView<char>(msg));
+    EXPECT_EQ((in_addr{hton(0xc0a80114)}), ret.addr);
+    EXPECT_EQ((ether_addr{1, 2, 3, 4, 5, 6}), ret.mac);
+}
+
 } // namespace phosphor::network::netlink
diff --git a/test/test_system_queries.cpp b/test/test_system_queries.cpp
index 047d968..a79bc77 100644
--- a/test/test_system_queries.cpp
+++ b/test/test_system_queries.cpp
@@ -122,5 +122,16 @@
     EXPECT_FALSE(validateNewAddr(info, {.ifidx = 3}));
 }
 
+TEST(ValidateNewNeigh, Filtering)
+{
+    NeighborInfo info = {};
+    EXPECT_TRUE(validateNewNeigh(info, {}));
+
+    info.ifidx = 2;
+    EXPECT_TRUE(validateNewNeigh(info, {}));
+    EXPECT_TRUE(validateNewNeigh(info, {.ifidx = 2}));
+    EXPECT_FALSE(validateNewNeigh(info, {.ifidx = 3}));
+}
+
 } // namespace detail
 } // namespace phosphor::network::system