NS-3 based Named Data Networking (NDN) simulator
ndnSIM 2.5: NDN, CCN, CCNx, content centric networks
API Documentation
netlink-socket.cpp
Go to the documentation of this file.
1 /* -*- 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 
26 #include "ndn-cxx/util/logger.hpp"
27 #include "ndn-cxx/util/time.hpp"
28 
29 #include <cerrno>
30 #include <linux/genetlink.h>
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 
42 NDN_LOG_INIT(ndn.NetworkMonitor);
43 
44 namespace ndn {
45 namespace net {
46 
47 NetlinkSocket::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 
56 {
57  boost::system::error_code ec;
58  m_sock->close(ec);
59 }
60 
61 void
62 NetlinkSocket::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 
112 void
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 
122 void
124 {
125  registerRequestCallback(0, std::move(cb));
126 }
127 
128 void
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  }
140 }
141 
142 std::string
144 {
145 #define NLMSG_STRINGIFY(x) case NLMSG_##x: return to_string(type) + "<" #x ">"
146  switch (type) {
147  NLMSG_STRINGIFY(NOOP);
148  NLMSG_STRINGIFY(ERROR);
149  NLMSG_STRINGIFY(DONE);
150  NLMSG_STRINGIFY(OVERRUN);
151  default:
152  return to_string(type);
153  }
154 #undef NLMSG_STRINGIFY
155 }
156 
157 void
158 NetlinkSocket::asyncWait()
159 {
160  m_sock->async_read_some(boost::asio::null_buffers(),
161  // capture a copy of 'm_sock' to prevent its deallocation while the handler is still pending
162  [this, sock = m_sock] (const boost::system::error_code& ec, size_t) {
163  if (!sock->is_open() || ec == boost::asio::error::operation_aborted) {
164  // socket was closed, ignore the error
165  NDN_LOG_DEBUG("netlink socket closed or operation aborted");
166  }
167  else if (ec) {
168  NDN_LOG_ERROR("read failed: " << ec.message());
169  BOOST_THROW_EXCEPTION(Error("Netlink socket read error (" + ec.message() + ")"));
170  }
171  else {
172  receiveAndValidate();
173  if (!m_pendingRequests.empty())
174  asyncWait();
175  }
176  });
177 }
178 
179 void
180 NetlinkSocket::receiveAndValidate()
181 {
182  sockaddr_nl sender{};
183  iovec iov{};
184  iov.iov_base = m_buffer.data();
185  iov.iov_len = m_buffer.size();
186  uint8_t cmsgBuffer[CMSG_SPACE(sizeof(nl_pktinfo))];
187  msghdr msg{};
188  msg.msg_name = &sender;
189  msg.msg_namelen = sizeof(sender);
190  msg.msg_iov = &iov;
191  msg.msg_iovlen = 1;
192  msg.msg_control = cmsgBuffer;
193  msg.msg_controllen = sizeof(cmsgBuffer);
194 
195  ssize_t nBytesRead = ::recvmsg(m_sock->native_handle(), &msg, 0);
196  if (nBytesRead < 0) {
197  std::string errorString = std::strerror(errno);
198  if (errno == EAGAIN || errno == EINTR || errno == EWOULDBLOCK) {
199  // not a fatal error
200  NDN_LOG_DEBUG("recvmsg failed: " << errorString);
201  return;
202  }
203  NDN_LOG_ERROR("recvmsg failed: " << errorString);
204  BOOST_THROW_EXCEPTION(Error("Netlink socket receive error (" + errorString + ")"));
205  }
206 
207  NDN_LOG_TRACE("read " << nBytesRead << " bytes from netlink socket");
208 
209  if (msg.msg_flags & MSG_TRUNC) {
210  NDN_LOG_ERROR("truncated message");
211  BOOST_THROW_EXCEPTION(Error("Received truncated netlink message"));
212  // TODO: grow the buffer and start over
213  }
214 
215  if (msg.msg_namelen >= sizeof(sender) && sender.nl_pid != 0) {
216  NDN_LOG_TRACE("ignoring message from pid=" << sender.nl_pid);
217  return;
218  }
219 
220  uint32_t nlGroup = 0;
221  for (cmsghdr* cmsg = CMSG_FIRSTHDR(&msg); cmsg != nullptr; cmsg = CMSG_NXTHDR(&msg, cmsg)) {
222  if (cmsg->cmsg_level == SOL_NETLINK &&
223  cmsg->cmsg_type == NETLINK_PKTINFO &&
224  cmsg->cmsg_len == CMSG_LEN(sizeof(nl_pktinfo))) {
225  const nl_pktinfo* pktinfo = reinterpret_cast<nl_pktinfo*>(CMSG_DATA(cmsg));
226  nlGroup = pktinfo->group;
227  }
228  }
229 
230  NetlinkMessage nlmsg(m_buffer.data(), static_cast<size_t>(nBytesRead));
231  for (; nlmsg.isValid(); nlmsg = nlmsg.getNext()) {
232  NDN_LOG_TRACE("parsing " << (nlmsg->nlmsg_flags & NLM_F_MULTI ? "multi-part " : "") <<
233  "message type=" << nlmsgTypeToString(nlmsg->nlmsg_type) <<
234  " len=" << nlmsg->nlmsg_len <<
235  " seq=" << nlmsg->nlmsg_seq <<
236  " pid=" << nlmsg->nlmsg_pid <<
237  " group=" << nlGroup);
238 
239  auto cbIt = m_pendingRequests.end();
240  if (nlGroup != 0) {
241  // it's a multicast notification
242  cbIt = m_pendingRequests.find(0);
243  }
244  else if (nlmsg->nlmsg_pid == m_pid) {
245  // it's for us
246  cbIt = m_pendingRequests.find(nlmsg->nlmsg_seq);
247  }
248  else {
249  NDN_LOG_TRACE("pid mismatch, ignoring");
250  continue;
251  }
252 
253  if (cbIt == m_pendingRequests.end()) {
254  NDN_LOG_TRACE("no handler registered, ignoring");
255  continue;
256  }
257  else if (nlmsg->nlmsg_flags & NLM_F_DUMP_INTR) {
258  NDN_LOG_ERROR("dump is inconsistent");
259  BOOST_THROW_EXCEPTION(Error("Inconsistency detected in netlink dump"));
260  // TODO: discard the rest of the message and retry the dump
261  }
262  else {
263  // invoke the callback
264  BOOST_ASSERT(cbIt->second);
265  cbIt->second(nlmsg);
266  }
267 
268  // garbage collect the handler if we don't need it anymore:
269  // do it only if this is a reply message (i.e. not a notification) and either
270  // (1) it's not a multi-part message, in which case this is the only fragment, or
271  // (2) it's the last fragment of a multi-part message
272  if (nlGroup == 0 && (!(nlmsg->nlmsg_flags & NLM_F_MULTI) || nlmsg->nlmsg_type == NLMSG_DONE)) {
273  NDN_LOG_TRACE("removing handler for seq=" << nlmsg->nlmsg_seq);
274  BOOST_ASSERT(cbIt != m_pendingRequests.end());
275  m_pendingRequests.erase(cbIt);
276  }
277  }
278 }
279 
280 RtnlSocket::RtnlSocket(boost::asio::io_service& io)
281  : NetlinkSocket(io)
282 {
283 }
284 
285 void
287 {
288  NDN_LOG_TRACE("opening rtnetlink socket");
289  NetlinkSocket::open(NETLINK_ROUTE);
290 }
291 
292 void
294 {
295  struct RtnlRequest
296  {
297  nlmsghdr nlh;
298  alignas(NLMSG_ALIGNTO) ifinfomsg ifi;
299  alignas(NLMSG_ALIGNTO) rtattr rta;
300  alignas(NLMSG_ALIGNTO) uint32_t rtext; // space for IFLA_EXT_MASK
301  };
302 
303  auto request = make_shared<RtnlRequest>();
304  request->nlh.nlmsg_type = nlmsgType;
305  request->nlh.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
306  request->nlh.nlmsg_seq = ++m_seqNum;
307  request->nlh.nlmsg_pid = m_pid;
308  request->ifi.ifi_family = AF_UNSPEC;
309  request->rta.rta_type = IFLA_EXT_MASK;
310  request->rta.rta_len = RTA_LENGTH(sizeof(request->rtext));
311  request->rtext = RTEXT_FILTER_SKIP_STATS;
312  request->nlh.nlmsg_len = NLMSG_SPACE(sizeof(ifinfomsg)) + request->rta.rta_len;
313 
314  registerRequestCallback(request->nlh.nlmsg_seq, std::move(cb));
315 
316  boost::asio::async_write(*m_sock, boost::asio::buffer(request.get(), request->nlh.nlmsg_len),
317  // capture 'request' to prevent its premature deallocation
318  [this, request] (const boost::system::error_code& ec, size_t) {
319  if (!ec) {
320  NDN_LOG_TRACE("sent dump request type=" << nlmsgTypeToString(request->nlh.nlmsg_type)
321  << " seq=" << request->nlh.nlmsg_seq);
322  }
323  else if (ec != boost::asio::error::operation_aborted) {
324  NDN_LOG_ERROR("write failed: " << ec.message());
325  BOOST_THROW_EXCEPTION(Error("Failed to send netlink request (" + ec.message() + ")"));
326  }
327  });
328 }
329 
330 std::string
331 RtnlSocket::nlmsgTypeToString(uint16_t type) const
332 {
333 #define RTM_STRINGIFY(x) case RTM_##x: return to_string(type) + "<" #x ">"
334  switch (type) {
335  RTM_STRINGIFY(NEWLINK);
336  RTM_STRINGIFY(DELLINK);
337  RTM_STRINGIFY(GETLINK);
338  RTM_STRINGIFY(NEWADDR);
339  RTM_STRINGIFY(DELADDR);
340  RTM_STRINGIFY(GETADDR);
341  RTM_STRINGIFY(NEWROUTE);
342  RTM_STRINGIFY(DELROUTE);
343  RTM_STRINGIFY(GETROUTE);
344  default:
346  }
347 #undef RTM_STRINGIFY
348 }
349 
350 GenlSocket::GenlSocket(boost::asio::io_service& io)
351  : NetlinkSocket(io)
352 {
353  m_cachedFamilyIds["nlctrl"] = GENL_ID_CTRL;
354 }
355 
356 void
358 {
359  NDN_LOG_TRACE("opening genetlink socket");
360  NetlinkSocket::open(NETLINK_GENERIC);
361 }
362 
363 void
364 GenlSocket::sendRequest(const std::string& familyName, uint8_t command,
365  const void* payload, size_t payloadLen,
366  MessageCallback messageCb, std::function<void()> errorCb)
367 {
368  auto it = m_cachedFamilyIds.find(familyName);
369  if (it != m_cachedFamilyIds.end()) {
370  if (it->second >= GENL_MIN_ID) {
371  sendRequest(it->second, command, payload, payloadLen, std::move(messageCb));
372  }
373  else if (errorCb) {
374  errorCb();
375  }
376  return;
377  }
378 
379  auto ret = m_familyResolvers.emplace(std::piecewise_construct,
380  std::forward_as_tuple(familyName),
381  std::forward_as_tuple(familyName, *this));
382  auto& resolver = ret.first->second;
383  if (ret.second) {
384  // cache the result
385  resolver.onResolved.connectSingleShot([=] (uint16_t familyId) {
386  m_cachedFamilyIds[familyName] = familyId;
387  });
388  resolver.onError.connectSingleShot([=] {
389  m_cachedFamilyIds[familyName] = 0;
390  });
391  }
392  resolver.onResolved.connectSingleShot([=, cb = std::move(messageCb)] (uint16_t familyId) {
393  sendRequest(familyId, command, payload, payloadLen, std::move(cb));
394  });
395  if (errorCb) {
396  resolver.onError.connectSingleShot(std::move(errorCb));
397  }
398 }
399 
400 void
401 GenlSocket::sendRequest(uint16_t familyId, uint8_t command,
402  const void* payload, size_t payloadLen, MessageCallback cb)
403 {
404  struct GenlRequestHeader
405  {
406  alignas(NLMSG_ALIGNTO) nlmsghdr nlh;
407  alignas(NLMSG_ALIGNTO) genlmsghdr genlh;
408  };
409  static_assert(sizeof(GenlRequestHeader) == NLMSG_SPACE(GENL_HDRLEN), "");
410 
411  auto hdr = make_shared<GenlRequestHeader>();
412  hdr->nlh.nlmsg_len = sizeof(GenlRequestHeader) + payloadLen;
413  hdr->nlh.nlmsg_type = familyId;
414  hdr->nlh.nlmsg_flags = NLM_F_REQUEST;
415  hdr->nlh.nlmsg_seq = ++m_seqNum;
416  hdr->nlh.nlmsg_pid = m_pid;
417  hdr->genlh.cmd = command;
418  hdr->genlh.version = 1;
419 
420  registerRequestCallback(hdr->nlh.nlmsg_seq, std::move(cb));
421 
422  std::array<boost::asio::const_buffer, 2> bufs = {
423  boost::asio::buffer(hdr.get(), sizeof(GenlRequestHeader)),
424  boost::asio::buffer(payload, payloadLen)
425  };
426  boost::asio::async_write(*m_sock, bufs,
427  // capture 'hdr' to prevent its premature deallocation
428  [this, hdr] (const boost::system::error_code& ec, size_t) {
429  if (!ec) {
430  NDN_LOG_TRACE("sent genl request type=" << nlmsgTypeToString(hdr->nlh.nlmsg_type) <<
431  " cmd=" << static_cast<unsigned>(hdr->genlh.cmd) <<
432  " seq=" << hdr->nlh.nlmsg_seq);
433  }
434  else if (ec != boost::asio::error::operation_aborted) {
435  NDN_LOG_ERROR("write failed: " << ec.message());
436  BOOST_THROW_EXCEPTION(Error("Failed to send netlink request (" + ec.message() + ")"));
437  }
438  });
439 }
440 
441 GenlFamilyResolver::GenlFamilyResolver(std::string familyName, GenlSocket& socket)
442  : m_sock(socket)
443  , m_family(std::move(familyName))
444 {
445  if (m_family.size() >= GENL_NAMSIZ) {
446  BOOST_THROW_EXCEPTION(std::invalid_argument("netlink family name '" + m_family + "' too long"));
447  }
448 
449  NDN_LOG_TRACE("resolving netlink family " << m_family);
450  asyncResolve();
451 }
452 
453 void
454 GenlFamilyResolver::asyncResolve()
455 {
456  struct FamilyNameAttribute
457  {
458  alignas(NLMSG_ALIGNTO) nlattr nla;
459  alignas(NLMSG_ALIGNTO) char name[GENL_NAMSIZ];
460  };
461 
462  auto attr = make_shared<FamilyNameAttribute>();
463  attr->nla.nla_type = CTRL_ATTR_FAMILY_NAME;
464  attr->nla.nla_len = NLA_HDRLEN + m_family.size() + 1;
465  ::strncpy(attr->name, m_family.data(), GENL_NAMSIZ);
466 
467  m_sock.sendRequest(GENL_ID_CTRL, CTRL_CMD_GETFAMILY, attr.get(), attr->nla.nla_len,
468  // capture 'attr' to prevent its premature deallocation
469  [this, attr] (const auto& msg) { this->handleResolve(msg); });
470 }
471 
472 void
473 GenlFamilyResolver::handleResolve(const NetlinkMessage& nlmsg)
474 {
475  switch (nlmsg->nlmsg_type) {
476  case NLMSG_ERROR: {
477  const nlmsgerr* err = nlmsg.getPayload<nlmsgerr>();
478  if (err == nullptr) {
479  NDN_LOG_WARN("malformed nlmsgerr");
480  }
481  else if (err->error != 0) {
482  NDN_LOG_DEBUG("failed to resolve netlink family " << m_family << ": "
483  << std::strerror(std::abs(err->error)));
484  }
485  onError();
486  break;
487  }
488 
489  case GENL_ID_CTRL: {
490  const genlmsghdr* genlh = nlmsg.getPayload<genlmsghdr>();
491  if (genlh == nullptr) {
492  NDN_LOG_WARN("malformed genlmsghdr");
493  return onError();
494  }
495  if (genlh->cmd != CTRL_CMD_NEWFAMILY) {
496  NDN_LOG_WARN("unexpected genl cmd=" << static_cast<unsigned>(genlh->cmd));
497  return onError();
498  }
499 
500  auto attrs = nlmsg.getAttributes<nlattr>(genlh);
501  auto familyName = attrs.getAttributeByType<std::string>(CTRL_ATTR_FAMILY_NAME);
502  if (familyName && *familyName != m_family) {
503  NDN_LOG_WARN("CTRL_ATTR_FAMILY_NAME mismatch: " << *familyName << " != " << m_family);
504  return onError();
505  }
506  auto familyId = attrs.getAttributeByType<uint16_t>(CTRL_ATTR_FAMILY_ID);
507  if (!familyId) {
508  NDN_LOG_WARN("missing CTRL_ATTR_FAMILY_ID");
509  return onError();
510  }
511  if (*familyId < GENL_MIN_ID) {
512  NDN_LOG_WARN("invalid CTRL_ATTR_FAMILY_ID=" << *familyId);
513  return onError();
514  }
515 
516  NDN_LOG_TRACE("resolved netlink family name=" << m_family << " id=" << *familyId);
517  onResolved(*familyId);
518  break;
519  }
520 
521  default: {
522  NDN_LOG_WARN("unexpected message type");
523  onError();
524  break;
525  }
526  }
527 }
528 
529 std::string
530 GenlSocket::nlmsgTypeToString(uint16_t type) const
531 {
532  if (type >= GENL_MIN_ID) {
533  for (const auto& p : m_cachedFamilyIds) {
534  if (p.second == type) {
535  return to_string(type) + "<" + p.first + ">";
536  }
537  }
538  }
539 
541 }
542 
543 } // namespace net
544 } // namespace ndn
Copyright (c) 2011-2015 Regents of the University of California.
util::Signal< GenlFamilyResolver > onError
#define NDN_LOG_WARN(expression)
Definition: logger.hpp:110
#define NDN_LOG_DEBUG(expression)
Definition: logger.hpp:108
RtnlSocket(boost::asio::io_service &io)
constexpr duration< Rep, Period > abs(duration< Rep, Period > d)
Definition: time.hpp:50
#define NDN_LOG_ERROR(expression)
Definition: logger.hpp:111
std::string nlmsgTypeToString(uint16_t type) const final
void sendRequest(const std::string &familyName, uint8_t command, const void *payload, size_t payloadLen, MessageCallback messageCb, std::function< void()> errorCb)
uint32_t m_seqNum
sequence number of the last netlink request sent to the kernel
void sendDumpRequest(uint16_t nlmsgType, MessageCallback cb)
void open(int protocol)
#define NDN_LOG_TRACE(expression)
Definition: logger.hpp:107
std::string nlmsgTypeToString(uint16_t type) const final
void registerRequestCallback(uint32_t seq, MessageCallback cb)
NetworkMonitor::Error Error
GenlSocket(boost::asio::io_service &io)
shared_ptr< boost::asio::posix::stream_descriptor > m_sock
netlink socket descriptor
void registerNotificationCallback(MessageCallback cb)
#define NDN_LOG_INIT(name)
declare a log module
Definition: logger.hpp:90
NetlinkSocket(boost::asio::io_service &io)
std::string to_string(const V &v)
Definition: backports.hpp:67
std::function< void(const NetlinkMessage &)> MessageCallback
virtual std::string nlmsgTypeToString(uint16_t type) const
uint32_t m_pid
port ID of this socket
util::Signal< GenlFamilyResolver, uint16_t > onResolved
GenlFamilyResolver(std::string familyName, GenlSocket &socket)