tests: expand test coverage of EthernetFactory and UdpFactory

Change-Id: I469ac8682502ca7a0e360703b462bfb119eaedf1
diff --git a/tests/daemon/face/udp-factory.t.cpp b/tests/daemon/face/udp-factory.t.cpp
index fd83656..55d601c 100644
--- a/tests/daemon/face/udp-factory.t.cpp
+++ b/tests/daemon/face/udp-factory.t.cpp
@@ -32,6 +32,7 @@
 
 namespace nfd::tests {
 
+namespace ip = boost::asio::ip;
 using face::UdpChannel;
 using face::UdpFactory;
 using ndn::net::NetworkInterface;
@@ -42,7 +43,7 @@
   shared_ptr<UdpChannel>
   createChannel(const std::string& localIp, uint16_t localPort)
   {
-    udp::Endpoint endpoint(boost::asio::ip::make_address(localIp), localPort);
+    udp::Endpoint endpoint(ip::make_address(localIp), localPort);
     return factory.createChannel(endpoint, 5_min);
   }
 };
@@ -53,7 +54,7 @@
   UdpFactoryMcastFixture()
   {
     for (const auto& netif : collectNetworkInterfaces()) {
-      // same filtering logic as UdpFactory::applyMcastConfigToNetif()
+      // similar filtering logic to UdpFactory::applyMcastConfigToNetif()
       if (netif->isUp() && !netif->isLoopback() && netif->canMulticast()) {
         bool hasValidIpAddress = false;
         if (hasAddressFamily(*netif, ndn::net::AddressFamily::V4)) {
@@ -69,38 +70,6 @@
         }
       }
     }
-    copyRealNetifsToNetmon();
-  }
-
-  shared_ptr<Face>
-  createMulticastFace(const std::string& localIp, const std::string& mcastIp, uint16_t mcastPort)
-  {
-    auto localAddress = boost::asio::ip::make_address(localIp);
-    udp::Endpoint mcastEndpoint(boost::asio::ip::make_address(mcastIp), mcastPort);
-
-    if (localAddress.is_v4()) {
-      BOOST_ASSERT(!netifsV4.empty());
-      return factory.createMulticastFace(*netifsV4.front(), localAddress, mcastEndpoint);
-    }
-    else {
-      BOOST_ASSERT(!netifsV6.empty());
-      return factory.createMulticastFace(*netifsV6.front(), localAddress, mcastEndpoint);
-    }
-  }
-
-  /** \brief Returns a non-loopback IP address suitable for the creation of a UDP multicast face.
-   */
-  boost::asio::ip::address
-  findNonLoopbackAddressForMulticastFace(ndn::net::AddressFamily af) const
-  {
-    const auto& netifList = af == ndn::net::AddressFamily::V4 ? netifsV4 : netifsV6;
-    for (const auto& netif : netifList) {
-      for (const auto& a : netif->getNetworkAddresses()) {
-        if (a.getFamily() == af && !a.getIp().is_loopback())
-          return a.getIp();
-      }
-    }
-    return {};
   }
 
   std::vector<const Face*>
@@ -115,6 +84,35 @@
     return listFacesByScheme("udp6", linkType);
   }
 
+  shared_ptr<NetworkInterface>
+  makeFakeNetif(bool addNetworkAddresses = true)
+  {
+    using namespace ndn::net;
+
+    static int counter = 0;
+    ++counter;
+
+    auto netif = netmon->makeNetworkInterface();
+    netif->setIndex(1000 + counter);
+    netif->setName("ethdummy" + std::to_string(counter));
+    netif->setType(InterfaceType::ETHERNET);
+    netif->setFlags(IFF_MULTICAST | IFF_UP);
+    netif->setState(InterfaceState::RUNNING);
+    netif->setMtu(1500);
+    netif->setEthernetAddress(ethernet::Address{0x3e, 0x15, 0xc2, 0x8b, 0x65, 0x00});
+    if (addNetworkAddresses) {
+      netif->addNetworkAddress(NetworkAddress(AddressFamily::V4,
+                                              ip::make_address_v4("192.168.2.1"),
+                                              ip::make_address_v4("192.168.2.255"),
+                                              24, AddressScope::GLOBAL, 0));
+      netif->addNetworkAddress(NetworkAddress(AddressFamily::V6,
+                                              ip::make_address_v6("2001:db8:2::1"),
+                                              ip::make_address_v6("2001:db8:2::ffff:ffff:ffff:ffff"),
+                                              64, AddressScope::GLOBAL, 0));
+    }
+    return netif;
+  }
+
   /**
    * \brief Determine whether \p netif has at least one IP address of the given family.
    */
@@ -131,9 +129,9 @@
   static bool
   isFaceOnNetif(const Face& face, const NetworkInterface& netif)
   {
-    auto ip = boost::asio::ip::make_address(face.getLocalUri().getHost());
+    auto faceIp = ip::make_address(face.getLocalUri().getHost());
     return std::any_of(netif.getNetworkAddresses().begin(), netif.getNetworkAddresses().end(),
-                       [ip] (const auto& a) { return a.getIp() == ip; });
+                       [faceIp] (const auto& a) { return a.getIp() == faceIp; });
   }
 
 protected:
@@ -152,6 +150,47 @@
   std::vector<shared_ptr<const NetworkInterface>> netifsV6;
 };
 
+class UdpFactoryMcastFixtureWithRealNetifs : public UdpFactoryMcastFixture
+{
+protected:
+  UdpFactoryMcastFixtureWithRealNetifs()
+  {
+    copyRealNetifsToNetmon();
+  }
+
+  shared_ptr<Face>
+  createMulticastFace(const std::string& localIp, const std::string& mcastIp, uint16_t mcastPort)
+  {
+    auto localAddress = ip::make_address(localIp);
+    udp::Endpoint mcastEndpoint(ip::make_address(mcastIp), mcastPort);
+
+    if (localAddress.is_v4()) {
+      BOOST_ASSERT(!netifsV4.empty());
+      return factory.createMulticastFace(*netifsV4.front(), localAddress, mcastEndpoint);
+    }
+    else {
+      BOOST_ASSERT(!netifsV6.empty());
+      return factory.createMulticastFace(*netifsV6.front(), localAddress, mcastEndpoint);
+    }
+  }
+
+  /**
+   * \brief Returns a non-loopback IP address suitable for the creation of a UDP multicast face.
+   */
+  ip::address
+  findNonLoopbackAddressForMulticastFace(ndn::net::AddressFamily af) const
+  {
+    const auto& netifList = af == ndn::net::AddressFamily::V4 ? netifsV4 : netifsV6;
+    for (const auto& netif : netifList) {
+      for (const auto& a : netif->getNetworkAddresses()) {
+        if (a.getFamily() == af && !a.getIp().is_loopback())
+          return a.getIp();
+      }
+    }
+    return {};
+  }
+};
+
 #define SKIP_IF_UDP_MCAST_NETIF_COUNT_LT(n) \
   do { \
     if (this->netifs.size() < (n)) { \
@@ -180,11 +219,9 @@
   } while (false)
 
 BOOST_AUTO_TEST_SUITE(Face)
-BOOST_FIXTURE_TEST_SUITE(TestUdpFactory, UdpFactoryFixture)
+BOOST_AUTO_TEST_SUITE(TestUdpFactory)
 
-BOOST_AUTO_TEST_SUITE(ProcessConfig)
-
-using nfd::Face;
+BOOST_FIXTURE_TEST_SUITE(ProcessConfig, UdpFactoryFixture)
 
 BOOST_AUTO_TEST_CASE(Defaults)
 {
@@ -278,7 +315,7 @@
   }
 }
 
-BOOST_FIXTURE_TEST_CASE(EnableDisableMcast, UdpFactoryMcastFixture)
+BOOST_FIXTURE_TEST_CASE(EnableDisableMcast, UdpFactoryMcastFixtureWithRealNetifs)
 {
   const std::string CONFIG_WITH_MCAST = R"CONFIG(
     face_system
@@ -313,13 +350,13 @@
   BOOST_CHECK_EQUAL(this->listUdp4McastFaces().size(), netifsV4.size());
   BOOST_CHECK_EQUAL(this->listUdp6McastFaces().size(), netifsV6.size());
 
-  BOOST_REQUIRE_EQUAL(factory.getChannels().size(), 2);
-  for (const auto& face : this->listUdp4McastFaces()) {
+  BOOST_CHECK_EQUAL(factory.getChannels().size(), 2);
+  for (const auto* face : this->listUdp4McastFaces()) {
     BOOST_REQUIRE(face->getChannel().lock());
     BOOST_CHECK_EQUAL(face->getChannel().lock()->getUri().getScheme(), "udp4");
   }
 
-  for (const auto& face : this->listUdp6McastFaces()) {
+  for (const auto* face : this->listUdp6McastFaces()) {
     BOOST_REQUIRE(face->getChannel().lock());
     BOOST_CHECK_EQUAL(face->getChannel().lock()->getUri().getScheme(), "udp6");
   }
@@ -330,7 +367,7 @@
   BOOST_CHECK_EQUAL(this->listUdp6McastFaces().size(), 0);
 }
 
-BOOST_FIXTURE_TEST_CASE(McastAdHoc, UdpFactoryMcastFixture)
+BOOST_FIXTURE_TEST_CASE(McastAdHoc, UdpFactoryMcastFixtureWithRealNetifs)
 {
 #ifdef __linux__
   // need superuser privileges to create multicast faces on Linux
@@ -349,11 +386,13 @@
   )CONFIG";
 
   parseConfig(CONFIG, false);
+  BOOST_CHECK_EQUAL(this->listUdp4McastFaces(ndn::nfd::LINK_TYPE_MULTI_ACCESS).size(), 0);
+  BOOST_CHECK_EQUAL(this->listUdp6McastFaces(ndn::nfd::LINK_TYPE_MULTI_ACCESS).size(), 0);
   BOOST_CHECK_EQUAL(this->listUdp4McastFaces(ndn::nfd::LINK_TYPE_AD_HOC).size(), netifsV4.size());
   BOOST_CHECK_EQUAL(this->listUdp6McastFaces(ndn::nfd::LINK_TYPE_AD_HOC).size(), netifsV6.size());
 }
 
-BOOST_FIXTURE_TEST_CASE(ChangeMcastEndpointV4, UdpFactoryMcastFixture)
+BOOST_FIXTURE_TEST_CASE(ChangeMcastEndpointV4, UdpFactoryMcastFixtureWithRealNetifs)
 {
 #ifdef __linux__
   // need superuser privileges to create multicast faces on Linux
@@ -383,6 +422,7 @@
   )CONFIG";
 
   parseConfig(CONFIG1, false);
+  g_io.poll();
   auto udpMcastFaces = this->listUdp4McastFaces();
   BOOST_REQUIRE_EQUAL(udpMcastFaces.size(), netifsV4.size());
   BOOST_CHECK_EQUAL(udpMcastFaces.front()->getRemoteUri(), FaceUri("udp4://239.66.30.1:7011"));
@@ -394,7 +434,7 @@
   BOOST_CHECK_EQUAL(udpMcastFaces.front()->getRemoteUri(), FaceUri("udp4://239.66.30.2:7012"));
 }
 
-BOOST_FIXTURE_TEST_CASE(ChangeMcastEndpointV6, UdpFactoryMcastFixture)
+BOOST_FIXTURE_TEST_CASE(ChangeMcastEndpointV6, UdpFactoryMcastFixtureWithRealNetifs)
 {
 #ifdef __linux__
   // need superuser privileges to create multicast faces on Linux
@@ -424,14 +464,15 @@
   )CONFIG";
 
   parseConfig(CONFIG1, false);
+  g_io.poll();
   auto udpMcastFaces = this->listUdp6McastFaces();
   BOOST_REQUIRE_EQUAL(udpMcastFaces.size(), netifsV6.size());
   auto uri = udpMcastFaces.front()->getRemoteUri();
   BOOST_CHECK_EQUAL(uri.getScheme(), "udp6");
   // check the address ignoring the scope id
-  auto addr = boost::asio::ip::make_address_v6(uri.getHost());
+  auto addr = ip::make_address_v6(uri.getHost());
   addr.scope_id(0);
-  BOOST_CHECK_EQUAL(addr, boost::asio::ip::make_address_v6("ff02::1101"));
+  BOOST_CHECK_EQUAL(addr, ip::make_address_v6("ff02::1101"));
   BOOST_CHECK_EQUAL(uri.getPort(), "7011");
 
   parseConfig(CONFIG2, false);
@@ -441,13 +482,13 @@
   uri = udpMcastFaces.front()->getRemoteUri();
   BOOST_CHECK_EQUAL(uri.getScheme(), "udp6");
   // check the address ignoring the scope id
-  addr = boost::asio::ip::make_address_v6(uri.getHost());
+  addr = ip::make_address_v6(uri.getHost());
   addr.scope_id(0);
-  BOOST_CHECK_EQUAL(addr, boost::asio::ip::make_address_v6("ff02::1102"));
+  BOOST_CHECK_EQUAL(addr, ip::make_address_v6("ff02::1102"));
   BOOST_CHECK_EQUAL(uri.getPort(), "7012");
 }
 
-BOOST_FIXTURE_TEST_CASE(Whitelist, UdpFactoryMcastFixture)
+BOOST_FIXTURE_TEST_CASE(Whitelist, UdpFactoryMcastFixtureWithRealNetifs)
 {
 #ifdef __linux__
   // need superuser privileges to create multicast faces on Linux
@@ -478,10 +519,10 @@
   udpMcastFaces.insert(udpMcastFaces.end(), udpMcastFacesV6.begin(), udpMcastFacesV6.end());
   BOOST_CHECK_GE(udpMcastFaces.size(), 1);
   BOOST_CHECK(std::all_of(udpMcastFaces.begin(), udpMcastFaces.end(),
-                          [this] (const Face* face) { return isFaceOnNetif(*face, *netifs.front()); }));
+                          [this] (const auto* face) { return isFaceOnNetif(*face, *netifs.front()); }));
 }
 
-BOOST_FIXTURE_TEST_CASE(Blacklist, UdpFactoryMcastFixture)
+BOOST_FIXTURE_TEST_CASE(Blacklist, UdpFactoryMcastFixtureWithRealNetifs)
 {
 #ifdef __linux__
   // need superuser privileges to create multicast faces on Linux
@@ -514,10 +555,10 @@
   udpMcastFaces.insert(udpMcastFaces.end(), udpMcastFacesV6.begin(), udpMcastFacesV6.end());
   BOOST_CHECK_LT(udpMcastFaces.size(), netifsV4.size() + netifsV6.size());
   BOOST_CHECK(std::none_of(udpMcastFaces.begin(), udpMcastFaces.end(),
-                           [this] (const Face* face) { return isFaceOnNetif(*face, *netifs.front()); }));
+                           [this] (const auto* face) { return isFaceOnNetif(*face, *netifs.front()); }));
 }
 
-BOOST_FIXTURE_TEST_CASE(ChangePredicate, UdpFactoryMcastFixture)
+BOOST_FIXTURE_TEST_CASE(ChangePredicate, UdpFactoryMcastFixtureWithRealNetifs)
 {
 #ifdef __linux__
   // need superuser privileges to create multicast faces on Linux
@@ -542,13 +583,14 @@
   boost::replace_first(CONFIG2, "%ifname", netifs.back()->getName());
 
   parseConfig(CONFIG1, false);
+  g_io.poll();
 
   auto udpMcastFaces = this->listUdp4McastFaces();
   auto udpMcastFacesV6 = this->listUdp6McastFaces();
   udpMcastFaces.insert(udpMcastFaces.end(), udpMcastFacesV6.begin(), udpMcastFacesV6.end());
   BOOST_CHECK_GE(udpMcastFaces.size(), 1);
   BOOST_CHECK(std::all_of(udpMcastFaces.begin(), udpMcastFaces.end(),
-                          [this] (const Face* face) { return isFaceOnNetif(*face, *netifs.front()); }));
+                          [this] (const auto* face) { return isFaceOnNetif(*face, *netifs.front()); }));
 
   parseConfig(CONFIG2, false);
   g_io.poll();
@@ -558,7 +600,7 @@
   udpMcastFaces.insert(udpMcastFaces.end(), udpMcastFacesV6.begin(), udpMcastFacesV6.end());
   BOOST_CHECK_GE(udpMcastFaces.size(), 1);
   BOOST_CHECK(std::all_of(udpMcastFaces.begin(), udpMcastFaces.end(),
-                          [this] (const Face* face) { return isFaceOnNetif(*face, *netifs.back()); }));
+                          [this] (const auto* face) { return isFaceOnNetif(*face, *netifs.back()); }));
 }
 
 BOOST_AUTO_TEST_CASE(Omitted)
@@ -914,7 +956,7 @@
 
 BOOST_AUTO_TEST_SUITE_END() // ProcessConfig
 
-BOOST_AUTO_TEST_CASE(GetChannels)
+BOOST_FIXTURE_TEST_CASE(GetChannels, UdpFactoryFixture)
 {
   BOOST_CHECK_EQUAL(factory.getChannels().empty(), true);
 
@@ -925,47 +967,49 @@
   checkChannelListEqual(factory, expected);
 }
 
-BOOST_FIXTURE_TEST_CASE(CreateChannel, UdpFactoryMcastFixture)
+BOOST_FIXTURE_TEST_CASE(CreateChannel, UdpFactoryMcastFixtureWithRealNetifs)
 {
   auto channel1 = createChannel("127.0.0.1", 20070);
   auto channel1a = createChannel("127.0.0.1", 20070);
   BOOST_CHECK_EQUAL(channel1, channel1a);
   BOOST_CHECK_EQUAL(channel1->getUri().toString(), "udp4://127.0.0.1:20070");
+  BOOST_CHECK_EQUAL(factory.getChannels().size(), 1);
 
   auto channel2 = createChannel("127.0.0.1", 20071);
   BOOST_CHECK_NE(channel1, channel2);
+  BOOST_CHECK_EQUAL(factory.getChannels().size(), 2);
 
   auto channel3 = createChannel("::1", 20071);
   BOOST_CHECK_NE(channel2, channel3);
   BOOST_CHECK_EQUAL(channel3->getUri().toString(), "udp6://[::1]:20071");
+  BOOST_CHECK_EQUAL(factory.getChannels().size(), 3);
 
 #ifdef __linux__
   // need superuser privileges to create multicast faces on Linux
   SKIP_IF_NOT_SUPERUSER();
 #endif // __linux__
+  SKIP_IF_UDP_MCAST_NETIF_COUNT_LT(1);
 
   // createChannel with a local endpoint that has already been allocated to a UDP multicast face
   if (!netifsV4.empty()) {
     auto mcastFace = createMulticastFace("127.0.0.1", "224.0.0.254", 20072);
     BOOST_CHECK_EXCEPTION(createChannel("127.0.0.1", 20072), UdpFactory::Error,
                           [] (const UdpFactory::Error& e) {
-                            return strcmp(e.what(),
-                                          "Cannot create UDP channel on 127.0.0.1:20072, "
-                                          "endpoint already allocated to a UDP multicast face") == 0;
+                            return e.what() == "Cannot create UDP channel on 127.0.0.1:20072, "
+                                               "endpoint already allocated to a UDP multicast face"s;
                           });
   }
   if (!netifsV6.empty()) {
     auto mcastFace = createMulticastFace("::1", "ff02::114", 20072);
     BOOST_CHECK_EXCEPTION(createChannel("::1", 20072), UdpFactory::Error,
                           [] (const UdpFactory::Error& e) {
-                            return strcmp(e.what(),
-                                          "Cannot create UDP channel on [::1]:20072, "
-                                          "endpoint already allocated to a UDP multicast face") == 0;
+                            return e.what() == "Cannot create UDP channel on [::1]:20072, "
+                                               "endpoint already allocated to a UDP multicast face"s;
                           });
   }
 }
 
-BOOST_FIXTURE_TEST_CASE(CreateMulticastFaceV4, UdpFactoryMcastFixture)
+BOOST_FIXTURE_TEST_CASE(CreateMulticastFaceV4, UdpFactoryMcastFixtureWithRealNetifs)
 {
 #ifdef __linux__
   // need superuser privileges to create multicast faces on Linux
@@ -990,21 +1034,19 @@
   auto channel = createChannel("127.0.0.1", 20071);
   BOOST_CHECK_EXCEPTION(createMulticastFace("127.0.0.1", "224.0.0.254", 20071), UdpFactory::Error,
                         [] (const UdpFactory::Error& e) {
-                          return strcmp(e.what(),
-                                        "Cannot create UDP multicast face on 127.0.0.1:20071, "
-                                        "endpoint already allocated to a UDP channel") == 0;
+                          return e.what() == "Cannot create UDP multicast face on 127.0.0.1:20071, "
+                                             "endpoint already allocated to a UDP channel"s;
                         });
 
   // create with a local endpoint already used by a multicast face on a different multicast group
   BOOST_CHECK_EXCEPTION(createMulticastFace("127.0.0.1", "224.0.0.42", 20070), UdpFactory::Error,
                         [] (const UdpFactory::Error& e) {
-                          return strcmp(e.what(),
-                                        "Cannot create UDP multicast face on 127.0.0.1:20070, "
-                                        "endpoint already allocated to a different UDP multicast face") == 0;
+                          return e.what() == "Cannot create UDP multicast face on 127.0.0.1:20070, "
+                                             "endpoint already allocated to a different UDP multicast face"s;
                         });
 }
 
-BOOST_FIXTURE_TEST_CASE(CreateMulticastFaceV6, UdpFactoryMcastFixture)
+BOOST_FIXTURE_TEST_CASE(CreateMulticastFaceV6, UdpFactoryMcastFixtureWithRealNetifs)
 {
 #ifdef __linux__
   // need superuser privileges to create multicast faces on Linux
@@ -1029,21 +1071,19 @@
   auto channel = createChannel("::1", 20071);
   BOOST_CHECK_EXCEPTION(createMulticastFace("::1", "ff02::114", 20071), UdpFactory::Error,
                         [] (const UdpFactory::Error& e) {
-                          return strcmp(e.what(),
-                                        "Cannot create UDP multicast face on [::1]:20071, "
-                                        "endpoint already allocated to a UDP channel") == 0;
+                          return e.what() == "Cannot create UDP multicast face on [::1]:20071, "
+                                             "endpoint already allocated to a UDP channel"s;
                         });
 
   // create with a local endpoint already used by a multicast face on a different multicast group
   BOOST_CHECK_EXCEPTION(createMulticastFace("::1", "ff02::42", 20070), UdpFactory::Error,
                         [] (const UdpFactory::Error& e) {
-                          return strcmp(e.what(),
-                                        "Cannot create UDP multicast face on [::1]:20070, "
-                                        "endpoint already allocated to a different UDP multicast face") == 0;
+                          return e.what() == "Cannot create UDP multicast face on [::1]:20070, "
+                                             "endpoint already allocated to a different UDP multicast face"s;
                         });
 }
 
-BOOST_AUTO_TEST_CASE(CreateFace)
+BOOST_FIXTURE_TEST_CASE(CreateFace, UdpFactoryFixture)
 {
   createFace(factory,
              FaceUri("udp4://127.0.0.1:6363"),
@@ -1090,10 +1130,8 @@
              {CreateFaceExpectedResult::SUCCESS, 0, ""});
 }
 
-BOOST_AUTO_TEST_CASE(UnsupportedCreateFace)
+BOOST_FIXTURE_TEST_CASE(CreateFaceInvalidRequest, UdpFactoryFixture)
 {
-  createChannel("127.0.0.1", 20071);
-
   createFace(factory,
              FaceUri("udp4://127.0.0.1:20072"),
              FaceUri("udp4://127.0.0.1:20071"),
@@ -1121,8 +1159,132 @@
              {ndn::nfd::FACE_PERSISTENCY_PERSISTENT, {}, {}, {}, true, false, false},
              {CreateFaceExpectedResult::FAILURE, 406,
               "Local fields can only be enabled on faces with local scope"});
+
+  createFace(factory,
+             FaceUri("udp4://127.0.0.1:20072"),
+             {},
+             {ndn::nfd::FACE_PERSISTENCY_PERSISTENT, {}, {}, 42, false, false, false},
+             {CreateFaceExpectedResult::FAILURE, 406,
+              "Override MTU cannot be less than 64"});
 }
 
+BOOST_FIXTURE_TEST_SUITE(OnInterfaceAdded, UdpFactoryMcastFixture)
+
+BOOST_AUTO_TEST_CASE(EligibleV4)
+{
+#ifdef __linux__
+  // need superuser privileges to create multicast faces on Linux
+  SKIP_IF_NOT_SUPERUSER();
+#endif // __linux__
+  SKIP_IF_UDP_MCAST_V4_NETIF_COUNT_LT(1);
+
+  parseConfig(R"CONFIG(
+    face_system
+    {
+      udp
+    }
+  )CONFIG", false);
+  g_io.poll();
+  BOOST_CHECK_EQUAL(this->listUdp4McastFaces().size(), 0);
+  BOOST_CHECK_EQUAL(this->listUdp6McastFaces().size(), 0);
+
+  netmon->addInterface(const_pointer_cast<NetworkInterface>(netifsV4.front()));
+  BOOST_CHECK_EQUAL(this->listUdp4McastFaces().size(), 1);
+  BOOST_CHECK_LE(this->listUdp6McastFaces().size(), 1);
+}
+
+BOOST_AUTO_TEST_CASE(EligibleV6)
+{
+#ifdef __linux__
+  // need superuser privileges to create multicast faces on Linux
+  SKIP_IF_NOT_SUPERUSER();
+#endif // __linux__
+  SKIP_IF_UDP_MCAST_V6_NETIF_COUNT_LT(1);
+
+  parseConfig(R"CONFIG(
+    face_system
+    {
+      udp
+    }
+  )CONFIG", false);
+  g_io.poll();
+  BOOST_CHECK_EQUAL(this->listUdp4McastFaces().size(), 0);
+  BOOST_CHECK_EQUAL(this->listUdp6McastFaces().size(), 0);
+
+  netmon->addInterface(const_pointer_cast<NetworkInterface>(netifsV6.front()));
+  BOOST_CHECK_LE(this->listUdp4McastFaces().size(), 1);
+  BOOST_CHECK_EQUAL(this->listUdp6McastFaces().size(), 1);
+}
+
+BOOST_AUTO_TEST_CASE(EligibleFailure)
+{
+  parseConfig(R"CONFIG(
+    face_system
+    {
+      udp
+    }
+  )CONFIG", false);
+  g_io.poll();
+
+  // Add a fake interface that satisfies the multicast criteria.
+  netmon->addInterface(this->makeFakeNetif());
+  // Creation of multicast faces fails because the interface does not actually exist.
+  // This test is to ensure that the factory handles failures gracefully.
+  BOOST_CHECK_EQUAL(this->listUdp4McastFaces().size(), 0);
+  BOOST_CHECK_EQUAL(this->listUdp6McastFaces().size(), 0);
+}
+
+BOOST_AUTO_TEST_CASE(Ineligible)
+{
+  parseConfig(R"CONFIG(
+    face_system
+    {
+      udp
+    }
+  )CONFIG", false);
+  g_io.poll();
+
+  // netif is down
+  auto netif = this->makeFakeNetif();
+  netif->setFlags(netif->getFlags() & ~IFF_UP);
+  netmon->addInterface(netif);
+  BOOST_CHECK_EQUAL(this->listUdp4McastFaces().size(), 0);
+  BOOST_CHECK_EQUAL(this->listUdp6McastFaces().size(), 0);
+
+  // netif is loopback
+  netif = this->makeFakeNetif();
+  netif->setFlags(netif->getFlags() | IFF_LOOPBACK);
+  netmon->addInterface(netif);
+  BOOST_CHECK_EQUAL(this->listUdp4McastFaces().size(), 0);
+  BOOST_CHECK_EQUAL(this->listUdp6McastFaces().size(), 0);
+
+  // netif cannot multicast
+  netif = this->makeFakeNetif();
+  netif->setFlags(netif->getFlags() & ~IFF_MULTICAST);
+  netmon->addInterface(netif);
+  BOOST_CHECK_EQUAL(this->listUdp4McastFaces().size(), 0);
+  BOOST_CHECK_EQUAL(this->listUdp6McastFaces().size(), 0);
+
+  // no viable IP address
+  netmon->addInterface(this->makeFakeNetif(false));
+  BOOST_CHECK_EQUAL(this->listUdp4McastFaces().size(), 0);
+  BOOST_CHECK_EQUAL(this->listUdp6McastFaces().size(), 0);
+}
+
+BOOST_AUTO_TEST_CASE(Disabled)
+{
+  SKIP_IF_UDP_MCAST_NETIF_COUNT_LT(1);
+
+  parseConfig("", false);
+  g_io.poll();
+
+  netmon->addInterface(const_pointer_cast<NetworkInterface>(netifs.front()));
+  BOOST_CHECK_EQUAL(this->listUdp4McastFaces().size(), 0);
+  BOOST_CHECK_EQUAL(this->listUdp6McastFaces().size(), 0);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // OnInterfaceAdded
+
 BOOST_AUTO_TEST_SUITE_END() // TestUdpFactory
 BOOST_AUTO_TEST_SUITE_END() // Face