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