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