neighbor: Refactor out netlink code

This will allow us to re-use the generic netlink bits for other netlink
request operations.

Adds test coverage to the netlink message parsing routines for sanity
and regression prevention.

Tested:
    Neighbor population still works when static neighbors are created on
    the BMC.

Change-Id: I755e86eb76a8f6f825616c13279328134de87da9
Signed-off-by: William A. Kennington III <wak@google.com>
diff --git a/neighbor.cpp b/neighbor.cpp
index f7e6f05..1961ad3 100644
--- a/neighbor.cpp
+++ b/neighbor.cpp
@@ -3,6 +3,7 @@
 #include "neighbor.hpp"
 
 #include "ethernet_interface.hpp"
+#include "netlink.hpp"
 #include "util.hpp"
 
 #include <linux/neighbour.h>
@@ -16,205 +17,65 @@
 #include <stdexcept>
 #include <string_view>
 #include <system_error>
+#include <vector>
 
 namespace phosphor
 {
 namespace network
 {
-
-NeighborInfo parseNeighbor(std::string_view msg)
+namespace detail
 {
-    struct ndmsg ndm;
-    if (msg.size() < sizeof(ndm))
-    {
-        throw std::runtime_error("Bad neighbor msg");
-    }
-    memcpy(&ndm, msg.data(), sizeof(ndm));
-    auto attrs = msg.substr(sizeof(ndm));
 
-    NeighborInfo info;
-    info.interface.resize(IF_NAMESIZE);
-    if (if_indextoname(ndm.ndm_ifindex, info.interface.data()) == nullptr)
+void parseNeighbor(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");
+    }
+    auto ndm = extract<ndmsg>(msg, "Bad neighbor msg");
+
+    NeighborInfo neighbor;
+    neighbor.interface.resize(IF_NAMESIZE);
+    if (if_indextoname(ndm.ndm_ifindex, neighbor.interface.data()) == nullptr)
     {
         throw std::system_error(errno, std::generic_category(),
                                 "if_indextoname");
     }
-    info.interface.resize(strlen(info.interface.c_str()));
-    info.permanent = ndm.ndm_state & NUD_PERMANENT;
+    neighbor.interface.resize(strlen(neighbor.interface.c_str()));
+    neighbor.permanent = ndm.ndm_state & NUD_PERMANENT;
     bool set_addr = false;
-    while (!attrs.empty())
+    while (!msg.empty())
     {
-        struct rtattr hdr;
-        if (attrs.size() < sizeof(hdr))
-        {
-            throw std::runtime_error("Bad rtattr header");
-        }
-        memcpy(&hdr, attrs.data(), sizeof(hdr));
-        if (hdr.rta_len < sizeof(hdr))
-        {
-            throw std::runtime_error("Invalid rtattr length");
-        }
-        if (attrs.size() < hdr.rta_len)
-        {
-            throw std::runtime_error("Not enough data for rtattr");
-        }
-        auto data = attrs.substr(RTA_LENGTH(0), hdr.rta_len - RTA_LENGTH(0));
+        auto [hdr, data] = netlink::extractRtAttr(msg);
         if (hdr.rta_type == NDA_LLADDR)
         {
-            info.mac = mac_address::fromBuf(data);
+            neighbor.mac = mac_address::fromBuf(data);
         }
         else if (hdr.rta_type == NDA_DST)
         {
-            info.address = addrFromBuf(ndm.ndm_family, data);
+            neighbor.address = addrFromBuf(ndm.ndm_family, data);
             set_addr = true;
         }
-        attrs.remove_prefix(RTA_ALIGN(hdr.rta_len));
     }
     if (!set_addr)
     {
         throw std::runtime_error("Missing address");
     }
-    return info;
+    neighbors.push_back(std::move(neighbor));
 }
 
-bool parseNeighborMsgs(std::string_view msgs, std::vector<NeighborInfo>& info)
-{
-    while (!msgs.empty())
-    {
-        struct nlmsghdr hdr;
-        if (msgs.size() < sizeof(hdr))
-        {
-            throw std::runtime_error("Bad neighbor netlink header");
-        }
-        memcpy(&hdr, msgs.data(), sizeof(hdr));
-        if (hdr.nlmsg_type == NLMSG_DONE)
-        {
-            if (msgs.size() > hdr.nlmsg_len)
-            {
-                throw std::runtime_error("Unexpected extra netlink messages");
-            }
-            return true;
-        }
-        else if (hdr.nlmsg_type != RTM_NEWNEIGH)
-        {
-            throw std::runtime_error("Bad neighbor msg type");
-        }
-        if (hdr.nlmsg_len < sizeof(hdr))
-        {
-            throw std::runtime_error("Invalid nlmsg length");
-        }
-        if (msgs.size() < hdr.nlmsg_len)
-        {
-            throw std::runtime_error("Bad neighbor payload");
-        }
-        auto msg = msgs.substr(NLMSG_HDRLEN, hdr.nlmsg_len - NLMSG_HDRLEN);
-        msgs.remove_prefix(NLMSG_ALIGN(hdr.nlmsg_len));
-        info.push_back(parseNeighbor(msg));
-    }
-
-    return false;
-}
-
-std::vector<NeighborInfo> receiveNeighbors(int sock)
-{
-    // We need to make sure we have enough room for an entire packet otherwise
-    // it gets truncated. The netlink docs guarantee packets will not exceed 8K
-    char buf[8192];
-
-    struct iovec iov;
-    memset(&iov, 0, sizeof(iov));
-    iov.iov_base = buf;
-    iov.iov_len = sizeof(buf);
-
-    struct sockaddr_nl from;
-    memset(&from, 0, sizeof(from));
-    from.nl_family = AF_NETLINK;
-
-    struct msghdr hdr;
-    memset(&hdr, 0, sizeof(hdr));
-    hdr.msg_name = &from;
-    hdr.msg_namelen = sizeof(from);
-    hdr.msg_iov = &iov;
-    hdr.msg_iovlen = 1;
-
-    std::vector<NeighborInfo> info;
-    while (true)
-    {
-        ssize_t recvd = recvmsg(sock, &hdr, 0);
-        if (recvd <= 0)
-        {
-            throw std::system_error(errno, std::generic_category(),
-                                    "recvmsg neighbor");
-        }
-        if (parseNeighborMsgs(std::string_view(buf, recvd), info))
-        {
-            return info;
-        }
-    }
-}
-
-void requestNeighbors(int sock)
-{
-    struct sockaddr_nl dst;
-    memset(&dst, 0, sizeof(dst));
-    dst.nl_family = AF_NETLINK;
-
-    struct
-    {
-        struct nlmsghdr hdr;
-        struct ndmsg msg;
-    } data;
-    memset(&data, 0, sizeof(data));
-    data.hdr.nlmsg_len = sizeof(data);
-    data.hdr.nlmsg_type = RTM_GETNEIGH;
-    data.hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
-    data.msg.ndm_family = AF_UNSPEC;
-
-    struct iovec iov;
-    memset(&iov, 0, sizeof(iov));
-    iov.iov_base = &data;
-    iov.iov_len = sizeof(data);
-
-    struct msghdr hdr;
-    memset(&hdr, 0, sizeof(hdr));
-    hdr.msg_name = reinterpret_cast<struct sockaddr*>(&dst);
-    hdr.msg_namelen = sizeof(dst);
-    hdr.msg_iov = &iov;
-    hdr.msg_iovlen = 1;
-
-    if (sendmsg(sock, &hdr, 0) < 0)
-    {
-        throw std::system_error(errno, std::generic_category(),
-                                "sendmsg neighbor dump");
-    }
-}
-
-int getNetlink(int protocol)
-{
-    int sock = socket(AF_NETLINK, SOCK_DGRAM, protocol);
-    if (sock < 0)
-    {
-        throw std::system_error(errno, std::generic_category(), "netlink open");
-    }
-
-    struct sockaddr_nl local;
-    memset(&local, 0, sizeof(local));
-    local.nl_family = AF_NETLINK;
-    int r =
-        bind(sock, reinterpret_cast<struct sockaddr*>(&local), sizeof(local));
-    if (r < 0)
-    {
-        close(sock);
-        throw std::system_error(errno, std::generic_category(), "netlink bind");
-    }
-    return sock;
-}
+} // namespace detail
 
 std::vector<NeighborInfo> getCurrentNeighbors()
 {
-    Descriptor netlink(getNetlink(NETLINK_ROUTE));
-    requestNeighbors(netlink());
-    return receiveNeighbors(netlink());
+    std::vector<NeighborInfo> neighbors;
+    auto cb = [&neighbors](const nlmsghdr& hdr, std::string_view msg) {
+        detail::parseNeighbor(hdr, msg, neighbors);
+    };
+    netlink::performRequest(NETLINK_ROUTE, RTM_GETNEIGH, NLM_F_DUMP, ndmsg{},
+                            cb);
+    return neighbors;
 }
 
 Neighbor::Neighbor(sdbusplus::bus::bus& bus, const char* objPath,