util: rtnetlink-based implementation of NetworkMonitor

Change-Id: Ia25ddf14ba38181f3a831c1eaf9b417baba7c9d3
Refs: #2443
diff --git a/src/util/network-monitor.cpp b/src/util/network-monitor.cpp
index b16e473..5513d0d 100644
--- a/src/util/network-monitor.cpp
+++ b/src/util/network-monitor.cpp
@@ -139,14 +139,85 @@
 } // namespace ndn
 
 // done with defined(NDN_CXX_HAVE_COREFOUNDATION_COREFOUNDATION_H)
-#elif defined(HAVE_DBUS)
+#elif defined(NDN_CXX_HAVE_RTNETLINK)
+
+#include <boost/asio.hpp>
+
+#include <netinet/in.h>
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+#include <net/if.h>
+
+#include <cerrno>
+#include <cstring>
 
 namespace ndn {
 namespace util {
 
-NetworkMonitor::NetworkMonitor(boost::asio::io_service&)
+const size_t NETLINK_BUFFER_SIZE = 4096;
+
+class NetworkMonitor::Impl
 {
-  throw Error("Not implemented yet");
+public:
+  Impl(NetworkMonitor& nm, boost::asio::io_service& io)
+    : m_nm(nm)
+    , m_socket(io)
+  {
+    int fd = ::socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
+    if (fd < 0)
+      throw Error(std::string("Cannot create netlink socket (") + std::strerror(errno) + ")");
+
+    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;
+
+    if (::bind(fd, reinterpret_cast<sockaddr*>(&addr), sizeof(addr)) == -1) {
+      throw Error(std::string("Cannot bind on netlink socket (") + std::strerror(errno) + ")");
+    }
+
+    m_socket.assign(fd);
+
+    m_socket.async_read_some(boost::asio::buffer(m_buffer, NETLINK_BUFFER_SIZE),
+                             bind(&Impl::onReceiveRtNetlink, this, _1, _2));
+  }
+
+private:
+  void
+  onReceiveRtNetlink(const boost::system::error_code& error, size_t nBytesReceived)
+  {
+    if (error) {
+      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);
+    }
+
+    m_socket.async_read_some(boost::asio::buffer(m_buffer, NETLINK_BUFFER_SIZE),
+                             bind(&Impl::onReceiveRtNetlink, this, _1, _2));
+  }
+
+private:
+  NetworkMonitor& m_nm;
+  uint8_t m_buffer[NETLINK_BUFFER_SIZE];
+
+  boost::asio::posix::stream_descriptor m_socket;
+};
+
+
+
+NetworkMonitor::NetworkMonitor(boost::asio::io_service& io)
+  : m_impl(new Impl(*this, io))
+{
 }
 
 NetworkMonitor::~NetworkMonitor()
@@ -156,7 +227,7 @@
 } // namespace util
 } // namespace ndn
 
-// done with defined(HAVE_DBUS)
+// done with defined(NDN_CXX_HAVE_RTNETLINK)
 #else // do not support network monitoring operations
 
 namespace ndn {
@@ -164,7 +235,6 @@
 
 class NetworkMonitor::Impl
 {
-public:
 };
 
 NetworkMonitor::NetworkMonitor(boost::asio::io_service&)
diff --git a/src/util/network-monitor.hpp b/src/util/network-monitor.hpp
index 9c7dc31..f3fc6b8 100644
--- a/src/util/network-monitor.hpp
+++ b/src/util/network-monitor.hpp
@@ -38,7 +38,7 @@
  * @note Implementation of this class is platform dependent and not all supported platforms
  *       are supported:
  *       - OS X: CFNotificationCenterAddObserver
- *       - Linux: NetworkManager notifications through DBus IPC
+ *       - Linux: rtnetlink notifications
  *
  * Network state change detection is not guaranteed to be precise and (zero or more)
  * notifications are expected to be fired for the following events:
diff --git a/wscript b/wscript
index 0000b7c..d4fcb82 100644
--- a/wscript
+++ b/wscript
@@ -64,6 +64,10 @@
 }
 ''')
 
+    conf.check_cxx(msg='Checking for rtnetlink', mandatory=False,
+                   define_name='HAVE_RTNETLINK',
+                   header_name=['netinet/in.h', 'linux/netlink.h', 'linux/rtnetlink.h', 'net/if.h'])
+
     conf.check_osx_security(mandatory=False)
 
     conf.check_sqlite3(mandatory=True)
@@ -192,7 +196,7 @@
         bld.recurse("examples")
 
     headers = bld.path.ant_glob(['src/**/*.hpp'],
-                                 excl=['src/**/*-osx.hpp', 'src/detail/*'])
+                                 excl=['src/**/*-osx.hpp', 'src/detail/**/*'])
     if bld.env['HAVE_OSX_SECURITY']:
         headers += bld.path.ant_glob('src/**/*-osx.hpp')