Static neighbor support
This changes adds support for assigning static ARP / NDP
neighbors on each ethernet interface.
Tested:
Ran inside a romulus VM and made sure that `ip neigh add`
commands triggered the refresh of static_neighbor objects.
Also verified that static neighbors could be added through
the CreateStatic dbus interface. Verified that deleting those
static entries would persist to the system.
Change-Id: Ifaf753525c532b6dade392d7eb4ebd6d7242d480
Signed-off-by: William A. Kennington III <wak@google.com>
diff --git a/neighbor.cpp b/neighbor.cpp
new file mode 100644
index 0000000..f7e6f05
--- /dev/null
+++ b/neighbor.cpp
@@ -0,0 +1,240 @@
+#include "config.h"
+
+#include "neighbor.hpp"
+
+#include "ethernet_interface.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 <cstring>
+#include <stdexcept>
+#include <string_view>
+#include <system_error>
+
+namespace phosphor
+{
+namespace network
+{
+
+NeighborInfo parseNeighbor(std::string_view msg)
+{
+ 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)
+ {
+ 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;
+ bool set_addr = false;
+ while (!attrs.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));
+ if (hdr.rta_type == NDA_LLADDR)
+ {
+ info.mac = mac_address::fromBuf(data);
+ }
+ else if (hdr.rta_type == NDA_DST)
+ {
+ info.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;
+}
+
+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;
+}
+
+std::vector<NeighborInfo> getCurrentNeighbors()
+{
+ Descriptor netlink(getNetlink(NETLINK_ROUTE));
+ requestNeighbors(netlink());
+ return receiveNeighbors(netlink());
+}
+
+Neighbor::Neighbor(sdbusplus::bus::bus& bus, const char* objPath,
+ EthernetInterface& parent, const std::string& ipAddress,
+ const std::string& macAddress, State state) :
+ NeighborObj(bus, objPath, true),
+ parent(parent)
+{
+ this->iPAddress(ipAddress);
+ this->mACAddress(macAddress);
+ this->state(state);
+
+ // Emit deferred signal.
+ emit_object_added();
+}
+
+void Neighbor::delete_()
+{
+ parent.deleteStaticNeighborObject(iPAddress());
+}
+
+} // namespace network
+} // namespace phosphor