1 #include "netlink.hpp" 2 3 #include <fmt/format.h> 4 #include <linux/netlink.h> 5 #include <linux/rtnetlink.h> 6 7 #include <array> 8 #include <stdexcept> 9 #include <stdplus/fd/create.hpp> 10 #include <stdplus/fd/ops.hpp> 11 #include <stdplus/raw.hpp> 12 #include <system_error> 13 14 using stdplus::raw::Aligned; 15 16 namespace phosphor 17 { 18 namespace network 19 { 20 namespace netlink 21 { 22 namespace detail 23 { 24 25 void processMsg(std::string_view& msgs, bool& done, ReceiveCallback cb) 26 { 27 // Parse and update the message buffer 28 const auto& hdr = stdplus::raw::refFrom<nlmsghdr, Aligned>(msgs); 29 if (hdr.nlmsg_len < sizeof(hdr)) 30 { 31 throw std::runtime_error( 32 fmt::format("nlmsg length shorter than header: {} < {}", 33 hdr.nlmsg_len, sizeof(hdr))); 34 } 35 if (msgs.size() < hdr.nlmsg_len) 36 { 37 throw std::runtime_error( 38 fmt::format("not enough message for nlmsg: {} < {}", msgs.size(), 39 hdr.nlmsg_len)); 40 } 41 auto msg = msgs.substr(NLMSG_HDRLEN, hdr.nlmsg_len - NLMSG_HDRLEN); 42 msgs.remove_prefix(NLMSG_ALIGN(hdr.nlmsg_len)); 43 44 // Figure out how to handle the individual message 45 bool doCallback = true; 46 if (hdr.nlmsg_flags & NLM_F_MULTI) 47 { 48 done = false; 49 } 50 if (hdr.nlmsg_type == NLMSG_NOOP) 51 { 52 doCallback = false; 53 } 54 else if (hdr.nlmsg_type == NLMSG_DONE) 55 { 56 if (done) 57 { 58 throw std::runtime_error("Got done for non-multi msg"); 59 } 60 done = true; 61 doCallback = false; 62 } 63 else if (hdr.nlmsg_type == NLMSG_ERROR) 64 { 65 const auto& err = stdplus::raw::refFrom<nlmsgerr, Aligned>(msg); 66 // This is just an ACK so don't do the callback 67 if (err.error <= 0) 68 { 69 doCallback = false; 70 } 71 } 72 // All multi-msg headers must have the multi flag 73 if (!done && !(hdr.nlmsg_flags & NLM_F_MULTI)) 74 { 75 throw std::runtime_error("Got non-multi msg before done"); 76 } 77 if (doCallback) 78 { 79 cb(hdr, msg); 80 } 81 } 82 83 static void requestSend(int sock, void* data, size_t size) 84 { 85 sockaddr_nl dst{}; 86 dst.nl_family = AF_NETLINK; 87 88 iovec iov{}; 89 iov.iov_base = data; 90 iov.iov_len = size; 91 92 msghdr hdr{}; 93 hdr.msg_name = reinterpret_cast<sockaddr*>(&dst); 94 hdr.msg_namelen = sizeof(dst); 95 hdr.msg_iov = &iov; 96 hdr.msg_iovlen = 1; 97 98 if (sendmsg(sock, &hdr, 0) < 0) 99 { 100 throw std::system_error(errno, std::generic_category(), 101 "netlink sendmsg"); 102 } 103 } 104 105 static stdplus::ManagedFd makeSocket(int protocol) 106 { 107 using namespace stdplus::fd; 108 109 auto sock = socket(SocketDomain::Netlink, SocketType::Raw, 110 static_cast<stdplus::fd::SocketProto>(protocol)); 111 112 sockaddr_nl local{}; 113 local.nl_family = AF_NETLINK; 114 bind(sock, local); 115 116 return sock; 117 } 118 119 void performRequest(int protocol, void* data, size_t size, ReceiveCallback cb) 120 { 121 auto sock = makeSocket(protocol); 122 requestSend(sock.get(), data, size); 123 receive(sock.get(), cb); 124 } 125 126 } // namespace detail 127 128 size_t receive(int sock, ReceiveCallback cb) 129 { 130 // We need to make sure we have enough room for an entire packet otherwise 131 // it gets truncated. The netlink docs guarantee packets will not exceed 8K 132 std::array<char, 8192> buf; 133 134 iovec iov{}; 135 iov.iov_base = buf.data(); 136 iov.iov_len = buf.size(); 137 138 sockaddr_nl from{}; 139 from.nl_family = AF_NETLINK; 140 141 msghdr hdr{}; 142 hdr.msg_name = &from; 143 hdr.msg_namelen = sizeof(from); 144 hdr.msg_iov = &iov; 145 hdr.msg_iovlen = 1; 146 147 // We only do multiple recvs if we have a MULTI type message 148 bool done = true; 149 size_t num_msgs = 0; 150 do 151 { 152 ssize_t recvd = recvmsg(sock, &hdr, 0); 153 if (recvd < 0 && errno != EAGAIN) 154 { 155 throw std::system_error(errno, std::generic_category(), 156 "netlink recvmsg"); 157 } 158 if (recvd <= 0) 159 { 160 if (!done) 161 { 162 throw std::runtime_error("netlink recvmsg: Got empty payload"); 163 } 164 return num_msgs; 165 } 166 167 std::string_view msgs(buf.data(), recvd); 168 do 169 { 170 detail::processMsg(msgs, done, cb); 171 num_msgs++; 172 } while (!done && !msgs.empty()); 173 174 if (done && !msgs.empty()) 175 { 176 throw std::runtime_error("Extra unprocessed netlink messages"); 177 } 178 } while (!done); 179 return num_msgs; 180 } 181 182 std::tuple<rtattr, std::string_view> extractRtAttr(std::string_view& data) 183 { 184 const auto& hdr = stdplus::raw::refFrom<rtattr, Aligned>(data); 185 if (hdr.rta_len < RTA_LENGTH(0)) 186 { 187 throw std::runtime_error(fmt::format( 188 "rtattr shorter than header: {} < {}", hdr.rta_len, RTA_LENGTH(0))); 189 } 190 if (data.size() < hdr.rta_len) 191 { 192 throw std::runtime_error( 193 fmt::format("not enough message for rtattr: {} < {}", data.size(), 194 hdr.rta_len)); 195 } 196 auto attr = data.substr(RTA_LENGTH(0), hdr.rta_len - RTA_LENGTH(0)); 197 data.remove_prefix(RTA_ALIGN(hdr.rta_len)); 198 return {hdr, attr}; 199 } 200 201 } // namespace netlink 202 } // namespace network 203 } // namespace phosphor 204