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/netlink.cpp b/netlink.cpp
new file mode 100644
index 0000000..947b828
--- /dev/null
+++ b/netlink.cpp
@@ -0,0 +1,194 @@
+#include "netlink.hpp"
+
+#include "util.hpp"
+
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+#include <sys/socket.h>
+#include <unistd.h>
+
+#include <array>
+#include <stdexcept>
+#include <system_error>
+
+namespace phosphor
+{
+namespace network
+{
+namespace netlink
+{
+namespace detail
+{
+
+void processMsg(std::string_view& msgs, bool& done, const ReceiveCallback& cb)
+{
+    // Parse and update the message buffer
+    auto hdr = copyFrom<nlmsghdr>(msgs, "Bad netlink header");
+    if (hdr.nlmsg_len < sizeof(hdr))
+    {
+        throw std::runtime_error("Invalid nlmsg length");
+    }
+    if (msgs.size() < hdr.nlmsg_len)
+    {
+        throw std::runtime_error("Bad nlmsg payload");
+    }
+    auto msg = msgs.substr(NLMSG_HDRLEN, hdr.nlmsg_len - NLMSG_HDRLEN);
+    msgs.remove_prefix(NLMSG_ALIGN(hdr.nlmsg_len));
+
+    // Figure out how to handle the individual message
+    bool doCallback = true;
+    if (hdr.nlmsg_flags & NLM_F_MULTI)
+    {
+        done = false;
+    }
+    if (hdr.nlmsg_type == NLMSG_NOOP)
+    {
+        doCallback = false;
+    }
+    else if (hdr.nlmsg_type == NLMSG_DONE)
+    {
+        if (done)
+        {
+            throw std::runtime_error("Got done for non-multi msg");
+        }
+        done = true;
+        doCallback = false;
+    }
+    else if (hdr.nlmsg_type == NLMSG_ERROR)
+    {
+        auto err = copyFrom<nlmsgerr>(msg, "Bad netlink error");
+        // This is just an ACK so don't do the callback
+        if (err.error <= 0)
+        {
+            doCallback = false;
+        }
+    }
+    // All multi-msg headers must have the multi flag
+    if (!done && !(hdr.nlmsg_flags & NLM_F_MULTI))
+    {
+        throw std::runtime_error("Got non-multi msg before done");
+    }
+    if (doCallback)
+    {
+        cb(hdr, msg);
+    }
+}
+
+static void receive(int sock, const ReceiveCallback& cb)
+{
+    // 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
+    std::array<char, 8192> buf;
+
+    iovec iov{};
+    iov.iov_base = buf.data();
+    iov.iov_len = buf.size();
+
+    sockaddr_nl from{};
+    from.nl_family = AF_NETLINK;
+
+    msghdr hdr{};
+    hdr.msg_name = &from;
+    hdr.msg_namelen = sizeof(from);
+    hdr.msg_iov = &iov;
+    hdr.msg_iovlen = 1;
+
+    // We only do multiple recvs if we have a MULTI type message
+    bool done = true;
+    do
+    {
+        ssize_t recvd = recvmsg(sock, &hdr, 0);
+        if (recvd < 0)
+        {
+            throw std::system_error(errno, std::generic_category(),
+                                    "netlink recvmsg");
+        }
+        if (recvd == 0)
+        {
+            throw std::runtime_error("netlink recvmsg: Got empty payload");
+        }
+
+        std::string_view msgs(buf.data(), recvd);
+        do
+        {
+            processMsg(msgs, done, cb);
+        } while (!done && !msgs.empty());
+
+        if (done && !msgs.empty())
+        {
+            throw std::runtime_error("Extra unprocessed netlink messages");
+        }
+    } while (!done);
+}
+
+static void requestSend(int sock, void* data, size_t size)
+{
+    sockaddr_nl dst{};
+    dst.nl_family = AF_NETLINK;
+
+    iovec iov{};
+    iov.iov_base = data;
+    iov.iov_len = size;
+
+    msghdr hdr{};
+    hdr.msg_name = reinterpret_cast<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(),
+                                "netlink sendmsg");
+    }
+}
+
+static int newRequestSocket(int protocol)
+{
+    int sock = socket(AF_NETLINK, SOCK_RAW, protocol);
+    if (sock < 0)
+    {
+        throw std::system_error(errno, std::generic_category(), "netlink open");
+    }
+
+    sockaddr_nl local{};
+    local.nl_family = AF_NETLINK;
+    int r = bind(sock, reinterpret_cast<sockaddr*>(&local), sizeof(local));
+    if (r < 0)
+    {
+        close(sock);
+        throw std::system_error(errno, std::generic_category(), "netlink bind");
+    }
+
+    return sock;
+}
+
+void performRequest(int protocol, void* data, size_t size,
+                    const ReceiveCallback& cb)
+{
+    Descriptor sock(newRequestSocket(protocol));
+    requestSend(sock(), data, size);
+    receive(sock(), cb);
+}
+
+} // namespace detail
+
+std::tuple<rtattr, std::string_view> extractRtAttr(std::string_view& data)
+{
+    auto hdr = copyFrom<rtattr>(data, "Bad rtattr header");
+    if (hdr.rta_len < RTA_LENGTH(0))
+    {
+        throw std::runtime_error("Invalid rtattr length");
+    }
+    if (data.size() < hdr.rta_len)
+    {
+        throw std::runtime_error("Not enough data for rtattr");
+    }
+    auto attr = data.substr(RTA_LENGTH(0), hdr.rta_len - RTA_LENGTH(0));
+    data.remove_prefix(RTA_ALIGN(hdr.rta_len));
+    return {hdr, attr};
+}
+
+} // namespace netlink
+} // namespace network
+} // namespace phosphor