util: NetworkMonitor: fine-grained signals on interface/address changes

Change-Id: I60d4cc6d673b920ba81d57502977f0340be0da48
Refs: #3353
diff --git a/src/util/detail/linux-if-constants.cpp b/src/util/detail/linux-if-constants.cpp
new file mode 100644
index 0000000..c50fa50
--- /dev/null
+++ b/src/util/detail/linux-if-constants.cpp
@@ -0,0 +1,51 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2013-2017 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>
+ */
+
+#ifdef __linux__
+
+#include "linux-if-constants.hpp"
+
+#include <sys/socket.h>
+#include <linux/if.h>
+
+namespace ndn {
+namespace util {
+namespace linux_if {
+
+const uint32_t FLAG_LOWER_UP = IFF_LOWER_UP;
+const uint32_t FLAG_DORMANT  = IFF_DORMANT;
+const uint32_t FLAG_ECHO     = IFF_ECHO;
+
+const uint8_t OPER_STATE_UNKNOWN        = IF_OPER_UNKNOWN;
+const uint8_t OPER_STATE_NOTPRESENT     = IF_OPER_NOTPRESENT;
+const uint8_t OPER_STATE_DOWN           = IF_OPER_DOWN;
+const uint8_t OPER_STATE_LOWERLAYERDOWN = IF_OPER_LOWERLAYERDOWN;
+const uint8_t OPER_STATE_TESTING        = IF_OPER_TESTING;
+const uint8_t OPER_STATE_DORMANT        = IF_OPER_DORMANT;
+const uint8_t OPER_STATE_UP             = IF_OPER_UP;
+
+} // namespace linux_if
+} // namespace util
+} // namespace ndn
+
+#endif // __linux__
diff --git a/src/util/detail/linux-if-constants.hpp b/src/util/detail/linux-if-constants.hpp
new file mode 100644
index 0000000..29e7115
--- /dev/null
+++ b/src/util/detail/linux-if-constants.hpp
@@ -0,0 +1,58 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2013-2017 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_UTIL_LINUX_IF_CONSTANTS_HPP
+#define NDN_UTIL_LINUX_IF_CONSTANTS_HPP
+#ifdef __linux__
+
+#include <cstdint>
+
+namespace ndn {
+namespace util {
+namespace linux_if {
+
+// linux/if.h and net/if.h cannot be (directly or indirectly) included in the
+// same translation unit because they contain duplicate declarations, therefore
+// we have to resort to this workaround when we need to include both linux/if.h
+// and any other headers that pull in net/if.h (e.g. boost/asio.hpp)
+
+// net_device_flags missing from <net/if.h>
+extern const uint32_t FLAG_LOWER_UP;
+extern const uint32_t FLAG_DORMANT;
+extern const uint32_t FLAG_ECHO;
+
+// RFC 2863 operational status
+extern const uint8_t OPER_STATE_UNKNOWN;
+extern const uint8_t OPER_STATE_NOTPRESENT;
+extern const uint8_t OPER_STATE_DOWN;
+extern const uint8_t OPER_STATE_LOWERLAYERDOWN;
+extern const uint8_t OPER_STATE_TESTING;
+extern const uint8_t OPER_STATE_DORMANT;
+extern const uint8_t OPER_STATE_UP;
+
+} // namespace linux_if
+} // namespace util
+} // namespace ndn
+
+#endif // __linux__
+#endif // NDN_UTIL_LINUX_IF_CONSTANTS_HPP
diff --git a/src/util/detail/network-monitor-impl-osx.cpp b/src/util/detail/network-monitor-impl-osx.cpp
index bfa3877..9f28f27 100644
--- a/src/util/detail/network-monitor-impl-osx.cpp
+++ b/src/util/detail/network-monitor-impl-osx.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /**
- * Copyright (c) 2013-2016 Regents of the University of California.
+ * Copyright (c) 2013-2017 Regents of the University of California.
  *
  * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
  *
@@ -55,6 +55,7 @@
 #ifdef NDN_CXX_HAVE_COREFOUNDATION_COREFOUNDATION_H
 
 #include "network-monitor-impl-osx.hpp"
+#include "../network-interface.hpp"
 
 namespace ndn {
 namespace util {
@@ -93,11 +94,23 @@
                                           static_cast<void*>(this));
 }
 
+shared_ptr<NetworkInterface>
+NetworkMonitor::Impl::getNetworkInterface(const std::string& ifname) const
+{
+  return nullptr;
+}
+
+std::vector<shared_ptr<NetworkInterface>>
+NetworkMonitor::Impl::listNetworkInterfaces() const
+{
+  return {};
+}
+
 void
 NetworkMonitor::Impl::afterNotificationCenterEvent(CFNotificationCenterRef center,
-                                                   void *observer,
+                                                   void* observer,
                                                    CFStringRef name,
-                                                   const void *object,
+                                                   const void* object,
                                                    CFDictionaryRef userInfo)
 {
   static_cast<Impl*>(observer)->m_nm.onNetworkStateChanged();
diff --git a/src/util/detail/network-monitor-impl-osx.hpp b/src/util/detail/network-monitor-impl-osx.hpp
index 4015a78..c36866a 100644
--- a/src/util/detail/network-monitor-impl-osx.hpp
+++ b/src/util/detail/network-monitor-impl-osx.hpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /**
- * Copyright (c) 2013-2016 Regents of the University of California.
+ * Copyright (c) 2013-2017 Regents of the University of California.
  *
  * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
  *
@@ -22,8 +22,13 @@
 #ifndef NDN_UTIL_NETWORK_MONITOR_IMPL_OSX_HPP
 #define NDN_UTIL_NETWORK_MONITOR_IMPL_OSX_HPP
 
+#include "ndn-cxx-config.hpp"
 #include "../network-monitor.hpp"
 
+#ifndef NDN_CXX_HAVE_COREFOUNDATION_COREFOUNDATION_H
+#error "This file should not be compiled ..."
+#endif
+
 #include "../scheduler.hpp"
 #include "../scheduler-scoped-event-id.hpp"
 
@@ -40,11 +45,17 @@
 
   ~Impl();
 
+  shared_ptr<NetworkInterface>
+  getNetworkInterface(const std::string& ifname) const;
+
+  std::vector<shared_ptr<NetworkInterface>>
+  listNetworkInterfaces() const;
+
   static void
   afterNotificationCenterEvent(CFNotificationCenterRef center,
-                               void *observer,
+                               void* observer,
                                CFStringRef name,
-                               const void *object,
+                               const void* object,
                                CFDictionaryRef userInfo);
 
 private:
diff --git a/src/util/detail/network-monitor-impl-rtnl.cpp b/src/util/detail/network-monitor-impl-rtnl.cpp
index 36c9190..587bc90 100644
--- a/src/util/detail/network-monitor-impl-rtnl.cpp
+++ b/src/util/detail/network-monitor-impl-rtnl.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /**
- * Copyright (c) 2013-2016 Regents of the University of California.
+ * Copyright (c) 2013-2017 Regents of the University of California.
  *
  * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
  *
@@ -17,74 +17,494 @@
  * <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 "ndn-cxx-config.hpp"
-
-#ifdef NDN_CXX_HAVE_RTNETLINK
-
 #include "network-monitor-impl-rtnl.hpp"
+#include "linux-if-constants.hpp"
+#include "../logger.hpp"
+#include "../network-address.hpp"
+#include "../network-interface.hpp"
+#include "../time.hpp"
 
-#include <netinet/in.h>
-#include <linux/netlink.h>
-#include <linux/rtnetlink.h>
-#include <net/if.h>
+#include <boost/asio/write.hpp>
 
 #include <cerrno>
-#include <cstring>
+#include <cstdlib>
+#include <net/if_arp.h>
+#include <sys/socket.h>
+
+NDN_LOG_INIT(ndn.NetworkMonitor);
 
 namespace ndn {
 namespace util {
 
 NetworkMonitor::Impl::Impl(NetworkMonitor& nm, boost::asio::io_service& io)
   : m_nm(nm)
-  , m_socket(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_isEnumeratingLinks(false)
+  , m_isEnumeratingAddresses(false)
 {
-  int fd = ::socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
-  if (fd < 0)
-    BOOST_THROW_EXCEPTION(Error(std::string("Cannot create netlink socket (") +
-                                std::strerror(errno) + ")"));
+  initSocket();
+  asyncRead();
 
-  sockaddr_nl addr{};
-  addr.nl_family = AF_NETLINK;
-  addr.nl_groups = RTMGRP_LINK |
-                   RTMGRP_IPV4_IFADDR | RTMGRP_IPV4_ROUTE |
-                   RTMGRP_IPV6_IFADDR | RTMGRP_IPV6_ROUTE;
+  NDN_LOG_TRACE("enumerating links");
+  sendDumpRequest(RTM_GETLINK);
+  m_isEnumeratingLinks = true;
+}
 
-  if (::bind(fd, reinterpret_cast<sockaddr*>(&addr), sizeof(addr)) == -1) {
-    BOOST_THROW_EXCEPTION(Error(std::string("Cannot bind on netlink socket (") +
-                                std::strerror(errno) + ")"));
+NetworkMonitor::Impl::~Impl()
+{
+  boost::system::error_code error;
+  m_socket->close(error);
+}
+
+shared_ptr<NetworkInterface>
+NetworkMonitor::Impl::getNetworkInterface(const std::string& ifname) const
+{
+  for (const auto& e : m_interfaces) {
+    if (e.second->getName() == ifname)
+      return e.second;
   }
+  return nullptr;
+}
 
-  m_socket.assign(fd);
+std::vector<shared_ptr<NetworkInterface>>
+NetworkMonitor::Impl::listNetworkInterfaces() const
+{
+  std::vector<shared_ptr<NetworkInterface>> v;
+  v.reserve(m_interfaces.size());
 
-  m_socket.async_read_some(boost::asio::buffer(m_buffer, NETLINK_BUFFER_SIZE),
-                           bind(&Impl::onReceiveRtNetlink, this, _1, _2));
+  for (const auto& e : m_interfaces) {
+    v.push_back(e.second);
+  }
+  return v;
+}
+
+bool
+NetworkMonitor::Impl::isEnumerating() const
+{
+  return m_isEnumeratingLinks || m_isEnumeratingAddresses;
 }
 
 void
-NetworkMonitor::Impl::onReceiveRtNetlink(const boost::system::error_code& error, size_t nBytesReceived)
+NetworkMonitor::Impl::initSocket()
 {
+  NDN_LOG_TRACE("creating netlink socket");
+
+  int fd = ::socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_ROUTE);
+  if (fd < 0) {
+    BOOST_THROW_EXCEPTION(Error(std::string("Cannot create netlink socket (") +
+                                std::strerror(errno) + ")"));
+  }
+  m_socket->assign(fd);
+
+  sockaddr_nl addr{};
+  addr.nl_family = AF_NETLINK;
+  addr.nl_groups = RTMGRP_LINK | RTMGRP_NOTIFY |
+                   RTMGRP_IPV4_IFADDR | RTMGRP_IPV4_ROUTE |
+                   RTMGRP_IPV6_IFADDR | RTMGRP_IPV6_ROUTE;
+  if (::bind(fd, reinterpret_cast<sockaddr*>(&addr), sizeof(addr)) < 0) {
+    BOOST_THROW_EXCEPTION(Error(std::string("Cannot bind netlink socket (") +
+                                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(std::string("Cannot obtain netlink socket address (") +
+                                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);
+}
+
+void
+NetworkMonitor::Impl::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 = 1 << 3; // 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 && error != boost::asio::error::operation_aborted) {
+        NDN_LOG_ERROR("write failed: " << error.message());
+        BOOST_THROW_EXCEPTION(Error("Failed to send netlink request (" + error.message() + ")"));
+      }
+    });
+}
+
+static const char*
+nlmsgTypeToString(uint16_t type)
+{
+#define NDN_NLMSG_STRING(x) case NLMSG_##x: return "<" #x ">"
+#define NDN_RTM_STRING(x) case RTM_##x: return "<" #x ">"
+  switch (type) {
+    NDN_NLMSG_STRING(NOOP);
+    NDN_NLMSG_STRING(ERROR);
+    NDN_NLMSG_STRING(DONE);
+    NDN_NLMSG_STRING(OVERRUN);
+    NDN_RTM_STRING(NEWLINK);
+    NDN_RTM_STRING(DELLINK);
+    NDN_RTM_STRING(NEWADDR);
+    NDN_RTM_STRING(DELADDR);
+    NDN_RTM_STRING(NEWROUTE);
+    NDN_RTM_STRING(DELROUTE);
+    default:
+      return "";
+  }
+#undef NDN_NLMSG_STRING
+#undef NDN_RTM_STRING
+}
+
+static InterfaceType
+ifiTypeToInterfaceType(uint16_t type)
+{
+  switch (type) {
+    case ARPHRD_ETHER:
+      return InterfaceType::ETHERNET;
+    case ARPHRD_LOOPBACK:
+      return InterfaceType::LOOPBACK;
+    default:
+      return InterfaceType::UNKNOWN;
+  }
+}
+
+static AddressFamily
+ifaFamilyToAddressFamily(uint8_t family)
+{
+  switch (family) {
+    case AF_INET:
+      return AddressFamily::V4;
+    case AF_INET6:
+      return AddressFamily::V6;
+    default:
+      return AddressFamily::UNSPECIFIED;
+  }
+}
+
+static AddressScope
+ifaScopeToAddressScope(uint8_t scope)
+{
+  switch (scope) {
+    case RT_SCOPE_NOWHERE:
+      return AddressScope::NOWHERE;
+    case RT_SCOPE_HOST:
+      return AddressScope::HOST;
+    case RT_SCOPE_LINK:
+      return AddressScope::LINK;
+    default:
+      return AddressScope::GLOBAL;
+  }
+}
+
+void
+NetworkMonitor::Impl::asyncRead()
+{
+  m_socket->async_read_some(boost::asio::buffer(m_buffer),
+                            bind(&Impl::handleRead, this, _1, _2, m_socket));
+}
+
+void
+NetworkMonitor::Impl::handleRead(const boost::system::error_code& error, size_t nBytesRead,
+                                 const shared_ptr<boost::asio::posix::stream_descriptor>& socket)
+{
+  if (!socket->is_open() ||
+      error == boost::asio::error::operation_aborted) {
+    // socket was closed, ignore the error
+    NDN_LOG_TRACE("socket closed or operation aborted");
+    return;
+  }
   if (error) {
+    NDN_LOG_ERROR("read failed: " << error.message());
+    BOOST_THROW_EXCEPTION(Error("Netlink socket read failed (" + error.message() + ")"));
+  }
+
+  NDN_LOG_TRACE("read " << nBytesRead << " bytes from netlink socket");
+
+  const nlmsghdr* nlh = reinterpret_cast<const nlmsghdr*>(m_buffer.data());
+  if (!isEnumerating() || (nlh->nlmsg_seq == m_sequenceNo && nlh->nlmsg_pid == m_pid)) {
+    parseNetlinkMessage(nlh, nBytesRead);
+  }
+  else {
+    NDN_LOG_TRACE("seq/pid mismatch, ignoring");
+  }
+
+  asyncRead();
+}
+
+void
+NetworkMonitor::Impl::parseNetlinkMessage(const nlmsghdr* nlh, size_t len)
+{
+  while (NLMSG_OK(nlh, len)) {
+    NDN_LOG_TRACE("parsing " << (nlh->nlmsg_flags & NLM_F_MULTI ? "multi-part " : "") <<
+                  "message type=" << nlh->nlmsg_type << nlmsgTypeToString(nlh->nlmsg_type) <<
+                  " len=" << nlh->nlmsg_len <<
+                  " seq=" << nlh->nlmsg_seq <<
+                  " pid=" << nlh->nlmsg_pid);
+
+    if (nlh->nlmsg_flags & NLM_F_DUMP_INTR) {
+      NDN_LOG_ERROR("netlink dump was interrupted");
+      // TODO: technically we should retry the dump...
+      break;
+    }
+
+    if (nlh->nlmsg_type == NLMSG_DONE)
+      break;
+
+    switch (nlh->nlmsg_type) {
+      case RTM_NEWLINK:
+      case RTM_DELLINK:
+        parseLinkMessage(nlh, reinterpret_cast<const ifinfomsg*>(NLMSG_DATA(nlh)));
+        if (!isEnumerating())
+          m_nm.onNetworkStateChanged(); // backward compat
+        break;
+
+      case RTM_NEWADDR:
+      case RTM_DELADDR:
+        parseAddressMessage(nlh, reinterpret_cast<const ifaddrmsg*>(NLMSG_DATA(nlh)));
+        if (!isEnumerating())
+          m_nm.onNetworkStateChanged(); // backward compat
+        break;
+
+      case RTM_NEWROUTE:
+      case RTM_DELROUTE:
+        parseRouteMessage(nlh, reinterpret_cast<const rtmsg*>(NLMSG_DATA(nlh)));
+        if (!isEnumerating())
+          m_nm.onNetworkStateChanged(); // backward compat
+        break;
+
+      case NLMSG_ERROR: {
+        const nlmsgerr* err = reinterpret_cast<const nlmsgerr*>(NLMSG_DATA(nlh));
+        if (nlh->nlmsg_len < NLMSG_LENGTH(sizeof(nlmsgerr)))
+          NDN_LOG_ERROR("truncated NLMSG_ERROR");
+        else if (err->error == 0)
+          // an error code of zero indicates an ACK message, not an error
+          NDN_LOG_TRACE("ACK");
+        else
+          NDN_LOG_ERROR("NLMSG_ERROR: " << std::strerror(std::abs(err->error)));
+        break;
+      }
+    }
+
+    nlh = NLMSG_NEXT(nlh, len);
+  }
+
+  if (nlh->nlmsg_type == NLMSG_DONE && 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 (nlh->nlmsg_type == NLMSG_DONE && m_isEnumeratingAddresses) {
+    // links and addresses enumeration complete
+    m_isEnumeratingAddresses = false;
+    // TODO: enumerate routes
+    NDN_LOG_DEBUG("enumeration complete");
+    m_nm.onEnumerationCompleted();
+  }
+}
+
+void
+NetworkMonitor::Impl::parseLinkMessage(const nlmsghdr* nlh, const ifinfomsg* ifi)
+{
+  if (ifiTypeToInterfaceType(ifi->ifi_type) == InterfaceType::UNKNOWN) {
+    NDN_LOG_DEBUG("unhandled interface type " << ifi->ifi_type);
     return;
   }
 
-  const nlmsghdr* nlh = reinterpret_cast<const nlmsghdr*>(m_buffer);
-  while ((NLMSG_OK(nlh, nBytesReceived)) && (nlh->nlmsg_type != NLMSG_DONE)) {
-    if (nlh->nlmsg_type == RTM_NEWADDR || nlh->nlmsg_type == RTM_DELADDR ||
-        nlh->nlmsg_type == RTM_NEWLINK || nlh->nlmsg_type == RTM_DELLINK ||
-        nlh->nlmsg_type == RTM_NEWROUTE || nlh->nlmsg_type == RTM_DELROUTE) {
-      m_nm.onNetworkStateChanged();
-      break;
-    }
-    nlh = NLMSG_NEXT(nlh, nBytesReceived);
+  shared_ptr<NetworkInterface> interface;
+  auto it = m_interfaces.find(ifi->ifi_index);
+  if (it != m_interfaces.end()) {
+    interface = it->second;
+    BOOST_ASSERT(interface != nullptr);
+    BOOST_ASSERT(interface->getIndex() == ifi->ifi_index);
   }
 
-  m_socket.async_read_some(boost::asio::buffer(m_buffer, NETLINK_BUFFER_SIZE),
-                           bind(&Impl::onReceiveRtNetlink, this, _1, _2));
+  if (nlh->nlmsg_type == RTM_DELLINK) {
+    if (interface != nullptr) {
+      NDN_LOG_DEBUG("removing interface " << interface->getName());
+      m_interfaces.erase(it);
+      m_nm.onInterfaceRemoved(interface);
+    }
+    return;
+  }
+
+  if (interface == nullptr) {
+    // cannot use make_shared because NetworkInterface constructor is private
+    interface.reset(new NetworkInterface);
+    interface->setIndex(ifi->ifi_index);
+  }
+  interface->setType(ifiTypeToInterfaceType(ifi->ifi_type));
+  interface->setFlags(ifi->ifi_flags);
+
+  const rtattr* rta = reinterpret_cast<const rtattr*>(IFLA_RTA(ifi));
+  size_t rtaTotalLen = IFLA_PAYLOAD(nlh);
+  uint8_t operState = linux_if::OPER_STATE_UNKNOWN;
+
+  while (RTA_OK(rta, rtaTotalLen)) {
+    size_t attrLen = RTA_PAYLOAD(rta);
+
+    switch (rta->rta_type) {
+      case IFLA_ADDRESS:
+        if (attrLen == ethernet::ADDR_LEN) {
+          ethernet::Address addr(reinterpret_cast<const uint8_t*>(RTA_DATA(rta)));
+          interface->setEthernetAddress(addr);
+        }
+        break;
+
+      case IFLA_BROADCAST:
+        if (attrLen == ethernet::ADDR_LEN) {
+          ethernet::Address addr(reinterpret_cast<const uint8_t*>(RTA_DATA(rta)));
+          interface->setEthernetBroadcastAddress(addr);
+        }
+        break;
+
+      case IFLA_IFNAME: {
+        auto attrData = reinterpret_cast<const char*>(RTA_DATA(rta));
+        if (::strnlen(attrData, attrLen) <= attrLen)
+          interface->setName(attrData);
+        break;
+      }
+
+      case IFLA_MTU:
+        if (attrLen == sizeof(uint32_t))
+          interface->setMtu(*(reinterpret_cast<const uint32_t*>(RTA_DATA(rta))));
+        break;
+
+      case IFLA_OPERSTATE:
+        if (attrLen == sizeof(uint8_t))
+          operState = *(reinterpret_cast<const uint8_t*>RTA_DATA(rta));
+        break;
+    }
+
+    rta = RTA_NEXT(rta, rtaTotalLen);
+  }
+
+  updateInterfaceState(*interface, operState);
+
+  if (it == m_interfaces.end()) {
+    NDN_LOG_DEBUG("adding interface " << interface->getName());
+    m_interfaces[interface->getIndex()] = interface;
+    m_nm.onInterfaceAdded(interface);
+  }
+}
+
+void
+NetworkMonitor::Impl::parseAddressMessage(const nlmsghdr* nlh, const ifaddrmsg* ifa)
+{
+  auto it = m_interfaces.find(ifa->ifa_index);
+  if (it == m_interfaces.end()) {
+    // unknown interface, ignore message
+    NDN_LOG_TRACE("unknown interface index " << ifa->ifa_index);
+    return;
+  }
+  auto interface = it->second;
+  BOOST_ASSERT(interface != nullptr);
+
+  namespace ip = boost::asio::ip;
+
+  NetworkAddress address;
+  address.m_family = ifaFamilyToAddressFamily(ifa->ifa_family);
+  BOOST_ASSERT(address.m_family != AddressFamily::UNSPECIFIED);
+  address.m_prefixLength = ifa->ifa_prefixlen;
+  address.m_flags = ifa->ifa_flags; // will be overridden by IFA_FLAGS below, if the attribute is present
+  address.m_scope = ifaScopeToAddressScope(ifa->ifa_scope);
+
+  const rtattr* rta = reinterpret_cast<const rtattr*>(IFA_RTA(ifa));
+  size_t rtaTotalLen = IFA_PAYLOAD(nlh);
+
+  while (RTA_OK(rta, rtaTotalLen)) {
+    auto attrData = reinterpret_cast<const unsigned char*>(RTA_DATA(rta));
+    size_t attrLen = RTA_PAYLOAD(rta);
+
+    switch (rta->rta_type) {
+      case IFA_LOCAL:
+        if (ifa->ifa_family == AF_INET && attrLen == sizeof(ip::address_v4::bytes_type)) {
+          ip::address_v4::bytes_type bytes;
+          std::copy_n(attrData, bytes.size(), bytes.begin());
+          address.m_ip = ip::address_v4(bytes);
+        }
+        break;
+
+      case IFA_ADDRESS:
+        if (ifa->ifa_family == AF_INET6 && attrLen == sizeof(ip::address_v6::bytes_type)) {
+          ip::address_v6::bytes_type bytes;
+          std::copy_n(attrData, bytes.size(), bytes.begin());
+          address.m_ip = ip::address_v6(bytes);
+        }
+        break;
+
+      case IFA_BROADCAST:
+        if (ifa->ifa_family == AF_INET && attrLen == sizeof(ip::address_v4::bytes_type)) {
+          ip::address_v4::bytes_type bytes;
+          std::copy_n(attrData, bytes.size(), bytes.begin());
+          address.m_broadcast = ip::address_v4(bytes);
+        }
+        break;
+
+#ifdef NDN_CXX_HAVE_IFA_FLAGS
+      case IFA_FLAGS:
+        if (attrLen == sizeof(uint32_t))
+          address.m_flags = *(reinterpret_cast<const uint32_t*>(attrData));
+        break;
+#endif // NDN_CXX_HAVE_IFA_FLAGS
+    }
+
+    rta = RTA_NEXT(rta, rtaTotalLen);
+  }
+
+  if (nlh->nlmsg_type == RTM_NEWADDR)
+    interface->addNetworkAddress(address);
+  else if (nlh->nlmsg_type == RTM_DELADDR)
+    interface->removeNetworkAddress(address);
+}
+
+void
+NetworkMonitor::Impl::parseRouteMessage(const nlmsghdr* nlh, const rtmsg* rtm)
+{
+  // TODO
+}
+
+void
+NetworkMonitor::Impl::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 util
 } // namespace ndn
-
-#endif // NDN_CXX_HAVE_RTNETLINK
diff --git a/src/util/detail/network-monitor-impl-rtnl.hpp b/src/util/detail/network-monitor-impl-rtnl.hpp
index 141554d..18996f9 100644
--- a/src/util/detail/network-monitor-impl-rtnl.hpp
+++ b/src/util/detail/network-monitor-impl-rtnl.hpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /**
- * Copyright (c) 2013-2016 Regents of the University of California.
+ * Copyright (c) 2013-2017 Regents of the University of California.
  *
  * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
  *
@@ -17,34 +17,97 @@
  * <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_UTIL_NETWORK_MONITOR_IMPL_RTNL_HPP
 #define NDN_UTIL_NETWORK_MONITOR_IMPL_RTNL_HPP
 
+#include "ndn-cxx-config.hpp"
 #include "../network-monitor.hpp"
 
+#ifndef NDN_CXX_HAVE_RTNETLINK
+#error "This file should not be compiled ..."
+#endif
+
 #include <boost/asio/posix/stream_descriptor.hpp>
 
+#include <array>
+#include <map>
+
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+#include <linux/if_addr.h>
+#include <linux/if_link.h>
+
 namespace ndn {
 namespace util {
 
-const size_t NETLINK_BUFFER_SIZE = 4096;
-
 class NetworkMonitor::Impl
 {
 public:
+  /** \brief initialize netlink socket and start enumerating interfaces
+   */
   Impl(NetworkMonitor& nm, boost::asio::io_service& io);
 
+  ~Impl();
+
+  shared_ptr<NetworkInterface>
+  getNetworkInterface(const std::string& ifname) const;
+
+  std::vector<shared_ptr<NetworkInterface>>
+  listNetworkInterfaces() const;
+
 private:
+  struct RtnlRequest
+  {
+    nlmsghdr nlh;
+    ifinfomsg ifi;
+    rtattr rta __attribute__((aligned(NLMSG_ALIGNTO))); // rtattr has to be aligned
+    uint32_t rtext;                                     // space for IFLA_EXT_MASK
+  };
+
+  bool
+  isEnumerating() const;
+
   void
-  onReceiveRtNetlink(const boost::system::error_code& error, size_t nBytesReceived);
+  initSocket();
+
+  void
+  sendDumpRequest(uint16_t nlmsgType);
+
+  void
+  asyncRead();
+
+  void
+  handleRead(const boost::system::error_code& error, size_t nBytesReceived,
+             const shared_ptr<boost::asio::posix::stream_descriptor>& socket);
+
+  void
+  parseNetlinkMessage(const nlmsghdr* nlh, size_t len);
+
+  void
+  parseLinkMessage(const nlmsghdr* nlh, const ifinfomsg* ifi);
+
+  void
+  parseAddressMessage(const nlmsghdr* nlh, const ifaddrmsg* ifa);
+
+  void
+  parseRouteMessage(const nlmsghdr* nlh, const rtmsg* rtm);
+
+  static void
+  updateInterfaceState(NetworkInterface& interface, uint8_t operState);
 
 private:
   NetworkMonitor& m_nm;
-
-  uint8_t m_buffer[NETLINK_BUFFER_SIZE];
-  boost::asio::posix::stream_descriptor m_socket;
+  std::map<int /*ifindex*/, shared_ptr<NetworkInterface>> m_interfaces; ///< interface map
+  std::array<uint8_t, 16384> m_buffer; ///< holds 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
+  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
 };
 
 } // namespace util