mgmt: Create UDP multicast faces according to whitelist/blacklist

refs #1712

Change-Id: Ia957bb1a3a3a0108d06716bfb25ecd29c6952d62
diff --git a/daemon/face/udp-factory.cpp b/daemon/face/udp-factory.cpp
index d4ee086..8ea503c 100644
--- a/daemon/face/udp-factory.cpp
+++ b/daemon/face/udp-factory.cpp
@@ -66,6 +66,13 @@
   //   mcast yes
   //   mcast_group 224.0.23.170
   //   mcast_port 56363
+  //   whitelist
+  //   {
+  //     *
+  //   }
+  //   blacklist
+  //   {
+  //   }
   // }
 
   uint16_t port = 6363;
@@ -116,6 +123,12 @@
       else if (key == "mcast_port") {
         mcastConfig.group.port(ConfigFile::parseNumber<uint16_t>(pair, "face_system.udp"));
       }
+      else if (key == "whitelist") {
+        mcastConfig.netifPredicate.parseWhitelist(value);
+      }
+      else if (key == "blacklist") {
+        mcastConfig.netifPredicate.parseBlacklist(value);
+      }
       else {
         BOOST_THROW_EXCEPTION(ConfigFile::Error("Unrecognized option face_system.udp." + key));
       }
@@ -167,6 +180,9 @@
       NFD_LOG_INFO("changing multicast group from " << m_mcastConfig.group <<
                    " to " << mcastConfig.group);
     }
+    else if (m_mcastConfig.netifPredicate != mcastConfig.netifPredicate) {
+      NFD_LOG_INFO("changing whitelist/blacklist");
+    }
     else {
       // There's no configuration change, but we still need to re-apply configuration because
       // netifs may have changed.
@@ -456,7 +472,8 @@
     auto capableNetifRange = context.listNetifs() |
                              boost::adaptors::filtered([this] (const NetworkInterfaceInfo& netif) {
                                return netif.isUp() && netif.isMulticastCapable() &&
-                                      !netif.ipv4Addresses.empty();
+                                      !netif.ipv4Addresses.empty() &&
+                                      m_mcastConfig.netifPredicate(netif);
                              });
 
     bool needIfname = false;
diff --git a/daemon/face/udp-factory.hpp b/daemon/face/udp-factory.hpp
index 1e1a6ab..9b68448 100644
--- a/daemon/face/udp-factory.hpp
+++ b/daemon/face/udp-factory.hpp
@@ -199,6 +199,7 @@
   {
     bool isEnabled = false;
     udp::Endpoint group = udp::getDefaultMulticastGroup();
+    NetworkInterfacePredicate netifPredicate;
   };
 
   MulticastConfig m_mcastConfig;
diff --git a/nfd.conf.sample.in b/nfd.conf.sample.in
index 58ef855..b1b704a 100644
--- a/nfd.conf.sample.in
+++ b/nfd.conf.sample.in
@@ -128,6 +128,20 @@
     mcast yes ; set to 'no' to disable UDP multicast, default 'yes'
     mcast_port 56363 ; UDP multicast port number
     mcast_group 224.0.23.170 ; UDP multicast group (IPv4 only)
+
+    ; Whitelist and blacklist can contain, in no particular order:
+    ; interface names (e.g., ifname eth0),
+    ; mac addresses (e.g., ether 85:3b:4d:d3:5f:c2),
+    ; subnets (e.g., subnet 192.0.2.0/24, note that only IPv4 is supported here),
+    ; or a wildcard (*) that matches all interfaces.
+
+    whitelist
+    {
+      *
+    }
+    blacklist
+    {
+    }
   }
 
   ; The ether section contains settings of Ethernet faces and channels.
@@ -161,7 +175,7 @@
   @IF_HAVE_LIBPCAP@  mcast yes ; set to 'no' to disable Ethernet multicast, default 'yes'
   @IF_HAVE_LIBPCAP@  mcast_group 01:00:5E:00:17:AA ; Ethernet multicast group
   @IF_HAVE_LIBPCAP@
-  @IF_HAVE_LIBPCAP@  ; Whitelist and blacklist can contain, in no particular order,
+  @IF_HAVE_LIBPCAP@  ; Whitelist and blacklist can contain, in no particular order:
   @IF_HAVE_LIBPCAP@  ; interface names (e.g., ifname eth0),
   @IF_HAVE_LIBPCAP@  ; mac addresses (e.g., ether 85:3b:4d:d3:5f:c2),
   @IF_HAVE_LIBPCAP@  ; subnets (e.g., subnet 192.0.2.0/24, note that only IPv4 is supported here),
diff --git a/tests/daemon/face/udp-factory.t.cpp b/tests/daemon/face/udp-factory.t.cpp
index ac468cb..265bf97 100644
--- a/tests/daemon/face/udp-factory.t.cpp
+++ b/tests/daemon/face/udp-factory.t.cpp
@@ -28,6 +28,8 @@
 #include "factory-test-common.hpp"
 #include "face-system-fixture.hpp"
 #include "tests/limited-io.hpp"
+#include <boost/algorithm/string/replace.hpp>
+#include <boost/range/algorithm.hpp>
 
 namespace nfd {
 namespace face {
@@ -133,6 +135,15 @@
     return this->listUdpMcastFaces().size();
   }
 
+  /** \brief determine whether a UDP multicast face is created on \p netif
+   */
+  static bool
+  isFaceOnNetif(const Face& face, const NetworkInterfaceInfo& netif)
+  {
+    auto ip = boost::asio::ip::address_v4::from_string(face.getLocalUri().getHost());
+    return boost::count(netif.ipv4Addresses, ip) > 0;
+  }
+
 protected:
   /** \brief MulticastUdpTransport-capable network interfaces
    */
@@ -231,6 +242,100 @@
                     FaceUri("udp4://239.66.30.2:7012"));
 }
 
+BOOST_FIXTURE_TEST_CASE(Whitelist, UdpMcastConfigFixture)
+{
+#ifdef __linux__
+  // need superuser privilege for creating multicast faces on linux
+  SKIP_IF_NOT_SUPERUSER();
+#endif // __linux__
+  SKIP_IF_UDP_MCAST_NETIF_COUNT_LT(1);
+
+  std::string CONFIG = R"CONFIG(
+    face_system
+    {
+      udp
+      {
+        whitelist
+        {
+          ifname %ifname
+        }
+      }
+    }
+  )CONFIG";
+  boost::replace_first(CONFIG, "%ifname", netifs.front().name);
+
+  parseConfig(CONFIG, false);
+  auto udpMcastFaces = this->listUdpMcastFaces();
+  BOOST_REQUIRE_EQUAL(udpMcastFaces.size(), 1);
+  BOOST_CHECK(isFaceOnNetif(*udpMcastFaces.front(), netifs.front()));
+}
+
+BOOST_FIXTURE_TEST_CASE(Blacklist, UdpMcastConfigFixture)
+{
+#ifdef __linux__
+  // need superuser privilege for creating multicast faces on linux
+  SKIP_IF_NOT_SUPERUSER();
+#endif // __linux__
+  SKIP_IF_UDP_MCAST_NETIF_COUNT_LT(1);
+
+  std::string CONFIG = R"CONFIG(
+    face_system
+    {
+      udp
+      {
+        blacklist
+        {
+          ifname %ifname
+        }
+      }
+    }
+  )CONFIG";
+  boost::replace_first(CONFIG, "%ifname", netifs.front().name);
+
+  parseConfig(CONFIG, false);
+  auto udpMcastFaces = this->listUdpMcastFaces();
+  BOOST_CHECK_EQUAL(udpMcastFaces.size(), netifs.size() - 1);
+  BOOST_CHECK_EQUAL(boost::count_if(udpMcastFaces, [=] (const Face* face) {
+    return isFaceOnNetif(*face, netifs.front());
+  }), 0);
+}
+
+BOOST_FIXTURE_TEST_CASE(ChangePredicate, UdpMcastConfigFixture)
+{
+#ifdef __linux__
+  // need superuser privilege for creating multicast faces on linux
+  SKIP_IF_NOT_SUPERUSER();
+#endif // __linux__
+  SKIP_IF_UDP_MCAST_NETIF_COUNT_LT(2);
+
+  std::string CONFIG1 = R"CONFIG(
+    face_system
+    {
+      udp
+      {
+        whitelist
+        {
+          ifname %ifname
+        }
+      }
+    }
+  )CONFIG";
+  std::string CONFIG2 = CONFIG1;
+  boost::replace_first(CONFIG1, "%ifname", netifs.front().name);
+  boost::replace_first(CONFIG2, "%ifname", netifs.back().name);
+
+  parseConfig(CONFIG1, false);
+  auto udpMcastFaces = this->listUdpMcastFaces();
+  BOOST_REQUIRE_EQUAL(udpMcastFaces.size(), 1);
+  BOOST_CHECK(isFaceOnNetif(*udpMcastFaces.front(), netifs.front()));
+
+  parseConfig(CONFIG2, false);
+  g_io.poll();
+  udpMcastFaces = this->listUdpMcastFaces();
+  BOOST_REQUIRE_EQUAL(udpMcastFaces.size(), 1);
+  BOOST_CHECK(isFaceOnNetif(*udpMcastFaces.front(), netifs.back()));
+}
+
 BOOST_AUTO_TEST_CASE(Omitted)
 {
   const std::string CONFIG = R"CONFIG(