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