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