face: set EthernetTransport UP/DOWN based on netif state

refs #3352

Change-Id: I24476ee293d229cd9222996f91f31cfb15728c47
diff --git a/tests/daemon/face/ethernet-fixture.hpp b/tests/daemon/face/ethernet-fixture.hpp
index 60402a6..993352d 100644
--- a/tests/daemon/face/ethernet-fixture.hpp
+++ b/tests/daemon/face/ethernet-fixture.hpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /*
- * Copyright (c) 2014-2017,  Regents of the University of California,
+ * Copyright (c) 2014-2018,  Regents of the University of California,
  *                           Arizona Board of Regents,
  *                           Colorado State University,
  *                           University Pierre & Marie Curie, Sorbonne University,
@@ -57,6 +57,9 @@
         }
       }
     }
+    if (!netifs.empty()) {
+      netif = const_pointer_cast<ndn::net::NetworkInterface>(netifs.front());
+    }
   }
 
   /** \brief create a UnicastEthernetTransport
@@ -65,10 +68,10 @@
   initializeUnicast(ndn::nfd::FacePersistency persistency = ndn::nfd::FACE_PERSISTENCY_PERSISTENT,
                     ethernet::Address remoteAddr = {0x00, 0x00, 0x5e, 0x00, 0x53, 0x5e})
   {
-    BOOST_ASSERT(netifs.size() > 0);
-    localEp = netifs.front()->getName();
+    BOOST_ASSERT(netif != nullptr);
+    localEp = netif->getName();
     remoteEp = remoteAddr;
-    transport = make_unique<UnicastEthernetTransport>(*netifs.front(), remoteEp, persistency, time::seconds(2));
+    transport = make_unique<UnicastEthernetTransport>(*netif, remoteEp, persistency, time::seconds(2));
   }
 
   /** \brief create a MulticastEthernetTransport
@@ -77,10 +80,10 @@
   initializeMulticast(ndn::nfd::LinkType linkType = ndn::nfd::LINK_TYPE_MULTI_ACCESS,
                       ethernet::Address mcastGroup = {0x01, 0x00, 0x5e, 0x90, 0x10, 0x5e})
   {
-    BOOST_ASSERT(netifs.size() > 0);
-    localEp = netifs.front()->getName();
+    BOOST_ASSERT(netif != nullptr);
+    localEp = netif->getName();
     remoteEp = mcastGroup;
-    transport = make_unique<MulticastEthernetTransport>(*netifs.front(), remoteEp, linkType);
+    transport = make_unique<MulticastEthernetTransport>(*netif, remoteEp, linkType);
   }
 
 protected:
@@ -90,6 +93,7 @@
    */
   std::vector<shared_ptr<const ndn::net::NetworkInterface>> netifs;
 
+  shared_ptr<ndn::net::NetworkInterface> netif;
   unique_ptr<EthernetTransport> transport;
   std::string localEp;
   ethernet::Address remoteEp;
diff --git a/tests/daemon/face/multicast-ethernet-transport.t.cpp b/tests/daemon/face/multicast-ethernet-transport.t.cpp
index 511f57a..d4e1fce 100644
--- a/tests/daemon/face/multicast-ethernet-transport.t.cpp
+++ b/tests/daemon/face/multicast-ethernet-transport.t.cpp
@@ -59,6 +59,34 @@
   BOOST_CHECK_EQUAL(transport->canChangePersistencyTo(ndn::nfd::FACE_PERSISTENCY_PERMANENT), true);
 }
 
+BOOST_AUTO_TEST_CASE(NetifStateChange)
+{
+  SKIP_IF_ETHERNET_NETIF_COUNT_LT(1);
+  initializeMulticast();
+  BOOST_CHECK_EQUAL(transport->getState(), TransportState::UP);
+
+  // simulate 'ip link set IFNAME down'
+  scheduler::schedule(10_ms, [=] { netif->setState(ndn::net::InterfaceState::DOWN); });
+  transport->afterStateChange.connectSingleShot([&] (TransportState oldState, TransportState newState) {
+    BOOST_CHECK_EQUAL(oldState, TransportState::UP);
+    BOOST_CHECK_EQUAL(newState, TransportState::DOWN);
+    limitedIo.afterOp();
+  });
+  BOOST_CHECK_EQUAL(limitedIo.run(1, 1_s), LimitedIo::EXCEED_OPS);
+  BOOST_CHECK_EQUAL(transport->getState(), TransportState::DOWN);
+
+  // simulate 'ip link set IFNAME up'
+  scheduler::schedule(10_ms, [=] { netif->setState(ndn::net::InterfaceState::NO_CARRIER); });
+  scheduler::schedule(80_ms, [=] { netif->setState(ndn::net::InterfaceState::RUNNING); });
+  transport->afterStateChange.connectSingleShot([&] (TransportState oldState, TransportState newState) {
+    BOOST_CHECK_EQUAL(oldState, TransportState::DOWN);
+    BOOST_CHECK_EQUAL(newState, TransportState::UP);
+    limitedIo.afterOp();
+  });
+  BOOST_CHECK_EQUAL(limitedIo.run(1, 1_s), LimitedIo::EXCEED_OPS);
+  BOOST_CHECK_EQUAL(transport->getState(), TransportState::UP);
+}
+
 BOOST_AUTO_TEST_CASE(Close)
 {
   SKIP_IF_ETHERNET_NETIF_COUNT_LT(1);
diff --git a/tests/daemon/face/test-netif.cpp b/tests/daemon/face/test-netif.cpp
index 56b9081..b3e6fde 100644
--- a/tests/daemon/face/test-netif.cpp
+++ b/tests/daemon/face/test-netif.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /*
- * Copyright (c) 2014-2017,  Regents of the University of California,
+ * Copyright (c) 2014-2018,  Regents of the University of California,
  *                           Arizona Board of Regents,
  *                           Colorado State University,
  *                           University Pierre & Marie Curie, Sorbonne University,
@@ -26,13 +26,24 @@
 #include "test-netif.hpp"
 #include "core/global-io.hpp"
 
-#include <ndn-cxx/net/network-monitor.hpp>
-
 namespace nfd {
 namespace face {
 namespace tests {
 
 std::vector<shared_ptr<const NetworkInterface>>
+enumerateNetworkInterfaces(NetworkMonitor& netmon)
+{
+  if ((netmon.getCapabilities() & NetworkMonitor::CAP_ENUM) == 0) {
+    BOOST_THROW_EXCEPTION(NetworkMonitor::Error("NetworkMonitor::CAP_ENUM is unavailable"));
+  }
+
+  netmon.onEnumerationCompleted.connect([] { getGlobalIoService().stop(); });
+  getGlobalIoService().run();
+  getGlobalIoService().reset();
+  return netmon.listNetworkInterfaces();
+}
+
+std::vector<shared_ptr<const NetworkInterface>>
 collectNetworkInterfaces(bool allowCached)
 {
   using ndn::net::NetworkMonitor;
@@ -44,15 +55,7 @@
 
   if (!allowCached || cached.empty()) {
     NetworkMonitor netmon(getGlobalIoService());
-    if ((netmon.getCapabilities() & NetworkMonitor::CAP_ENUM) == 0) {
-      BOOST_THROW_EXCEPTION(NetworkMonitor::Error("NetworkMonitor::CAP_ENUM is unavailable"));
-    }
-
-    netmon.onEnumerationCompleted.connect([] { getGlobalIoService().stop(); });
-    getGlobalIoService().run();
-    getGlobalIoService().reset();
-
-    cached = netmon.listNetworkInterfaces();
+    cached = enumerateNetworkInterfaces(netmon);
   }
 
   return cached;
diff --git a/tests/daemon/face/test-netif.hpp b/tests/daemon/face/test-netif.hpp
index d30d865..7c926e6 100644
--- a/tests/daemon/face/test-netif.hpp
+++ b/tests/daemon/face/test-netif.hpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /*
- * Copyright (c) 2014-2017,  Regents of the University of California,
+ * Copyright (c) 2014-2018,  Regents of the University of California,
  *                           Arizona Board of Regents,
  *                           Colorado State University,
  *                           University Pierre & Marie Curie, Sorbonne University,
@@ -30,6 +30,7 @@
 
 #include <ndn-cxx/net/network-address.hpp>
 #include <ndn-cxx/net/network-interface.hpp>
+#include <ndn-cxx/net/network-monitor.hpp>
 
 namespace nfd {
 namespace face {
@@ -37,10 +38,21 @@
 
 using ndn::net::NetworkAddress;
 using ndn::net::NetworkInterface;
+using ndn::net::NetworkMonitor;
+
+/** \brief Enumerate network interfaces using the given NetworkMonitor
+ *  \param netmon a NetworkMonitor constructed on the global io_service.
+ *  \note This function is blocking
+ *  \warning Signals are supported if caller keeps NetworkMonitor running
+ *  \throw ndn::net::NetworkMonitor::Error NetworkMonitor::CAP_ENUM is unavailable
+ */
+std::vector<shared_ptr<const NetworkInterface>>
+enumerateNetworkInterfaces(NetworkMonitor& netmon);
 
 /** \brief Collect information about network interfaces
  *  \param allowCached if true, previously collected information can be returned
  *  \note This function is blocking if \p allowCached is false or no previous information exists
+ *  \warning Signals are not triggered on returned NetworkInterfaces because NetworkMonitor is not running
  *  \throw ndn::net::NetworkMonitor::Error NetworkMonitor::CAP_ENUM is unavailable
  */
 std::vector<shared_ptr<const NetworkInterface>>