face: use NetworkMonitor in EthernetFactory

EthernetFactory now creates unicast channels and multicast faces
on eligible netifs found by NetworkMonitor. However,
EthernetFactory does not yet react to fine-grained signals from
NetworkMonitor.

refs #4021

Change-Id: I58aac67d5a1b00d3cda76e78627b887f93ffa541
diff --git a/daemon/face/ethernet-factory.cpp b/daemon/face/ethernet-factory.cpp
index 0ba49ae..ce8e455 100644
--- a/daemon/face/ethernet-factory.cpp
+++ b/daemon/face/ethernet-factory.cpp
@@ -45,6 +45,11 @@
 EthernetFactory::EthernetFactory(const CtorParams& params)
   : ProtocolFactory(params)
 {
+  m_netifAddConn = netmon->onInterfaceAdded.connect(
+    [this] (const shared_ptr<const ndn::net::NetworkInterface>& netif) {
+      this->applyUnicastConfigToNetif(netif);
+      this->applyMcastConfigToNetif(*netif);
+    });
 }
 
 void
@@ -67,23 +72,22 @@
   //   }
   // }
 
-  bool wantListen = true;
-  uint32_t idleTimeout = 600;
+  UnicastConfig unicastConfig;
   MulticastConfig mcastConfig;
 
   if (configSection) {
-    // face_system.ether.mcast defaults to 'yes' but only if face_system.ether section is present
-    mcastConfig.isEnabled = true;
+    // listen and mcast default to 'yes' but only if face_system.ether section is present
+    unicastConfig.isEnabled = unicastConfig.wantListen = mcastConfig.isEnabled = true;
 
     for (const auto& pair : *configSection) {
       const std::string& key = pair.first;
       const ConfigSection& value = pair.second;
 
       if (key == "listen") {
-        wantListen = ConfigFile::parseYesNo(pair, "face_system.ether");
+        unicastConfig.wantListen = ConfigFile::parseYesNo(pair, "face_system.ether");
       }
       else if (key == "idle_timeout") {
-        idleTimeout = ConfigFile::parseNumber<uint32_t>(pair, "face_system.ether");
+        unicastConfig.idleTimeout = time::seconds(ConfigFile::parseNumber<uint32_t>(pair, "face_system.ether"));
       }
       else if (key == "mcast") {
         mcastConfig.isEnabled = ConfigFile::parseYesNo(pair, "face_system.ether");
@@ -116,60 +120,48 @@
     }
   }
 
-  if (!context.isDryRun) {
-    if (configSection) {
-      providedSchemes.insert("ether");
-
-      // determine the interfaces on which channels should be created
-      auto netifs = context.listNetifs() |
-        boost::adaptors::transformed([] (const NetworkInterfaceInfo& nii) { return nii.asNetworkInterface(); }) |
-        boost::adaptors::filtered([] (const shared_ptr<const ndn::net::NetworkInterface>& netif) {
-          return netif->isUp() && !netif->isLoopback();
-        });
-
-      // create channels
-      for (const auto& netif : netifs) {
-        auto channel = this->createChannel(netif, time::seconds(idleTimeout));
-        if (wantListen && !channel->isListening()) {
-          try {
-            channel->listen(this->addFace, nullptr);
-          }
-          catch (const EthernetChannel::Error& e) {
-            NFD_LOG_WARN("Cannot listen on " << netif->getName() << ": " << e.what());
-          }
-        }
-      }
-    }
-    else if (!m_channels.empty()) {
-      NFD_LOG_WARN("Cannot disable dev channels after initialization");
-    }
-
-    if (m_mcastConfig.isEnabled != mcastConfig.isEnabled) {
-      if (mcastConfig.isEnabled) {
-        NFD_LOG_INFO("enabling multicast on " << mcastConfig.group);
-      }
-      else {
-        NFD_LOG_INFO("disabling multicast");
-      }
-    }
-    else if (mcastConfig.isEnabled) {
-      if (m_mcastConfig.linkType != mcastConfig.linkType && !m_mcastFaces.empty()) {
-        NFD_LOG_WARN("Cannot change ad hoc setting on existing faces");
-      }
-      if (m_mcastConfig.group != mcastConfig.group) {
-        NFD_LOG_INFO("changing multicast group from " << m_mcastConfig.group <<
-                     " to " << mcastConfig.group);
-      }
-      if (m_mcastConfig.netifPredicate != mcastConfig.netifPredicate) {
-        NFD_LOG_INFO("changing whitelist/blacklist");
-      }
-    }
-
-    // Even if there's no configuration change, we still need to re-apply configuration because
-    // netifs may have changed.
-    m_mcastConfig = mcastConfig;
-    this->applyConfig(context);
+  if (context.isDryRun) {
+    return;
   }
+
+  if (unicastConfig.isEnabled) {
+    if (m_unicastConfig.wantListen && !unicastConfig.wantListen && !m_channels.empty()) {
+      NFD_LOG_WARN("Cannot stop listening on Ethernet channels");
+    }
+    if (m_unicastConfig.idleTimeout != unicastConfig.idleTimeout && !m_channels.empty()) {
+      NFD_LOG_WARN("Idle timeout setting applies to new Ethernet channels only");
+    }
+  }
+  else if (m_unicastConfig.isEnabled && !m_channels.empty()) {
+    NFD_LOG_WARN("Cannot disable Ethernet channels after initialization");
+  }
+
+  if (m_mcastConfig.isEnabled != mcastConfig.isEnabled) {
+    if (mcastConfig.isEnabled) {
+      NFD_LOG_INFO("enabling multicast on " << mcastConfig.group);
+    }
+    else {
+      NFD_LOG_INFO("disabling multicast");
+    }
+  }
+  else if (mcastConfig.isEnabled) {
+    if (m_mcastConfig.linkType != mcastConfig.linkType && !m_mcastFaces.empty()) {
+      NFD_LOG_WARN("Cannot change ad hoc setting on existing faces");
+    }
+    if (m_mcastConfig.group != mcastConfig.group) {
+      NFD_LOG_INFO("changing multicast group from " << m_mcastConfig.group <<
+                   " to " << mcastConfig.group);
+    }
+    if (m_mcastConfig.netifPredicate != mcastConfig.netifPredicate) {
+      NFD_LOG_INFO("changing whitelist/blacklist");
+    }
+  }
+
+  // Even if there's no configuration change, we still need to re-apply configuration because
+  // netifs may have changed.
+  m_unicastConfig = unicastConfig;
+  m_mcastConfig = mcastConfig;
+  this->applyConfig(context);
 }
 
 void
@@ -265,45 +257,100 @@
   return face;
 }
 
+shared_ptr<EthernetChannel>
+EthernetFactory::applyUnicastConfigToNetif(const shared_ptr<const ndn::net::NetworkInterface>& netif)
+{
+  if (!m_unicastConfig.isEnabled) {
+    return nullptr;
+  }
+
+  if (!netif->isUp()) {
+    NFD_LOG_DEBUG("Not creating channel on " << netif->getName() << ": netif is down");
+    return nullptr;
+  }
+
+  if (netif->isLoopback()) {
+    NFD_LOG_DEBUG("Not creating channel on " << netif->getName() << ": netif is loopback");
+    return nullptr;
+  }
+
+  auto channel = this->createChannel(netif, m_unicastConfig.idleTimeout);
+  if (m_unicastConfig.wantListen && !channel->isListening()) {
+    try {
+      channel->listen(this->addFace, nullptr);
+    }
+    catch (const EthernetChannel::Error& e) {
+      NFD_LOG_WARN("Cannot listen on " << netif->getName() << ": " << e.what());
+    }
+  }
+  return channel;
+}
+
+shared_ptr<Face>
+EthernetFactory::applyMcastConfigToNetif(const ndn::net::NetworkInterface& netif)
+{
+  if (!m_mcastConfig.isEnabled) {
+    return nullptr;
+  }
+
+  if (!netif.isUp()) {
+    NFD_LOG_DEBUG("Not creating multicast face on " << netif.getName() << ": netif is down");
+    return nullptr;
+  }
+
+  if (!netif.canMulticast()) {
+    NFD_LOG_DEBUG("Not creating multicast face on " << netif.getName() << ": netif cannot multicast");
+    return nullptr;
+  }
+
+  if (!m_mcastConfig.netifPredicate(netif)) {
+    NFD_LOG_DEBUG("Not creating multicast face on " << netif.getName() << ": rejected by whitelist/blacklist");
+    return nullptr;
+  }
+
+  NFD_LOG_DEBUG("Creating multicast face on " << netif.getName());
+  shared_ptr<Face> face;
+  try {
+    face = this->createMulticastFace(netif, m_mcastConfig.group);
+  }
+  catch (const EthernetTransport::Error& e) {
+    NFD_LOG_WARN("Cannot create multicast face on " << netif.getName() << ": " << e.what());
+    return nullptr;
+  }
+
+  if (face->getId() == face::INVALID_FACEID) {
+    // new face: register with forwarding
+    this->addFace(face);
+  }
+  return face;
+}
+
 void
 EthernetFactory::applyConfig(const FaceSystem::ConfigContext& context)
 {
-  // collect old faces
+  if (m_unicastConfig.isEnabled) {
+    providedSchemes.insert("ether");
+  }
+  else {
+    providedSchemes.erase("ether");
+  }
+
+  // collect old multicast faces
   std::set<shared_ptr<Face>> oldFaces;
-  boost::copy(m_mcastFaces | boost::adaptors::map_values,
-              std::inserter(oldFaces, oldFaces.end()));
+  boost::copy(m_mcastFaces | boost::adaptors::map_values, std::inserter(oldFaces, oldFaces.end()));
 
-  if (m_mcastConfig.isEnabled) {
-    // determine interfaces on which faces should be created or retained
-    auto capableNetifs = context.listNetifs() |
-      boost::adaptors::transformed([] (const NetworkInterfaceInfo& nii) { return nii.asNetworkInterface(); }) |
-      boost::adaptors::filtered([this] (const shared_ptr<const ndn::net::NetworkInterface>& netif) {
-        return netif->isUp() && netif->canMulticast() && m_mcastConfig.netifPredicate(*netif);
-      });
+  // create channels and multicast faces if requested by config
+  for (const auto& netif : netmon->listNetworkInterfaces()) {
+    this->applyUnicastConfigToNetif(netif);
 
-    // create faces
-    for (const auto& netif : capableNetifs) {
-      shared_ptr<Face> face;
-      try {
-        face = this->createMulticastFace(*netif, m_mcastConfig.group);
-      }
-      catch (const EthernetTransport::Error& e) {
-        NFD_LOG_WARN("Cannot create Ethernet multicast face on " << netif->getName() << ": " << e.what());
-        continue;
-      }
-
-      if (face->getId() == face::INVALID_FACEID) {
-        // new face: register with forwarding
-        this->addFace(face);
-      }
-      else {
-        // existing face: don't destroy
-        oldFaces.erase(face);
-      }
+    auto face = this->applyMcastConfigToNetif(*netif);
+    if (face != nullptr) {
+      // don't destroy face
+      oldFaces.erase(face);
     }
   }
 
-  // destroy old faces that are not needed in new configuration
+  // destroy old multicast faces that are not needed in new configuration
   for (const auto& face : oldFaces) {
     face->close();
   }
diff --git a/daemon/face/ethernet-factory.hpp b/daemon/face/ethernet-factory.hpp
index 244f769..7f53cff 100644
--- a/daemon/face/ethernet-factory.hpp
+++ b/daemon/face/ethernet-factory.hpp
@@ -87,12 +87,32 @@
                       const ethernet::Address& group);
 
 private:
+  /** \brief Create EthernetChannel on \p netif if requested by \p m_unicastConfig.
+   *  \return new or existing channel, or nullptr if no channel should be created
+   */
+  shared_ptr<EthernetChannel>
+  applyUnicastConfigToNetif(const shared_ptr<const ndn::net::NetworkInterface>& netif);
+
+  /** \brief Create Ethernet multicast face on \p netif if requested by \p m_mcastConfig.
+   *  \return new or existing face, or nullptr if no face should be created
+   */
+  shared_ptr<Face>
+  applyMcastConfigToNetif(const ndn::net::NetworkInterface& netif);
+
   void
   applyConfig(const FaceSystem::ConfigContext& context);
 
 private:
   std::map<std::string, shared_ptr<EthernetChannel>> m_channels; ///< ifname => channel
 
+  struct UnicastConfig
+  {
+    bool isEnabled = false;
+    bool wantListen = false;
+    time::nanoseconds idleTimeout = time::seconds(600);
+  };
+  UnicastConfig m_unicastConfig;
+
   struct MulticastConfig
   {
     bool isEnabled = false;
@@ -100,11 +120,12 @@
     ndn::nfd::LinkType linkType = ndn::nfd::LINK_TYPE_MULTI_ACCESS;
     NetworkInterfacePredicate netifPredicate;
   };
-
   MulticastConfig m_mcastConfig;
 
   /// (ifname, group) => face
   std::map<std::pair<std::string, ethernet::Address>, shared_ptr<Face>> m_mcastFaces;
+
+  signal::ScopedConnection m_netifAddConn;
 };
 
 } // namespace face
diff --git a/daemon/face/udp-factory.cpp b/daemon/face/udp-factory.cpp
index ff78461..da42416 100644
--- a/daemon/face/udp-factory.cpp
+++ b/daemon/face/udp-factory.cpp
@@ -151,59 +151,61 @@
     }
   }
 
-  if (!context.isDryRun) {
-    if (enableV4) {
-      udp::Endpoint endpoint(ip::udp::v4(), port);
-      shared_ptr<UdpChannel> v4Channel = this->createChannel(endpoint, time::seconds(idleTimeout));
-      if (!v4Channel->isListening()) {
-        v4Channel->listen(this->addFace, nullptr);
-      }
-      providedSchemes.insert("udp");
-      providedSchemes.insert("udp4");
-    }
-    else if (providedSchemes.count("udp4") > 0) {
-      NFD_LOG_WARN("Cannot close udp4 channel after its creation");
-    }
-
-    if (enableV6) {
-      udp::Endpoint endpoint(ip::udp::v6(), port);
-      shared_ptr<UdpChannel> v6Channel = this->createChannel(endpoint, time::seconds(idleTimeout));
-      if (!v6Channel->isListening()) {
-        v6Channel->listen(this->addFace, nullptr);
-      }
-      providedSchemes.insert("udp");
-      providedSchemes.insert("udp6");
-    }
-    else if (providedSchemes.count("udp6") > 0) {
-      NFD_LOG_WARN("Cannot close udp6 channel after its creation");
-    }
-
-    if (m_mcastConfig.isEnabled != mcastConfig.isEnabled) {
-      if (mcastConfig.isEnabled) {
-        NFD_LOG_INFO("enabling multicast on " << mcastConfig.group);
-      }
-      else {
-        NFD_LOG_INFO("disabling multicast");
-      }
-    }
-    else if (mcastConfig.isEnabled) {
-      if (m_mcastConfig.linkType != mcastConfig.linkType && !m_mcastFaces.empty()) {
-        NFD_LOG_WARN("Cannot change ad hoc setting on existing faces");
-      }
-      if (m_mcastConfig.group != mcastConfig.group) {
-        NFD_LOG_INFO("changing multicast group from " << m_mcastConfig.group <<
-                     " to " << mcastConfig.group);
-      }
-      if (m_mcastConfig.netifPredicate != mcastConfig.netifPredicate) {
-        NFD_LOG_INFO("changing whitelist/blacklist");
-      }
-    }
-
-    // Even if there's no configuration change, we still need to re-apply configuration because
-    // netifs may have changed.
-    m_mcastConfig = mcastConfig;
-    this->applyMcastConfig(context);
+  if (context.isDryRun) {
+    return;
   }
+
+  if (enableV4) {
+    udp::Endpoint endpoint(ip::udp::v4(), port);
+    shared_ptr<UdpChannel> v4Channel = this->createChannel(endpoint, time::seconds(idleTimeout));
+    if (!v4Channel->isListening()) {
+      v4Channel->listen(this->addFace, nullptr);
+    }
+    providedSchemes.insert("udp");
+    providedSchemes.insert("udp4");
+  }
+  else if (providedSchemes.count("udp4") > 0) {
+    NFD_LOG_WARN("Cannot close udp4 channel after its creation");
+  }
+
+  if (enableV6) {
+    udp::Endpoint endpoint(ip::udp::v6(), port);
+    shared_ptr<UdpChannel> v6Channel = this->createChannel(endpoint, time::seconds(idleTimeout));
+    if (!v6Channel->isListening()) {
+      v6Channel->listen(this->addFace, nullptr);
+    }
+    providedSchemes.insert("udp");
+    providedSchemes.insert("udp6");
+  }
+  else if (providedSchemes.count("udp6") > 0) {
+    NFD_LOG_WARN("Cannot close udp6 channel after its creation");
+  }
+
+  if (m_mcastConfig.isEnabled != mcastConfig.isEnabled) {
+    if (mcastConfig.isEnabled) {
+      NFD_LOG_INFO("enabling multicast on " << mcastConfig.group);
+    }
+    else {
+      NFD_LOG_INFO("disabling multicast");
+    }
+  }
+  else if (mcastConfig.isEnabled) {
+    if (m_mcastConfig.linkType != mcastConfig.linkType && !m_mcastFaces.empty()) {
+      NFD_LOG_WARN("Cannot change ad hoc setting on existing faces");
+    }
+    if (m_mcastConfig.group != mcastConfig.group) {
+      NFD_LOG_INFO("changing multicast group from " << m_mcastConfig.group <<
+                   " to " << mcastConfig.group);
+    }
+    if (m_mcastConfig.netifPredicate != mcastConfig.netifPredicate) {
+      NFD_LOG_INFO("changing whitelist/blacklist");
+    }
+  }
+
+  // Even if there's no configuration change, we still need to re-apply configuration because
+  // netifs may have changed.
+  m_mcastConfig = mcastConfig;
+  this->applyMcastConfig(context);
 }
 
 void
@@ -430,15 +432,15 @@
   }
 
   if (!m_mcastConfig.netifPredicate(*netif)) {
-    NFD_LOG_DEBUG("Not creating multicast face on " << netif->getName() << ": rejected by predicate");
+    NFD_LOG_DEBUG("Not creating multicast face on " << netif->getName() << ": rejected by whitelist/blacklist");
     return nullptr;
   }
 
   NFD_LOG_DEBUG("Creating multicast face on " << netif->getName());
   udp::Endpoint localEndpoint(address->getIp(), m_mcastConfig.group.port());
   auto face = this->createMulticastFace(localEndpoint, m_mcastConfig.group, netif->getName());
-  // ifname is only used on Linux. It isn't required if there is only one multicast-capable netif,
-  // but it's always supplied because a new netif can be added at anytime.
+  // ifname is only used on Linux. It is not required if there is only one multicast-capable netif,
+  // but it is always supplied because a new netif can be added at anytime.
 
   if (face->getId() == INVALID_FACEID) {
     // new face: register with forwarding