face: use NetworkMonitor in UdpFactory

UdpFactory now creates multicast UDP faces on eligible netifs
found by NetworkMonitor. However, UdpFactory currently does not
react to fine-grained signals from NetworkMonitor.

refs #4021

Change-Id: I7802acb6f3aaa19db8f3c3141bbcdfc338e34eba
diff --git a/daemon/face/udp-factory.cpp b/daemon/face/udp-factory.cpp
index 228af1c..ff78461 100644
--- a/daemon/face/udp-factory.cpp
+++ b/daemon/face/udp-factory.cpp
@@ -54,6 +54,7 @@
 UdpFactory::UdpFactory(const CtorParams& params)
   : ProtocolFactory(params)
 {
+  m_netifAddConn = netmon->onInterfaceAdded.connect(bind(&UdpFactory::applyMcastConfigToNetif, this, _1));
 }
 
 void
@@ -201,7 +202,7 @@
     // Even if there's no configuration change, we still need to re-apply configuration because
     // netifs may have changed.
     m_mcastConfig = mcastConfig;
-    this->applyMulticastConfig(context);
+    this->applyMcastConfig(context);
   }
 }
 
@@ -349,14 +350,12 @@
                                                      localEndpoint.address().to_v4()));
 
 #ifdef __linux__
-  /*
-   * On Linux, if there is more than one MulticastUdpFace for the same multicast
-   * group but they are bound to different network interfaces, the socket needs
-   * to be bound to the specific interface using SO_BINDTODEVICE, otherwise the
-   * face will receive all packets sent to the other interfaces as well.
-   * This happens only on Linux. On OS X, the ip::multicast::join_group option
-   * is enough to get the desired behaviour.
-   */
+  // On Linux, if there is more than one multicast UDP face for the same multicast
+  // group but they are bound to different network interfaces, the socket needs
+  // to be bound to the specific interface using SO_BINDTODEVICE, otherwise the
+  // face will receive all packets sent to the other interfaces as well.
+  // This happens only on Linux. On macOS, the ip::multicast::join_group option
+  // is enough to get the desired behaviour.
   if (!networkInterfaceName.empty()) {
     if (::setsockopt(receiveSocket.native_handle(), SOL_SOCKET, SO_BINDTODEVICE,
                      networkInterfaceName.c_str(), networkInterfaceName.size() + 1) < 0) {
@@ -392,46 +391,75 @@
   return createMulticastFace(localEndpoint, multicastEndpoint, networkInterfaceName);
 }
 
+static ndn::optional<ndn::net::NetworkAddress>
+getV4Address(const ndn::net::NetworkInterface& netif)
+{
+  using namespace ndn::net;
+  for (const NetworkAddress& na : netif.getNetworkAddresses()) {
+    if (na.getFamily() == AddressFamily::V4 && na.getScope() != AddressScope::NOWHERE) {
+      return na;
+    }
+  }
+  return ndn::nullopt;
+}
+
+shared_ptr<Face>
+UdpFactory::applyMcastConfigToNetif(const shared_ptr<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;
+  }
+
+  auto address = getV4Address(*netif);
+  if (!address) {
+    NFD_LOG_DEBUG("Not creating multicast face on " << netif->getName() << ": no IPv4 address");
+    // keep an eye on new addresses
+    m_netifConns[netif->getIndex()].addrAddConn =
+      netif->onAddressAdded.connectSingleShot(bind(&UdpFactory::applyMcastConfigToNetif, this, netif));
+    return nullptr;
+  }
+
+  if (!m_mcastConfig.netifPredicate(*netif)) {
+    NFD_LOG_DEBUG("Not creating multicast face on " << netif->getName() << ": rejected by predicate");
+    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.
+
+  if (face->getId() == INVALID_FACEID) {
+    // new face: register with forwarding
+    this->addFace(face);
+  }
+  return face;
+}
+
 void
-UdpFactory::applyMulticastConfig(const FaceSystem::ConfigContext& context)
+UdpFactory::applyMcastConfig(const FaceSystem::ConfigContext& context)
 {
   // collect old 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 capableNetifRange = context.listNetifs() |
-                             boost::adaptors::filtered([this] (const NetworkInterfaceInfo& netif) {
-                               return netif.isUp() && netif.isMulticastCapable() &&
-                                      !netif.ipv4Addresses.empty() &&
-                                      m_mcastConfig.netifPredicate(netif);
-                             });
-
-    bool needIfname = false;
-#ifdef __linux__
-    std::vector<NetworkInterfaceInfo> capableNetifs;
-    boost::copy(capableNetifRange, std::back_inserter(capableNetifs));
-    // on Linux, ifname is needed to create more than one UDP multicast face on the same group
-    needIfname = capableNetifs.size() > 1;
-#else
-    auto& capableNetifs = capableNetifRange;
-#endif // __linux__
-
-    // create faces
-    for (const auto& netif : capableNetifs) {
-      udp::Endpoint localEndpoint(netif.ipv4Addresses.front(), m_mcastConfig.group.port());
-      shared_ptr<Face> face = this->createMulticastFace(localEndpoint, m_mcastConfig.group,
-                                                        needIfname ? netif.name : "");
-      if (face->getId() == INVALID_FACEID) {
-        // new face: register with forwarding
-        this->addFace(face);
-      }
-      else {
-        // existing face: don't destroy
-        oldFaces.erase(face);
-      }
+  // create faces if requested by config
+  for (const auto& netif : netmon->listNetworkInterfaces()) {
+    auto face = this->applyMcastConfigToNetif(netif);
+    if (face != nullptr) {
+      // don't destroy face
+      oldFaces.erase(face);
     }
   }
 
diff --git a/daemon/face/udp-factory.hpp b/daemon/face/udp-factory.hpp
index 5ffa3b6..db8d666 100644
--- a/daemon/face/udp-factory.hpp
+++ b/daemon/face/udp-factory.hpp
@@ -111,7 +111,7 @@
   getChannels() const override;
 
   /**
-   * \brief Create MulticastUdpFace using udp::Endpoint
+   * \brief Create multicast UDP face using udp::Endpoint
    *
    * udp::Endpoint is really an alias for boost::asio::ip::udp::endpoint.
    *
@@ -126,10 +126,10 @@
    * \param localEndpoint local endpoint
    * \param multicastEndpoint multicast endpoint
    * \param networkInterfaceName name of the network interface on which the face will be bound
-   *        (Used only on multihomed linux machine with more than one MulticastUdpFace for
-   *        the same multicast group. If specified, will requires CAP_NET_RAW capability)
+   *        (Used only on multihomed linux machine with more than one multicast UDP face for
+   *        the same multicast group. If specified, will require CAP_NET_RAW capability)
    *        An empty string can be provided in other system or in linux machine with only one
-   *        MulticastUdpFace per multicast group
+   *        multicast UDP face per multicast group
    *
    * \return always a valid pointer to a MulticastUdpFace object, an exception
    *         is thrown if it cannot be created.
@@ -147,8 +147,16 @@
                       const std::string& networkInterfaceName = "");
 
 private:
+  /** \brief Create UDP multicast face on \p netif if needed by \p m_mcastConfig.
+   *  \return new or existing face, or nullptr if no face should be created
+   */
+  shared_ptr<Face>
+  applyMcastConfigToNetif(const shared_ptr<const ndn::net::NetworkInterface>& netif);
+
+  /** \brief Create and destroy UDP multicast faces according to \p m_mcastConfig.
+   */
   void
-  applyMulticastConfig(const FaceSystem::ConfigContext& context);
+  applyMcastConfig(const FaceSystem::ConfigContext& context);
 
 private:
   std::map<udp::Endpoint, shared_ptr<UdpChannel>> m_channels;
@@ -163,6 +171,13 @@
 
   MulticastConfig m_mcastConfig;
   std::map<udp::Endpoint, shared_ptr<Face>> m_mcastFaces;
+
+  signal::ScopedConnection m_netifAddConn;
+  struct NetifConns
+  {
+    signal::ScopedConnection addrAddConn;
+  };
+  std::map<int, NetifConns> m_netifConns; // ifindex => signal connections
 };
 
 } // namespace face
diff --git a/tests/daemon/face/udp-factory.t.cpp b/tests/daemon/face/udp-factory.t.cpp
index 7f12af3..b5225cd 100644
--- a/tests/daemon/face/udp-factory.t.cpp
+++ b/tests/daemon/face/udp-factory.t.cpp
@@ -118,7 +118,22 @@
       if (netif->isUp() && netif->canMulticast() && hasAddressFamily<AddressFamily::V4>(*netif)) {
         netifs.push_back(netif);
       }
+
+      auto copy = netmon->makeNetworkInterface();
+      copy->setIndex(netif->getIndex());
+      copy->setName(netif->getName());
+      copy->setType(netif->getType());
+      copy->setFlags(netif->getFlags());
+      copy->setState(netif->getState());
+      copy->setMtu(netif->getMtu());
+      copy->setEthernetAddress(netif->getEthernetAddress());
+      copy->setEthernetBroadcastAddress(netif->getEthernetBroadcastAddress());
+      for (const auto& na : netif->getNetworkAddresses()) {
+        copy->addNetworkAddress(na);
+      }
+      netmon->addInterface(copy);
     }
+    netmon->emitEnumerationCompleted();
   }
 
   std::vector<const Face*>
@@ -161,7 +176,7 @@
 BOOST_FIXTURE_TEST_CASE(EnableDisableMcast, UdpMcastConfigFixture)
 {
 #ifdef __linux__
-  // need superuser privilege for creating multicast faces on linux
+  // need superuser privilege for creating multicast faces on Linux
   SKIP_IF_NOT_SUPERUSER();
 #endif // __linux__
 
@@ -201,7 +216,7 @@
 BOOST_FIXTURE_TEST_CASE(McastAdHoc, UdpMcastConfigFixture)
 {
 #ifdef __linux__
-  // need superuser privilege for creating multicast faces on linux
+  // need superuser privilege for creating multicast faces on Linux
   SKIP_IF_NOT_SUPERUSER();
 #endif // __linux__
   SKIP_IF_UDP_MCAST_NETIF_COUNT_LT(1);
@@ -223,7 +238,7 @@
 BOOST_FIXTURE_TEST_CASE(ChangeMcastEndpoint, UdpMcastConfigFixture)
 {
 #ifdef __linux__
-  // need superuser privilege for creating multicast faces on linux
+  // need superuser privilege for creating multicast faces on Linux
   SKIP_IF_NOT_SUPERUSER();
 #endif // __linux__
   SKIP_IF_UDP_MCAST_NETIF_COUNT_LT(1);
@@ -266,7 +281,7 @@
 BOOST_FIXTURE_TEST_CASE(Whitelist, UdpMcastConfigFixture)
 {
 #ifdef __linux__
-  // need superuser privilege for creating multicast faces on linux
+  // need superuser privilege for creating multicast faces on Linux
   SKIP_IF_NOT_SUPERUSER();
 #endif // __linux__
   SKIP_IF_UDP_MCAST_NETIF_COUNT_LT(1);
@@ -294,7 +309,7 @@
 BOOST_FIXTURE_TEST_CASE(Blacklist, UdpMcastConfigFixture)
 {
 #ifdef __linux__
-  // need superuser privilege for creating multicast faces on linux
+  // need superuser privilege for creating multicast faces on Linux
   SKIP_IF_NOT_SUPERUSER();
 #endif // __linux__
   SKIP_IF_UDP_MCAST_NETIF_COUNT_LT(1);
@@ -324,7 +339,7 @@
 BOOST_FIXTURE_TEST_CASE(ChangePredicate, UdpMcastConfigFixture)
 {
 #ifdef __linux__
-  // need superuser privilege for creating multicast faces on linux
+  // need superuser privilege for creating multicast faces on Linux
   SKIP_IF_NOT_SUPERUSER();
 #endif // __linux__
   SKIP_IF_UDP_MCAST_NETIF_COUNT_LT(2);