net: refactor netlink socket handling

In preparation for generic netlink support

Change-Id: I69d93b69c08d4c3f7fd0dabbf3e51dbd49f9ef06
Refs: #4020
diff --git a/src/net/detail/netlink-util.hpp b/src/net/detail/netlink-message.hpp
similarity index 90%
rename from src/net/detail/netlink-util.hpp
rename to src/net/detail/netlink-message.hpp
index 15f54f3..3908d4e 100644
--- a/src/net/detail/netlink-util.hpp
+++ b/src/net/detail/netlink-message.hpp
@@ -21,8 +21,8 @@
  * @author Davide Pesavento <davide.pesavento@lip6.fr>
  */
 
-#ifndef NDN_NET_NETLINK_UTIL_HPP
-#define NDN_NET_NETLINK_UTIL_HPP
+#ifndef NDN_NET_NETLINK_MESSAGE_HPP
+#define NDN_NET_NETLINK_MESSAGE_HPP
 
 #include "../../common.hpp"
 #include "../ethernet.hpp"
@@ -307,33 +307,7 @@
   std::map<uint16_t, const T*> m_attrs;
 };
 
-inline const char*
-nlmsgTypeToString(uint16_t type) noexcept
-{
-#define NLMSG_STRINGIFY(x) case NLMSG_##x: return "<" #x ">"
-#define RTM_STRINGIFY(x) case RTM_##x: return "<" #x ">"
-  switch (type) {
-    NLMSG_STRINGIFY(NOOP);
-    NLMSG_STRINGIFY(ERROR);
-    NLMSG_STRINGIFY(DONE);
-    NLMSG_STRINGIFY(OVERRUN);
-    RTM_STRINGIFY(NEWLINK);
-    RTM_STRINGIFY(DELLINK);
-    RTM_STRINGIFY(GETLINK);
-    RTM_STRINGIFY(NEWADDR);
-    RTM_STRINGIFY(DELADDR);
-    RTM_STRINGIFY(GETADDR);
-    RTM_STRINGIFY(NEWROUTE);
-    RTM_STRINGIFY(DELROUTE);
-    RTM_STRINGIFY(GETROUTE);
-    default:
-      return "";
-  }
-#undef NLMSG_STRINGIFY
-#undef RTM_STRINGIFY
-}
-
 } // namespace net
 } // namespace ndn
 
-#endif // NDN_NET_NETLINK_UTIL_HPP
+#endif // NDN_NET_NETLINK_MESSAGE_HPP
diff --git a/src/net/detail/netlink-socket.cpp b/src/net/detail/netlink-socket.cpp
new file mode 100644
index 0000000..b564afe
--- /dev/null
+++ b/src/net/detail/netlink-socket.cpp
@@ -0,0 +1,309 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ *
+ * @author Davide Pesavento <davide.pesavento@lip6.fr>
+ */
+
+#include "netlink-socket.hpp"
+#include "netlink-message.hpp"
+#include "../../util/logger.hpp"
+#include "../../util/time.hpp"
+
+#include <cerrno>
+#include <cstring>
+#include <sys/socket.h>
+
+#include <boost/asio/write.hpp>
+
+#ifndef SOL_NETLINK
+#define SOL_NETLINK 270
+#endif
+#ifndef RTEXT_FILTER_SKIP_STATS
+#define RTEXT_FILTER_SKIP_STATS (1 << 3)
+#endif
+
+NDN_LOG_INIT(ndn.NetworkMonitor);
+
+namespace ndn {
+namespace net {
+
+NetlinkSocket::NetlinkSocket(boost::asio::io_service& io)
+  : m_sock(make_shared<boost::asio::posix::stream_descriptor>(io))
+  , m_pid(0)
+  , m_seqNum(static_cast<uint32_t>(time::system_clock::now().time_since_epoch().count()))
+  , m_buffer(16 * 1024) // 16 KiB
+{
+}
+
+NetlinkSocket::~NetlinkSocket()
+{
+  boost::system::error_code ec;
+  m_sock->close(ec);
+}
+
+void
+NetlinkSocket::open(int protocol)
+{
+  int fd = ::socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, protocol);
+  if (fd < 0) {
+    BOOST_THROW_EXCEPTION(Error("Cannot create netlink socket ("s + std::strerror(errno) + ")"));
+  }
+  m_sock->assign(fd);
+
+  // increase socket receive buffer to 1MB to avoid losing messages
+  const int bufsize = 1 * 1024 * 1024;
+  if (::setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &bufsize, sizeof(bufsize)) < 0) {
+    // not a fatal error
+    NDN_LOG_DEBUG("setting SO_RCVBUF failed: " << std::strerror(errno));
+  }
+
+  // enable control messages for received packets to get the destination group
+  const int one = 1;
+  if (::setsockopt(fd, SOL_NETLINK, NETLINK_PKTINFO, &one, sizeof(one)) < 0) {
+    BOOST_THROW_EXCEPTION(Error("Cannot enable NETLINK_PKTINFO ("s + std::strerror(errno) + ")"));
+  }
+
+  sockaddr_nl addr{};
+  addr.nl_family = AF_NETLINK;
+  if (::bind(fd, reinterpret_cast<sockaddr*>(&addr), sizeof(addr)) < 0) {
+    BOOST_THROW_EXCEPTION(Error("Cannot bind netlink socket ("s + std::strerror(errno) + ")"));
+  }
+
+  // find out what pid has been assigned to us
+  socklen_t len = sizeof(addr);
+  if (::getsockname(fd, reinterpret_cast<sockaddr*>(&addr), &len) < 0) {
+    BOOST_THROW_EXCEPTION(Error("Cannot obtain netlink socket address ("s + std::strerror(errno) + ")"));
+  }
+  if (len != sizeof(addr)) {
+    BOOST_THROW_EXCEPTION(Error("Wrong address length (" + to_string(len) + ")"));
+  }
+  if (addr.nl_family != AF_NETLINK) {
+    BOOST_THROW_EXCEPTION(Error("Wrong address family (" + to_string(addr.nl_family) + ")"));
+  }
+  m_pid = addr.nl_pid;
+  NDN_LOG_TRACE("our pid is " << m_pid);
+
+#ifdef NDN_CXX_HAVE_NETLINK_EXT_ACK
+  // enable extended ACK reporting
+  if (::setsockopt(fd, SOL_NETLINK, NETLINK_EXT_ACK, &one, sizeof(one)) < 0) {
+    // not a fatal error
+    NDN_LOG_DEBUG("setting NETLINK_EXT_ACK failed: " << std::strerror(errno));
+  }
+#endif // NDN_CXX_HAVE_NETLINK_EXT_ACK
+}
+
+void
+NetlinkSocket::joinGroup(int group)
+{
+  if (::setsockopt(m_sock->native_handle(), SOL_NETLINK, NETLINK_ADD_MEMBERSHIP,
+                   &group, sizeof(group)) < 0) {
+    BOOST_THROW_EXCEPTION(Error("Cannot join netlink group " + to_string(group) +
+                                " (" + std::strerror(errno) + ")"));
+  }
+}
+
+void
+NetlinkSocket::startAsyncReceive(MessageCallback cb)
+{
+  BOOST_ASSERT(cb != nullptr);
+  BOOST_ASSERT(m_onMessage == nullptr);
+
+  m_onMessage = std::move(cb);
+  asyncWait();
+}
+
+static const char*
+nlmsgTypeToString(uint16_t type)
+{
+#define NLMSG_STRINGIFY(x) case NLMSG_##x: return "<" #x ">"
+#define RTM_STRINGIFY(x) case RTM_##x: return "<" #x ">"
+  switch (type) {
+    NLMSG_STRINGIFY(NOOP);
+    NLMSG_STRINGIFY(ERROR);
+    NLMSG_STRINGIFY(DONE);
+    NLMSG_STRINGIFY(OVERRUN);
+    RTM_STRINGIFY(NEWLINK);
+    RTM_STRINGIFY(DELLINK);
+    RTM_STRINGIFY(GETLINK);
+    RTM_STRINGIFY(NEWADDR);
+    RTM_STRINGIFY(DELADDR);
+    RTM_STRINGIFY(GETADDR);
+    RTM_STRINGIFY(NEWROUTE);
+    RTM_STRINGIFY(DELROUTE);
+    RTM_STRINGIFY(GETROUTE);
+    default:
+      return "";
+  }
+#undef NLMSG_STRINGIFY
+#undef RTM_STRINGIFY
+}
+
+void
+NetlinkSocket::asyncWait()
+{
+  m_sock->async_read_some(boost::asio::null_buffers(),
+    // capture a copy of 'm_sock' to prevent its deallocation while the handler is still pending
+    [this, sock = m_sock] (const boost::system::error_code& ec, size_t) {
+      if (!sock->is_open() || ec == boost::asio::error::operation_aborted) {
+        // socket was closed, ignore the error
+        NDN_LOG_DEBUG("netlink socket closed or operation aborted");
+      }
+      else if (ec) {
+        NDN_LOG_ERROR("read failed: " << ec.message());
+        BOOST_THROW_EXCEPTION(Error("Netlink socket read error (" + ec.message() + ")"));
+      }
+      else {
+        receiveAndValidate();
+        asyncWait();
+      }
+  });
+}
+
+void
+NetlinkSocket::receiveAndValidate()
+{
+  sockaddr_nl sender{};
+  iovec iov{};
+  iov.iov_base = m_buffer.data();
+  iov.iov_len = m_buffer.size();
+  uint8_t cmsgBuffer[CMSG_SPACE(sizeof(nl_pktinfo))];
+  msghdr msg{};
+  msg.msg_name = &sender;
+  msg.msg_namelen = sizeof(sender);
+  msg.msg_iov = &iov;
+  msg.msg_iovlen = 1;
+  msg.msg_control = cmsgBuffer;
+  msg.msg_controllen = sizeof(cmsgBuffer);
+
+  ssize_t nBytesRead = ::recvmsg(m_sock->native_handle(), &msg, 0);
+  if (nBytesRead < 0) {
+    std::string errorString = std::strerror(errno);
+    if (errno == EAGAIN || errno == EINTR || errno == EWOULDBLOCK) {
+      // not a fatal error
+      NDN_LOG_DEBUG("recvmsg failed: " << errorString);
+      return;
+    }
+    NDN_LOG_ERROR("recvmsg failed: " << errorString);
+    BOOST_THROW_EXCEPTION(Error("Netlink socket receive error (" + errorString + ")"));
+  }
+
+  NDN_LOG_TRACE("read " << nBytesRead << " bytes from netlink socket");
+
+  if (msg.msg_flags & MSG_TRUNC) {
+    NDN_LOG_ERROR("truncated message");
+    BOOST_THROW_EXCEPTION(Error("Received truncated netlink message"));
+    // TODO: grow the buffer and start over
+  }
+
+  if (msg.msg_namelen >= sizeof(sender) && sender.nl_pid != 0) {
+    NDN_LOG_TRACE("ignoring message from pid=" << sender.nl_pid);
+    return;
+  }
+
+  uint32_t nlGroup = 0;
+  for (cmsghdr* cmsg = CMSG_FIRSTHDR(&msg); cmsg != nullptr; cmsg = CMSG_NXTHDR(&msg, cmsg)) {
+    if (cmsg->cmsg_level == SOL_NETLINK &&
+        cmsg->cmsg_type == NETLINK_PKTINFO &&
+        cmsg->cmsg_len == CMSG_LEN(sizeof(nl_pktinfo))) {
+      const nl_pktinfo* pktinfo = reinterpret_cast<nl_pktinfo*>(CMSG_DATA(cmsg));
+      nlGroup = pktinfo->group;
+    }
+  }
+
+  NetlinkMessage nlmsg(m_buffer.data(), static_cast<size_t>(nBytesRead));
+  for (; nlmsg.isValid(); nlmsg = nlmsg.getNext()) {
+    NDN_LOG_TRACE("parsing " << (nlmsg->nlmsg_flags & NLM_F_MULTI ? "multi-part " : "") <<
+                  "message type=" << nlmsg->nlmsg_type << nlmsgTypeToString(nlmsg->nlmsg_type) <<
+                  " len=" << nlmsg->nlmsg_len <<
+                  " seq=" << nlmsg->nlmsg_seq <<
+                  " pid=" << nlmsg->nlmsg_pid <<
+                  " group=" << nlGroup);
+
+    if (nlGroup == 0 && // not a multicast notification
+        (nlmsg->nlmsg_pid != m_pid || nlmsg->nlmsg_seq != m_seqNum)) { // not for us
+      NDN_LOG_TRACE("seq/pid mismatch, ignoring");
+      continue;
+    }
+
+    if (nlmsg->nlmsg_flags & NLM_F_DUMP_INTR) {
+      NDN_LOG_ERROR("dump is inconsistent");
+      BOOST_THROW_EXCEPTION(Error("Inconsistency detected in netlink dump"));
+      // TODO: discard the rest of the message and retry the dump
+    }
+
+    m_onMessage(nlmsg);
+
+    if (nlmsg->nlmsg_type == NLMSG_DONE) {
+      break;
+    }
+  }
+}
+
+RtnlSocket::RtnlSocket(boost::asio::io_service& io)
+  : NetlinkSocket(io)
+{
+}
+
+void
+RtnlSocket::open()
+{
+  NDN_LOG_TRACE("opening rtnetlink socket");
+  NetlinkSocket::open(NETLINK_ROUTE);
+}
+
+void
+RtnlSocket::sendDumpRequest(uint16_t nlmsgType)
+{
+  struct RtnlRequest
+  {
+    nlmsghdr nlh;
+    alignas(NLMSG_ALIGNTO) ifinfomsg ifi;
+    alignas(NLMSG_ALIGNTO) rtattr rta;
+    alignas(NLMSG_ALIGNTO) uint32_t rtext; // space for IFLA_EXT_MASK
+  };
+
+  auto request = make_shared<RtnlRequest>();
+  request->nlh.nlmsg_type = nlmsgType;
+  request->nlh.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
+  request->nlh.nlmsg_seq = ++m_seqNum;
+  request->nlh.nlmsg_pid = m_pid;
+  request->ifi.ifi_family = AF_UNSPEC;
+  request->rta.rta_type = IFLA_EXT_MASK;
+  request->rta.rta_len = RTA_LENGTH(sizeof(request->rtext));
+  request->rtext = RTEXT_FILTER_SKIP_STATS;
+  request->nlh.nlmsg_len = NLMSG_SPACE(sizeof(ifinfomsg)) + request->rta.rta_len;
+
+  boost::asio::async_write(*m_sock, boost::asio::buffer(request.get(), request->nlh.nlmsg_len),
+    // capture 'request' to prevent its premature deallocation
+    [request] (const boost::system::error_code& ec, size_t) {
+      if (!ec) {
+        auto type = request->nlh.nlmsg_type;
+        NDN_LOG_TRACE("sent dump request type=" << type << nlmsgTypeToString(type)
+                      << " seq=" << request->nlh.nlmsg_seq);
+      }
+      else if (ec != boost::asio::error::operation_aborted) {
+        NDN_LOG_ERROR("write failed: " << ec.message());
+        BOOST_THROW_EXCEPTION(Error("Failed to send netlink request (" + ec.message() + ")"));
+      }
+  });
+}
+
+} // namespace net
+} // namespace ndn
diff --git a/src/net/detail/netlink-socket.hpp b/src/net/detail/netlink-socket.hpp
new file mode 100644
index 0000000..e7043fc
--- /dev/null
+++ b/src/net/detail/netlink-socket.hpp
@@ -0,0 +1,98 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2013-2018 Regents of the University of California.
+ *
+ * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
+ *
+ * ndn-cxx library is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ *
+ * You should have received copies of the GNU General Public License and GNU Lesser
+ * General Public License along with ndn-cxx, e.g., in COPYING.md file.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
+ *
+ * @author Davide Pesavento <davide.pesavento@lip6.fr>
+ */
+
+#ifndef NDN_NET_NETLINK_SOCKET_HPP
+#define NDN_NET_NETLINK_SOCKET_HPP
+
+#include "../../common.hpp"
+#include "../network-monitor.hpp"
+
+#include <boost/asio/posix/stream_descriptor.hpp>
+#include <vector>
+
+#ifndef NDN_CXX_HAVE_RTNETLINK
+#error "This file should not be included ..."
+#endif
+
+namespace ndn {
+namespace net {
+
+class NetlinkMessage;
+
+class NetlinkSocket : noncopyable
+{
+public:
+  using Error = NetworkMonitor::Error;
+  using MessageCallback = std::function<void(const NetlinkMessage&)>;
+
+  void
+  joinGroup(int group);
+
+protected:
+  explicit
+  NetlinkSocket(boost::asio::io_service& io);
+
+  ~NetlinkSocket();
+
+  void
+  open(int protocol);
+
+  void
+  startAsyncReceive(MessageCallback cb);
+
+private:
+  void
+  asyncWait();
+
+  void
+  receiveAndValidate();
+
+protected:
+  shared_ptr<boost::asio::posix::stream_descriptor> m_sock; ///< netlink socket descriptor
+  uint32_t m_pid; ///< port ID of this socket
+  uint32_t m_seqNum; ///< sequence number of the last netlink request sent to the kernel
+
+private:
+  std::vector<uint8_t> m_buffer; ///< buffer for netlink messages from the kernel
+  MessageCallback m_onMessage; ///< callback invoked when a valid netlink message is received
+};
+
+class RtnlSocket : public NetlinkSocket
+{
+public:
+  explicit
+  RtnlSocket(boost::asio::io_service& io);
+
+  void
+  open();
+
+  void
+  sendDumpRequest(uint16_t nlmsgType);
+
+  using NetlinkSocket::startAsyncReceive;
+};
+
+} // namespace net
+} // namespace ndn
+
+#endif // NDN_NET_NETLINK_SOCKET_HPP
diff --git a/src/net/detail/network-monitor-impl-netlink.cpp b/src/net/detail/network-monitor-impl-netlink.cpp
index 3c651d6..71a1ace 100644
--- a/src/net/detail/network-monitor-impl-netlink.cpp
+++ b/src/net/detail/network-monitor-impl-netlink.cpp
@@ -23,71 +23,39 @@
 
 #include "network-monitor-impl-netlink.hpp"
 #include "linux-if-constants.hpp"
-#include "netlink-util.hpp"
+#include "netlink-message.hpp"
 #include "../network-address.hpp"
 #include "../network-interface.hpp"
 #include "../../util/logger.hpp"
-#include "../../util/time.hpp"
-
-#include <cerrno>
-#include <cstdlib>
 
 #include <linux/if_addr.h>
 #include <linux/if_link.h>
 #include <net/if_arp.h>
-#include <sys/socket.h>
-
-#include <boost/asio/write.hpp>
-
-#ifndef SOL_NETLINK
-#define SOL_NETLINK 270
-#endif
-
-#ifndef RTEXT_FILTER_SKIP_STATS
-#define RTEXT_FILTER_SKIP_STATS (1 << 3)
-#endif
 
 NDN_LOG_INIT(ndn.NetworkMonitor);
 
 namespace ndn {
 namespace net {
 
-struct RtnlRequest
-{
-  nlmsghdr nlh;
-  ifinfomsg ifi;
-  rtattr rta alignas(NLMSG_ALIGNTO); // rtattr has to be aligned
-  uint32_t rtext;                    // space for IFLA_EXT_MASK
-};
-
 NetworkMonitorImplNetlink::NetworkMonitorImplNetlink(boost::asio::io_service& io)
-  : m_socket(make_shared<boost::asio::posix::stream_descriptor>(io))
-  , m_pid(0)
-  , m_sequenceNo(static_cast<uint32_t>(time::system_clock::now().time_since_epoch().count()))
+  : m_rtnlSocket(io)
   , m_isEnumeratingLinks(false)
   , m_isEnumeratingAddresses(false)
 {
-  NDN_LOG_TRACE("creating NETLINK_ROUTE socket");
-  initSocket(NETLINK_ROUTE);
+  m_rtnlSocket.open();
   for (auto group : {RTNLGRP_LINK,
                      RTNLGRP_IPV4_IFADDR, RTNLGRP_IPV4_ROUTE,
                      RTNLGRP_IPV6_IFADDR, RTNLGRP_IPV6_ROUTE}) {
-    joinGroup(group);
+    m_rtnlSocket.joinGroup(group);
   }
 
-  asyncRead();
+  m_rtnlSocket.startAsyncReceive([this] (const auto& msg) { this->parseRtnlMessage(msg); });
 
   NDN_LOG_TRACE("enumerating links");
-  sendDumpRequest(RTM_GETLINK);
+  m_rtnlSocket.sendDumpRequest(RTM_GETLINK);
   m_isEnumeratingLinks = true;
 }
 
-NetworkMonitorImplNetlink::~NetworkMonitorImplNetlink()
-{
-  boost::system::error_code error;
-  m_socket->close(error);
-}
-
 shared_ptr<const NetworkInterface>
 NetworkMonitorImplNetlink::getNetworkInterface(const std::string& ifname) const
 {
@@ -117,220 +85,7 @@
 }
 
 void
-NetworkMonitorImplNetlink::initSocket(int family)
-{
-  int fd = ::socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, family);
-  if (fd < 0) {
-    BOOST_THROW_EXCEPTION(Error("Cannot create netlink socket ("s + std::strerror(errno) + ")"));
-  }
-  m_socket->assign(fd);
-
-  // increase socket receive buffer to 1MB to avoid losing messages
-  const int bufsize = 1 * 1024 * 1024;
-  if (::setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &bufsize, sizeof(bufsize)) < 0) {
-    // not a fatal error
-    NDN_LOG_DEBUG("setting SO_RCVBUF failed: " << std::strerror(errno));
-  }
-
-  // enable control messages for received packets to get the destination group
-  const int one = 1;
-  if (::setsockopt(fd, SOL_NETLINK, NETLINK_PKTINFO, &one, sizeof(one)) < 0) {
-    BOOST_THROW_EXCEPTION(Error("Cannot enable NETLINK_PKTINFO ("s + std::strerror(errno) + ")"));
-  }
-
-  sockaddr_nl addr{};
-  addr.nl_family = AF_NETLINK;
-  if (::bind(fd, reinterpret_cast<sockaddr*>(&addr), sizeof(addr)) < 0) {
-    BOOST_THROW_EXCEPTION(Error("Cannot bind netlink socket ("s + std::strerror(errno) + ")"));
-  }
-
-  // find out what pid has been assigned to us
-  socklen_t len = sizeof(addr);
-  if (::getsockname(fd, reinterpret_cast<sockaddr*>(&addr), &len) < 0) {
-    BOOST_THROW_EXCEPTION(Error("Cannot obtain netlink socket address ("s + std::strerror(errno) + ")"));
-  }
-  if (len != sizeof(addr)) {
-    BOOST_THROW_EXCEPTION(Error("Wrong address length (" + to_string(len) + ")"));
-  }
-  if (addr.nl_family != AF_NETLINK) {
-    BOOST_THROW_EXCEPTION(Error("Wrong address family (" + to_string(addr.nl_family) + ")"));
-  }
-  m_pid = addr.nl_pid;
-  NDN_LOG_TRACE("our pid is " << m_pid);
-
-#ifdef NDN_CXX_HAVE_NETLINK_EXT_ACK
-  // enable extended ACK reporting
-  if (::setsockopt(fd, SOL_NETLINK, NETLINK_EXT_ACK, &one, sizeof(one)) < 0) {
-    // not a fatal error
-    NDN_LOG_DEBUG("setting NETLINK_EXT_ACK failed: " << std::strerror(errno));
-  }
-#endif // NDN_CXX_HAVE_NETLINK_EXT_ACK
-}
-
-void
-NetworkMonitorImplNetlink::joinGroup(int group)
-{
-  if (::setsockopt(m_socket->native_handle(), SOL_NETLINK, NETLINK_ADD_MEMBERSHIP,
-                   &group, sizeof(group)) < 0) {
-    BOOST_THROW_EXCEPTION(Error("Cannot join netlink group " + to_string(group) +
-                                " (" + std::strerror(errno) + ")"));
-  }
-}
-
-void
-NetworkMonitorImplNetlink::sendDumpRequest(uint16_t nlmsgType)
-{
-  auto request = make_shared<RtnlRequest>();
-  request->nlh.nlmsg_len = sizeof(RtnlRequest);
-  request->nlh.nlmsg_type = nlmsgType;
-  request->nlh.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
-  request->nlh.nlmsg_seq = ++m_sequenceNo;
-  request->nlh.nlmsg_pid = m_pid;
-  request->ifi.ifi_family = AF_UNSPEC;
-  request->rta.rta_type = IFLA_EXT_MASK;
-  request->rta.rta_len = RTA_LENGTH(sizeof(request->rtext));
-  request->rtext = RTEXT_FILTER_SKIP_STATS;
-
-  boost::asio::async_write(*m_socket, boost::asio::buffer(request.get(), sizeof(RtnlRequest)),
-    // capture 'request' to prevent its premature deallocation
-    [request] (const boost::system::error_code& error, size_t) {
-      if (!error) {
-        auto type = request->nlh.nlmsg_type;
-        NDN_LOG_TRACE("sent dump request type=" << type << nlmsgTypeToString(type)
-                      << " seq=" << request->nlh.nlmsg_seq);
-      }
-      else if (error != boost::asio::error::operation_aborted) {
-        NDN_LOG_ERROR("write failed: " << error.message());
-        BOOST_THROW_EXCEPTION(Error("Failed to send netlink request (" + error.message() + ")"));
-      }
-    });
-}
-
-void
-NetworkMonitorImplNetlink::asyncRead()
-{
-  m_socket->async_read_some(boost::asio::null_buffers(),
-    // capture a copy of 'm_socket' to prevent its deallocation while the handler is still pending
-    [this, socket = m_socket] (const auto& error, auto&&...) {
-      if (!socket->is_open() || error == boost::asio::error::operation_aborted) {
-        // socket was closed, ignore the error
-        NDN_LOG_DEBUG("socket closed or operation aborted");
-      }
-      else if (error) {
-        NDN_LOG_ERROR("read failed: " << error.message());
-        BOOST_THROW_EXCEPTION(Error("Netlink socket read error (" + error.message() + ")"));
-      }
-      else {
-        this->receiveMessage();
-        this->asyncRead();
-      }
-  });
-}
-
-void
-NetworkMonitorImplNetlink::receiveMessage()
-{
-  msghdr msg{};
-  sockaddr_nl sender{};
-  msg.msg_name = &sender;
-  msg.msg_namelen = sizeof(sender);
-  iovec iov{};
-  iov.iov_base = m_buffer.data();
-  iov.iov_len = m_buffer.size();
-  msg.msg_iov = &iov;
-  msg.msg_iovlen = 1;
-  std::array<uint8_t, CMSG_SPACE(sizeof(nl_pktinfo))> cmsgBuffer;
-  msg.msg_control = cmsgBuffer.data();
-  msg.msg_controllen = cmsgBuffer.size();
-
-  ssize_t nBytesRead = ::recvmsg(m_socket->native_handle(), &msg, 0);
-  if (nBytesRead < 0) {
-    std::string errorString = std::strerror(errno);
-    if (errno == EAGAIN || errno == EINTR || errno == EWOULDBLOCK) {
-      NDN_LOG_DEBUG("recvmsg failed: " << errorString);
-      return;
-    }
-    else {
-      NDN_LOG_ERROR("recvmsg failed: " << errorString);
-      BOOST_THROW_EXCEPTION(Error("Netlink socket receive error (" + errorString + ")"));
-    }
-  }
-
-  NDN_LOG_TRACE("read " << nBytesRead << " bytes from netlink socket");
-
-  if (msg.msg_flags & MSG_TRUNC) {
-    NDN_LOG_ERROR("truncated message");
-    BOOST_THROW_EXCEPTION(Error("Received truncated netlink message"));
-    // TODO: grow the buffer and start over
-  }
-
-  if (msg.msg_namelen >= sizeof(sockaddr_nl) && sender.nl_pid != 0) {
-    NDN_LOG_TRACE("ignoring message from pid=" << sender.nl_pid);
-    return;
-  }
-
-  if (nBytesRead == 0) {
-    return;
-  }
-
-  uint32_t nlGroup = 0;
-  for (cmsghdr* cmsg = CMSG_FIRSTHDR(&msg); cmsg != nullptr; cmsg = CMSG_NXTHDR(&msg, cmsg)) {
-    if (cmsg->cmsg_level == SOL_NETLINK &&
-        cmsg->cmsg_type == NETLINK_PKTINFO &&
-        cmsg->cmsg_len == CMSG_LEN(sizeof(nl_pktinfo))) {
-      const nl_pktinfo* pktinfo = reinterpret_cast<nl_pktinfo*>(CMSG_DATA(cmsg));
-      nlGroup = pktinfo->group;
-    }
-  }
-
-  NetlinkMessage nlmsg(m_buffer.data(), static_cast<size_t>(nBytesRead));
-  for (; nlmsg.isValid(); nlmsg = nlmsg.getNext()) {
-    NDN_LOG_TRACE("parsing " << (nlmsg->nlmsg_flags & NLM_F_MULTI ? "multi-part " : "") <<
-                  "message type=" << nlmsg->nlmsg_type << nlmsgTypeToString(nlmsg->nlmsg_type) <<
-                  " len=" << nlmsg->nlmsg_len <<
-                  " seq=" << nlmsg->nlmsg_seq <<
-                  " pid=" << nlmsg->nlmsg_pid <<
-                  " group=" << nlGroup);
-
-    if (nlGroup == 0 && // not a multicast notification
-        (nlmsg->nlmsg_pid != m_pid || nlmsg->nlmsg_seq != m_sequenceNo)) { // not for us
-      NDN_LOG_TRACE("seq/pid mismatch, ignoring");
-      continue;
-    }
-
-    if (nlmsg->nlmsg_flags & NLM_F_DUMP_INTR) {
-      NDN_LOG_ERROR("dump is inconsistent");
-      BOOST_THROW_EXCEPTION(Error("Inconsistency detected in netlink dump"));
-      // TODO: discard the rest of the message and retry the dump
-    }
-
-    if (nlmsg->nlmsg_type == NLMSG_DONE) {
-      break;
-    }
-
-    parseNetlinkMessage(nlmsg);
-  }
-
-  if (nlmsg->nlmsg_type == NLMSG_DONE) {
-    if (m_isEnumeratingLinks) {
-      // links enumeration complete, now request all the addresses
-      m_isEnumeratingLinks = false;
-      NDN_LOG_TRACE("enumerating addresses");
-      sendDumpRequest(RTM_GETADDR);
-      m_isEnumeratingAddresses = true;
-    }
-    else if (m_isEnumeratingAddresses) {
-      // links and addresses enumeration complete
-      m_isEnumeratingAddresses = false;
-      // TODO: enumerate routes
-      NDN_LOG_DEBUG("enumeration complete");
-      this->emitSignal(onEnumerationCompleted);
-    }
-  }
-}
-
-void
-NetworkMonitorImplNetlink::parseNetlinkMessage(const NetlinkMessage& nlmsg)
+NetworkMonitorImplNetlink::parseRtnlMessage(const NetlinkMessage& nlmsg)
 {
   switch (nlmsg->nlmsg_type) {
   case RTM_NEWLINK:
@@ -354,6 +109,23 @@
       this->emitSignal(onNetworkStateChanged); // backward compat
     break;
 
+  case NLMSG_DONE:
+    if (m_isEnumeratingLinks) {
+      // links enumeration complete, now request all the addresses
+      m_isEnumeratingLinks = false;
+      NDN_LOG_TRACE("enumerating addresses");
+      m_rtnlSocket.sendDumpRequest(RTM_GETADDR);
+      m_isEnumeratingAddresses = true;
+    }
+    else if (m_isEnumeratingAddresses) {
+      // links and addresses enumeration complete
+      m_isEnumeratingAddresses = false;
+      // TODO: enumerate routes
+      NDN_LOG_DEBUG("enumeration complete");
+      this->emitSignal(onEnumerationCompleted);
+    }
+    break;
+
   case NLMSG_ERROR:
     parseErrorMessage(nlmsg);
     break;
@@ -401,6 +173,27 @@
   }
 }
 
+static void
+updateInterfaceState(NetworkInterface& interface, uint8_t operState)
+{
+  if (operState == linux_if::OPER_STATE_UP) {
+    interface.setState(InterfaceState::RUNNING);
+  }
+  else if (operState == linux_if::OPER_STATE_DORMANT) {
+    interface.setState(InterfaceState::DORMANT);
+  }
+  else {
+    // fallback to flags
+    auto flags = interface.getFlags();
+    if ((flags & linux_if::FLAG_LOWER_UP) && !(flags & linux_if::FLAG_DORMANT))
+      interface.setState(InterfaceState::RUNNING);
+    else if (flags & IFF_UP)
+      interface.setState(InterfaceState::NO_CARRIER);
+    else
+      interface.setState(InterfaceState::DOWN);
+  }
+}
+
 void
 NetworkMonitorImplNetlink::parseLinkMessage(const NetlinkMessage& nlmsg)
 {
@@ -568,31 +361,10 @@
   auto nla = reinterpret_cast<const nlattr*>(reinterpret_cast<const uint8_t*>(&*nlmsg) + errLen);
   auto attrs = NetlinkMessageAttributes<nlattr>(nla, nlmsg->nlmsg_len - errLen);
   auto msg = attrs.getAttributeByType<std::string>(NLMSGERR_ATTR_MSG);
-  if (msg)
+  if (msg && !msg->empty())
     NDN_LOG_ERROR("kernel message: " << *msg);
 #endif // NDN_CXX_HAVE_NETLINK_EXT_ACK
 }
 
-void
-NetworkMonitorImplNetlink::updateInterfaceState(NetworkInterface& interface, uint8_t operState)
-{
-  if (operState == linux_if::OPER_STATE_UP) {
-    interface.setState(InterfaceState::RUNNING);
-  }
-  else if (operState == linux_if::OPER_STATE_DORMANT) {
-    interface.setState(InterfaceState::DORMANT);
-  }
-  else {
-    // fallback to flags
-    auto flags = interface.getFlags();
-    if ((flags & linux_if::FLAG_LOWER_UP) && !(flags & linux_if::FLAG_DORMANT))
-      interface.setState(InterfaceState::RUNNING);
-    else if (flags & IFF_UP)
-      interface.setState(InterfaceState::NO_CARRIER);
-    else
-      interface.setState(InterfaceState::DOWN);
-  }
-}
-
 } // namespace net
 } // namespace ndn
diff --git a/src/net/detail/network-monitor-impl-netlink.hpp b/src/net/detail/network-monitor-impl-netlink.hpp
index 4075437..0b461fb 100644
--- a/src/net/detail/network-monitor-impl-netlink.hpp
+++ b/src/net/detail/network-monitor-impl-netlink.hpp
@@ -31,28 +31,21 @@
 #error "This file should not be included ..."
 #endif
 
-#include <boost/asio/posix/stream_descriptor.hpp>
+#include "netlink-socket.hpp"
 
-#include <array>
 #include <map>
 
 namespace ndn {
 namespace net {
 
-class NetlinkMessage;
-
 class NetworkMonitorImplNetlink : public NetworkMonitorImpl
 {
 public:
-  using Error = NetworkMonitor::Error;
-
   /** \brief initialize netlink socket and start enumerating interfaces
    */
   explicit
   NetworkMonitorImplNetlink(boost::asio::io_service& io);
 
-  ~NetworkMonitorImplNetlink();
-
   uint32_t
   getCapabilities() const final
   {
@@ -74,22 +67,7 @@
   isEnumerating() const;
 
   void
-  initSocket(int family);
-
-  void
-  joinGroup(int group);
-
-  void
-  sendDumpRequest(uint16_t nlmsgType);
-
-  void
-  asyncRead();
-
-  void
-  receiveMessage();
-
-  void
-  parseNetlinkMessage(const NetlinkMessage& nlmsg);
+  parseRtnlMessage(const NetlinkMessage& nlmsg);
 
   void
   parseLinkMessage(const NetlinkMessage& nlmsg);
@@ -103,15 +81,9 @@
   void
   parseErrorMessage(const NetlinkMessage& nlmsg);
 
-  static void
-  updateInterfaceState(NetworkInterface& interface, uint8_t operState);
-
 private:
   std::map<int, shared_ptr<NetworkInterface>> m_interfaces; ///< ifindex => interface
-  std::array<uint8_t, 16384> m_buffer; ///< netlink messages received from the kernel
-  shared_ptr<boost::asio::posix::stream_descriptor> m_socket; ///< the netlink socket
-  uint32_t m_pid; ///< our port ID (unicast address for netlink sockets)
-  uint32_t m_sequenceNo; ///< sequence number of the last netlink request sent to the kernel
+  RtnlSocket m_rtnlSocket; ///< rtnetlink socket
   bool m_isEnumeratingLinks; ///< true if a dump of all links is in progress
   bool m_isEnumeratingAddresses; ///< true if a dump of all addresses is in progress
 };
diff --git a/src/net/detail/network-monitor-impl-osx.hpp b/src/net/detail/network-monitor-impl-osx.hpp
index f536b5d..874f01a 100644
--- a/src/net/detail/network-monitor-impl-osx.hpp
+++ b/src/net/detail/network-monitor-impl-osx.hpp
@@ -47,8 +47,6 @@
 class NetworkMonitorImplOsx : public NetworkMonitorImpl
 {
 public:
-  using Error = NetworkMonitor::Error;
-
   NetworkMonitorImplOsx(boost::asio::io_service& io);
 
   ~NetworkMonitorImplOsx();
diff --git a/src/net/network-monitor.hpp b/src/net/network-monitor.hpp
index afafd58..9b2d08d 100644
--- a/src/net/network-monitor.hpp
+++ b/src/net/network-monitor.hpp
@@ -133,6 +133,8 @@
 class NetworkMonitorImpl : noncopyable
 {
 public:
+  using Error = NetworkMonitor::Error;
+
   virtual
   ~NetworkMonitorImpl() = default;