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