1 #include "netlink.hpp"
2
3 #include <linux/netlink.h>
4 #include <linux/rtnetlink.h>
5
6 #include <stdplus/fd/create.hpp>
7 #include <stdplus/fd/ops.hpp>
8 #include <stdplus/raw.hpp>
9
10 #include <array>
11 #include <format>
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
processMsg(std::string_view & msgs,bool & done,ReceiveCallback cb)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 std::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 std::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
requestSend(int sock,void * data,size_t size)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
makeSocket(int protocol)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
performRequest(int protocol,void * data,size_t size,ReceiveCallback cb)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
receive(int sock,ReceiveCallback cb)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
extractRtAttr(std::string_view & data)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(std::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 std::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