blob: ea5a421c1acc4f1b864c0cd67cb80f67a40d8930 [file] [log] [blame]
Davide Pesaventoa0b2a2c2018-07-19 01:01:06 -04001/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
2/*
3 * Copyright (c) 2013-2018 Regents of the University of California.
4 *
5 * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
6 *
7 * ndn-cxx library is free software: you can redistribute it and/or modify it under the
8 * terms of the GNU Lesser General Public License as published by the Free Software
9 * Foundation, either version 3 of the License, or (at your option) any later version.
10 *
11 * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
12 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
13 * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
14 *
15 * You should have received copies of the GNU General Public License and GNU Lesser
16 * General Public License along with ndn-cxx, e.g., in COPYING.md file. If not, see
17 * <http://www.gnu.org/licenses/>.
18 *
19 * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
20 *
21 * @author Davide Pesavento <davide.pesavento@lip6.fr>
22 */
23
24#include "netlink-socket.hpp"
25#include "netlink-message.hpp"
26#include "../../util/logger.hpp"
27#include "../../util/time.hpp"
28
29#include <cerrno>
30#include <cstring>
31#include <sys/socket.h>
32
33#include <boost/asio/write.hpp>
34
35#ifndef SOL_NETLINK
36#define SOL_NETLINK 270
37#endif
38#ifndef RTEXT_FILTER_SKIP_STATS
39#define RTEXT_FILTER_SKIP_STATS (1 << 3)
40#endif
41
42NDN_LOG_INIT(ndn.NetworkMonitor);
43
44namespace ndn {
45namespace net {
46
47NetlinkSocket::NetlinkSocket(boost::asio::io_service& io)
48 : m_sock(make_shared<boost::asio::posix::stream_descriptor>(io))
49 , m_pid(0)
50 , m_seqNum(static_cast<uint32_t>(time::system_clock::now().time_since_epoch().count()))
51 , m_buffer(16 * 1024) // 16 KiB
52{
53}
54
55NetlinkSocket::~NetlinkSocket()
56{
57 boost::system::error_code ec;
58 m_sock->close(ec);
59}
60
61void
62NetlinkSocket::open(int protocol)
63{
64 int fd = ::socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, protocol);
65 if (fd < 0) {
66 BOOST_THROW_EXCEPTION(Error("Cannot create netlink socket ("s + std::strerror(errno) + ")"));
67 }
68 m_sock->assign(fd);
69
70 // increase socket receive buffer to 1MB to avoid losing messages
71 const int bufsize = 1 * 1024 * 1024;
72 if (::setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &bufsize, sizeof(bufsize)) < 0) {
73 // not a fatal error
74 NDN_LOG_DEBUG("setting SO_RCVBUF failed: " << std::strerror(errno));
75 }
76
77 // enable control messages for received packets to get the destination group
78 const int one = 1;
79 if (::setsockopt(fd, SOL_NETLINK, NETLINK_PKTINFO, &one, sizeof(one)) < 0) {
80 BOOST_THROW_EXCEPTION(Error("Cannot enable NETLINK_PKTINFO ("s + std::strerror(errno) + ")"));
81 }
82
83 sockaddr_nl addr{};
84 addr.nl_family = AF_NETLINK;
85 if (::bind(fd, reinterpret_cast<sockaddr*>(&addr), sizeof(addr)) < 0) {
86 BOOST_THROW_EXCEPTION(Error("Cannot bind netlink socket ("s + std::strerror(errno) + ")"));
87 }
88
89 // find out what pid has been assigned to us
90 socklen_t len = sizeof(addr);
91 if (::getsockname(fd, reinterpret_cast<sockaddr*>(&addr), &len) < 0) {
92 BOOST_THROW_EXCEPTION(Error("Cannot obtain netlink socket address ("s + std::strerror(errno) + ")"));
93 }
94 if (len != sizeof(addr)) {
95 BOOST_THROW_EXCEPTION(Error("Wrong address length (" + to_string(len) + ")"));
96 }
97 if (addr.nl_family != AF_NETLINK) {
98 BOOST_THROW_EXCEPTION(Error("Wrong address family (" + to_string(addr.nl_family) + ")"));
99 }
100 m_pid = addr.nl_pid;
101 NDN_LOG_TRACE("our pid is " << m_pid);
102
103#ifdef NDN_CXX_HAVE_NETLINK_EXT_ACK
104 // enable extended ACK reporting
105 if (::setsockopt(fd, SOL_NETLINK, NETLINK_EXT_ACK, &one, sizeof(one)) < 0) {
106 // not a fatal error
107 NDN_LOG_DEBUG("setting NETLINK_EXT_ACK failed: " << std::strerror(errno));
108 }
109#endif // NDN_CXX_HAVE_NETLINK_EXT_ACK
110}
111
112void
113NetlinkSocket::joinGroup(int group)
114{
115 if (::setsockopt(m_sock->native_handle(), SOL_NETLINK, NETLINK_ADD_MEMBERSHIP,
116 &group, sizeof(group)) < 0) {
117 BOOST_THROW_EXCEPTION(Error("Cannot join netlink group " + to_string(group) +
118 " (" + std::strerror(errno) + ")"));
119 }
120}
121
122void
Davide Pesaventodc5bb962018-08-12 23:40:38 -0400123NetlinkSocket::registerNotificationCallback(MessageCallback cb)
Davide Pesaventoa0b2a2c2018-07-19 01:01:06 -0400124{
Davide Pesaventodc5bb962018-08-12 23:40:38 -0400125 registerRequestCallback(0, std::move(cb));
126}
Davide Pesaventoa0b2a2c2018-07-19 01:01:06 -0400127
Davide Pesaventodc5bb962018-08-12 23:40:38 -0400128void
129NetlinkSocket::registerRequestCallback(uint32_t seq, MessageCallback cb)
130{
131 if (cb == nullptr) {
132 m_pendingRequests.erase(seq);
133 }
134 else {
135 bool wasEmpty = m_pendingRequests.empty();
136 m_pendingRequests.emplace(seq, std::move(cb));
137 if (wasEmpty)
138 asyncWait();
139 }
Davide Pesaventoa0b2a2c2018-07-19 01:01:06 -0400140}
141
142static const char*
143nlmsgTypeToString(uint16_t type)
144{
145#define NLMSG_STRINGIFY(x) case NLMSG_##x: return "<" #x ">"
146#define RTM_STRINGIFY(x) case RTM_##x: return "<" #x ">"
147 switch (type) {
148 NLMSG_STRINGIFY(NOOP);
149 NLMSG_STRINGIFY(ERROR);
150 NLMSG_STRINGIFY(DONE);
151 NLMSG_STRINGIFY(OVERRUN);
152 RTM_STRINGIFY(NEWLINK);
153 RTM_STRINGIFY(DELLINK);
154 RTM_STRINGIFY(GETLINK);
155 RTM_STRINGIFY(NEWADDR);
156 RTM_STRINGIFY(DELADDR);
157 RTM_STRINGIFY(GETADDR);
158 RTM_STRINGIFY(NEWROUTE);
159 RTM_STRINGIFY(DELROUTE);
160 RTM_STRINGIFY(GETROUTE);
161 default:
162 return "";
163 }
164#undef NLMSG_STRINGIFY
165#undef RTM_STRINGIFY
166}
167
168void
169NetlinkSocket::asyncWait()
170{
171 m_sock->async_read_some(boost::asio::null_buffers(),
172 // capture a copy of 'm_sock' to prevent its deallocation while the handler is still pending
173 [this, sock = m_sock] (const boost::system::error_code& ec, size_t) {
174 if (!sock->is_open() || ec == boost::asio::error::operation_aborted) {
175 // socket was closed, ignore the error
176 NDN_LOG_DEBUG("netlink socket closed or operation aborted");
177 }
178 else if (ec) {
179 NDN_LOG_ERROR("read failed: " << ec.message());
180 BOOST_THROW_EXCEPTION(Error("Netlink socket read error (" + ec.message() + ")"));
181 }
182 else {
183 receiveAndValidate();
Davide Pesaventodc5bb962018-08-12 23:40:38 -0400184 if (!m_pendingRequests.empty())
185 asyncWait();
Davide Pesaventoa0b2a2c2018-07-19 01:01:06 -0400186 }
187 });
188}
189
190void
191NetlinkSocket::receiveAndValidate()
192{
193 sockaddr_nl sender{};
194 iovec iov{};
195 iov.iov_base = m_buffer.data();
196 iov.iov_len = m_buffer.size();
197 uint8_t cmsgBuffer[CMSG_SPACE(sizeof(nl_pktinfo))];
198 msghdr msg{};
199 msg.msg_name = &sender;
200 msg.msg_namelen = sizeof(sender);
201 msg.msg_iov = &iov;
202 msg.msg_iovlen = 1;
203 msg.msg_control = cmsgBuffer;
204 msg.msg_controllen = sizeof(cmsgBuffer);
205
206 ssize_t nBytesRead = ::recvmsg(m_sock->native_handle(), &msg, 0);
207 if (nBytesRead < 0) {
208 std::string errorString = std::strerror(errno);
209 if (errno == EAGAIN || errno == EINTR || errno == EWOULDBLOCK) {
210 // not a fatal error
211 NDN_LOG_DEBUG("recvmsg failed: " << errorString);
212 return;
213 }
214 NDN_LOG_ERROR("recvmsg failed: " << errorString);
215 BOOST_THROW_EXCEPTION(Error("Netlink socket receive error (" + errorString + ")"));
216 }
217
218 NDN_LOG_TRACE("read " << nBytesRead << " bytes from netlink socket");
219
220 if (msg.msg_flags & MSG_TRUNC) {
221 NDN_LOG_ERROR("truncated message");
222 BOOST_THROW_EXCEPTION(Error("Received truncated netlink message"));
223 // TODO: grow the buffer and start over
224 }
225
226 if (msg.msg_namelen >= sizeof(sender) && sender.nl_pid != 0) {
227 NDN_LOG_TRACE("ignoring message from pid=" << sender.nl_pid);
228 return;
229 }
230
231 uint32_t nlGroup = 0;
232 for (cmsghdr* cmsg = CMSG_FIRSTHDR(&msg); cmsg != nullptr; cmsg = CMSG_NXTHDR(&msg, cmsg)) {
233 if (cmsg->cmsg_level == SOL_NETLINK &&
234 cmsg->cmsg_type == NETLINK_PKTINFO &&
235 cmsg->cmsg_len == CMSG_LEN(sizeof(nl_pktinfo))) {
236 const nl_pktinfo* pktinfo = reinterpret_cast<nl_pktinfo*>(CMSG_DATA(cmsg));
237 nlGroup = pktinfo->group;
238 }
239 }
240
241 NetlinkMessage nlmsg(m_buffer.data(), static_cast<size_t>(nBytesRead));
242 for (; nlmsg.isValid(); nlmsg = nlmsg.getNext()) {
243 NDN_LOG_TRACE("parsing " << (nlmsg->nlmsg_flags & NLM_F_MULTI ? "multi-part " : "") <<
244 "message type=" << nlmsg->nlmsg_type << nlmsgTypeToString(nlmsg->nlmsg_type) <<
245 " len=" << nlmsg->nlmsg_len <<
246 " seq=" << nlmsg->nlmsg_seq <<
247 " pid=" << nlmsg->nlmsg_pid <<
248 " group=" << nlGroup);
249
Davide Pesaventodc5bb962018-08-12 23:40:38 -0400250 auto cbIt = m_pendingRequests.end();
251 if (nlGroup != 0) {
252 // it's a multicast notification
253 cbIt = m_pendingRequests.find(0);
254 }
255 else if (nlmsg->nlmsg_pid == m_pid) {
256 // it's for us
257 cbIt = m_pendingRequests.find(nlmsg->nlmsg_seq);
258 }
259 else {
260 NDN_LOG_TRACE("pid mismatch, ignoring");
Davide Pesaventoa0b2a2c2018-07-19 01:01:06 -0400261 continue;
262 }
263
Davide Pesaventodc5bb962018-08-12 23:40:38 -0400264 if (cbIt == m_pendingRequests.end()) {
265 NDN_LOG_TRACE("no handler registered, ignoring");
266 continue;
267 }
268 else if (nlmsg->nlmsg_flags & NLM_F_DUMP_INTR) {
Davide Pesaventoa0b2a2c2018-07-19 01:01:06 -0400269 NDN_LOG_ERROR("dump is inconsistent");
270 BOOST_THROW_EXCEPTION(Error("Inconsistency detected in netlink dump"));
271 // TODO: discard the rest of the message and retry the dump
272 }
Davide Pesaventodc5bb962018-08-12 23:40:38 -0400273 else {
274 // invoke the callback
275 BOOST_ASSERT(cbIt->second);
276 cbIt->second(nlmsg);
277 }
Davide Pesaventoa0b2a2c2018-07-19 01:01:06 -0400278
Davide Pesaventodc5bb962018-08-12 23:40:38 -0400279 // garbage collect the handler if we don't need it anymore:
280 // do it only if this is a reply message (i.e. not a notification) and either
281 // (1) it's not a multi-part message, in which case this is the only fragment, or
282 // (2) it's the last fragment of a multi-part message
283 if (nlGroup == 0 && (!(nlmsg->nlmsg_flags & NLM_F_MULTI) || nlmsg->nlmsg_type == NLMSG_DONE)) {
284 NDN_LOG_TRACE("removing handler for seq=" << nlmsg->nlmsg_seq);
285 BOOST_ASSERT(cbIt != m_pendingRequests.end());
286 m_pendingRequests.erase(cbIt);
Davide Pesaventoa0b2a2c2018-07-19 01:01:06 -0400287 }
288 }
289}
290
291RtnlSocket::RtnlSocket(boost::asio::io_service& io)
292 : NetlinkSocket(io)
293{
294}
295
296void
297RtnlSocket::open()
298{
299 NDN_LOG_TRACE("opening rtnetlink socket");
300 NetlinkSocket::open(NETLINK_ROUTE);
301}
302
303void
Davide Pesaventodc5bb962018-08-12 23:40:38 -0400304RtnlSocket::sendDumpRequest(uint16_t nlmsgType, MessageCallback cb)
Davide Pesaventoa0b2a2c2018-07-19 01:01:06 -0400305{
306 struct RtnlRequest
307 {
308 nlmsghdr nlh;
309 alignas(NLMSG_ALIGNTO) ifinfomsg ifi;
310 alignas(NLMSG_ALIGNTO) rtattr rta;
311 alignas(NLMSG_ALIGNTO) uint32_t rtext; // space for IFLA_EXT_MASK
312 };
313
314 auto request = make_shared<RtnlRequest>();
315 request->nlh.nlmsg_type = nlmsgType;
316 request->nlh.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
317 request->nlh.nlmsg_seq = ++m_seqNum;
318 request->nlh.nlmsg_pid = m_pid;
319 request->ifi.ifi_family = AF_UNSPEC;
320 request->rta.rta_type = IFLA_EXT_MASK;
321 request->rta.rta_len = RTA_LENGTH(sizeof(request->rtext));
322 request->rtext = RTEXT_FILTER_SKIP_STATS;
323 request->nlh.nlmsg_len = NLMSG_SPACE(sizeof(ifinfomsg)) + request->rta.rta_len;
324
Davide Pesaventodc5bb962018-08-12 23:40:38 -0400325 registerRequestCallback(request->nlh.nlmsg_seq, std::move(cb));
326
Davide Pesaventoa0b2a2c2018-07-19 01:01:06 -0400327 boost::asio::async_write(*m_sock, boost::asio::buffer(request.get(), request->nlh.nlmsg_len),
328 // capture 'request' to prevent its premature deallocation
329 [request] (const boost::system::error_code& ec, size_t) {
330 if (!ec) {
331 auto type = request->nlh.nlmsg_type;
332 NDN_LOG_TRACE("sent dump request type=" << type << nlmsgTypeToString(type)
333 << " seq=" << request->nlh.nlmsg_seq);
334 }
335 else if (ec != boost::asio::error::operation_aborted) {
336 NDN_LOG_ERROR("write failed: " << ec.message());
337 BOOST_THROW_EXCEPTION(Error("Failed to send netlink request (" + ec.message() + ")"));
338 }
339 });
340}
341
342} // namespace net
343} // namespace ndn