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