| #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 |