xref: /openbmc/phosphor-networkd/src/netlink.cpp (revision 3bf1c74e)
1 #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>
12 #include <stdplus/raw.hpp>
13 #include <system_error>
14 
15 namespace phosphor
16 {
17 namespace network
18 {
19 namespace netlink
20 {
21 namespace detail
22 {
23 
24 void processMsg(std::string_view& msgs, bool& done, const ReceiveCallback& cb)
25 {
26     // Parse and update the message buffer
27     auto hdr = stdplus::raw::copyFrom<nlmsghdr>(msgs);
28     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     {
60         auto err = stdplus::raw::copyFrom<nlmsgerr>(msg);
61         // 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 
78 static 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 
125 static 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 
147 static 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 
167 void 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 
177 std::tuple<rtattr, std::string_view> extractRtAttr(std::string_view& data)
178 {
179     auto hdr = stdplus::raw::copyFrom<rtattr>(data);
180     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
196