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