net: move network-related files to src/net

Some namespaces are changed:

* ndn::util::FaceUri is now ndn::FaceUri
* ndn::util::ethernet is now ndn::ethernet
* ndn::util::NetworkMonitor and related classes are now in ndn::net

refs #3940

Change-Id: Ia4754caef9726efc73f5a303cec46fc95c744d70
diff --git a/src/net/detail/link-type-helper-osx.mm b/src/net/detail/link-type-helper-osx.mm
new file mode 100644
index 0000000..c45fa04
--- /dev/null
+++ b/src/net/detail/link-type-helper-osx.mm
@@ -0,0 +1,65 @@
+/* -*- 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.
+ */
+
+#include "link-type-helper.hpp"
+
+#ifndef NDN_CXX_HAVE_OSX_FRAMEWORKS
+#error "This file should not be compiled ..."
+#endif
+
+#import <Foundation/Foundation.h>
+#import <CoreWLAN/CoreWLAN.h>
+#import <CoreWLAN/CWInterface.h>
+#import <CoreWLAN/CWWiFiClient.h>
+
+namespace ndn {
+namespace net {
+namespace detail {
+
+ndn::nfd::LinkType
+getLinkType(const std::string& ifName)
+{
+  @autoreleasepool {
+    NSString* interfaceName = [NSString stringWithCString:ifName.c_str()
+                                                 encoding:[NSString defaultCStringEncoding]];
+
+    CWWiFiClient* wifiInterface = [CWWiFiClient sharedWiFiClient];
+    if (wifiInterface == nullptr) {
+      return nfd::LINK_TYPE_NONE;
+    }
+
+    CWInterface* airport = [wifiInterface interfaceWithName:interfaceName];
+    if (airport == nullptr) {
+      return nfd::LINK_TYPE_NONE;
+    }
+
+    if ([airport interfaceMode] == kCWInterfaceModeIBSS) {
+      return nfd::LINK_TYPE_AD_HOC;
+    }
+    else {
+      return nfd::LINK_TYPE_MULTI_ACCESS;
+    }
+  }
+}
+
+} // namespace detail
+} // namespace net
+} // namespace ndn
diff --git a/src/net/detail/link-type-helper.cpp b/src/net/detail/link-type-helper.cpp
new file mode 100644
index 0000000..36d51de
--- /dev/null
+++ b/src/net/detail/link-type-helper.cpp
@@ -0,0 +1,43 @@
+/* -*- 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.
+ */
+
+#include "link-type-helper.hpp"
+#include "ndn-cxx-config.hpp"
+
+#ifdef NDN_CXX_HAVE_OSX_FRAMEWORKS
+// implemented in link-type-helper-osx.mm
+#else
+
+namespace ndn {
+namespace net {
+namespace detail {
+
+ndn::nfd::LinkType
+getLinkType(const std::string& ifName)
+{
+  return nfd::LINK_TYPE_NONE;
+}
+
+} // namespace detail
+} // namespace net
+} // namespace ndn
+
+#endif // NDN_CXX_HAVE_OSX_FRAMEWORKS
diff --git a/src/net/detail/link-type-helper.hpp b/src/net/detail/link-type-helper.hpp
new file mode 100644
index 0000000..9512f31
--- /dev/null
+++ b/src/net/detail/link-type-helper.hpp
@@ -0,0 +1,41 @@
+/* -*- 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.
+ */
+
+#ifndef NDN_NET_DETAIL_LINK_TYPE_HELPER_HPP
+#define NDN_NET_DETAIL_LINK_TYPE_HELPER_HPP
+
+#include "../../encoding/nfd-constants.hpp"
+
+namespace ndn {
+namespace net {
+namespace detail {
+
+/**
+ * @brief Obtain information about WiFi link type
+ */
+ndn::nfd::LinkType
+getLinkType(const std::string& ifName);
+
+} // namespace detail
+} // namespace net
+} // namespace ndn
+
+#endif // NDN_NET_DETAIL_LINK_TYPE_HELPER_HPP
diff --git a/src/net/detail/linux-if-constants.cpp b/src/net/detail/linux-if-constants.cpp
new file mode 100644
index 0000000..dbcf54b
--- /dev/null
+++ b/src/net/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 net {
+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 net
+} // namespace ndn
+
+#endif // __linux__
diff --git a/src/net/detail/linux-if-constants.hpp b/src/net/detail/linux-if-constants.hpp
new file mode 100644
index 0000000..0670e70
--- /dev/null
+++ b/src/net/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_NET_LINUX_IF_CONSTANTS_HPP
+#define NDN_NET_LINUX_IF_CONSTANTS_HPP
+#ifdef __linux__
+
+#include <cstdint>
+
+namespace ndn {
+namespace net {
+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 net
+} // namespace ndn
+
+#endif // __linux__
+#endif // NDN_NET_LINUX_IF_CONSTANTS_HPP
diff --git a/src/net/detail/network-monitor-impl-noop.hpp b/src/net/detail/network-monitor-impl-noop.hpp
new file mode 100644
index 0000000..f0c5fb0
--- /dev/null
+++ b/src/net/detail/network-monitor-impl-noop.hpp
@@ -0,0 +1,61 @@
+/* -*- 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_NET_NETWORK_MONITOR_IMPL_NOOP_HPP
+#define NDN_NET_NETWORK_MONITOR_IMPL_NOOP_HPP
+
+#include "../network-monitor.hpp"
+
+namespace ndn {
+namespace net {
+
+class NetworkMonitor::Impl
+{
+public:
+  Impl(NetworkMonitor& nm, boost::asio::io_service& io)
+  {
+  }
+
+  uint32_t
+  getCapabilities() const
+  {
+    return NetworkMonitor::CAP_NONE;
+  }
+
+  shared_ptr<NetworkInterface>
+  getNetworkInterface(const std::string&) const
+  {
+    return {};
+  }
+
+  std::vector<shared_ptr<NetworkInterface>>
+  listNetworkInterfaces() const
+  {
+    return {};
+  }
+};
+
+} // namespace net
+} // namespace ndn
+
+#endif // NDN_NET_NETWORK_MONITOR_IMPL_NOOP_HPP
diff --git a/src/net/detail/network-monitor-impl-osx.cpp b/src/net/detail/network-monitor-impl-osx.cpp
new file mode 100644
index 0000000..97f3e7c
--- /dev/null
+++ b/src/net/detail/network-monitor-impl-osx.cpp
@@ -0,0 +1,453 @@
+/* -*- 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.
+ *
+ *
+ * Parts of this implementation is based on daemondo command of MacPorts
+ * (https://www.macports.org/):
+ *
+ *    Copyright (c) 2005-2007 James Berry <jberry@macports.org>
+ *   All rights reserved.
+ *
+ *   Redistribution and use in source and binary forms, with or without
+ *   modification, are permitted provided that the following conditions
+ *   are met:
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *   3. Neither the name of The MacPorts Project nor the names of its contributors
+ *      may be used to endorse or promote products derived from this software
+ *      without specific prior written permission.
+ *
+ *   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ *   AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ *   IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ *   ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ *   LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ *   CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ *   SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ *   INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ *   CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ *   ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ *   POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "ndn-cxx-config.hpp"
+
+#include "network-monitor-impl-osx.hpp"
+#include "../../name.hpp"
+#include "../../util/logger.hpp"
+#include "../network-address.hpp"
+
+#include <ifaddrs.h>       // for getifaddrs()
+#include <arpa/inet.h>     // for inet_ntop()
+#include <netinet/in.h>    // for struct sockaddr_in{,6}
+#include <net/if_dl.h>     // for struct sockaddr_dl
+#include <net/if_types.h>  // for IFT_* constants
+
+#include <boost/asio.hpp>
+
+namespace ndn {
+namespace net {
+
+NDN_LOG_INIT(ndn.NetworkMonitor);
+
+NetworkMonitor::Impl::Impl(NetworkMonitor& nm, boost::asio::io_service& io)
+  : m_nm(nm)
+  , m_scheduler(io)
+  , m_cfLoopEvent(m_scheduler)
+  , m_context{0, this, nullptr, nullptr, nullptr}
+  , m_scStore(SCDynamicStoreCreate(nullptr, CFSTR("net.named-data.ndn-cxx.NetworkMonitor"),
+                                   &Impl::onConfigChanged, &m_context))
+  , m_loopSource(SCDynamicStoreCreateRunLoopSource(nullptr, m_scStore.get(), 0))
+  , m_nullUdpSocket(io, boost::asio::ip::udp::v4())
+
+{
+  scheduleCfLoop();
+
+  // Notifications from Darwin Notify Center:
+  //
+  // com.apple.system.config.network_change
+  //
+  CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(),
+                                  static_cast<void*>(this),
+                                  &Impl::afterNotificationCenterEvent,
+                                  CFSTR("com.apple.system.config.network_change"),
+                                  nullptr, // object to observe
+                                  CFNotificationSuspensionBehaviorDeliverImmediately);
+
+  io.post([this] { enumerateInterfaces(); });
+
+  CFRunLoopAddSource(CFRunLoopGetCurrent(), m_loopSource.get(), kCFRunLoopDefaultMode);
+
+  // Notifications from SystemConfiguration:
+  //
+  // State:/Network/Interface/.*/Link
+  // State:/Network/Interface/.*/IPv4
+  // State:/Network/Interface/.*/IPv6
+  // State:/Network/Global/DNS
+  // State:/Network/Global/IPv4
+  //
+  auto patterns = CFArrayCreateMutable(nullptr, 0, &kCFTypeArrayCallBacks);
+  CFArrayAppendValue(patterns, CFSTR("State:/Network/Interface/.*/Link"));
+  CFArrayAppendValue(patterns, CFSTR("State:/Network/Interface/.*/IPv4"));
+  CFArrayAppendValue(patterns, CFSTR("State:/Network/Interface/.*/IPv6"));
+  // CFArrayAppendValue(patterns, CFSTR("State:/Network/Global/DNS"));
+  // CFArrayAppendValue(patterns, CFSTR("State:/Network/Global/IPv4"));
+
+  SCDynamicStoreSetNotificationKeys(m_scStore.get(), nullptr, patterns);
+}
+
+NetworkMonitor::Impl::~Impl()
+{
+  CFRunLoopRemoveSource(CFRunLoopGetCurrent(), m_loopSource.get(), kCFRunLoopDefaultMode);
+
+  CFNotificationCenterRemoveEveryObserver(CFNotificationCenterGetDarwinNotifyCenter(),
+                                          static_cast<void*>(this));
+}
+
+shared_ptr<NetworkInterface>
+NetworkMonitor::Impl::getNetworkInterface(const std::string& ifname) const
+{
+  auto it = m_interfaces.find(ifname);
+  if (it != m_interfaces.end()) {
+    return it->second;
+  }
+  else {
+    return nullptr;
+  }
+}
+
+std::vector<shared_ptr<NetworkInterface>>
+NetworkMonitor::Impl::listNetworkInterfaces() const
+{
+  std::vector<shared_ptr<NetworkInterface>> v;
+  v.reserve(m_interfaces.size());
+
+  for (const auto& e : m_interfaces) {
+    v.push_back(e.second);
+  }
+  return v;
+}
+
+void
+NetworkMonitor::Impl::afterNotificationCenterEvent(CFNotificationCenterRef center,
+                                                   void* observer,
+                                                   CFStringRef name,
+                                                   const void* object,
+                                                   CFDictionaryRef userInfo)
+{
+  static_cast<Impl*>(observer)->m_nm.onNetworkStateChanged();
+}
+
+void
+NetworkMonitor::Impl::scheduleCfLoop()
+{
+  // poll each second for new events
+  m_cfLoopEvent = m_scheduler.scheduleEvent(time::seconds(1), bind(&Impl::pollCfLoop, this));
+}
+
+void
+NetworkMonitor::Impl::pollCfLoop()
+{
+  // this should dispatch ready events and exit
+  CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, true);
+
+  scheduleCfLoop();
+}
+
+void
+NetworkMonitor::Impl::addNewInterface(const std::string& ifName)
+{
+  shared_ptr<NetworkInterface> interface(new NetworkInterface);
+
+  interface->setName(ifName);
+  interface->setState(getInterfaceState(interface->getName()));
+  updateInterfaceInfo(*interface);
+  if (interface->getType() == InterfaceType::UNKNOWN) {
+    NDN_LOG_DEBUG("ignoring " << ifName << " because it has unhandled interface type");
+    return;
+  }
+
+  NDN_LOG_DEBUG("adding interface " << interface->getName());
+  m_interfaces.insert(make_pair(interface->getName(), interface));
+  m_nm.onInterfaceAdded(interface);
+}
+
+void
+NetworkMonitor::Impl::enumerateInterfaces()
+{
+  for (const auto& ifName : getInterfaceNames()) {
+    addNewInterface(ifName);
+  }
+  m_nm.onEnumerationCompleted();
+}
+
+static std::string
+convertToStdString(CFStringRef cfString)
+{
+  const char* cStr = CFStringGetCStringPtr(cfString, kCFStringEncodingASCII);
+  if (cStr != nullptr) {
+    return cStr;
+  }
+
+  size_t stringSize =  CFStringGetLength(cfString);
+  char* buffer = new char[stringSize + 1];
+  CFStringGetCString(cfString, buffer, sizeof(buffer), kCFStringEncodingASCII);
+  std::string retval = buffer;
+  delete [] buffer;
+  return retval;
+}
+
+std::set<std::string>
+NetworkMonitor::Impl::getInterfaceNames()
+{
+  CFReleaser<CFDictionaryRef> dict = (CFDictionaryRef)SCDynamicStoreCopyValue(m_scStore.get(), CFSTR("State:/Network/Interface"));
+  CFArrayRef interfaces = (CFArrayRef)CFDictionaryGetValue(dict.get(), CFSTR("Interfaces"));
+
+  std::set<std::string> ifNames;
+  size_t count = CFArrayGetCount(interfaces);
+  for (size_t i = 0; i != count; ++i) {
+    auto ifName = (CFStringRef)CFArrayGetValueAtIndex(interfaces, i);
+    ifNames.insert(convertToStdString(ifName));
+  }
+  return ifNames;
+}
+
+InterfaceState
+NetworkMonitor::Impl::getInterfaceState(const std::string& ifName)
+{
+  CFReleaser<CFStringRef> linkName = CFStringCreateWithCString(nullptr,
+                                                               ("State:/Network/Interface/" + ifName + "/Link").c_str(),
+                                                               kCFStringEncodingASCII);
+
+  CFReleaser<CFDictionaryRef> dict = (CFDictionaryRef)SCDynamicStoreCopyValue(m_scStore.get(), linkName.get());
+  if (dict.get() == nullptr) {
+    return InterfaceState::UNKNOWN;
+  }
+
+  CFBooleanRef isActive = (CFBooleanRef)CFDictionaryGetValue(dict.get(), CFSTR("Active"));
+  if (isActive == nullptr) {
+    return InterfaceState::UNKNOWN;
+  }
+
+  return CFBooleanGetValue(isActive) ? InterfaceState::RUNNING : InterfaceState::DOWN;
+}
+
+void
+NetworkMonitor::Impl::updateInterfaceInfo(NetworkInterface& netif)
+{
+  ifaddrs* ifa_list = nullptr;
+  if (::getifaddrs(&ifa_list) < 0) {
+    BOOST_THROW_EXCEPTION(Error(std::string("getifaddrs() failed: ") + strerror(errno)));
+  }
+
+  for (ifaddrs* ifa = ifa_list; ifa != nullptr; ifa = ifa->ifa_next) {
+    if (ifa->ifa_name != netif.getName()) {
+      continue;
+    }
+
+    netif.setFlags(ifa->ifa_flags);
+    netif.setMtu(getInterfaceMtu(netif.getName()));
+
+    if (ifa->ifa_addr == nullptr)
+      continue;
+
+    NetworkAddress address;
+
+    switch (ifa->ifa_addr->sa_family) {
+      case AF_INET: {
+        address.m_family = AddressFamily::V4;
+
+        const sockaddr_in* sin = reinterpret_cast<sockaddr_in*>(ifa->ifa_addr);
+        boost::asio::ip::address_v4::bytes_type bytes;
+        std::copy_n(reinterpret_cast<const unsigned char*>(&sin->sin_addr), bytes.size(), bytes.begin());
+        address.m_ip = boost::asio::ip::address_v4(bytes);
+
+        const sockaddr_in* sinMask = reinterpret_cast<sockaddr_in*>(ifa->ifa_netmask);
+        std::copy_n(reinterpret_cast<const unsigned char*>(&sinMask->sin_addr), bytes.size(), bytes.begin());
+        uint8_t mask = 0;
+        for (auto byte : bytes) {
+          while (byte != 0) {
+            ++mask;
+            byte <<= 1;
+          }
+        }
+        address.m_prefixLength = mask;
+        break;
+      }
+
+      case AF_INET6: {
+        address.m_family = AddressFamily::V6;
+
+        const sockaddr_in6* sin6 = reinterpret_cast<sockaddr_in6*>(ifa->ifa_addr);
+        boost::asio::ip::address_v6::bytes_type bytes;
+        std::copy_n(reinterpret_cast<const unsigned char*>(&sin6->sin6_addr), bytes.size(), bytes.begin());
+        address.m_ip = boost::asio::ip::address_v6(bytes);
+
+        const sockaddr_in6* sinMask = reinterpret_cast<sockaddr_in6*>(ifa->ifa_netmask);
+        std::copy_n(reinterpret_cast<const unsigned char*>(&sinMask->sin6_addr), bytes.size(), bytes.begin());
+        uint8_t mask = 0;
+        for (auto byte : bytes) {
+          while (byte != 0) {
+            ++mask;
+            byte <<= 1;
+          }
+        }
+        address.m_prefixLength = mask;
+        break;
+      }
+
+      case AF_LINK: {
+        const sockaddr_dl* sdl = reinterpret_cast<sockaddr_dl*>(ifa->ifa_addr);
+        netif.setIndex(sdl->sdl_index);
+        if (sdl->sdl_type == IFT_ETHER && sdl->sdl_alen == ethernet::ADDR_LEN) {
+          netif.setType(InterfaceType::ETHERNET);
+          netif.setEthernetAddress(ethernet::Address(reinterpret_cast<uint8_t*>(LLADDR(sdl))));
+          NDN_LOG_TRACE(netif.getName() << ": set Ethernet address " << netif.getEthernetAddress());
+        }
+        else if (sdl->sdl_type == IFT_LOOP) {
+          netif.setType(InterfaceType::LOOPBACK);
+        }
+        else {
+          netif.setType(InterfaceType::UNKNOWN);
+        }
+        break;
+      }
+
+      default:
+        continue;
+    }
+
+    if (netif.canBroadcast() && ifa->ifa_broadaddr != nullptr) {
+      const sockaddr_in* sin = reinterpret_cast<sockaddr_in*>(ifa->ifa_broadaddr);
+      boost::asio::ip::address_v4::bytes_type bytes;
+      std::copy_n(reinterpret_cast<const unsigned char*>(&sin->sin_addr), bytes.size(), bytes.begin());
+      address.m_broadcast = boost::asio::ip::address_v4(bytes);
+      NDN_LOG_TRACE(netif.getName() << ": set IPv4 broadcast address " << address.m_broadcast);
+    }
+
+    if (netif.canBroadcast()) {
+      netif.setEthernetBroadcastAddress(ethernet::getBroadcastAddress());
+    }
+
+    netif.addNetworkAddress(address);
+  }
+
+  ::freeifaddrs(ifa_list);
+}
+
+size_t
+NetworkMonitor::Impl::getInterfaceMtu(const std::string& ifName)
+{
+  ifreq ifr{};
+  std::strncpy(ifr.ifr_name, ifName.c_str(), sizeof(ifr.ifr_name) - 1);
+
+  if (::ioctl(m_nullUdpSocket.native_handle(), SIOCGIFMTU, &ifr) == 0) {
+    return static_cast<size_t>(ifr.ifr_mtu);
+  }
+
+  NDN_LOG_WARN("Failed to get interface MTU: " << std::strerror(errno));
+  return ethernet::MAX_DATA_LEN;
+}
+
+void
+NetworkMonitor::Impl::onConfigChanged(SCDynamicStoreRef m_scStore, CFArrayRef changedKeys, void* context)
+{
+  static_cast<Impl*>(context)->onConfigChanged(changedKeys);
+}
+
+void
+NetworkMonitor::Impl::onConfigChanged(CFArrayRef changedKeys)
+{
+  size_t count = CFArrayGetCount(changedKeys);
+  for (size_t i = 0; i != count; ++i) {
+    std::string keyName = convertToStdString((CFStringRef)CFArrayGetValueAtIndex(changedKeys, i));
+    Name key(keyName);
+    std::string ifName = key.at(-2).toUri();
+
+    auto ifIt = m_interfaces.find(ifName);
+    if (ifIt == m_interfaces.end()) {
+      addNewInterface(ifName);
+      return;
+    }
+
+    NetworkInterface& netif = *ifIt->second;
+
+    auto removeInterface = [&] {
+      NDN_LOG_DEBUG("removing interface " << ifName);
+      shared_ptr<NetworkInterface> removedInterface = ifIt->second;
+      m_interfaces.erase(ifIt);
+      m_nm.onInterfaceRemoved(removedInterface);
+    };
+
+    if (key.at(-1).toUri() == "Link") {
+      auto newState = getInterfaceState(ifName);
+
+      if (newState == InterfaceState::UNKNOWN) {
+        // check if it is really unknown or interface removed
+        if (getInterfaceNames().count(ifName) == 0) {
+          // newState = InterfaceState::DOWN;
+          removeInterface();
+          return;
+        }
+      }
+
+      NDN_LOG_TRACE("Status of " << ifName << " changed from " << netif.getState() << " to " << newState);
+      netif.setState(newState);
+    }
+
+    if (key.at(-1).toUri() == "IPv4" || key.at(-1).toUri() == "IPv6") {
+      NetworkInterface updatedInterface;
+      updatedInterface.setName(ifName);
+      updateInterfaceInfo(updatedInterface);
+      if (updatedInterface.getType() == InterfaceType::UNKNOWN) {
+        // somehow, type of interface changed to unknown
+        NDN_LOG_DEBUG("Removing " << ifName << " because it changed to unhandled interface type");
+        removeInterface();
+        return;
+      }
+
+      const auto& newAddrs = updatedInterface.getNetworkAddresses();
+      const auto& oldAddrs = netif.getNetworkAddresses();
+
+      std::set<NetworkAddress> added;
+      std::set<NetworkAddress> removed;
+
+      std::set_difference(newAddrs.begin(), newAddrs.end(),
+                          oldAddrs.begin(), oldAddrs.end(), std::inserter(added, added.end()));
+
+      std::set_difference(oldAddrs.begin(), oldAddrs.end(),
+                          newAddrs.begin(), newAddrs.end(), std::inserter(removed, removed.end()));
+
+      for (const auto& addr : removed) {
+        netif.removeNetworkAddress(addr);
+      }
+
+      for (const auto& addr : added) {
+        netif.addNetworkAddress(addr);
+      }
+    }
+  }
+}
+
+} // namespace net
+} // namespace ndn
diff --git a/src/net/detail/network-monitor-impl-osx.hpp b/src/net/detail/network-monitor-impl-osx.hpp
new file mode 100644
index 0000000..3aac23f
--- /dev/null
+++ b/src/net/detail/network-monitor-impl-osx.hpp
@@ -0,0 +1,120 @@
+/* -*- 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.
+ */
+
+#ifndef NDN_NET_NETWORK_MONITOR_IMPL_OSX_HPP
+#define NDN_NET_NETWORK_MONITOR_IMPL_OSX_HPP
+
+#include "ndn-cxx-config.hpp"
+#include "../network-monitor.hpp"
+
+#ifndef NDN_CXX_HAVE_OSX_FRAMEWORKS
+#error "This file should not be compiled ..."
+#endif
+
+#include "../network-interface.hpp"
+#include "../../security/tpm/helper-osx.hpp"
+#include "../../util/scheduler.hpp"
+#include "../../util/scheduler-scoped-event-id.hpp"
+
+#include <CoreFoundation/CoreFoundation.h>
+#include <SystemConfiguration/SystemConfiguration.h>
+
+#include <boost/asio/ip/udp.hpp>
+
+namespace ndn {
+namespace net {
+
+class NetworkMonitor::Impl
+{
+public:
+  Impl(NetworkMonitor& nm, boost::asio::io_service& io);
+
+  ~Impl();
+
+  uint32_t
+  getCapabilities() const
+  {
+    return NetworkMonitor::CAP_ENUM | NetworkMonitor::CAP_IF_ADD_REMOVE |
+      NetworkMonitor::CAP_STATE_CHANGE | NetworkMonitor::CAP_ADDR_ADD_REMOVE;
+  }
+
+  shared_ptr<NetworkInterface>
+  getNetworkInterface(const std::string& ifname) const;
+
+  std::vector<shared_ptr<NetworkInterface>>
+  listNetworkInterfaces() const;
+
+  static void
+  afterNotificationCenterEvent(CFNotificationCenterRef center,
+                               void* observer,
+                               CFStringRef name,
+                               const void* object,
+                               CFDictionaryRef userInfo);
+
+private:
+  void
+  scheduleCfLoop();
+
+  void
+  pollCfLoop();
+
+  void
+  addNewInterface(const std::string& ifName);
+
+  void
+  enumerateInterfaces();
+
+  std::set<std::string>
+  getInterfaceNames();
+
+  InterfaceState
+  getInterfaceState(const std::string& ifName);
+
+  void
+  updateInterfaceInfo(NetworkInterface& netif);
+
+  size_t
+  getInterfaceMtu(const std::string& ifName);
+
+  static void
+  onConfigChanged(SCDynamicStoreRef store, CFArrayRef changedKeys, void* context);
+
+  void
+  onConfigChanged(CFArrayRef changedKeys);
+
+private:
+  NetworkMonitor& m_nm;
+  std::map<std::string /*ifname*/, shared_ptr<NetworkInterface>> m_interfaces; ///< interface map
+
+  util::Scheduler m_scheduler;
+  util::scheduler::ScopedEventId m_cfLoopEvent;
+
+  SCDynamicStoreContext m_context;
+  CFReleaser<SCDynamicStoreRef> m_scStore;
+  CFReleaser<CFRunLoopSourceRef> m_loopSource;
+
+  boost::asio::ip::udp::socket m_nullUdpSocket;
+};
+
+} // namespace net
+} // namespace ndn
+
+#endif // NDN_NET_NETWORK_MONITOR_IMPL_OSX_HPP
diff --git a/src/net/detail/network-monitor-impl-rtnl.cpp b/src/net/detail/network-monitor-impl-rtnl.cpp
new file mode 100644
index 0000000..1a05986
--- /dev/null
+++ b/src/net/detail/network-monitor-impl-rtnl.cpp
@@ -0,0 +1,510 @@
+/* -*- 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>
+ */
+
+#include "network-monitor-impl-rtnl.hpp"
+#include "linux-if-constants.hpp"
+#include "../network-address.hpp"
+#include "../network-interface.hpp"
+#include "../../util/logger.hpp"
+#include "../../util/time.hpp"
+
+#include <boost/asio/write.hpp>
+
+#include <cerrno>
+#include <cstdlib>
+#include <net/if_arp.h>
+#include <sys/socket.h>
+
+NDN_LOG_INIT(ndn.NetworkMonitor);
+
+namespace ndn {
+namespace net {
+
+NetworkMonitor::Impl::Impl(NetworkMonitor& nm, boost::asio::io_service& io)
+  : m_nm(nm)
+  , 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)
+{
+  initSocket();
+  asyncRead();
+
+  NDN_LOG_TRACE("enumerating links");
+  sendDumpRequest(RTM_GETLINK);
+  m_isEnumeratingLinks = true;
+}
+
+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;
+}
+
+std::vector<shared_ptr<NetworkInterface>>
+NetworkMonitor::Impl::listNetworkInterfaces() const
+{
+  std::vector<shared_ptr<NetworkInterface>> v;
+  v.reserve(m_interfaces.size());
+
+  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::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;
+  }
+
+  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);
+  }
+
+  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 net
+} // namespace ndn
diff --git a/src/net/detail/network-monitor-impl-rtnl.hpp b/src/net/detail/network-monitor-impl-rtnl.hpp
new file mode 100644
index 0000000..d43abce
--- /dev/null
+++ b/src/net/detail/network-monitor-impl-rtnl.hpp
@@ -0,0 +1,126 @@
+/* -*- 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_NET_NETWORK_MONITOR_IMPL_RTNL_HPP
+#define NDN_NET_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 net {
+
+class NetworkMonitor::Impl
+{
+public:
+  /** \brief initialize netlink socket and start enumerating interfaces
+   */
+  Impl(NetworkMonitor& nm, boost::asio::io_service& io);
+
+  ~Impl();
+
+  uint32_t
+  getCapabilities() const
+  {
+    return NetworkMonitor::CAP_ENUM |
+           NetworkMonitor::CAP_IF_ADD_REMOVE |
+           NetworkMonitor::CAP_STATE_CHANGE |
+           NetworkMonitor::CAP_MTU_CHANGE |
+           NetworkMonitor::CAP_ADDR_ADD_REMOVE;
+  }
+
+  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
+  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;
+  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 net
+} // namespace ndn
+
+#endif // NDN_NET_NETWORK_MONITOR_IMPL_RTNL_HPP
diff --git a/src/net/dns.cpp b/src/net/dns.cpp
new file mode 100644
index 0000000..0043ec9
--- /dev/null
+++ b/src/net/dns.cpp
@@ -0,0 +1,160 @@
+/* -*- 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.
+ */
+
+#include "dns.hpp"
+#include "../util/scheduler.hpp"
+
+#include <boost/asio/io_service.hpp>
+#include <boost/asio/ip/udp.hpp>
+
+namespace ndn {
+namespace dns {
+
+class Resolver : noncopyable
+{
+public:
+  typedef boost::asio::ip::udp protocol;
+  typedef protocol::resolver::iterator iterator;
+  typedef protocol::resolver::query query;
+
+public:
+  Resolver(boost::asio::io_service& ioService,
+           const AddressSelector& addressSelector)
+    : m_resolver(ioService)
+    , m_addressSelector(addressSelector)
+    , m_scheduler(ioService)
+  {
+    BOOST_ASSERT(m_addressSelector != nullptr);
+  }
+
+  void
+  asyncResolve(const query& q,
+               const SuccessCallback& onSuccess,
+               const ErrorCallback& onError,
+               time::nanoseconds timeout,
+               const shared_ptr<Resolver>& self)
+  {
+    m_onSuccess = onSuccess;
+    m_onError = onError;
+
+    m_resolver.async_resolve(q, bind(&Resolver::onResolveResult, this, _1, _2, self));
+
+    m_resolveTimeout = m_scheduler.scheduleEvent(timeout, bind(&Resolver::onResolveTimeout, this, self));
+  }
+
+  iterator
+  syncResolve(const query& q)
+  {
+    return selectAddress(m_resolver.resolve(q));
+  }
+
+private:
+  void
+  onResolveResult(const boost::system::error_code& error,
+                  iterator it, const shared_ptr<Resolver>& self)
+  {
+    m_scheduler.cancelEvent(m_resolveTimeout);
+    // ensure the Resolver isn't destructed while callbacks are still pending, see #2653
+    m_resolver.get_io_service().post(bind([] (const shared_ptr<Resolver>&) {}, self));
+
+    if (error) {
+      if (error == boost::asio::error::operation_aborted)
+        return;
+
+      if (m_onError)
+        m_onError("Hostname cannot be resolved: " + error.message());
+
+      return;
+    }
+
+    it = selectAddress(it);
+
+    if (it != iterator() && m_onSuccess) {
+      m_onSuccess(it->endpoint().address());
+    }
+    else if (m_onError) {
+      m_onError("No endpoints match the specified address selector");
+    }
+  }
+
+  void
+  onResolveTimeout(const shared_ptr<Resolver>& self)
+  {
+    m_resolver.cancel();
+    // ensure the Resolver isn't destructed while callbacks are still pending, see #2653
+    m_resolver.get_io_service().post(bind([] (const shared_ptr<Resolver>&) {}, self));
+
+    if (m_onError)
+      m_onError("Hostname resolution timed out");
+  }
+
+  iterator
+  selectAddress(iterator it) const
+  {
+    while (it != iterator() &&
+           !m_addressSelector(it->endpoint().address())) {
+      ++it;
+    }
+
+    return it;
+  }
+
+private:
+  protocol::resolver m_resolver;
+
+  AddressSelector m_addressSelector;
+  SuccessCallback m_onSuccess;
+  ErrorCallback m_onError;
+
+  util::scheduler::Scheduler m_scheduler;
+  util::scheduler::EventId m_resolveTimeout;
+};
+
+void
+asyncResolve(const std::string& host,
+             const SuccessCallback& onSuccess,
+             const ErrorCallback& onError,
+             boost::asio::io_service& ioService,
+             const AddressSelector& addressSelector,
+             time::nanoseconds timeout)
+{
+  auto resolver = make_shared<Resolver>(ref(ioService), addressSelector);
+  resolver->asyncResolve(Resolver::query(host, ""), onSuccess, onError, timeout, resolver);
+  // resolver will be destroyed when async operation finishes or ioService stops
+}
+
+IpAddress
+syncResolve(const std::string& host,
+            boost::asio::io_service& ioService,
+            const AddressSelector& addressSelector)
+{
+  Resolver resolver(ioService, addressSelector);
+  auto it = resolver.syncResolve(Resolver::query(host, ""));
+
+  if (it == Resolver::iterator()) {
+    BOOST_THROW_EXCEPTION(Error("No endpoints match the specified address selector"));
+  }
+
+  return it->endpoint().address();
+}
+
+} // namespace dns
+} // namespace ndn
diff --git a/src/net/dns.hpp b/src/net/dns.hpp
new file mode 100644
index 0000000..79d9a10
--- /dev/null
+++ b/src/net/dns.hpp
@@ -0,0 +1,124 @@
+/* -*- 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.
+ */
+
+#ifndef NDN_NET_DNS_HPP
+#define NDN_NET_DNS_HPP
+
+#include "../util/time.hpp"
+
+#include <boost/asio/ip/address.hpp>
+
+// forward declaration
+namespace boost {
+namespace asio {
+class io_service;
+} // namespace asio
+} // namespace boost
+
+namespace ndn {
+namespace dns {
+
+typedef boost::asio::ip::address IpAddress;
+typedef function<bool (const IpAddress& address)> AddressSelector;
+
+struct AnyAddress
+{
+  bool
+  operator()(const IpAddress& address) const
+  {
+    return true;
+  }
+};
+
+struct Ipv4Only
+{
+  bool
+  operator()(const IpAddress& address) const
+  {
+    return address.is_v4();
+  }
+};
+
+struct Ipv6Only
+{
+  bool
+  operator()(const IpAddress& address) const
+  {
+    return address.is_v6();
+  }
+};
+
+struct Error : public std::runtime_error
+{
+  explicit
+  Error(const std::string& what)
+    : std::runtime_error(what)
+  {
+  }
+};
+
+typedef function<void (const IpAddress& address)> SuccessCallback;
+typedef function<void (const std::string& reason)> ErrorCallback;
+
+/** \brief Asynchronously resolve host
+ *
+ * If an address selector predicate is specified, then each resolved IP address
+ * is checked against the predicate.
+ *
+ * Available address selector predicates:
+ *
+ * - dns::AnyAddress()
+ * - dns::Ipv4Address()
+ * - dns::Ipv6Address()
+ *
+ * \warning Even after the DNS resolution has timed out, it's possible that
+ *          \p ioService keeps running and \p onSuccess is invoked at a later time.
+ *          This could cause segmentation fault if \p onSuccess is deallocated.
+ *          To stop the io_service, explicitly invoke \p ioService.stop().
+ */
+void
+asyncResolve(const std::string& host,
+             const SuccessCallback& onSuccess,
+             const ErrorCallback& onError,
+             boost::asio::io_service& ioService,
+             const AddressSelector& addressSelector = AnyAddress(),
+             time::nanoseconds timeout = time::seconds(4));
+
+/** \brief Synchronously resolve host
+ *
+ * If an address selector predicate is specified, then each resolved IP address
+ * is checked against the predicate.
+ *
+ * Available address selector predicates:
+ *
+ * - dns::AnyAddress()
+ * - dns::Ipv4Address()
+ * - dns::Ipv6Address()
+ */
+IpAddress
+syncResolve(const std::string& host,
+            boost::asio::io_service& ioService,
+            const AddressSelector& addressSelector = AnyAddress());
+
+} // namespace dns
+} // namespace ndn
+
+#endif // NDN_NET_DNS_HPP
diff --git a/src/net/ethernet.cpp b/src/net/ethernet.cpp
new file mode 100644
index 0000000..0efdd58
--- /dev/null
+++ b/src/net/ethernet.cpp
@@ -0,0 +1,147 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2017 Regents of the University of California,
+ *                         Arizona Board of Regents,
+ *                         Colorado State University,
+ *                         University Pierre & Marie Curie, Sorbonne University,
+ *                         Washington University in St. Louis,
+ *                         Beijing Institute of Technology,
+ *                         The University of Memphis.
+ *
+ * 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.
+ */
+
+#include "ethernet.hpp"
+
+#include <boost/functional/hash.hpp>
+
+#include <stdio.h>
+#include <ostream>
+
+namespace ndn {
+namespace ethernet {
+
+Address::Address()
+{
+  fill(0);
+}
+
+Address::Address(uint8_t a1, uint8_t a2, uint8_t a3, uint8_t a4, uint8_t a5, uint8_t a6)
+{
+  data()[0] = a1;
+  data()[1] = a2;
+  data()[2] = a3;
+  data()[3] = a4;
+  data()[4] = a5;
+  data()[5] = a6;
+}
+
+Address::Address(const uint8_t octets[ADDR_LEN])
+{
+  std::copy(octets, octets + size(), begin());
+}
+
+bool
+Address::isBroadcast() const
+{
+  return *this == getBroadcastAddress();
+}
+
+bool
+Address::isMulticast() const
+{
+  return (at(0) & 1) != 0;
+}
+
+bool
+Address::isNull() const
+{
+  return *this == Address();
+}
+
+std::string
+Address::toString(char sep) const
+{
+  char s[18]; // 12 digits + 5 separators + null terminator
+
+  // - apparently gcc-4.6 does not support the 'hh' type modifier
+  // - std::snprintf not found in some environments
+  //   https://redmine.named-data.net/issues/2299 for more information
+  snprintf(s, sizeof(s), "%02x%c%02x%c%02x%c%02x%c%02x%c%02x",
+           at(0), sep, at(1), sep, at(2), sep, at(3), sep, at(4), sep, at(5));
+
+  return std::string(s);
+}
+
+Address
+Address::fromString(const std::string& str)
+{
+  Address a;
+  unsigned short temp[a.size()];
+  char sep[5][2]; // 5 * (1 separator char + 1 null terminator)
+  int n = 0; // num of chars read from the input string
+
+  // apparently gcc-4.6 does not support the 'hh' type modifier
+  int ret = std::sscanf(str.c_str(), "%2hx%1[:-]%2hx%1[:-]%2hx%1[:-]%2hx%1[:-]%2hx%1[:-]%2hx%n",
+                        &temp[0], &sep[0][0], &temp[1], &sep[1][0], &temp[2], &sep[2][0],
+                        &temp[3], &sep[3][0], &temp[4], &sep[4][0], &temp[5], &n);
+
+  if (ret < 11 || static_cast<size_t>(n) != str.length())
+    return Address();
+
+  for (size_t i = 0; i < a.size(); ++i)
+    {
+      // check that all separators are actually the same char (: or -)
+      if (i < 5 && sep[i][0] != sep[0][0])
+        return Address();
+
+      // check that each value fits into a uint8_t
+      if (temp[i] > 0xFF)
+        return Address();
+
+      a[i] = static_cast<uint8_t>(temp[i]);
+    }
+
+  return a;
+}
+
+Address
+getBroadcastAddress()
+{
+  return { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF };
+}
+
+Address
+getDefaultMulticastAddress()
+{
+  return { 0x01, 0x00, 0x5E, 0x00, 0x17, 0xAA };
+}
+
+std::ostream&
+operator<<(std::ostream& o, const Address& a)
+{
+  return o << a.toString();
+}
+
+} // namespace ethernet
+} // namespace ndn
+
+std::size_t
+std::hash<ndn::ethernet::Address>::operator()(const ndn::ethernet::Address& a) const noexcept
+{
+  return boost::hash_range(a.cbegin(), a.cend());
+}
diff --git a/src/net/ethernet.hpp b/src/net/ethernet.hpp
new file mode 100644
index 0000000..d4b8f47
--- /dev/null
+++ b/src/net/ethernet.hpp
@@ -0,0 +1,125 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2017 Regents of the University of California,
+ *                         Arizona Board of Regents,
+ *                         Colorado State University,
+ *                         University Pierre & Marie Curie, Sorbonne University,
+ *                         Washington University in St. Louis,
+ *                         Beijing Institute of Technology,
+ *                         The University of Memphis.
+ *
+ * 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.
+ */
+
+#ifndef NDN_NET_ETHERNET_HPP
+#define NDN_NET_ETHERNET_HPP
+
+#include <array>
+#include <cstdint>
+#include <functional>
+#include <string>
+
+namespace ndn {
+namespace ethernet {
+
+const uint16_t ETHERTYPE_NDN = 0x8624;
+
+const size_t ADDR_LEN     = 6;      ///< Octets in one Ethernet address
+const size_t TYPE_LEN     = 2;      ///< Octets in Ethertype field
+const size_t HDR_LEN      = 14;     ///< Total octets in Ethernet header (without 802.1Q tag)
+const size_t TAG_LEN      = 4;      ///< Octets in 802.1Q tag (TPID + priority + VLAN)
+const size_t MIN_DATA_LEN = 46;     ///< Min octets in Ethernet payload (assuming no 802.1Q tag)
+const size_t MAX_DATA_LEN = 1500;   ///< Max octets in Ethernet payload
+const size_t CRC_LEN      = 4;      ///< Octets in Ethernet frame check sequence
+
+
+/** \brief represents an Ethernet hardware address
+ */
+class Address : public std::array<uint8_t, ADDR_LEN>
+{
+public:
+  /// Constructs a null Ethernet address (00:00:00:00:00:00)
+  Address();
+
+  /// Constructs a new Ethernet address with the given octets
+  Address(uint8_t a1, uint8_t a2, uint8_t a3,
+          uint8_t a4, uint8_t a5, uint8_t a6);
+
+  /// Constructs a new Ethernet address with the given octets
+  explicit
+  Address(const uint8_t octets[ADDR_LEN]);
+
+  /// True if this is a broadcast address (ff:ff:ff:ff:ff:ff)
+  bool
+  isBroadcast() const;
+
+  /// True if this is a multicast address
+  bool
+  isMulticast() const;
+
+  /// True if this is a null address (00:00:00:00:00:00)
+  bool
+  isNull() const;
+
+  /**
+   * \brief Converts the address to a human-readable string
+   *
+   * \param sep A character used to visually separate the octets,
+   *            usually ':' (the default value) or '-'
+   */
+  std::string
+  toString(char sep = ':') const;
+
+  /**
+   * \brief Creates an Address from a string containing an Ethernet address
+   *        in hexadecimal notation, with colons or hyphens as separators
+   *
+   * \param str The string to be parsed
+   * \return Always an instance of Address, which will be null
+   *         if the parsing fails
+   */
+  static Address
+  fromString(const std::string& str);
+};
+
+/// Returns the Ethernet broadcast address (ff:ff:ff:ff:ff:ff)
+Address
+getBroadcastAddress();
+
+/// Returns the default Ethernet multicast address for NDN
+Address
+getDefaultMulticastAddress();
+
+std::ostream&
+operator<<(std::ostream& o, const Address& a);
+
+} // namespace ethernet
+} // namespace ndn
+
+namespace std {
+
+// specialize std::hash<> for ethernet::Address
+template<>
+struct hash<ndn::ethernet::Address>
+{
+  size_t
+  operator()(const ndn::ethernet::Address& a) const noexcept;
+};
+
+} // namespace std
+
+#endif // NDN_NET_ETHERNET_HPP
diff --git a/src/net/face-uri.cpp b/src/net/face-uri.cpp
new file mode 100644
index 0000000..d550fd5
--- /dev/null
+++ b/src/net/face-uri.cpp
@@ -0,0 +1,622 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2013-2017 Regents of the University of California,
+ *                         Arizona Board of Regents,
+ *                         Colorado State University,
+ *                         University Pierre & Marie Curie, Sorbonne University,
+ *                         Washington University in St. Louis,
+ *                         Beijing Institute of Technology,
+ *                         The University of Memphis.
+ *
+ * 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.
+ */
+
+#include "face-uri.hpp"
+#include "dns.hpp"
+
+#include <boost/lexical_cast.hpp>
+#include <boost/mpl/vector.hpp>
+#include <boost/mpl/for_each.hpp>
+#include <boost/regex.hpp>
+#include <set>
+
+namespace ndn {
+
+BOOST_CONCEPT_ASSERT((boost::EqualityComparable<FaceUri>));
+
+FaceUri::FaceUri()
+  : m_isV6(false)
+{
+}
+
+FaceUri::FaceUri(const std::string& uri)
+{
+  if (!parse(uri)) {
+    BOOST_THROW_EXCEPTION(Error("Malformed URI: " + uri));
+  }
+}
+
+FaceUri::FaceUri(const char* uri)
+  : FaceUri(std::string(uri))
+{
+}
+
+bool
+FaceUri::parse(const std::string& uri)
+{
+  m_scheme.clear();
+  m_host.clear();
+  m_port.clear();
+  m_path.clear();
+  m_isV6 = false;
+
+  static const boost::regex protocolExp("(\\w+\\d?(\\+\\w+)?)://([^/]*)(\\/[^?]*)?");
+  boost::smatch protocolMatch;
+  if (!boost::regex_match(uri, protocolMatch, protocolExp)) {
+    return false;
+  }
+  m_scheme = protocolMatch[1];
+  const std::string& authority = protocolMatch[3];
+  m_path = protocolMatch[4];
+
+  // pattern for IPv6 address enclosed in [ ], with optional port number
+  static const boost::regex v6Exp("^\\[([a-fA-F0-9:]+)\\](?:\\:(\\d+))?$");
+  // pattern for Ethernet address in standard hex-digits-and-colons notation
+  static const boost::regex etherExp("^\\[((?:[a-fA-F0-9]{1,2}\\:){5}(?:[a-fA-F0-9]{1,2}))\\]$");
+  // pattern for IPv4-mapped IPv6 address, with optional port number
+  static const boost::regex v4MappedV6Exp("^\\[::ffff:(\\d+(?:\\.\\d+){3})\\](?:\\:(\\d+))?$");
+  // pattern for IPv4/hostname/fd/ifname, with optional port number
+  static const boost::regex v4HostExp("^([^:]+)(?:\\:(\\d+))?$");
+
+  if (authority.empty()) {
+    // UNIX, internal
+  }
+  else {
+    boost::smatch match;
+    m_isV6 = boost::regex_match(authority, match, v6Exp);
+    if (m_isV6 ||
+        boost::regex_match(authority, match, etherExp) ||
+        boost::regex_match(authority, match, v4MappedV6Exp) ||
+        boost::regex_match(authority, match, v4HostExp)) {
+      m_host = match[1];
+      m_port = match[2];
+    }
+    else {
+      return false;
+    }
+  }
+
+  return true;
+}
+
+FaceUri::FaceUri(const boost::asio::ip::udp::endpoint& endpoint)
+{
+  m_isV6 = endpoint.address().is_v6();
+  m_scheme = m_isV6 ? "udp6" : "udp4";
+  m_host = endpoint.address().to_string();
+  m_port = to_string(endpoint.port());
+}
+
+FaceUri::FaceUri(const boost::asio::ip::tcp::endpoint& endpoint)
+{
+  m_isV6 = endpoint.address().is_v6();
+  m_scheme = m_isV6 ? "tcp6" : "tcp4";
+  m_host = endpoint.address().to_string();
+  m_port = to_string(endpoint.port());
+}
+
+FaceUri::FaceUri(const boost::asio::ip::tcp::endpoint& endpoint, const std::string& scheme)
+{
+  m_isV6 = endpoint.address().is_v6();
+  m_scheme = scheme;
+  m_host = endpoint.address().to_string();
+  m_port = to_string(endpoint.port());
+}
+
+#ifdef BOOST_ASIO_HAS_LOCAL_SOCKETS
+FaceUri::FaceUri(const boost::asio::local::stream_protocol::endpoint& endpoint)
+  : m_scheme("unix")
+  , m_path(endpoint.path())
+  , m_isV6(false)
+{
+}
+#endif // BOOST_ASIO_HAS_LOCAL_SOCKETS
+
+FaceUri
+FaceUri::fromFd(int fd)
+{
+  FaceUri uri;
+  uri.m_scheme = "fd";
+  uri.m_host = to_string(fd);
+  return uri;
+}
+
+FaceUri::FaceUri(const ethernet::Address& address)
+  : m_scheme("ether")
+  , m_host(address.toString())
+  , m_isV6(true)
+{
+}
+
+FaceUri
+FaceUri::fromDev(const std::string& ifname)
+{
+  FaceUri uri;
+  uri.m_scheme = "dev";
+  uri.m_host = ifname;
+  return uri;
+}
+
+FaceUri
+FaceUri::fromUdpDev(const boost::asio::ip::udp::endpoint& endpoint, const std::string& ifname)
+{
+  FaceUri uri;
+  uri.m_scheme = endpoint.address().is_v6() ? "udp6+dev" : "udp4+dev";
+  uri.m_host = ifname;
+  uri.m_port = to_string(endpoint.port());
+  return uri;
+}
+
+bool
+FaceUri::operator==(const FaceUri& rhs) const
+{
+  return m_isV6 == rhs.m_isV6 &&
+         m_scheme == rhs.m_scheme &&
+         m_host == rhs.m_host &&
+         m_port == rhs.m_port &&
+         m_path == rhs.m_path;
+}
+
+bool
+FaceUri::operator!=(const FaceUri& rhs) const
+{
+  return !(*this == rhs);
+}
+
+std::string
+FaceUri::toString() const
+{
+  std::ostringstream os;
+  os << *this;
+  return os.str();
+}
+
+std::ostream&
+operator<<(std::ostream& os, const FaceUri& uri)
+{
+  os << uri.m_scheme << "://";
+  if (uri.m_isV6) {
+    os << "[" << uri.m_host << "]";
+  }
+  else {
+    os << uri.m_host;
+  }
+  if (!uri.m_port.empty()) {
+    os << ":" << uri.m_port;
+  }
+  os << uri.m_path;
+  return os;
+}
+
+
+/** \brief a CanonizeProvider provides FaceUri canonization functionality for a group of schemes
+ */
+class CanonizeProvider : noncopyable
+{
+public:
+  virtual
+  ~CanonizeProvider() = default;
+
+  virtual std::set<std::string>
+  getSchemes() const = 0;
+
+  virtual bool
+  isCanonical(const FaceUri& faceUri) const = 0;
+
+  virtual void
+  canonize(const FaceUri& faceUri,
+           const FaceUri::CanonizeSuccessCallback& onSuccess,
+           const FaceUri::CanonizeFailureCallback& onFailure,
+           boost::asio::io_service& io, time::nanoseconds timeout) const = 0;
+};
+
+template<typename Protocol>
+class IpHostCanonizeProvider : public CanonizeProvider
+{
+public:
+  std::set<std::string>
+  getSchemes() const override
+  {
+    return {m_baseScheme, m_v4Scheme, m_v6Scheme};
+  }
+
+  bool
+  isCanonical(const FaceUri& faceUri) const override
+  {
+    if (faceUri.getPort().empty()) {
+      return false;
+    }
+    if (!faceUri.getPath().empty()) {
+      return false;
+    }
+
+    boost::system::error_code ec;
+    boost::asio::ip::address addr;
+    if (faceUri.getScheme() == m_v4Scheme) {
+      addr = boost::asio::ip::address_v4::from_string(faceUri.getHost(), ec);
+    }
+    else if (faceUri.getScheme() == m_v6Scheme) {
+      addr = boost::asio::ip::address_v6::from_string(faceUri.getHost(), ec);
+    }
+    else {
+      return false;
+    }
+
+    return !ec && addr.to_string() == faceUri.getHost() && checkAddress(addr).first;
+  }
+
+  void
+  canonize(const FaceUri& faceUri,
+           const FaceUri::CanonizeSuccessCallback& onSuccess,
+           const FaceUri::CanonizeFailureCallback& onFailure,
+           boost::asio::io_service& io, time::nanoseconds timeout) const override
+  {
+    if (this->isCanonical(faceUri)) {
+      onSuccess(faceUri);
+      return;
+    }
+
+    // make a copy because caller may modify faceUri
+    auto uri = make_shared<FaceUri>(faceUri);
+    boost::system::error_code ec;
+    auto ipAddress = boost::asio::ip::address::from_string(faceUri.getHost(), ec);
+    if (!ec) {
+      // No need to resolve IP address if host is already an IP
+      if ((faceUri.getScheme() == m_v4Scheme && !ipAddress.is_v4()) ||
+          (faceUri.getScheme() == m_v6Scheme && !ipAddress.is_v6())) {
+        return onFailure("IPv4/v6 mismatch");
+      }
+
+      onDnsSuccess(uri, onSuccess, onFailure, ipAddress);
+    }
+    else {
+      dns::AddressSelector addressSelector;
+      if (faceUri.getScheme() == m_v4Scheme) {
+        addressSelector = dns::Ipv4Only();
+      }
+      else if (faceUri.getScheme() == m_v6Scheme) {
+        addressSelector = dns::Ipv6Only();
+      }
+      else {
+        BOOST_ASSERT(faceUri.getScheme() == m_baseScheme);
+        addressSelector = dns::AnyAddress();
+      }
+
+      dns::asyncResolve(faceUri.getHost(),
+        bind(&IpHostCanonizeProvider<Protocol>::onDnsSuccess, this, uri, onSuccess, onFailure, _1),
+        bind(&IpHostCanonizeProvider<Protocol>::onDnsFailure, this, uri, onFailure, _1),
+        io, addressSelector, timeout);
+    }
+  }
+
+protected:
+  explicit
+  IpHostCanonizeProvider(const std::string& baseScheme,
+                         uint16_t defaultUnicastPort = 6363,
+                         uint16_t defaultMulticastPort = 56363)
+    : m_baseScheme(baseScheme)
+    , m_v4Scheme(baseScheme + '4')
+    , m_v6Scheme(baseScheme + '6')
+    , m_defaultUnicastPort(defaultUnicastPort)
+    , m_defaultMulticastPort(defaultMulticastPort)
+  {
+  }
+
+private:
+  void
+  onDnsSuccess(const shared_ptr<FaceUri>& faceUri,
+               const FaceUri::CanonizeSuccessCallback& onSuccess,
+               const FaceUri::CanonizeFailureCallback& onFailure,
+               const dns::IpAddress& ipAddress) const
+  {
+    bool isOk = false;
+    std::string reason;
+    std::tie(isOk, reason) = this->checkAddress(ipAddress);
+    if (!isOk) {
+      return onFailure(reason);
+    }
+
+    uint16_t port = 0;
+    if (faceUri->getPort().empty()) {
+      port = ipAddress.is_multicast() ? m_defaultMulticastPort : m_defaultUnicastPort;
+    }
+    else {
+      try {
+        port = boost::lexical_cast<uint16_t>(faceUri->getPort());
+      }
+      catch (const boost::bad_lexical_cast&) {
+        return onFailure("invalid port number '" + faceUri->getPort() + "'");
+      }
+    }
+
+    FaceUri canonicalUri(typename Protocol::endpoint(ipAddress, port));
+    BOOST_ASSERT(canonicalUri.isCanonical());
+    onSuccess(canonicalUri);
+  }
+
+  void
+  onDnsFailure(const shared_ptr<FaceUri>& faceUri,
+               const FaceUri::CanonizeFailureCallback& onFailure,
+               const std::string& reason) const
+  {
+    onFailure(reason);
+  }
+
+  /** \brief when overriden in a subclass, check the IP address is allowable
+   *  \return (true,ignored) if the address is allowable;
+   *          (false,reason) if the address is not allowable.
+   */
+  virtual std::pair<bool, std::string>
+  checkAddress(const dns::IpAddress& ipAddress) const
+  {
+    return {true, ""};
+  }
+
+private:
+  std::string m_baseScheme;
+  std::string m_v4Scheme;
+  std::string m_v6Scheme;
+  uint16_t m_defaultUnicastPort;
+  uint16_t m_defaultMulticastPort;
+};
+
+class UdpCanonizeProvider : public IpHostCanonizeProvider<boost::asio::ip::udp>
+{
+public:
+  UdpCanonizeProvider()
+    : IpHostCanonizeProvider("udp")
+  {
+  }
+
+protected:
+  // checkAddress is not overriden:
+  // Although NFD doesn't support IPv6 multicast, it's an implementation limitation.
+  // FaceMgmt protocol allows IPv6 multicast address in UDP.
+};
+
+class TcpCanonizeProvider : public IpHostCanonizeProvider<boost::asio::ip::tcp>
+{
+public:
+  TcpCanonizeProvider()
+    : IpHostCanonizeProvider("tcp")
+  {
+  }
+
+protected:
+  std::pair<bool, std::string>
+  checkAddress(const dns::IpAddress& ipAddress) const override
+  {
+    if (ipAddress.is_multicast()) {
+      return {false, "cannot use multicast address"};
+    }
+    return {true, ""};
+  }
+};
+
+class EtherCanonizeProvider : public CanonizeProvider
+{
+public:
+  std::set<std::string>
+  getSchemes() const override
+  {
+    return {"ether"};
+  }
+
+  bool
+  isCanonical(const FaceUri& faceUri) const override
+  {
+    if (!faceUri.getPort().empty()) {
+      return false;
+    }
+    if (!faceUri.getPath().empty()) {
+      return false;
+    }
+
+    auto addr = ethernet::Address::fromString(faceUri.getHost());
+    return addr.toString() == faceUri.getHost();
+  }
+
+  void
+  canonize(const FaceUri& faceUri,
+           const FaceUri::CanonizeSuccessCallback& onSuccess,
+           const FaceUri::CanonizeFailureCallback& onFailure,
+           boost::asio::io_service& io, time::nanoseconds timeout) const override
+  {
+    auto addr = ethernet::Address::fromString(faceUri.getHost());
+    if (addr.isNull()) {
+      return onFailure("invalid ethernet address '" + faceUri.getHost() + "'");
+    }
+
+    FaceUri canonicalUri(addr);
+    BOOST_ASSERT(canonicalUri.isCanonical());
+    onSuccess(canonicalUri);
+  }
+};
+
+class DevCanonizeProvider : public CanonizeProvider
+{
+public:
+  std::set<std::string>
+  getSchemes() const override
+  {
+    return {"dev"};
+  }
+
+  bool
+  isCanonical(const FaceUri& faceUri) const override
+  {
+    return !faceUri.getHost().empty() && faceUri.getPort().empty() && faceUri.getPath().empty();
+  }
+
+  void
+  canonize(const FaceUri& faceUri,
+           const FaceUri::CanonizeSuccessCallback& onSuccess,
+           const FaceUri::CanonizeFailureCallback& onFailure,
+           boost::asio::io_service& io, time::nanoseconds timeout) const override
+  {
+    if (faceUri.getHost().empty()) {
+      onFailure("network interface name is missing");
+      return;
+    }
+    if (!faceUri.getPort().empty()) {
+      onFailure("port number is not allowed");
+      return;
+    }
+    if (!faceUri.getPath().empty() && faceUri.getPath() != "/") { // permit trailing slash only
+      onFailure("path is not allowed");
+      return;
+    }
+
+    FaceUri canonicalUri = FaceUri::fromDev(faceUri.getHost());
+    BOOST_ASSERT(canonicalUri.isCanonical());
+    onSuccess(canonicalUri);
+  }
+};
+
+class UdpDevCanonizeProvider : public CanonizeProvider
+{
+public:
+  std::set<std::string>
+  getSchemes() const override
+  {
+    return {"udp4+dev", "udp6+dev"};
+  }
+
+  bool
+  isCanonical(const FaceUri& faceUri) const override
+  {
+    if (faceUri.getPort().empty()) {
+      return false;
+    }
+    if (!faceUri.getPath().empty()) {
+      return false;
+    }
+    return true;
+  }
+
+  void
+  canonize(const FaceUri& faceUri,
+           const FaceUri::CanonizeSuccessCallback& onSuccess,
+           const FaceUri::CanonizeFailureCallback& onFailure,
+           boost::asio::io_service& io, time::nanoseconds timeout) const override
+  {
+    if (this->isCanonical(faceUri)) {
+      onSuccess(faceUri);
+    }
+    else {
+      onFailure("cannot canonize " + faceUri.toString());
+    }
+  }
+};
+
+using CanonizeProviders = boost::mpl::vector<UdpCanonizeProvider*,
+                                             TcpCanonizeProvider*,
+                                             EtherCanonizeProvider*,
+                                             DevCanonizeProvider*,
+                                             UdpDevCanonizeProvider*>;
+using CanonizeProviderTable = std::map<std::string, shared_ptr<CanonizeProvider>>;
+
+class CanonizeProviderTableInitializer
+{
+public:
+  explicit
+  CanonizeProviderTableInitializer(CanonizeProviderTable& providerTable)
+    : m_providerTable(providerTable)
+  {
+  }
+
+  template<typename CP>
+  void
+  operator()(CP*)
+  {
+    shared_ptr<CanonizeProvider> cp = make_shared<CP>();
+    auto schemes = cp->getSchemes();
+    BOOST_ASSERT(!schemes.empty());
+
+    for (const auto& scheme : schemes) {
+      BOOST_ASSERT(m_providerTable.count(scheme) == 0);
+      m_providerTable[scheme] = cp;
+    }
+  }
+
+private:
+  CanonizeProviderTable& m_providerTable;
+};
+
+static const CanonizeProvider*
+getCanonizeProvider(const std::string& scheme)
+{
+  static CanonizeProviderTable providerTable;
+  if (providerTable.empty()) {
+    boost::mpl::for_each<CanonizeProviders>(CanonizeProviderTableInitializer(providerTable));
+    BOOST_ASSERT(!providerTable.empty());
+  }
+
+  auto it = providerTable.find(scheme);
+  return it == providerTable.end() ? nullptr : it->second.get();
+}
+
+
+bool
+FaceUri::canCanonize(const std::string& scheme)
+{
+  return getCanonizeProvider(scheme) != nullptr;
+}
+
+bool
+FaceUri::isCanonical() const
+{
+  const CanonizeProvider* cp = getCanonizeProvider(this->getScheme());
+  if (cp == nullptr) {
+    return false;
+  }
+
+  return cp->isCanonical(*this);
+}
+
+void
+FaceUri::canonize(const CanonizeSuccessCallback& onSuccess,
+                  const CanonizeFailureCallback& onFailure,
+                  boost::asio::io_service& io, time::nanoseconds timeout) const
+{
+  const CanonizeProvider* cp = getCanonizeProvider(this->getScheme());
+  if (cp == nullptr) {
+    if (onFailure) {
+      onFailure("scheme not supported");
+    }
+    return;
+  }
+
+  static CanonizeSuccessCallback successNop = bind([]{});
+  static CanonizeFailureCallback failureNop = bind([]{});
+  cp->canonize(*this,
+               onSuccess ? onSuccess : successNop,
+               onFailure ? onFailure : failureNop,
+               io, timeout);
+}
+
+} // namespace ndn
diff --git a/src/net/face-uri.hpp b/src/net/face-uri.hpp
new file mode 100644
index 0000000..12276cd
--- /dev/null
+++ b/src/net/face-uri.hpp
@@ -0,0 +1,197 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2013-2017 Regents of the University of California,
+ *                         Arizona Board of Regents,
+ *                         Colorado State University,
+ *                         University Pierre & Marie Curie, Sorbonne University,
+ *                         Washington University in St. Louis,
+ *                         Beijing Institute of Technology,
+ *                         The University of Memphis.
+ *
+ * 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.
+ */
+
+#ifndef NDN_NET_FACE_URI_HPP
+#define NDN_NET_FACE_URI_HPP
+
+#include "ethernet.hpp"
+#include "../util/time.hpp"
+
+#include <boost/asio/ip/tcp.hpp>
+#include <boost/asio/ip/udp.hpp>
+#include <boost/asio/local/stream_protocol.hpp>
+
+namespace ndn {
+
+/** \brief represents the underlying protocol and address used by a Face
+ *  \sa https://redmine.named-data.net/projects/nfd/wiki/FaceMgmt#FaceUri
+ */
+class FaceUri
+{
+public:
+  class Error : public std::invalid_argument
+  {
+  public:
+    explicit
+    Error(const std::string& what)
+      : std::invalid_argument(what)
+    {
+    }
+  };
+
+  FaceUri();
+
+  /** \brief construct by parsing
+   *
+   *  \param uri scheme://host[:port]/path
+   *  \throw FaceUri::Error if URI cannot be parsed
+   */
+  explicit
+  FaceUri(const std::string& uri);
+
+  // This overload is needed so that calls with string literal won't be
+  // resolved to boost::asio::local::stream_protocol::endpoint overload.
+  explicit
+  FaceUri(const char* uri);
+
+  /// exception-safe parsing
+  bool
+  parse(const std::string& uri);
+
+public: // scheme-specific construction
+  /// construct udp4 or udp6 canonical FaceUri
+  explicit
+  FaceUri(const boost::asio::ip::udp::endpoint& endpoint);
+
+  /// construct tcp4 or tcp6 canonical FaceUri
+  explicit
+  FaceUri(const boost::asio::ip::tcp::endpoint& endpoint);
+
+  /// construct tcp canonical FaceUri with custom scheme
+  FaceUri(const boost::asio::ip::tcp::endpoint& endpoint, const std::string& scheme);
+
+#ifdef BOOST_ASIO_HAS_LOCAL_SOCKETS
+  /// construct unix canonical FaceUri
+  explicit
+  FaceUri(const boost::asio::local::stream_protocol::endpoint& endpoint);
+#endif // BOOST_ASIO_HAS_LOCAL_SOCKETS
+
+  /// create fd FaceUri from file descriptor
+  static FaceUri
+  fromFd(int fd);
+
+  /// construct ether canonical FaceUri
+  explicit
+  FaceUri(const ethernet::Address& address);
+
+  /// create dev FaceUri from network device name
+  static FaceUri
+  fromDev(const std::string& ifname);
+
+  /// create udp4 or udp6 NIC-associated FaceUri from endpoint and network device name
+  static FaceUri
+  fromUdpDev(const boost::asio::ip::udp::endpoint& endpoint, const std::string& ifname);
+
+public: // getters
+  /// get scheme (protocol)
+  const std::string&
+  getScheme() const
+  {
+    return m_scheme;
+  }
+
+  /// get host (domain)
+  const std::string&
+  getHost() const
+  {
+    return m_host;
+  }
+
+  /// get port
+  const std::string&
+  getPort() const
+  {
+    return m_port;
+  }
+
+  /// get path
+  const std::string&
+  getPath() const
+  {
+    return m_path;
+  }
+
+  /// write as a string
+  std::string
+  toString() const;
+
+public: // EqualityComparable concept
+  bool
+  operator==(const FaceUri& rhs) const;
+
+  bool
+  operator!=(const FaceUri& rhs) const;
+
+public: // canonical FaceUri
+  /** \return whether a FaceUri of the scheme can be canonized
+   */
+  static bool
+  canCanonize(const std::string& scheme);
+
+  /** \brief determine whether this FaceUri is in canonical form
+   *  \return true if this FaceUri is in canonical form,
+   *          false if this FaceUri is not in canonical form or
+   *          or it's undetermined whether this FaceUri is in canonical form
+   */
+  bool
+  isCanonical() const;
+
+  typedef function<void(const FaceUri&)> CanonizeSuccessCallback;
+  typedef function<void(const std::string& reason)> CanonizeFailureCallback;
+
+  /** \brief asynchronously convert this FaceUri to canonical form
+   *  \param onSuccess function to call after this FaceUri is converted to canonical form
+   *  \note A new FaceUri in canonical form will be created; this FaceUri is unchanged.
+   *  \param onFailure function to call if this FaceUri cannot be converted to canonical form
+   *  \param io        reference to `boost::asio::io_service` instance
+   *  \param timeout   maximum allowable duration of the operations.
+   *                   It's intentional not to provide a default value: the caller should set
+   *                   a reasonable value in balance between network delay and user experience.
+   */
+  void
+  canonize(const CanonizeSuccessCallback& onSuccess,
+           const CanonizeFailureCallback& onFailure,
+           boost::asio::io_service& io,
+           time::nanoseconds timeout) const;
+
+private:
+  std::string m_scheme;
+  std::string m_host;
+  std::string m_port;
+  std::string m_path;
+  /// whether to add [] around host when writing string
+  bool m_isV6;
+
+  friend std::ostream& operator<<(std::ostream& os, const FaceUri& uri);
+};
+
+std::ostream&
+operator<<(std::ostream& os, const FaceUri& uri);
+
+} // namespace ndn
+
+#endif // NDN_NET_FACE_URI_HPP
diff --git a/src/net/network-address.cpp b/src/net/network-address.cpp
new file mode 100644
index 0000000..148c3ec
--- /dev/null
+++ b/src/net/network-address.cpp
@@ -0,0 +1,60 @@
+/* -*- 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>
+ */
+
+#include "network-address.hpp"
+
+namespace ndn {
+namespace net {
+
+std::ostream&
+operator<<(std::ostream& os, AddressScope scope)
+{
+  switch (scope) {
+    case AddressScope::NOWHERE:
+      return os << "nowhere";
+    case AddressScope::HOST:
+      return os << "host";
+    case AddressScope::LINK:
+      return os << "link";
+    case AddressScope::GLOBAL:
+      return os << "global";
+  }
+  return os;
+}
+
+NetworkAddress::NetworkAddress()
+  : m_family(AddressFamily::UNSPECIFIED)
+  , m_flags(0)
+  , m_scope(AddressScope::NOWHERE)
+  , m_prefixLength(0)
+{
+}
+
+std::ostream&
+operator<<(std::ostream& os, const NetworkAddress& addr)
+{
+  return os << addr.getIp() << '/' << static_cast<unsigned int>(addr.getPrefixLength());
+}
+
+} // namespace net
+} // namespace ndn
diff --git a/src/net/network-address.hpp b/src/net/network-address.hpp
new file mode 100644
index 0000000..632a30f
--- /dev/null
+++ b/src/net/network-address.hpp
@@ -0,0 +1,130 @@
+/* -*- 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_NET_NETWORK_ADDRESS_HPP
+#define NDN_NET_NETWORK_ADDRESS_HPP
+
+#include "network-monitor.hpp"
+
+#include <boost/asio/ip/address.hpp>
+
+namespace ndn {
+namespace net {
+
+enum class AddressFamily {
+  UNSPECIFIED,
+  V4,
+  V6,
+};
+
+enum class AddressScope {
+  NOWHERE,
+  HOST,
+  LINK,
+  GLOBAL,
+};
+
+std::ostream&
+operator<<(std::ostream& os, AddressScope scope);
+
+/**
+ * @brief Stores one IP address supported by a network interface.
+ */
+class NetworkAddress
+{
+public: // getters
+  /** @brief Returns the address family
+   */
+  AddressFamily
+  getFamily() const
+  {
+    return m_family;
+  }
+
+  /** @brief Returns the IP address (v4 or v6)
+   */
+  boost::asio::ip::address
+  getIp() const
+  {
+    return m_ip;
+  }
+
+  /** @brief Returns the IP broadcast address
+   */
+  boost::asio::ip::address
+  getBroadcast() const
+  {
+    return m_broadcast;
+  }
+
+  /** @brief Returns a bitset of platform-specific flags enabled on the address
+   */
+  uint32_t
+  getFlags() const
+  {
+    return m_flags;
+  }
+
+  /** @brief Returns the address scope
+   */
+  AddressScope
+  getScope() const
+  {
+    return m_scope;
+  }
+
+  /** @brief Returns the prefix length
+   */
+  uint8_t
+  getPrefixLength() const
+  {
+    return m_prefixLength;
+  }
+
+  friend bool
+  operator<(const NetworkAddress& a, const NetworkAddress& b)
+  {
+    return a.m_ip < b.m_ip;
+  }
+
+private: // constructor
+  NetworkAddress();
+
+private:
+  friend class NetworkMonitor::Impl;
+
+  AddressFamily m_family;
+  boost::asio::ip::address m_ip;
+  boost::asio::ip::address m_broadcast;
+  uint32_t m_flags; // IFA_F_* in if_addr.h
+  AddressScope m_scope;
+  uint8_t m_prefixLength;
+};
+
+std::ostream&
+operator<<(std::ostream& os, const NetworkAddress& address);
+
+} // namespace net
+} // namespace ndn
+
+#endif // NDN_NET_NETWORK_ADDRESS_HPP
diff --git a/src/net/network-interface.cpp b/src/net/network-interface.cpp
new file mode 100644
index 0000000..a03d850
--- /dev/null
+++ b/src/net/network-interface.cpp
@@ -0,0 +1,240 @@
+/* -*- 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>
+ */
+
+#include "network-interface.hpp"
+#include "detail/linux-if-constants.hpp"
+#include "../util/logger.hpp"
+#include "../util/string-helper.hpp"
+
+#include <net/if.h>
+
+NDN_LOG_INIT(ndn.NetworkMonitor);
+
+namespace ndn {
+namespace net {
+
+NetworkInterface::NetworkInterface()
+  : m_index(0)
+  , m_type(InterfaceType::UNKNOWN)
+  , m_flags(0)
+  , m_state(InterfaceState::UNKNOWN)
+  , m_mtu(0)
+{
+}
+
+bool
+NetworkInterface::addNetworkAddress(const NetworkAddress& address)
+{
+  if (!address.getIp().is_unspecified()) {
+    // need to erase the existing address before inserting
+    // because the address flags may have changed
+    bool isNew = m_netAddresses.erase(address) == 0;
+    m_netAddresses.insert(address);
+    if (isNew) {
+      NDN_LOG_DEBUG("added address " << address << " to " << m_name);
+      onAddressAdded(address);
+      return true;
+    }
+  }
+  return false;
+}
+
+bool
+NetworkInterface::removeNetworkAddress(const NetworkAddress& address)
+{
+  if (m_netAddresses.erase(address) > 0) {
+    NDN_LOG_DEBUG("removed address " << address << " from " << m_name);
+    onAddressRemoved(address);
+    return true;
+  }
+  return false;
+}
+
+void
+NetworkInterface::setIndex(int index)
+{
+  m_index = index;
+}
+
+void
+NetworkInterface::setName(const std::string& name)
+{
+  BOOST_ASSERT(!name.empty());
+  m_name = name;
+}
+
+void
+NetworkInterface::setType(InterfaceType type)
+{
+  m_type = type;
+}
+
+void
+NetworkInterface::setFlags(uint32_t flags)
+{
+  m_flags = flags;
+}
+
+void
+NetworkInterface::setState(InterfaceState state)
+{
+  if (m_state != state) {
+    std::swap(m_state, state);
+    onStateChanged(state, m_state);
+  }
+}
+
+void
+NetworkInterface::setMtu(uint32_t mtu)
+{
+  if (m_mtu != mtu) {
+    std::swap(m_mtu, mtu);
+    onMtuChanged(mtu, m_mtu);
+  }
+}
+
+void
+NetworkInterface::setEthernetAddress(const ethernet::Address& address)
+{
+  m_etherAddress = address;
+}
+
+void
+NetworkInterface::setEthernetBroadcastAddress(const ethernet::Address& address)
+{
+  m_etherBrdAddress = address;
+}
+
+std::ostream&
+operator<<(std::ostream& os, InterfaceType type)
+{
+  switch (type) {
+    case InterfaceType::UNKNOWN:
+      return os << "unknown";
+    case InterfaceType::LOOPBACK:
+      return os << "loopback";
+    case InterfaceType::ETHERNET:
+      return os << "ether";
+  }
+  return os;
+}
+
+std::ostream&
+operator<<(std::ostream& os, InterfaceState state)
+{
+  switch (state) {
+    case InterfaceState::UNKNOWN:
+      return os << "unknown";
+    case InterfaceState::DOWN:
+      return os << "down";
+    case InterfaceState::NO_CARRIER:
+      return os << "no-carrier";
+    case InterfaceState::DORMANT:
+      return os << "dormant";
+    case InterfaceState::RUNNING:
+      return os << "running";
+  }
+  return os;
+}
+
+static void
+printFlag(std::ostream& os, uint32_t& flags, uint32_t flagVal, const char* flagStr)
+{
+  if (flags & flagVal) {
+    flags &= ~flagVal;
+    os << flagStr << (flags ? "," : "");
+  }
+}
+
+std::ostream&
+operator<<(std::ostream& os, const NetworkInterface& netif)
+{
+  os << netif.getIndex() << ": " << netif.getName() << ": ";
+
+  auto flags = netif.getFlags();
+  os << "<";
+#define PRINT_IFF(flag) printFlag(os, flags, IFF_##flag, #flag)
+  PRINT_IFF(UP);
+  PRINT_IFF(BROADCAST);
+  PRINT_IFF(DEBUG);
+  PRINT_IFF(LOOPBACK);
+  PRINT_IFF(POINTOPOINT);
+#if defined(IFF_NOTRAILERS)
+  PRINT_IFF(NOTRAILERS);
+#endif
+  PRINT_IFF(RUNNING);
+  PRINT_IFF(NOARP);
+  PRINT_IFF(PROMISC);
+  PRINT_IFF(ALLMULTI);
+  PRINT_IFF(MULTICAST);
+#if defined(__linux__)
+  PRINT_IFF(MASTER);
+  PRINT_IFF(SLAVE);
+  PRINT_IFF(PORTSEL);
+  PRINT_IFF(AUTOMEDIA);
+  PRINT_IFF(DYNAMIC);
+#elif defined(__APPLE__) || defined(__FreeBSD__)
+  PRINT_IFF(OACTIVE);
+  PRINT_IFF(SIMPLEX);
+  PRINT_IFF(LINK0);
+  PRINT_IFF(LINK1);
+  PRINT_IFF(LINK2);
+#endif
+#if defined(__FreeBSD__)
+  PRINT_IFF(CANTCONFIG);
+  PRINT_IFF(PPROMISC);
+  PRINT_IFF(MONITOR);
+  PRINT_IFF(STATICARP);
+  PRINT_IFF(DYING);
+  PRINT_IFF(RENAMING);
+#endif
+#undef PRINT_IFF
+#if defined(__linux__)
+#define PRINT_IF_FLAG(flag) printFlag(os, flags, linux_if::FLAG_##flag, #flag)
+  PRINT_IF_FLAG(LOWER_UP);
+  PRINT_IF_FLAG(DORMANT);
+  PRINT_IF_FLAG(ECHO);
+#undef PRINT_IF_FLAG
+#endif
+  if (flags) {
+    // print unknown flags in hex
+    os << AsHex{flags};
+  }
+  os << ">";
+
+  os << " state " << netif.getState() << " mtu " << netif.getMtu() << "\n"
+     << "    link/" << netif.getType() << " " << netif.getEthernetAddress()
+     << " brd " << netif.getEthernetBroadcastAddress() << "\n";
+
+  for (const auto& addr : netif.getNetworkAddresses()) {
+    os << "    " << (addr.getFamily() == AddressFamily::V4 ? "inet " : "inet6 ") << addr;
+    if (netif.canBroadcast() && !addr.getBroadcast().is_unspecified())
+      os << " brd " << addr.getBroadcast();
+    os << " scope " << addr.getScope() << "\n";
+  }
+
+  return os;
+}
+
+} // namespace net
+} // namespace ndn
diff --git a/src/net/network-interface.hpp b/src/net/network-interface.hpp
new file mode 100644
index 0000000..f5fff7e
--- /dev/null
+++ b/src/net/network-interface.hpp
@@ -0,0 +1,256 @@
+/* -*- 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_NET_NETWORK_INTERFACE_HPP
+#define NDN_NET_NETWORK_INTERFACE_HPP
+
+#include "ethernet.hpp"
+#include "network-address.hpp"
+#include "network-monitor.hpp"
+#include "../util/signal.hpp"
+
+#include <set>
+
+namespace ndn {
+namespace net {
+
+/** @brief Indicates the hardware type of a network interface
+ */
+enum class InterfaceType {
+  UNKNOWN,
+  LOOPBACK,
+  ETHERNET,
+  // we do not support anything else for now
+};
+
+std::ostream&
+operator<<(std::ostream& os, InterfaceType type);
+
+/** @brief Indicates the state of a network interface
+ */
+enum class InterfaceState {
+  UNKNOWN,    ///< interface is in an unknown state
+  DOWN,       ///< interface is administratively down
+  NO_CARRIER, ///< interface is administratively up but has no carrier
+  DORMANT,    ///< interface has a carrier but it cannot send or receive normal user traffic yet
+  RUNNING,    ///< interface can be used to send and receive packets
+};
+
+std::ostream&
+operator<<(std::ostream& os, InterfaceState state);
+
+/**
+ * @brief Represents one network interface attached to the host.
+ *
+ * Each network interface has a unique index, a name, and a set of flags indicating its
+ * capabilities and current state. It may contain one hardware (Ethernet) address, and
+ * zero or more network-layer (IP) addresses. Specific signals are emitted when the
+ * interface data change.
+ */
+class NetworkInterface
+{
+public: // signals
+  /** @brief Fires when interface state changes
+   */
+  util::Signal<NetworkInterface, InterfaceState /*old*/, InterfaceState /*new*/> onStateChanged;
+
+  /** @brief Fires when interface mtu changes
+   */
+  util::Signal<NetworkInterface, uint32_t /*old*/, uint32_t /*new*/> onMtuChanged;
+
+  /** @brief Fires when a network-layer address is added to the interface
+   */
+  util::Signal<NetworkInterface, NetworkAddress> onAddressAdded;
+
+  /** @brief Fires when a network-layer address is removed from the interface
+   */
+  util::Signal<NetworkInterface, NetworkAddress> onAddressRemoved;
+
+public: // getters
+  /** @brief Returns an opaque ID that uniquely identifies the interface on the system
+   */
+  int
+  getIndex() const
+  {
+    return m_index;
+  }
+
+  /** @brief Returns the name of the interface, unique on the system
+   */
+  std::string
+  getName() const
+  {
+    return m_name;
+  }
+
+  /** @brief Returns the hardware type of the interface
+   */
+  InterfaceType
+  getType() const
+  {
+    return m_type;
+  }
+
+  /** @brief Returns a bitset of platform-specific flags enabled on the interface
+   */
+  uint32_t
+  getFlags() const
+  {
+    return m_flags;
+  }
+
+  /** @brief Returns the current state of the interface
+   */
+  InterfaceState
+  getState() const
+  {
+    return m_state;
+  }
+
+  /** @brief Returns the MTU (maximum transmission unit) of the interface
+   */
+  uint32_t
+  getMtu() const
+  {
+    return m_mtu;
+  }
+
+  /** @brief Returns the link-layer (Ethernet) address of the interface
+   */
+  ethernet::Address
+  getEthernetAddress() const
+  {
+    return m_etherAddress;
+  }
+
+  /** @brief Returns the link-layer (Ethernet) broadcast address of the interface
+   */
+  ethernet::Address
+  getEthernetBroadcastAddress() const
+  {
+    return m_etherBrdAddress;
+  }
+
+  /** @brief Returns a list of all network-layer addresses present on the interface
+   */
+  const std::set<NetworkAddress>&
+  getNetworkAddresses() const
+  {
+    return m_netAddresses;
+  }
+
+  /** @brief Returns true if the interface is a loopback interface
+   */
+  bool
+  isLoopback() const
+  {
+    return (m_flags & IFF_LOOPBACK) != 0;
+  }
+
+  /** @brief Returns true if the interface is a point-to-point interface
+   */
+  bool
+  isPointToPoint() const
+  {
+    return (m_flags & IFF_POINTOPOINT) != 0;
+  }
+
+  /** @brief Returns true if the interface supports broadcast communication
+   */
+  bool
+  canBroadcast() const
+  {
+    return (m_flags & IFF_BROADCAST) != 0;
+  }
+
+  /** @brief Returns true if the interface supports multicast communication
+   */
+  bool
+  canMulticast() const
+  {
+    return (m_flags & IFF_MULTICAST) != 0;
+  }
+
+  /** @brief Returns true if the interface is administratively up
+   */
+  bool
+  isUp() const
+  {
+    return (m_flags & IFF_UP) != 0;
+  }
+
+private: // constructor
+  NetworkInterface();
+
+private: // modifiers
+  bool
+  addNetworkAddress(const NetworkAddress& address);
+
+  bool
+  removeNetworkAddress(const NetworkAddress& address);
+
+  void
+  setIndex(int index);
+
+  void
+  setName(const std::string& name);
+
+  void
+  setType(InterfaceType type);
+
+  void
+  setFlags(uint32_t flags);
+
+  void
+  setState(InterfaceState state);
+
+  void
+  setMtu(uint32_t mtu);
+
+  void
+  setEthernetAddress(const ethernet::Address& address);
+
+  void
+  setEthernetBroadcastAddress(const ethernet::Address& address);
+
+private:
+  friend class NetworkMonitor::Impl;
+
+  int m_index;
+  std::string m_name;
+  InterfaceType m_type;
+  uint32_t m_flags; // IFF_* in <net/if.h>
+  InterfaceState m_state;
+  uint32_t m_mtu;
+  ethernet::Address m_etherAddress;
+  ethernet::Address m_etherBrdAddress;
+  std::set<NetworkAddress> m_netAddresses;
+};
+
+std::ostream&
+operator<<(std::ostream& os, const NetworkInterface& interface);
+
+} // namespace net
+} // namespace ndn
+
+#endif // NDN_NET_NETWORK_INTERFACE_HPP
diff --git a/src/net/network-monitor.cpp b/src/net/network-monitor.cpp
new file mode 100644
index 0000000..c4a2e2e
--- /dev/null
+++ b/src/net/network-monitor.cpp
@@ -0,0 +1,65 @@
+/* -*- 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 Alexander Afanasyev <alexander.afanasyev@ucla.edu>
+ * @author Davide Pesavento <davide.pesavento@lip6.fr>
+ */
+
+#include "network-monitor.hpp"
+#include "ndn-cxx-config.hpp"
+
+#if defined(NDN_CXX_HAVE_OSX_FRAMEWORKS)
+#include "detail/network-monitor-impl-osx.hpp"
+#elif defined(NDN_CXX_HAVE_RTNETLINK)
+#include "detail/network-monitor-impl-rtnl.hpp"
+#else
+#include "detail/network-monitor-impl-noop.hpp"
+#endif
+
+namespace ndn {
+namespace net {
+
+NetworkMonitor::NetworkMonitor(boost::asio::io_service& io)
+  : m_impl(make_unique<Impl>(*this, io))
+{
+}
+
+NetworkMonitor::~NetworkMonitor() = default;
+
+uint32_t
+NetworkMonitor::getCapabilities() const
+{
+  return m_impl->getCapabilities();
+}
+
+shared_ptr<NetworkInterface>
+NetworkMonitor::getNetworkInterface(const std::string& ifname) const
+{
+  return m_impl->getNetworkInterface(ifname);
+}
+
+std::vector<shared_ptr<NetworkInterface>>
+NetworkMonitor::listNetworkInterfaces() const
+{
+  return m_impl->listNetworkInterfaces();
+}
+
+} // namespace net
+} // namespace ndn
diff --git a/src/net/network-monitor.hpp b/src/net/network-monitor.hpp
new file mode 100644
index 0000000..f3326b7
--- /dev/null
+++ b/src/net/network-monitor.hpp
@@ -0,0 +1,135 @@
+/* -*- 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 Alexander Afanasyev <alexander.afanasyev@ucla.edu>
+ * @author Davide Pesavento <davide.pesavento@lip6.fr>
+ */
+
+#ifndef NDN_NET_NETWORK_MONITOR_HPP
+#define NDN_NET_NETWORK_MONITOR_HPP
+
+#include "../util/signal.hpp"
+
+#include <vector>
+
+// forward declaration
+namespace boost {
+namespace asio {
+class io_service;
+} // namespace asio
+} // namespace boost
+
+namespace ndn {
+namespace net {
+
+class NetworkInterface;
+
+/**
+ * @brief Network interfaces monitor
+ *
+ * Maintains an up-to-date view of every system network interface and notifies when an interface
+ * is added or removed.
+ *
+ * @note Implementation of this class is platform dependent and not all supported platforms
+ *       are supported:
+ *       - OS X: SystemConfiguration and CFNotificationCenterAddObserver notifications (no
+ *         notification on MTU change)
+ *       - Linux: rtnetlink notifications
+ */
+class NetworkMonitor : noncopyable
+{
+public:
+  class Error : public std::runtime_error
+  {
+  public:
+    explicit
+    Error(const std::string& what)
+      : std::runtime_error(what)
+    {
+    }
+  };
+
+  class Impl;
+
+  /**
+   * @brief Construct instance, request enumeration of all network interfaces, and start
+   *        monitoring for network state changes
+   *
+   * @param io io_service thread that will dispatch events
+   * @throw Error when network monitoring is not supported or there is an error starting monitoring
+   */
+  explicit
+  NetworkMonitor(boost::asio::io_service& io);
+
+  ~NetworkMonitor();
+
+  enum Capability : uint32_t {
+    /// NetworkMonitor is not supported and is a no-op
+    CAP_NONE = 0,
+    /// listNetworkInterfaces() and getNetworkInterface() are supported
+    CAP_ENUM = 1 << 0,
+    /// NetworkMonitor onInterfaceAdded and onInterfaceRemoved signals are supported
+    CAP_IF_ADD_REMOVE = 1 << 1,
+    /// NetworkInterface onStateChanged signal is supported
+    CAP_STATE_CHANGE = 1 << 2,
+    /// NetworkInterface onMtuChanged signal is supported
+    CAP_MTU_CHANGE = 1 << 3,
+    /// NetworkInterface onAddressAdded and onAddressRemoved signals are supported
+    CAP_ADDR_ADD_REMOVE = 1 << 4
+  };
+
+  /** \return bitwise OR'ed \p Capability supported on current platform
+   */
+  uint32_t
+  getCapabilities() const;
+
+  shared_ptr<NetworkInterface>
+  getNetworkInterface(const std::string& ifname) const;
+
+  std::vector<shared_ptr<NetworkInterface>>
+  listNetworkInterfaces() const;
+
+public: // signals
+  /** @brief Fires when network interfaces enumeration is complete
+   */
+  util::Signal<NetworkMonitor> onEnumerationCompleted;
+
+  /** @brief Fires when a new interface is added
+   */
+  util::Signal<NetworkMonitor, shared_ptr<NetworkInterface>> onInterfaceAdded;
+
+  /**
+   * @brief Fires when an interface is removed
+   * @note The NetworkInterface object is no longer present in the network
+   *       interfaces map when the signal is emitted
+   */
+  util::Signal<NetworkMonitor, shared_ptr<NetworkInterface>> onInterfaceRemoved;
+
+  // only for backward compatibility
+  util::Signal<NetworkMonitor> onNetworkStateChanged;
+
+private:
+  const unique_ptr<Impl> m_impl;
+};
+
+} // namespace net
+} // namespace ndn
+
+#endif // NDN_NET_NETWORK_MONITOR_HPP