|  | #include "netlink.hpp" | 
|  |  | 
|  | #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 <format> | 
|  | #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( | 
|  | std::format("nlmsg length shorter than header: {} < {}", | 
|  | hdr.nlmsg_len, sizeof(hdr))); | 
|  | } | 
|  | if (msgs.size() < hdr.nlmsg_len) | 
|  | { | 
|  | throw std::runtime_error( | 
|  | std::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(std::format( | 
|  | "rtattr shorter than header: {} < {}", hdr.rta_len, RTA_LENGTH(0))); | 
|  | } | 
|  | if (data.size() < hdr.rta_len) | 
|  | { | 
|  | throw std::runtime_error( | 
|  | std::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 |