net: add NetworkMonitorStub

refs #4024

Change-Id: I20da6f3351f51667b045987dc07e88ba814a4456
diff --git a/src/net/detail/network-monitor-impl-noop.hpp b/src/net/detail/network-monitor-impl-noop.hpp
index f0c5fb0..919d231 100644
--- a/src/net/detail/network-monitor-impl-noop.hpp
+++ b/src/net/detail/network-monitor-impl-noop.hpp
@@ -29,27 +29,26 @@
 namespace ndn {
 namespace net {
 
-class NetworkMonitor::Impl
+class NetworkMonitorImplNoop : public NetworkMonitorImpl
 {
 public:
-  Impl(NetworkMonitor& nm, boost::asio::io_service& io)
-  {
-  }
+  explicit
+  NetworkMonitorImplNoop(boost::asio::io_service& io) = default;
 
   uint32_t
-  getCapabilities() const
+  getCapabilities() const final
   {
     return NetworkMonitor::CAP_NONE;
   }
 
-  shared_ptr<NetworkInterface>
-  getNetworkInterface(const std::string&) const
+  shared_ptr<const NetworkInterface>
+  getNetworkInterface(const std::string&) const final
   {
     return {};
   }
 
-  std::vector<shared_ptr<NetworkInterface>>
-  listNetworkInterfaces() const
+  std::vector<shared_ptr<const NetworkInterface>>
+  listNetworkInterfaces() const final
   {
     return {};
   }
diff --git a/src/net/detail/network-monitor-impl-osx.cpp b/src/net/detail/network-monitor-impl-osx.cpp
index 97f3e7c..b2129b5 100644
--- a/src/net/detail/network-monitor-impl-osx.cpp
+++ b/src/net/detail/network-monitor-impl-osx.cpp
@@ -70,13 +70,12 @@
 
 NDN_LOG_INIT(ndn.NetworkMonitor);
 
-NetworkMonitor::Impl::Impl(NetworkMonitor& nm, boost::asio::io_service& io)
-  : m_nm(nm)
-  , m_scheduler(io)
+NetworkMonitorImplOsx::NetworkMonitorImplOsx(boost::asio::io_service& io)
+  : 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))
+                                   &NetworkMonitorImplOsx::onConfigChanged, &m_context))
   , m_loopSource(SCDynamicStoreCreateRunLoopSource(nullptr, m_scStore.get(), 0))
   , m_nullUdpSocket(io, boost::asio::ip::udp::v4())
 
@@ -89,7 +88,7 @@
   //
   CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(),
                                   static_cast<void*>(this),
-                                  &Impl::afterNotificationCenterEvent,
+                                  &NetworkMonitorImplOsx::afterNotificationCenterEvent,
                                   CFSTR("com.apple.system.config.network_change"),
                                   nullptr, // object to observe
                                   CFNotificationSuspensionBehaviorDeliverImmediately);
@@ -116,7 +115,7 @@
   SCDynamicStoreSetNotificationKeys(m_scStore.get(), nullptr, patterns);
 }
 
-NetworkMonitor::Impl::~Impl()
+NetworkMonitorImplOsx::~NetworkMonitorImplOsx()
 {
   CFRunLoopRemoveSource(CFRunLoopGetCurrent(), m_loopSource.get(), kCFRunLoopDefaultMode);
 
@@ -124,22 +123,17 @@
                                           static_cast<void*>(this));
 }
 
-shared_ptr<NetworkInterface>
-NetworkMonitor::Impl::getNetworkInterface(const std::string& ifname) const
+shared_ptr<const NetworkInterface>
+NetworkMonitorImplOsx::getNetworkInterface(const std::string& ifname) const
 {
   auto it = m_interfaces.find(ifname);
-  if (it != m_interfaces.end()) {
-    return it->second;
-  }
-  else {
-    return nullptr;
-  }
+  return it == m_interfaces.end() ? nullptr : it->second;
 }
 
-std::vector<shared_ptr<NetworkInterface>>
-NetworkMonitor::Impl::listNetworkInterfaces() const
+std::vector<shared_ptr<const NetworkInterface>>
+NetworkMonitorImplOsx::listNetworkInterfaces() const
 {
-  std::vector<shared_ptr<NetworkInterface>> v;
+  std::vector<shared_ptr<const NetworkInterface>> v;
   v.reserve(m_interfaces.size());
 
   for (const auto& e : m_interfaces) {
@@ -149,24 +143,24 @@
 }
 
 void
-NetworkMonitor::Impl::afterNotificationCenterEvent(CFNotificationCenterRef center,
-                                                   void* observer,
-                                                   CFStringRef name,
-                                                   const void* object,
-                                                   CFDictionaryRef userInfo)
+NetworkMonitorImplOsx::afterNotificationCenterEvent(CFNotificationCenterRef center,
+                                                    void* observer,
+                                                    CFStringRef name,
+                                                    const void* object,
+                                                    CFDictionaryRef userInfo)
 {
-  static_cast<Impl*>(observer)->m_nm.onNetworkStateChanged();
+  static_cast<NetworkMonitorImplOsx*>(observer)->emitSignal(onNetworkStateChanged);
 }
 
 void
-NetworkMonitor::Impl::scheduleCfLoop()
+NetworkMonitorImplOsx::scheduleCfLoop()
 {
   // poll each second for new events
-  m_cfLoopEvent = m_scheduler.scheduleEvent(time::seconds(1), bind(&Impl::pollCfLoop, this));
+  m_cfLoopEvent = m_scheduler.scheduleEvent(time::seconds(1), [this] { pollCfLoop(); });
 }
 
 void
-NetworkMonitor::Impl::pollCfLoop()
+NetworkMonitorImplOsx::pollCfLoop()
 {
   // this should dispatch ready events and exit
   CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, true);
@@ -175,10 +169,9 @@
 }
 
 void
-NetworkMonitor::Impl::addNewInterface(const std::string& ifName)
+NetworkMonitorImplOsx::addNewInterface(const std::string& ifName)
 {
-  shared_ptr<NetworkInterface> interface(new NetworkInterface);
-
+  shared_ptr<NetworkInterface> interface = makeNetworkInterface();
   interface->setName(ifName);
   interface->setState(getInterfaceState(interface->getName()));
   updateInterfaceInfo(*interface);
@@ -189,16 +182,16 @@
 
   NDN_LOG_DEBUG("adding interface " << interface->getName());
   m_interfaces.insert(make_pair(interface->getName(), interface));
-  m_nm.onInterfaceAdded(interface);
+  this->emitSignal(onInterfaceAdded, interface);
 }
 
 void
-NetworkMonitor::Impl::enumerateInterfaces()
+NetworkMonitorImplOsx::enumerateInterfaces()
 {
   for (const auto& ifName : getInterfaceNames()) {
     addNewInterface(ifName);
   }
-  m_nm.onEnumerationCompleted();
+  this->emitSignal(onEnumerationCompleted);
 }
 
 static std::string
@@ -209,18 +202,19 @@
     return cStr;
   }
 
-  size_t stringSize =  CFStringGetLength(cfString);
+  size_t stringSize = CFStringGetLength(cfString);
   char* buffer = new char[stringSize + 1];
   CFStringGetCString(cfString, buffer, sizeof(buffer), kCFStringEncodingASCII);
   std::string retval = buffer;
-  delete [] buffer;
+  delete[] buffer;
   return retval;
 }
 
 std::set<std::string>
-NetworkMonitor::Impl::getInterfaceNames()
+NetworkMonitorImplOsx::getInterfaceNames()
 {
-  CFReleaser<CFDictionaryRef> dict = (CFDictionaryRef)SCDynamicStoreCopyValue(m_scStore.get(), CFSTR("State:/Network/Interface"));
+  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;
@@ -233,11 +227,11 @@
 }
 
 InterfaceState
-NetworkMonitor::Impl::getInterfaceState(const std::string& ifName)
+NetworkMonitorImplOsx::getInterfaceState(const std::string& ifName)
 {
-  CFReleaser<CFStringRef> linkName = CFStringCreateWithCString(nullptr,
-                                                               ("State:/Network/Interface/" + ifName + "/Link").c_str(),
-                                                               kCFStringEncodingASCII);
+  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) {
@@ -252,8 +246,22 @@
   return CFBooleanGetValue(isActive) ? InterfaceState::RUNNING : InterfaceState::DOWN;
 }
 
+template<typename AddressBytes>
+static uint8_t
+computePrefixLength(const AddressBytes& mask)
+{
+  uint8_t prefixLength = 0;
+  for (auto byte : mask) {
+    while (byte != 0) {
+      ++prefixLength;
+      byte <<= 1;
+    }
+  }
+  return prefixLength;
+}
+
 void
-NetworkMonitor::Impl::updateInterfaceInfo(NetworkInterface& netif)
+NetworkMonitorImplOsx::updateInterfaceInfo(NetworkInterface& netif)
 {
   ifaddrs* ifa_list = nullptr;
   if (::getifaddrs(&ifa_list) < 0) {
@@ -271,48 +279,38 @@
     if (ifa->ifa_addr == nullptr)
       continue;
 
-    NetworkAddress address;
+    namespace ip = boost::asio::ip;
+    AddressFamily addressFamily = AddressFamily::UNSPECIFIED;
+    ip::address ipAddr, broadcast;
+    uint8_t prefixLength = 0;
+
 
     switch (ifa->ifa_addr->sa_family) {
       case AF_INET: {
-        address.m_family = AddressFamily::V4;
+        addressFamily = AddressFamily::V4;
 
         const sockaddr_in* sin = reinterpret_cast<sockaddr_in*>(ifa->ifa_addr);
-        boost::asio::ip::address_v4::bytes_type bytes;
+        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);
+        ipAddr = 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;
+        prefixLength = computePrefixLength(bytes);
         break;
       }
 
       case AF_INET6: {
-        address.m_family = AddressFamily::V6;
+        addressFamily = AddressFamily::V6;
 
         const sockaddr_in6* sin6 = reinterpret_cast<sockaddr_in6*>(ifa->ifa_addr);
-        boost::asio::ip::address_v6::bytes_type bytes;
+        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);
+        ipAddr = 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;
+        prefixLength = computePrefixLength(bytes);
         break;
       }
 
@@ -339,24 +337,26 @@
 
     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;
+      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);
+      broadcast = ip::address_v4(bytes);
+      NDN_LOG_TRACE(netif.getName() << ": set IPv4 broadcast address " << broadcast);
     }
 
     if (netif.canBroadcast()) {
       netif.setEthernetBroadcastAddress(ethernet::getBroadcastAddress());
     }
 
-    netif.addNetworkAddress(address);
+    netif.addNetworkAddress(NetworkAddress(addressFamily, ipAddr, broadcast, prefixLength,
+                                           AddressScope::NOWHERE, 0));
+    ///\todo #3817 extract AddressScope from OS
   }
 
   ::freeifaddrs(ifa_list);
 }
 
 size_t
-NetworkMonitor::Impl::getInterfaceMtu(const std::string& ifName)
+NetworkMonitorImplOsx::getInterfaceMtu(const std::string& ifName)
 {
   ifreq ifr{};
   std::strncpy(ifr.ifr_name, ifName.c_str(), sizeof(ifr.ifr_name) - 1);
@@ -370,13 +370,13 @@
 }
 
 void
-NetworkMonitor::Impl::onConfigChanged(SCDynamicStoreRef m_scStore, CFArrayRef changedKeys, void* context)
+NetworkMonitorImplOsx::onConfigChanged(SCDynamicStoreRef m_scStore, CFArrayRef changedKeys, void* context)
 {
-  static_cast<Impl*>(context)->onConfigChanged(changedKeys);
+  static_cast<NetworkMonitorImplOsx*>(context)->onConfigChanged(changedKeys);
 }
 
 void
-NetworkMonitor::Impl::onConfigChanged(CFArrayRef changedKeys)
+NetworkMonitorImplOsx::onConfigChanged(CFArrayRef changedKeys)
 {
   size_t count = CFArrayGetCount(changedKeys);
   for (size_t i = 0; i != count; ++i) {
@@ -396,7 +396,7 @@
       NDN_LOG_DEBUG("removing interface " << ifName);
       shared_ptr<NetworkInterface> removedInterface = ifIt->second;
       m_interfaces.erase(ifIt);
-      m_nm.onInterfaceRemoved(removedInterface);
+      this->emitSignal(onInterfaceRemoved, removedInterface);
     };
 
     if (key.at(-1).toUri() == "Link") {
@@ -416,17 +416,17 @@
     }
 
     if (key.at(-1).toUri() == "IPv4" || key.at(-1).toUri() == "IPv6") {
-      NetworkInterface updatedInterface;
-      updatedInterface.setName(ifName);
-      updateInterfaceInfo(updatedInterface);
-      if (updatedInterface.getType() == InterfaceType::UNKNOWN) {
+      shared_ptr<NetworkInterface> updatedInterface = makeNetworkInterface();
+      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& newAddrs = updatedInterface->getNetworkAddresses();
       const auto& oldAddrs = netif.getNetworkAddresses();
 
       std::set<NetworkAddress> added;
diff --git a/src/net/detail/network-monitor-impl-osx.hpp b/src/net/detail/network-monitor-impl-osx.hpp
index 3aac23f..d1ce8f2 100644
--- a/src/net/detail/network-monitor-impl-osx.hpp
+++ b/src/net/detail/network-monitor-impl-osx.hpp
@@ -29,7 +29,6 @@
 #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"
@@ -42,25 +41,29 @@
 namespace ndn {
 namespace net {
 
-class NetworkMonitor::Impl
+class NetworkMonitorImplOsx : public NetworkMonitorImpl
 {
 public:
-  Impl(NetworkMonitor& nm, boost::asio::io_service& io);
+  using Error = NetworkMonitor::Error;
 
-  ~Impl();
+  NetworkMonitorImplOsx(boost::asio::io_service& io);
+
+  ~NetworkMonitorImplOsx();
 
   uint32_t
-  getCapabilities() const
+  getCapabilities() const final
   {
-    return NetworkMonitor::CAP_ENUM | NetworkMonitor::CAP_IF_ADD_REMOVE |
-      NetworkMonitor::CAP_STATE_CHANGE | NetworkMonitor::CAP_ADDR_ADD_REMOVE;
+    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;
+  shared_ptr<const NetworkInterface>
+  getNetworkInterface(const std::string& ifname) const final;
 
-  std::vector<shared_ptr<NetworkInterface>>
-  listNetworkInterfaces() const;
+  std::vector<shared_ptr<const NetworkInterface>>
+  listNetworkInterfaces() const final;
 
   static void
   afterNotificationCenterEvent(CFNotificationCenterRef center,
@@ -101,8 +104,7 @@
   onConfigChanged(CFArrayRef changedKeys);
 
 private:
-  NetworkMonitor& m_nm;
-  std::map<std::string /*ifname*/, shared_ptr<NetworkInterface>> m_interfaces; ///< interface map
+  std::map<std::string, shared_ptr<NetworkInterface>> m_interfaces; ///< ifname => interface
 
   util::Scheduler m_scheduler;
   util::scheduler::ScopedEventId m_cfLoopEvent;
diff --git a/src/net/detail/network-monitor-impl-rtnl.cpp b/src/net/detail/network-monitor-impl-rtnl.cpp
index 1a05986..71274a1 100644
--- a/src/net/detail/network-monitor-impl-rtnl.cpp
+++ b/src/net/detail/network-monitor-impl-rtnl.cpp
@@ -40,9 +40,8 @@
 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))
+NetworkMonitorImplRtnl::NetworkMonitorImplRtnl(boost::asio::io_service& io)
+  : m_socket(make_shared<boost::asio::posix::stream_descriptor>(io))
   , m_pid(0)
   , m_sequenceNo(static_cast<uint32_t>(time::system_clock::now().time_since_epoch().count()))
   , m_isEnumeratingLinks(false)
@@ -56,14 +55,14 @@
   m_isEnumeratingLinks = true;
 }
 
-NetworkMonitor::Impl::~Impl()
+NetworkMonitorImplRtnl::~NetworkMonitorImplRtnl()
 {
   boost::system::error_code error;
   m_socket->close(error);
 }
 
-shared_ptr<NetworkInterface>
-NetworkMonitor::Impl::getNetworkInterface(const std::string& ifname) const
+shared_ptr<const NetworkInterface>
+NetworkMonitorImplRtnl::getNetworkInterface(const std::string& ifname) const
 {
   for (const auto& e : m_interfaces) {
     if (e.second->getName() == ifname)
@@ -72,10 +71,10 @@
   return nullptr;
 }
 
-std::vector<shared_ptr<NetworkInterface>>
-NetworkMonitor::Impl::listNetworkInterfaces() const
+std::vector<shared_ptr<const NetworkInterface>>
+NetworkMonitorImplRtnl::listNetworkInterfaces() const
 {
-  std::vector<shared_ptr<NetworkInterface>> v;
+  std::vector<shared_ptr<const NetworkInterface>> v;
   v.reserve(m_interfaces.size());
 
   for (const auto& e : m_interfaces) {
@@ -85,13 +84,13 @@
 }
 
 bool
-NetworkMonitor::Impl::isEnumerating() const
+NetworkMonitorImplRtnl::isEnumerating() const
 {
   return m_isEnumeratingLinks || m_isEnumeratingAddresses;
 }
 
 void
-NetworkMonitor::Impl::initSocket()
+NetworkMonitorImplRtnl::initSocket()
 {
   NDN_LOG_TRACE("creating netlink socket");
 
@@ -129,7 +128,7 @@
 }
 
 void
-NetworkMonitor::Impl::sendDumpRequest(uint16_t nlmsgType)
+NetworkMonitorImplRtnl::sendDumpRequest(uint16_t nlmsgType)
 {
   auto request = make_shared<RtnlRequest>();
   request->nlh.nlmsg_len = sizeof(RtnlRequest);
@@ -217,14 +216,14 @@
 }
 
 void
-NetworkMonitor::Impl::asyncRead()
+NetworkMonitorImplRtnl::asyncRead()
 {
   m_socket->async_read_some(boost::asio::buffer(m_buffer),
-                            bind(&Impl::handleRead, this, _1, _2, m_socket));
+                            bind(&NetworkMonitorImplRtnl::handleRead, this, _1, _2, m_socket));
 }
 
 void
-NetworkMonitor::Impl::handleRead(const boost::system::error_code& error, size_t nBytesRead,
+NetworkMonitorImplRtnl::handleRead(const boost::system::error_code& error, size_t nBytesRead,
                                  const shared_ptr<boost::asio::posix::stream_descriptor>& socket)
 {
   if (!socket->is_open() ||
@@ -252,7 +251,7 @@
 }
 
 void
-NetworkMonitor::Impl::parseNetlinkMessage(const nlmsghdr* nlh, size_t len)
+NetworkMonitorImplRtnl::parseNetlinkMessage(const nlmsghdr* nlh, size_t len)
 {
   while (NLMSG_OK(nlh, len)) {
     NDN_LOG_TRACE("parsing " << (nlh->nlmsg_flags & NLM_F_MULTI ? "multi-part " : "") <<
@@ -275,21 +274,21 @@
       case RTM_DELLINK:
         parseLinkMessage(nlh, reinterpret_cast<const ifinfomsg*>(NLMSG_DATA(nlh)));
         if (!isEnumerating())
-          m_nm.onNetworkStateChanged(); // backward compat
+          this->emitSignal(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
+          this->emitSignal(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
+          this->emitSignal(onNetworkStateChanged); // backward compat
         break;
 
       case NLMSG_ERROR: {
@@ -320,12 +319,12 @@
     m_isEnumeratingAddresses = false;
     // TODO: enumerate routes
     NDN_LOG_DEBUG("enumeration complete");
-    m_nm.onEnumerationCompleted();
+    this->emitSignal(onEnumerationCompleted);
   }
 }
 
 void
-NetworkMonitor::Impl::parseLinkMessage(const nlmsghdr* nlh, const ifinfomsg* ifi)
+NetworkMonitorImplRtnl::parseLinkMessage(const nlmsghdr* nlh, const ifinfomsg* ifi)
 {
   if (ifiTypeToInterfaceType(ifi->ifi_type) == InterfaceType::UNKNOWN) {
     NDN_LOG_DEBUG("unhandled interface type " << ifi->ifi_type);
@@ -344,14 +343,13 @@
     if (interface != nullptr) {
       NDN_LOG_DEBUG("removing interface " << interface->getName());
       m_interfaces.erase(it);
-      m_nm.onInterfaceRemoved(interface);
+      this->emitSignal(onInterfaceRemoved, interface);
     }
     return;
   }
 
   if (interface == nullptr) {
-    // cannot use make_shared because NetworkInterface constructor is private
-    interface.reset(new NetworkInterface);
+    interface = makeNetworkInterface();
     interface->setIndex(ifi->ifi_index);
   }
   interface->setType(ifiTypeToInterfaceType(ifi->ifi_type));
@@ -405,12 +403,12 @@
   if (it == m_interfaces.end()) {
     NDN_LOG_DEBUG("adding interface " << interface->getName());
     m_interfaces[interface->getIndex()] = interface;
-    m_nm.onInterfaceAdded(interface);
+    this->emitSignal(onInterfaceAdded, interface);
   }
 }
 
 void
-NetworkMonitor::Impl::parseAddressMessage(const nlmsghdr* nlh, const ifaddrmsg* ifa)
+NetworkMonitorImplRtnl::parseAddressMessage(const nlmsghdr* nlh, const ifaddrmsg* ifa)
 {
   auto it = m_interfaces.find(ifa->ifa_index);
   if (it == m_interfaces.end()) {
@@ -422,13 +420,8 @@
   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);
+  ip::address ipAddr, broadcast;
+  uint32_t flags = ifa->ifa_flags; // will be overridden by IFA_FLAGS if the attribute is present
 
   const rtattr* rta = reinterpret_cast<const rtattr*>(IFA_RTA(ifa));
   size_t rtaTotalLen = IFA_PAYLOAD(nlh);
@@ -442,7 +435,7 @@
         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);
+          ipAddr = ip::address_v4(bytes);
         }
         break;
 
@@ -450,7 +443,7 @@
         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);
+          ipAddr = ip::address_v6(bytes);
         }
         break;
 
@@ -458,14 +451,14 @@
         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);
+          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));
+          flags = *(reinterpret_cast<const uint32_t*>(attrData));
         break;
 #endif // NDN_CXX_HAVE_IFA_FLAGS
     }
@@ -473,6 +466,15 @@
     rta = RTA_NEXT(rta, rtaTotalLen);
   }
 
+  NetworkAddress address(
+    ifaFamilyToAddressFamily(ifa->ifa_family),
+    ipAddr,
+    broadcast,
+    ifa->ifa_prefixlen,
+    ifaScopeToAddressScope(ifa->ifa_scope),
+    flags);
+  BOOST_ASSERT(address.getFamily() != AddressFamily::UNSPECIFIED);
+
   if (nlh->nlmsg_type == RTM_NEWADDR)
     interface->addNetworkAddress(address);
   else if (nlh->nlmsg_type == RTM_DELADDR)
@@ -480,13 +482,13 @@
 }
 
 void
-NetworkMonitor::Impl::parseRouteMessage(const nlmsghdr* nlh, const rtmsg* rtm)
+NetworkMonitorImplRtnl::parseRouteMessage(const nlmsghdr* nlh, const rtmsg* rtm)
 {
   // TODO
 }
 
 void
-NetworkMonitor::Impl::updateInterfaceState(NetworkInterface& interface, uint8_t operState)
+NetworkMonitorImplRtnl::updateInterfaceState(NetworkInterface& interface, uint8_t operState)
 {
   if (operState == linux_if::OPER_STATE_UP) {
     interface.setState(InterfaceState::RUNNING);
diff --git a/src/net/detail/network-monitor-impl-rtnl.hpp b/src/net/detail/network-monitor-impl-rtnl.hpp
index d43abce..ce15dd5 100644
--- a/src/net/detail/network-monitor-impl-rtnl.hpp
+++ b/src/net/detail/network-monitor-impl-rtnl.hpp
@@ -44,17 +44,20 @@
 namespace ndn {
 namespace net {
 
-class NetworkMonitor::Impl
+class NetworkMonitorImplRtnl : public NetworkMonitorImpl
 {
 public:
+  using Error = NetworkMonitor::Error;
+
   /** \brief initialize netlink socket and start enumerating interfaces
    */
-  Impl(NetworkMonitor& nm, boost::asio::io_service& io);
+  explicit
+  NetworkMonitorImplRtnl(boost::asio::io_service& io);
 
-  ~Impl();
+  ~NetworkMonitorImplRtnl();
 
   uint32_t
-  getCapabilities() const
+  getCapabilities() const final
   {
     return NetworkMonitor::CAP_ENUM |
            NetworkMonitor::CAP_IF_ADD_REMOVE |
@@ -63,11 +66,11 @@
            NetworkMonitor::CAP_ADDR_ADD_REMOVE;
   }
 
-  shared_ptr<NetworkInterface>
-  getNetworkInterface(const std::string& ifname) const;
+  shared_ptr<const NetworkInterface>
+  getNetworkInterface(const std::string& ifname) const final;
 
-  std::vector<shared_ptr<NetworkInterface>>
-  listNetworkInterfaces() const;
+  std::vector<shared_ptr<const NetworkInterface>>
+  listNetworkInterfaces() const final;
 
 private:
   struct RtnlRequest
@@ -110,9 +113,8 @@
   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
+  std::map<int, shared_ptr<NetworkInterface>> m_interfaces; ///< ifindex => interface
+  std::array<uint8_t, 16384> m_buffer; ///< netlink messages received from the kernel
   shared_ptr<boost::asio::posix::stream_descriptor> m_socket; ///< the netlink socket
   uint32_t m_pid; ///< our port ID (unicast address for netlink sockets)
   uint32_t m_sequenceNo; ///< sequence number of the last netlink request sent to the kernel
diff --git a/src/net/network-address.cpp b/src/net/network-address.cpp
index 148c3ec..748cd00 100644
--- a/src/net/network-address.cpp
+++ b/src/net/network-address.cpp
@@ -42,11 +42,18 @@
   return os;
 }
 
-NetworkAddress::NetworkAddress()
-  : m_family(AddressFamily::UNSPECIFIED)
-  , m_flags(0)
-  , m_scope(AddressScope::NOWHERE)
-  , m_prefixLength(0)
+NetworkAddress::NetworkAddress(AddressFamily family,
+                               boost::asio::ip::address ip,
+                               boost::asio::ip::address broadcast,
+                               uint8_t prefixLength,
+                               AddressScope scope,
+                               uint32_t flags)
+  : m_family(family)
+  , m_ip(ip)
+  , m_broadcast(broadcast)
+  , m_prefixLength(prefixLength)
+  , m_scope(scope)
+  , m_flags(flags)
 {
 }
 
diff --git a/src/net/network-address.hpp b/src/net/network-address.hpp
index 632a30f..fc13c22 100644
--- a/src/net/network-address.hpp
+++ b/src/net/network-address.hpp
@@ -24,8 +24,7 @@
 #ifndef NDN_NET_NETWORK_ADDRESS_HPP
 #define NDN_NET_NETWORK_ADDRESS_HPP
 
-#include "network-monitor.hpp"
-
+#include "../common.hpp"
 #include <boost/asio/ip/address.hpp>
 
 namespace ndn {
@@ -52,7 +51,14 @@
  */
 class NetworkAddress
 {
-public: // getters
+public:
+  NetworkAddress(AddressFamily family,
+                 boost::asio::ip::address ip,
+                 boost::asio::ip::address broadcast,
+                 uint8_t prefixLength,
+                 AddressScope scope,
+                 uint32_t flags);
+
   /** @brief Returns the address family
    */
   AddressFamily
@@ -77,12 +83,12 @@
     return m_broadcast;
   }
 
-  /** @brief Returns a bitset of platform-specific flags enabled on the address
+  /** @brief Returns the prefix length
    */
-  uint32_t
-  getFlags() const
+  uint8_t
+  getPrefixLength() const
   {
-    return m_flags;
+    return m_prefixLength;
   }
 
   /** @brief Returns the address scope
@@ -93,12 +99,12 @@
     return m_scope;
   }
 
-  /** @brief Returns the prefix length
+  /** @brief Returns a bitset of platform-specific flags enabled on the address
    */
-  uint8_t
-  getPrefixLength() const
+  uint32_t
+  getFlags() const
   {
-    return m_prefixLength;
+    return m_flags;
   }
 
   friend bool
@@ -107,18 +113,13 @@
     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;
+  AddressScope m_scope;
+  uint32_t m_flags; // IFA_F_* in if_addr.h
 };
 
 std::ostream&
diff --git a/src/net/network-interface.hpp b/src/net/network-interface.hpp
index f5fff7e..b58072f 100644
--- a/src/net/network-interface.hpp
+++ b/src/net/network-interface.hpp
@@ -26,9 +26,7 @@
 
 #include "ethernet.hpp"
 #include "network-address.hpp"
-#include "network-monitor.hpp"
 #include "../util/signal.hpp"
-
 #include <set>
 
 namespace ndn {
@@ -69,22 +67,22 @@
  */
 class NetworkInterface
 {
-public: // signals
+public: // signals, marked 'mutable' so they can be connected on 'const NetworkInterface'
   /** @brief Fires when interface state changes
    */
-  util::Signal<NetworkInterface, InterfaceState /*old*/, InterfaceState /*new*/> onStateChanged;
+  mutable util::Signal<NetworkInterface, InterfaceState /*old*/, InterfaceState /*new*/> onStateChanged;
 
   /** @brief Fires when interface mtu changes
    */
-  util::Signal<NetworkInterface, uint32_t /*old*/, uint32_t /*new*/> onMtuChanged;
+  mutable 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;
+  mutable util::Signal<NetworkInterface, NetworkAddress> onAddressAdded;
 
   /** @brief Fires when a network-layer address is removed from the interface
    */
-  util::Signal<NetworkInterface, NetworkAddress> onAddressRemoved;
+  mutable util::Signal<NetworkInterface, NetworkAddress> onAddressRemoved;
 
 public: // getters
   /** @brief Returns an opaque ID that uniquely identifies the interface on the system
@@ -199,10 +197,7 @@
     return (m_flags & IFF_UP) != 0;
   }
 
-private: // constructor
-  NetworkInterface();
-
-private: // modifiers
+public: // modifiers: they update information on this instance, but do not change netif in the OS
   bool
   addNetworkAddress(const NetworkAddress& address);
 
@@ -233,9 +228,10 @@
   void
   setEthernetBroadcastAddress(const ethernet::Address& address);
 
-private:
-  friend class NetworkMonitor::Impl;
+private: // constructor
+  NetworkInterface(); // accessible through NetworkMonitorImpl::makeNetworkInterface
 
+private:
   int m_index;
   std::string m_name;
   InterfaceType m_type;
@@ -245,6 +241,8 @@
   ethernet::Address m_etherAddress;
   ethernet::Address m_etherBrdAddress;
   std::set<NetworkAddress> m_netAddresses;
+
+  friend class NetworkMonitorImpl;
 };
 
 std::ostream&
diff --git a/src/net/network-monitor-stub.cpp b/src/net/network-monitor-stub.cpp
new file mode 100644
index 0000000..9c3425b
--- /dev/null
+++ b/src/net/network-monitor-stub.cpp
@@ -0,0 +1,136 @@
+/* -*- 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-stub.hpp"
+#include <unordered_map>
+#include <boost/range/adaptor/map.hpp>
+#include <boost/range/algorithm/copy.hpp>
+
+namespace ndn {
+namespace net {
+
+class NetworkMonitorImplStub : public NetworkMonitorImpl
+{
+public:
+  explicit
+  NetworkMonitorImplStub(uint32_t capabilities)
+    : m_capabilities(capabilities)
+  {
+  }
+
+  uint32_t
+  getCapabilities() const final
+  {
+    return m_capabilities;
+  }
+
+  shared_ptr<const NetworkInterface>
+  getNetworkInterface(const std::string& ifname) const final
+  {
+    auto i = m_interfaces.find(ifname);
+    return i == m_interfaces.end() ? nullptr : i->second;
+  }
+
+  std::vector<shared_ptr<const NetworkInterface>>
+  listNetworkInterfaces() const final
+  {
+    std::vector<shared_ptr<const NetworkInterface>> v;
+    boost::copy(m_interfaces | boost::adaptors::map_values, std::back_inserter(v));
+    return v;
+  }
+
+public: // internal
+  using NetworkMonitorImpl::makeNetworkInterface;
+
+  void
+  addInterface(shared_ptr<NetworkInterface> netif)
+  {
+    BOOST_ASSERT(netif != nullptr);
+    bool isNew = m_interfaces.emplace(netif->getName(), netif).second;
+    if (!isNew) {
+      BOOST_THROW_EXCEPTION(std::invalid_argument("duplicate ifname"));
+    }
+    this->emitSignal(onInterfaceAdded, netif);
+  }
+
+  void
+  removeInterface(const std::string& ifname)
+  {
+    auto i = m_interfaces.find(ifname);
+    if (i == m_interfaces.end()) {
+      return;
+    }
+    shared_ptr<NetworkInterface> netif = i->second;
+    m_interfaces.erase(i);
+    this->emitSignal(onInterfaceRemoved, netif);
+  }
+
+  void
+  emitEnumerationCompleted()
+  {
+    this->emitSignal(onEnumerationCompleted);
+  }
+
+private:
+  uint32_t m_capabilities;
+  std::unordered_map<std::string, shared_ptr<NetworkInterface>> m_interfaces;
+};
+
+NetworkMonitorStub::NetworkMonitorStub(uint32_t capabilities)
+  : NetworkMonitor(make_unique<NetworkMonitorImplStub>(capabilities))
+{
+}
+
+NetworkMonitorImplStub&
+NetworkMonitorStub::getImpl()
+{
+  return static_cast<NetworkMonitorImplStub&>(this->NetworkMonitor::getImpl());
+}
+
+shared_ptr<NetworkInterface>
+NetworkMonitorStub::makeNetworkInterface()
+{
+  return NetworkMonitorImplStub::makeNetworkInterface();
+}
+
+void
+NetworkMonitorStub::addInterface(shared_ptr<NetworkInterface> netif)
+{
+  this->getImpl().addInterface(std::move(netif));
+}
+
+void
+NetworkMonitorStub::removeInterface(const std::string& ifname)
+{
+  this->getImpl().removeInterface(ifname);
+}
+
+void
+NetworkMonitorStub::emitEnumerationCompleted()
+{
+  this->getImpl().emitEnumerationCompleted();
+}
+
+} // namespace net
+} // namespace ndn
diff --git a/src/net/network-monitor-stub.hpp b/src/net/network-monitor-stub.hpp
new file mode 100644
index 0000000..97b9d46
--- /dev/null
+++ b/src/net/network-monitor-stub.hpp
@@ -0,0 +1,88 @@
+/* -*- 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_STUB_HPP
+#define NDN_NET_NETWORK_MONITOR_STUB_HPP
+
+#include "network-monitor.hpp"
+
+namespace ndn {
+namespace net {
+
+class NetworkMonitorImplStub;
+
+/** \brief a stub NetworkMonitor for unit testing
+ */
+class NetworkMonitorStub : public NetworkMonitor
+{
+public:
+  /** \brief constructor
+   *  \param capabilities capabilities reported by \p getCapabilities
+   */
+  explicit
+  NetworkMonitorStub(uint32_t capabilities);
+
+  /** \brief create a NetworkInterface instance
+   */
+  static shared_ptr<NetworkInterface>
+  makeNetworkInterface();
+
+  /** \brief emit the \p onInterfaceAdded signal and add \p netif internally
+   *  \param netif new network interface
+   *  \post getNetworkInterface(netif->getName()) == netif
+   *  \post listNetworkInterface() contains netif
+   *  \throw std::invalid_argument a network interface with same name already exists
+   */
+  void
+  addInterface(shared_ptr<NetworkInterface> netif);
+
+  /** \brief emit the \p onInterfaceRemoved signal and remove \p netif internally
+   *  \param ifname network interface name
+   *  \post getNetworkInterface(ifname) == nullptr
+   *  \post listNetworkInterface() does not contains an interface with specified name
+   *  \note If specified interface name does not exist, this operation has no effect.
+   */
+  void
+  removeInterface(const std::string& ifname);
+
+  /** \brief emit the \p onEnumerationCompleted signal
+   *
+   *  A real NetworkMonitor starts with an "enumerating" state, during which the initial
+   *  information about network interfaces is collected from the OS. Upon discovering a network
+   *  interface, it emits the \p onInterfaceAdded signal. When the initial enumerating completes,
+   *  it emits the onEnumerationCompleted signal.
+   *
+   *  To simulate this procedure on a newly constructed MockNetworkMonitor, the caller should
+   *  invoke \p addInterface once for each network interface that already exists, and then invoke
+   *  \p signalEnumerationCompleted .
+   */
+  void
+  emitEnumerationCompleted();
+
+private:
+  NetworkMonitorImplStub&
+  getImpl();
+};
+
+} // namespace net
+} // namespace ndn
+
+#endif // NDN_NET_NETWORK_MONITOR_STUB_HPP
diff --git a/src/net/network-monitor.cpp b/src/net/network-monitor.cpp
index c4a2e2e..a45ff57 100644
--- a/src/net/network-monitor.cpp
+++ b/src/net/network-monitor.cpp
@@ -27,21 +27,31 @@
 
 #if defined(NDN_CXX_HAVE_OSX_FRAMEWORKS)
 #include "detail/network-monitor-impl-osx.hpp"
+#define NETWORK_MONITOR_IMPL_TYPE NetworkMonitorImplOsx
 #elif defined(NDN_CXX_HAVE_RTNETLINK)
 #include "detail/network-monitor-impl-rtnl.hpp"
+#define NETWORK_MONITOR_IMPL_TYPE NetworkMonitorImplRtnl
 #else
 #include "detail/network-monitor-impl-noop.hpp"
+#define NETWORK_MONITOR_IMPL_TYPE NetworkMonitorImplNoop
 #endif
 
 namespace ndn {
 namespace net {
 
 NetworkMonitor::NetworkMonitor(boost::asio::io_service& io)
-  : m_impl(make_unique<Impl>(*this, io))
+  : NetworkMonitor(make_unique<NETWORK_MONITOR_IMPL_TYPE>(io))
 {
 }
 
-NetworkMonitor::~NetworkMonitor() = default;
+NetworkMonitor::NetworkMonitor(unique_ptr<NetworkMonitorImpl> impl)
+  : m_impl(std::move(impl))
+  , onEnumerationCompleted(m_impl->onEnumerationCompleted)
+  , onInterfaceAdded(m_impl->onInterfaceAdded)
+  , onInterfaceRemoved(m_impl->onInterfaceRemoved)
+  , onNetworkStateChanged(m_impl->onNetworkStateChanged)
+{
+}
 
 uint32_t
 NetworkMonitor::getCapabilities() const
@@ -49,17 +59,24 @@
   return m_impl->getCapabilities();
 }
 
-shared_ptr<NetworkInterface>
+shared_ptr<const NetworkInterface>
 NetworkMonitor::getNetworkInterface(const std::string& ifname) const
 {
   return m_impl->getNetworkInterface(ifname);
 }
 
-std::vector<shared_ptr<NetworkInterface>>
+std::vector<shared_ptr<const NetworkInterface>>
 NetworkMonitor::listNetworkInterfaces() const
 {
   return m_impl->listNetworkInterfaces();
 }
 
+shared_ptr<NetworkInterface>
+NetworkMonitorImpl::makeNetworkInterface()
+{
+  // cannot use make_shared because NetworkInterface constructor is private
+  return shared_ptr<NetworkInterface>(new NetworkInterface);
+}
+
 } // namespace net
 } // namespace ndn
diff --git a/src/net/network-monitor.hpp b/src/net/network-monitor.hpp
index f3326b7..2b3fae5 100644
--- a/src/net/network-monitor.hpp
+++ b/src/net/network-monitor.hpp
@@ -25,11 +25,9 @@
 #ifndef NDN_NET_NETWORK_MONITOR_HPP
 #define NDN_NET_NETWORK_MONITOR_HPP
 
-#include "../util/signal.hpp"
-
+#include "network-interface.hpp"
 #include <vector>
 
-// forward declaration
 namespace boost {
 namespace asio {
 class io_service;
@@ -39,7 +37,7 @@
 namespace ndn {
 namespace net {
 
-class NetworkInterface;
+class NetworkMonitorImpl;
 
 /**
  * @brief Network interfaces monitor
@@ -66,20 +64,16 @@
     }
   };
 
-  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
+   * @throw Error 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,
@@ -100,33 +94,77 @@
   uint32_t
   getCapabilities() const;
 
-  shared_ptr<NetworkInterface>
+  shared_ptr<const NetworkInterface>
   getNetworkInterface(const std::string& ifname) const;
 
-  std::vector<shared_ptr<NetworkInterface>>
+  std::vector<shared_ptr<const NetworkInterface>>
   listNetworkInterfaces() const;
 
+protected:
+  explicit
+  NetworkMonitor(unique_ptr<NetworkMonitorImpl> impl);
+
+  NetworkMonitorImpl&
+  getImpl()
+  {
+    return *m_impl;
+  }
+
+private:
+  const unique_ptr<NetworkMonitorImpl> m_impl;
+  // Intentional violation of code-style rule 1.4: m_impl must be assigned before its signals can
+  // be assigned to references below.
+
 public: // signals
   /** @brief Fires when network interfaces enumeration is complete
    */
-  util::Signal<NetworkMonitor> onEnumerationCompleted;
+  util::Signal<NetworkMonitorImpl>& onEnumerationCompleted;
 
   /** @brief Fires when a new interface is added
    */
-  util::Signal<NetworkMonitor, shared_ptr<NetworkInterface>> onInterfaceAdded;
+  util::Signal<NetworkMonitorImpl, shared_ptr<const 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;
+  util::Signal<NetworkMonitorImpl, shared_ptr<const NetworkInterface>>& onInterfaceRemoved;
 
   // only for backward compatibility
-  util::Signal<NetworkMonitor> onNetworkStateChanged;
+  util::Signal<NetworkMonitorImpl>& onNetworkStateChanged;
+};
 
-private:
-  const unique_ptr<Impl> m_impl;
+class NetworkMonitorImpl : noncopyable
+{
+public:
+  virtual
+  ~NetworkMonitorImpl() = default;
+
+  virtual uint32_t
+  getCapabilities() const = 0;
+
+  virtual shared_ptr<const NetworkInterface>
+  getNetworkInterface(const std::string&) const = 0;
+
+  virtual std::vector<shared_ptr<const NetworkInterface>>
+  listNetworkInterfaces() const = 0;
+
+protected:
+  static shared_ptr<NetworkInterface>
+  makeNetworkInterface();
+
+public:
+  util::Signal<NetworkMonitorImpl> onEnumerationCompleted;
+  util::Signal<NetworkMonitorImpl, shared_ptr<const NetworkInterface>> onInterfaceAdded;
+  util::Signal<NetworkMonitorImpl, shared_ptr<const NetworkInterface>> onInterfaceRemoved;
+  util::Signal<NetworkMonitorImpl> onNetworkStateChanged;
+
+protected:
+  DECLARE_SIGNAL_EMIT(onEnumerationCompleted)
+  DECLARE_SIGNAL_EMIT(onInterfaceAdded)
+  DECLARE_SIGNAL_EMIT(onInterfaceRemoved)
+  DECLARE_SIGNAL_EMIT(onNetworkStateChanged)
 };
 
 } // namespace net
diff --git a/tests/integrated/network-monitor.cpp b/tests/integrated/network-monitor.cpp
index 917dcae..eb23e5b 100644
--- a/tests/integrated/network-monitor.cpp
+++ b/tests/integrated/network-monitor.cpp
@@ -43,7 +43,7 @@
 BOOST_AUTO_TEST_SUITE(TestNetworkMonitor)
 
 static std::ostream&
-logEvent(const shared_ptr<NetworkInterface>& ni = nullptr, std::ostream& os = std::cout)
+logEvent(const shared_ptr<const NetworkInterface>& ni = nullptr, std::ostream& os = std::cout)
 {
   os << '[' << time::toIsoString(time::system_clock::now()) << "] ";
   if (ni != nullptr)
@@ -69,13 +69,13 @@
     }
   });
 
-  monitor.onInterfaceAdded.connect([] (const shared_ptr<NetworkInterface>& ni) {
+  monitor.onInterfaceAdded.connect([] (const shared_ptr<const NetworkInterface>& ni) {
     logEvent(ni) << "onInterfaceAdded\n" << *ni;
     logEvent(ni) << "link-type: " << detail::getLinkType(ni->getName()) << "\n";
 
     ni->onAddressAdded.connect([ni] (const NetworkAddress& address) {
       logEvent(ni) << "onAddressAdded " << address << std::endl;
-      });
+    });
 
     ni->onAddressRemoved.connect([ni] (const NetworkAddress& address) {
       logEvent(ni) << "onAddressRemoved " << address << std::endl;
@@ -91,7 +91,7 @@
     });
   }); // monitor.onInterfaceAdded.connect
 
-  monitor.onInterfaceRemoved.connect([] (const shared_ptr<NetworkInterface>& ni) {
+  monitor.onInterfaceRemoved.connect([] (const shared_ptr<const NetworkInterface>& ni) {
     logEvent(ni) << "onInterfaceRemoved" << std::endl;
   });
 
diff --git a/tests/unit-tests/net/network-monitor-stub.t.cpp b/tests/unit-tests/net/network-monitor-stub.t.cpp
new file mode 100644
index 0000000..03a1dc0
--- /dev/null
+++ b/tests/unit-tests/net/network-monitor-stub.t.cpp
@@ -0,0 +1,125 @@
+/* -*- 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 "net/network-monitor-stub.hpp"
+
+#include "boost-test.hpp"
+
+namespace ndn {
+namespace net {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(Net)
+BOOST_AUTO_TEST_SUITE(TestNetworkMonitorStub)
+
+BOOST_AUTO_TEST_CASE(Capabilities)
+{
+  NetworkMonitorStub stub(NetworkMonitor::CAP_ENUM | NetworkMonitor::CAP_IF_ADD_REMOVE);
+  BOOST_CHECK_EQUAL(stub.getCapabilities(),
+                    NetworkMonitor::CAP_ENUM | NetworkMonitor::CAP_IF_ADD_REMOVE);
+}
+
+class StubFixture
+{
+public:
+  StubFixture()
+    : stub(~0)
+  {
+    stub.onEnumerationCompleted.connect([this] { signals.push_back("EnumerationCompleted"); });
+    stub.onInterfaceAdded.connect([this] (shared_ptr<const NetworkInterface> netif) {
+      signals.push_back("InterfaceAdded " + netif->getName()); });
+    stub.onInterfaceRemoved.connect([this] (shared_ptr<const NetworkInterface> netif) {
+      signals.push_back("InterfaceRemoved " + netif->getName()); });
+  }
+
+public:
+  NetworkMonitorStub stub;
+  std::vector<std::string> signals;
+};
+
+BOOST_FIXTURE_TEST_CASE(AddInterface, StubFixture)
+{
+  BOOST_CHECK_EQUAL(stub.listNetworkInterfaces().size(), 0);
+  BOOST_CHECK(stub.getNetworkInterface("eth1") == nullptr);
+
+  shared_ptr<NetworkInterface> if1 = stub.makeNetworkInterface();
+  if1->setIndex(13697);
+  if1->setName("eth1");
+  stub.addInterface(if1);
+  if1.reset();
+  BOOST_REQUIRE(stub.getNetworkInterface("eth1") != nullptr);
+  BOOST_CHECK_EQUAL(stub.getNetworkInterface("eth1")->getIndex(), 13697);
+  BOOST_CHECK_EQUAL(stub.listNetworkInterfaces().at(0)->getIndex(), 13697);
+  BOOST_REQUIRE_EQUAL(signals.size(), 1);
+  BOOST_CHECK_EQUAL(signals.back(), "InterfaceAdded eth1");
+
+  shared_ptr<NetworkInterface> if1b = stub.makeNetworkInterface();
+  if1b->setIndex(3280);
+  if1b->setName("eth1");
+  BOOST_CHECK_THROW(stub.addInterface(if1b), std::invalid_argument);
+  BOOST_CHECK(stub.getNetworkInterface("eth1") != nullptr);
+  BOOST_CHECK_EQUAL(stub.getNetworkInterface("eth1")->getIndex(), 13697);
+  BOOST_CHECK_EQUAL(stub.listNetworkInterfaces().at(0)->getIndex(), 13697);
+  BOOST_CHECK_EQUAL(signals.size(), 1);
+
+  stub.emitEnumerationCompleted();
+  BOOST_REQUIRE_EQUAL(signals.size(), 2);
+  BOOST_CHECK_EQUAL(signals.back(), "EnumerationCompleted");
+
+  shared_ptr<NetworkInterface> if2 = stub.makeNetworkInterface();
+  if2->setIndex(19243);
+  if2->setName("eth2");
+  stub.addInterface(if2);
+  if2.reset();
+  BOOST_REQUIRE(stub.getNetworkInterface("eth2") != nullptr);
+  BOOST_CHECK_EQUAL(stub.getNetworkInterface("eth2")->getIndex(), 19243);
+  BOOST_CHECK_EQUAL(stub.listNetworkInterfaces().size(), 2);
+  BOOST_REQUIRE_EQUAL(signals.size(), 3);
+  BOOST_CHECK_EQUAL(signals.back(), "InterfaceAdded eth2");
+}
+
+BOOST_FIXTURE_TEST_CASE(RemoveInterface, StubFixture)
+{
+  shared_ptr<NetworkInterface> if1 = stub.makeNetworkInterface();
+  if1->setIndex(13697);
+  if1->setName("eth1");
+  stub.addInterface(if1);
+
+  stub.emitEnumerationCompleted();
+  BOOST_REQUIRE_EQUAL(signals.size(), 2);
+  BOOST_CHECK_EQUAL(signals.back(), "EnumerationCompleted");
+
+  stub.removeInterface("eth1");
+  BOOST_CHECK(stub.getNetworkInterface("eth1") == nullptr);
+  BOOST_CHECK_EQUAL(stub.listNetworkInterfaces().size(), 0);
+  BOOST_REQUIRE_EQUAL(signals.size(), 3);
+  BOOST_CHECK_EQUAL(signals.back(), "InterfaceRemoved eth1");
+
+  stub.removeInterface("eth2"); // non-existent
+  BOOST_CHECK_EQUAL(signals.size(), 3);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestNetworkMonitorStub
+BOOST_AUTO_TEST_SUITE_END() // Net
+
+} // namespace tests
+} // namespace net
+} // namespace ndn
diff --git a/tests/unit-tests/net/network-monitor.t.cpp b/tests/unit-tests/net/network-monitor.t.cpp
index d23cb38..e46075b 100644
--- a/tests/unit-tests/net/network-monitor.t.cpp
+++ b/tests/unit-tests/net/network-monitor.t.cpp
@@ -53,7 +53,7 @@
   auto nm = make_unique<NetworkMonitor>(io);
   NM_REQUIRE_CAP(ENUM);
 
-  nm->onInterfaceAdded.connect([&] (const shared_ptr<NetworkInterface>&) {
+  nm->onInterfaceAdded.connect([&] (const shared_ptr<const NetworkInterface>&) {
     io.post([&] { nm.reset(); });
   });
   nm->onEnumerationCompleted.connect([&] {