blob: 69ea5c6fc17d269dc34064397fdcdced59c8bb74 [file] [log] [blame]
#include "netlink.hpp"
#include <fmt/format.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <stdplus/fd/create.hpp>
#include <stdplus/fd/ops.hpp>
#include <stdplus/raw.hpp>
#include <array>
#include <stdexcept>
#include <system_error>
using stdplus::raw::Aligned;
namespace phosphor
{
namespace network
{
namespace netlink
{
namespace detail
{
void processMsg(std::string_view& msgs, bool& done, ReceiveCallback cb)
{
// Parse and update the message buffer
const auto& hdr = stdplus::raw::refFrom<nlmsghdr, Aligned>(msgs);
if (hdr.nlmsg_len < sizeof(hdr))
{
throw std::runtime_error(
fmt::format("nlmsg length shorter than header: {} < {}",
hdr.nlmsg_len, sizeof(hdr)));
}
if (msgs.size() < hdr.nlmsg_len)
{
throw std::runtime_error(
fmt::format("not enough message for nlmsg: {} < {}", msgs.size(),
hdr.nlmsg_len));
}
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)
{
const auto& err = stdplus::raw::refFrom<nlmsgerr, Aligned>(msg);
// 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 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 stdplus::ManagedFd makeSocket(int protocol)
{
using namespace stdplus::fd;
auto sock = socket(SocketDomain::Netlink, SocketType::Raw,
static_cast<stdplus::fd::SocketProto>(protocol));
sockaddr_nl local{};
local.nl_family = AF_NETLINK;
bind(sock, local);
return sock;
}
void performRequest(int protocol, void* data, size_t size, ReceiveCallback cb)
{
auto sock = makeSocket(protocol);
requestSend(sock.get(), data, size);
receive(sock.get(), cb);
}
} // namespace detail
size_t receive(int sock, 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;
size_t num_msgs = 0;
do
{
ssize_t recvd = recvmsg(sock, &hdr, 0);
if (recvd < 0 && errno != EAGAIN)
{
throw std::system_error(errno, std::generic_category(),
"netlink recvmsg");
}
if (recvd <= 0)
{
if (!done)
{
throw std::runtime_error("netlink recvmsg: Got empty payload");
}
return num_msgs;
}
std::string_view msgs(buf.data(), recvd);
do
{
detail::processMsg(msgs, done, cb);
num_msgs++;
} while (!done && !msgs.empty());
if (done && !msgs.empty())
{
throw std::runtime_error("Extra unprocessed netlink messages");
}
} while (!done);
return num_msgs;
}
std::tuple<rtattr, std::string_view> extractRtAttr(std::string_view& data)
{
const auto& hdr = stdplus::raw::refFrom<rtattr, Aligned>(data);
if (hdr.rta_len < RTA_LENGTH(0))
{
throw std::runtime_error(fmt::format(
"rtattr shorter than header: {} < {}", hdr.rta_len, RTA_LENGTH(0)));
}
if (data.size() < hdr.rta_len)
{
throw std::runtime_error(
fmt::format("not enough message for rtattr: {} < {}", data.size(),
hdr.rta_len));
}
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