xref: /openbmc/phosphor-networkd/src/netlink.cpp (revision ad205028)
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 
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 
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(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