blob: f7e6f05c8b2f1452ac73f5dd48eb17325e33337a [file] [log] [blame]
#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