face: pass the whole NetworkInterface to UdpFactory::createMulticastFace

Instead of just the interface name. This is in preparation
for adding IPv6 support to createMulticastFace.

Change-Id: Ie3c02af4483b041d39d4e85c85cd712368e9bb8f
Refs: #4222
diff --git a/.jenkins.d/20-tests.sh b/.jenkins.d/20-tests.sh
index b5d512a..76d0d34 100755
--- a/.jenkins.d/20-tests.sh
+++ b/.jenkins.d/20-tests.sh
@@ -50,5 +50,5 @@
 # Then use sudo to run those tests that need superuser powers
 sudo -E ./build/unit-tests-core -t TestPrivilegeHelper $(ut_log_args core-privilege)
 sudo -E ./build/unit-tests-daemon -t Face/*Ethernet* $(ut_log_args daemon-ethernet)
-sudo -E ./build/unit-tests-daemon -t Face/*Factory/ProcessConfig $(ut_log_args daemon-face-config)
+sudo -E ./build/unit-tests-daemon -t Face/TestUdpFactory $(ut_log_args daemon-udp-factory)
 sudo -E ./build/unit-tests-daemon -t Mgmt/TestGeneralConfigSection/UserAndGroupConfig,NoUserConfig $(ut_log_args daemon-user-config)
diff --git a/daemon/face/udp-factory.cpp b/daemon/face/udp-factory.cpp
index 7477fb0..7b1f4f0 100644
--- a/daemon/face/udp-factory.cpp
+++ b/daemon/face/udp-factory.cpp
@@ -29,7 +29,7 @@
 #include "core/global-io.hpp"
 
 #include <ndn-cxx/net/address-converter.hpp>
-#include <boost/range/adaptors.hpp>
+#include <boost/range/adaptor/map.hpp>
 #include <boost/range/algorithm/copy.hpp>
 
 #ifdef __linux__
@@ -42,6 +42,7 @@
 namespace face {
 
 namespace ip = boost::asio::ip;
+namespace net = ndn::net;
 
 NFD_LOG_INIT("UdpFactory");
 NFD_REGISTER_PROTOCOL_FACTORY(UdpFactory);
@@ -118,7 +119,7 @@
       else if (key == "mcast_group") {
         const std::string& valueStr = value.get_value<std::string>();
         boost::system::error_code ec;
-        mcastConfig.group.address(boost::asio::ip::address_v4::from_string(valueStr, ec));
+        mcastConfig.group.address(ip::address_v4::from_string(valueStr, ec));
         if (ec) {
           BOOST_THROW_EXCEPTION(ConfigFile::Error("face_system.udp.mcast_group: '" +
                                 valueStr + "' cannot be parsed as an IPv4 address"));
@@ -293,9 +294,12 @@
 shared_ptr<Face>
 UdpFactory::createMulticastFace(const udp::Endpoint& localEndpoint,
                                 const udp::Endpoint& multicastEndpoint,
-                                const std::string& networkInterfaceName)
+                                const net::NetworkInterface& netif)
 {
-  // checking if the local and multicast endpoints are already in use for a multicast face
+  BOOST_ASSERT(multicastEndpoint.address().is_multicast());
+  BOOST_ASSERT(localEndpoint.port() == multicastEndpoint.port());
+
+  // check if the local and multicast endpoints are already in use for a multicast face
   auto it = m_mcastFaces.find(localEndpoint);
   if (it != m_mcastFaces.end()) {
     if (it->second->getRemoteUri() == FaceUri(multicastEndpoint))
@@ -306,7 +310,7 @@
                                   "on a different multicast group"));
   }
 
-  // checking if the local endpoint is already in use for a unicast channel
+  // check if the local endpoint is already used by a unicast channel
   if (m_channels.find(localEndpoint) != m_channels.end()) {
     BOOST_THROW_EXCEPTION(Error("Cannot create the requested UDP multicast face, local "
                                 "endpoint is already allocated for a UDP unicast channel"));
@@ -317,16 +321,6 @@
                                 "address"));
   }
 
-  if (localEndpoint.port() != multicastEndpoint.port()) {
-    BOOST_THROW_EXCEPTION(Error("Cannot create the requested UDP multicast face, "
-                                "both endpoints should have the same port number. "));
-  }
-
-  if (!multicastEndpoint.address().is_multicast()) {
-    BOOST_THROW_EXCEPTION(Error("Cannot create the requested UDP multicast face, "
-                                "the multicast group given as input is not a multicast address"));
-  }
-
   ip::udp::socket receiveSocket(getGlobalIoService());
   receiveSocket.open(multicastEndpoint.protocol());
   receiveSocket.set_option(ip::udp::socket::reuse_address(true));
@@ -352,12 +346,10 @@
   // 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) {
-      BOOST_THROW_EXCEPTION(Error("Cannot bind multicast face to " + networkInterfaceName +
-                                  ": " + std::strerror(errno)));
-    }
+  if (::setsockopt(receiveSocket.native_handle(), SOL_SOCKET, SO_BINDTODEVICE,
+                   netif.getName().data(), netif.getName().size() + 1) < 0) {
+    BOOST_THROW_EXCEPTION(Error("Cannot bind multicast face to " + netif.getName() +
+                                ": " + std::strerror(errno)));
   }
 #endif // __linux__
 
@@ -374,34 +366,22 @@
   return face;
 }
 
-shared_ptr<Face>
-UdpFactory::createMulticastFace(const std::string& localIp,
-                                const std::string& multicastIp,
-                                const std::string& multicastPort,
-                                const std::string& networkInterfaceName)
+static ndn::optional<ip::address>
+getV4Address(const net::NetworkInterface& netif)
 {
-  udp::Endpoint localEndpoint(ndn::ip::addressFromString(localIp),
-                              boost::lexical_cast<uint16_t>(multicastPort));
-  udp::Endpoint multicastEndpoint(ndn::ip::addressFromString(multicastIp),
-                                  boost::lexical_cast<uint16_t>(multicastPort));
-  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;
+  for (const auto& na : netif.getNetworkAddresses()) {
+    if (na.getFamily() == net::AddressFamily::V4 && na.getScope() != net::AddressScope::NOWHERE) {
+      return na.getIp();
     }
   }
   return ndn::nullopt;
 }
 
 shared_ptr<Face>
-UdpFactory::applyMcastConfigToNetif(const shared_ptr<const ndn::net::NetworkInterface>& netif)
+UdpFactory::applyMcastConfigToNetif(const shared_ptr<const net::NetworkInterface>& netif)
 {
+  BOOST_ASSERT(netif != nullptr);
+
   if (!m_mcastConfig.isEnabled) {
     return nullptr;
   }
@@ -431,8 +411,8 @@
   }
 
   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());
+  udp::Endpoint localEndpoint(*address, m_mcastConfig.group.port());
+  auto face = this->createMulticastFace(localEndpoint, m_mcastConfig.group, *netif);
   // 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.
 
diff --git a/daemon/face/udp-factory.hpp b/daemon/face/udp-factory.hpp
index 55556ab..9e20be4 100644
--- a/daemon/face/udp-factory.hpp
+++ b/daemon/face/udp-factory.hpp
@@ -93,7 +93,7 @@
   getChannels() const override;
 
   /**
-   * \brief Create multicast UDP face using udp::Endpoint
+   * \brief Create a multicast UDP face
    *
    * udp::Endpoint is really an alias for boost::asio::ip::udp::endpoint.
    *
@@ -102,40 +102,30 @@
    * If this method is called twice with the same pair of endpoints, only one
    * face will be created. The second call will just return the existing face.
    *
-   * If an unicast face is already active on the same local NIC and port, the
-   * creation fails and an exception is thrown.
+   * If a UDP channel, unicast face, or multicast face already exists on the
+   * same local endpoint, the creation fails and an exception is thrown.
    *
-   * \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 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
-   *        multicast UDP face per multicast group
+   * \param localEndpoint the local endpoint
+   * \param multicastEndpoint the multicast endpoint
+   * \param netif the network interface to which the face will be bound
    *
-   * \return always a valid pointer to a MulticastUdpFace object, an exception
-   *         is thrown if it cannot be created.
+   * \return always a valid shared pointer to the created face;
+   *         an exception is thrown if the face cannot be created.
    * \throw UdpFactory::Error
    */
   shared_ptr<Face>
   createMulticastFace(const udp::Endpoint& localEndpoint,
                       const udp::Endpoint& multicastEndpoint,
-                      const std::string& networkInterfaceName = "");
-
-  shared_ptr<Face>
-  createMulticastFace(const std::string& localIp,
-                      const std::string& multicastIp,
-                      const std::string& multicastPort,
-                      const std::string& networkInterfaceName = "");
+                      const ndn::net::NetworkInterface& netif);
 
 private:
-  /** \brief Create UDP multicast face on \p netif if needed by \p m_mcastConfig.
+  /** \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.
+  /** \brief Create and destroy UDP multicast faces according to \p m_mcastConfig
    */
   void
   applyMcastConfig(const FaceSystem::ConfigContext& context);
diff --git a/tests/daemon/face/udp-factory.t.cpp b/tests/daemon/face/udp-factory.t.cpp
index 36ccba4..84d4529 100644
--- a/tests/daemon/face/udp-factory.t.cpp
+++ b/tests/daemon/face/udp-factory.t.cpp
@@ -40,14 +40,77 @@
 {
 protected:
   shared_ptr<UdpChannel>
-  createChannel(const std::string& localIp, const std::string& localPort)
+  createChannel(const std::string& localIp, uint16_t localPort)
   {
-    udp::Endpoint endpoint(ndn::ip::addressFromString(localIp),
-                           boost::lexical_cast<uint16_t>(localPort));
+    udp::Endpoint endpoint(ndn::ip::addressFromString(localIp), localPort);
     return factory.createChannel(endpoint, time::minutes(5));
   }
 };
 
+class UdpFactoryMcastFixture : public UdpFactoryFixture
+{
+protected:
+  UdpFactoryMcastFixture()
+  {
+    for (const auto& netif : collectNetworkInterfaces()) {
+      if (netif->isUp() && netif->canMulticast() &&
+          hasAddressFamily(*netif, ndn::net::AddressFamily::V4)) {
+        netifs.push_back(netif);
+      }
+    }
+
+    this->copyRealNetifsToNetmon();
+  }
+
+  shared_ptr<Face>
+  createMulticastFace(const std::string& localIp, const std::string& mcastIp, uint16_t mcastPort)
+  {
+    BOOST_ASSERT(!netifs.empty());
+    udp::Endpoint localEndpoint(ndn::ip::addressFromString(localIp), mcastPort);
+    udp::Endpoint mcastEndpoint(ndn::ip::addressFromString(mcastIp), mcastPort);
+    return factory.createMulticastFace(localEndpoint, mcastEndpoint, *netifs.front());
+  }
+
+  std::vector<const Face*>
+  listUdp4McastFaces(ndn::nfd::LinkType linkType = ndn::nfd::LINK_TYPE_MULTI_ACCESS) const
+  {
+    return this->listFacesByScheme("udp4", linkType);
+  }
+
+  /** \brief determine whether \p netif has at least one address of the given family
+   */
+  static bool
+  hasAddressFamily(const NetworkInterface& netif, ndn::net::AddressFamily af)
+  {
+    return std::any_of(netif.getNetworkAddresses().begin(), netif.getNetworkAddresses().end(),
+                       [af] (const NetworkAddress& a) { return a.getFamily() == af; });
+  }
+
+  /** \brief determine whether a UDP multicast face is created on \p netif
+   */
+  static bool
+  isFaceOnNetif(const Face& face, const shared_ptr<const NetworkInterface>& netif)
+  {
+    auto ip = boost::asio::ip::address_v4::from_string(face.getLocalUri().getHost());
+    return std::any_of(netif->getNetworkAddresses().begin(), netif->getNetworkAddresses().end(),
+                       [ip] (const NetworkAddress& a) { return a.getIp() == ip; });
+  }
+
+protected:
+  /** \brief MulticastUdpTransport-capable network interfaces
+   */
+  std::vector<shared_ptr<const NetworkInterface>> netifs;
+};
+
+#define SKIP_IF_UDP_MCAST_NETIF_COUNT_LT(n) \
+  do { \
+    if (this->netifs.size() < (n)) { \
+      BOOST_WARN_MESSAGE(false, "skipping assertions that require " #n \
+                                " or more MulticastUdpTransport-capable network interfaces"); \
+      return; \
+    } \
+  } while (false)
+
 BOOST_AUTO_TEST_SUITE(Face)
 BOOST_FIXTURE_TEST_SUITE(TestUdpFactory, UdpFactoryFixture)
 
@@ -119,68 +182,7 @@
   checkChannelListEqual(factory, {"udp6://[::]:7001"});
 }
 
-class UdpMcastConfigFixture : public UdpFactoryFixture
-{
-protected:
-  UdpMcastConfigFixture()
-  {
-    for (const auto& netif : collectNetworkInterfaces()) {
-      if (netif->isUp() && netif->canMulticast() &&
-          hasAddressFamily(*netif, ndn::net::AddressFamily::V4)) {
-        netifs.push_back(netif);
-      }
-    }
-
-    this->copyRealNetifsToNetmon();
-  }
-
-  std::vector<const Face*>
-  listUdpMcastFaces(ndn::nfd::LinkType linkType = ndn::nfd::LINK_TYPE_MULTI_ACCESS) const
-  {
-    return this->listFacesByScheme("udp4", linkType);
-  }
-
-  size_t
-  countUdpMcastFaces(ndn::nfd::LinkType linkType = ndn::nfd::LINK_TYPE_MULTI_ACCESS) const
-  {
-    return this->listUdpMcastFaces(linkType).size();
-  }
-
-  /** \brief determine whether \p netif has at least one address of the given family
-   */
-  static bool
-  hasAddressFamily(const NetworkInterface& netif, ndn::net::AddressFamily af)
-  {
-    return std::any_of(netif.getNetworkAddresses().begin(), netif.getNetworkAddresses().end(),
-                       [af] (const NetworkAddress& a) { return a.getFamily() == af; });
-  }
-
-  /** \brief determine whether a UDP multicast face is created on \p netif
-   */
-  static bool
-  isFaceOnNetif(const Face& face, const shared_ptr<const NetworkInterface>& netif)
-  {
-    auto ip = boost::asio::ip::address_v4::from_string(face.getLocalUri().getHost());
-    return std::any_of(netif->getNetworkAddresses().begin(), netif->getNetworkAddresses().end(),
-                       [ip] (const NetworkAddress& a) { return a.getIp() == ip; });
-  }
-
-protected:
-  /** \brief MulticastUdpTransport-capable network interfaces
-   */
-  std::vector<shared_ptr<const NetworkInterface>> netifs;
-};
-
-#define SKIP_IF_UDP_MCAST_NETIF_COUNT_LT(n) \
-  do { \
-    if (this->netifs.size() < (n)) { \
-      BOOST_WARN_MESSAGE(false, "skipping assertions that require " #n \
-                                " or more MulticastUdpTransport-capable network interfaces"); \
-      return; \
-    } \
-  } while (false)
-
-BOOST_FIXTURE_TEST_CASE(EnableDisableMcast, UdpMcastConfigFixture)
+BOOST_FIXTURE_TEST_CASE(EnableDisableMcast, UdpFactoryMcastFixture)
 {
 #ifdef __linux__
   // need superuser privilege for creating multicast faces on Linux
@@ -207,20 +209,20 @@
   )CONFIG";
 
   parseConfig(CONFIG_WITHOUT_MCAST, false);
-  BOOST_CHECK_EQUAL(this->countUdpMcastFaces(), 0);
+  BOOST_CHECK_EQUAL(this->listUdp4McastFaces().size(), 0);
 
   SKIP_IF_UDP_MCAST_NETIF_COUNT_LT(1);
 
   parseConfig(CONFIG_WITH_MCAST, false);
   g_io.poll();
-  BOOST_CHECK_EQUAL(this->countUdpMcastFaces(), netifs.size());
+  BOOST_CHECK_EQUAL(this->listUdp4McastFaces().size(), netifs.size());
 
   parseConfig(CONFIG_WITHOUT_MCAST, false);
   g_io.poll();
-  BOOST_CHECK_EQUAL(this->countUdpMcastFaces(), 0);
+  BOOST_CHECK_EQUAL(this->listUdp4McastFaces().size(), 0);
 }
 
-BOOST_FIXTURE_TEST_CASE(McastAdHoc, UdpMcastConfigFixture)
+BOOST_FIXTURE_TEST_CASE(McastAdHoc, UdpFactoryMcastFixture)
 {
 #ifdef __linux__
   // need superuser privilege for creating multicast faces on Linux
@@ -239,10 +241,10 @@
   )CONFIG";
 
   parseConfig(CONFIG, false);
-  BOOST_CHECK_EQUAL(this->countUdpMcastFaces(ndn::nfd::LINK_TYPE_AD_HOC), netifs.size());
+  BOOST_CHECK_EQUAL(this->listUdp4McastFaces(ndn::nfd::LINK_TYPE_AD_HOC).size(), netifs.size());
 }
 
-BOOST_FIXTURE_TEST_CASE(ChangeMcastEndpoint, UdpMcastConfigFixture)
+BOOST_FIXTURE_TEST_CASE(ChangeMcastEndpoint, UdpFactoryMcastFixture)
 {
 #ifdef __linux__
   // need superuser privilege for creating multicast faces on Linux
@@ -272,20 +274,20 @@
   )CONFIG";
 
   parseConfig(CONFIG1, false);
-  auto udpMcastFaces = this->listUdpMcastFaces();
+  auto udpMcastFaces = this->listUdp4McastFaces();
   BOOST_REQUIRE_EQUAL(udpMcastFaces.size(), netifs.size());
   BOOST_CHECK_EQUAL(udpMcastFaces.front()->getRemoteUri(),
                     FaceUri("udp4://239.66.30.1:7011"));
 
   parseConfig(CONFIG2, false);
   g_io.poll();
-  udpMcastFaces = this->listUdpMcastFaces();
+  udpMcastFaces = this->listUdp4McastFaces();
   BOOST_REQUIRE_EQUAL(udpMcastFaces.size(), netifs.size());
   BOOST_CHECK_EQUAL(udpMcastFaces.front()->getRemoteUri(),
                     FaceUri("udp4://239.66.30.2:7012"));
 }
 
-BOOST_FIXTURE_TEST_CASE(Whitelist, UdpMcastConfigFixture)
+BOOST_FIXTURE_TEST_CASE(Whitelist, UdpFactoryMcastFixture)
 {
 #ifdef __linux__
   // need superuser privilege for creating multicast faces on Linux
@@ -308,12 +310,12 @@
   boost::replace_first(CONFIG, "%ifname", netifs.front()->getName());
 
   parseConfig(CONFIG, false);
-  auto udpMcastFaces = this->listUdpMcastFaces();
+  auto udpMcastFaces = this->listUdp4McastFaces();
   BOOST_REQUIRE_EQUAL(udpMcastFaces.size(), 1);
   BOOST_CHECK(isFaceOnNetif(*udpMcastFaces.front(), netifs.front()));
 }
 
-BOOST_FIXTURE_TEST_CASE(Blacklist, UdpMcastConfigFixture)
+BOOST_FIXTURE_TEST_CASE(Blacklist, UdpFactoryMcastFixture)
 {
 #ifdef __linux__
   // need superuser privilege for creating multicast faces on Linux
@@ -336,14 +338,14 @@
   boost::replace_first(CONFIG, "%ifname", netifs.front()->getName());
 
   parseConfig(CONFIG, false);
-  auto udpMcastFaces = this->listUdpMcastFaces();
+  auto udpMcastFaces = this->listUdp4McastFaces();
   BOOST_CHECK_EQUAL(udpMcastFaces.size(), netifs.size() - 1);
   BOOST_CHECK_EQUAL(boost::count_if(udpMcastFaces, [this] (const Face* face) {
     return isFaceOnNetif(*face, netifs.front());
   }), 0);
 }
 
-BOOST_FIXTURE_TEST_CASE(ChangePredicate, UdpMcastConfigFixture)
+BOOST_FIXTURE_TEST_CASE(ChangePredicate, UdpFactoryMcastFixture)
 {
 #ifdef __linux__
   // need superuser privilege for creating multicast faces on Linux
@@ -368,13 +370,13 @@
   boost::replace_first(CONFIG2, "%ifname", netifs.back()->getName());
 
   parseConfig(CONFIG1, false);
-  auto udpMcastFaces = this->listUdpMcastFaces();
+  auto udpMcastFaces = this->listUdp4McastFaces();
   BOOST_REQUIRE_EQUAL(udpMcastFaces.size(), 1);
   BOOST_CHECK(isFaceOnNetif(*udpMcastFaces.front(), netifs.front()));
 
   parseConfig(CONFIG2, false);
   g_io.poll();
-  udpMcastFaces = this->listUdpMcastFaces();
+  udpMcastFaces = this->listUdp4McastFaces();
   BOOST_REQUIRE_EQUAL(udpMcastFaces.size(), 1);
   BOOST_CHECK(isFaceOnNetif(*udpMcastFaces.front(), netifs.back()));
 }
@@ -394,6 +396,24 @@
   BOOST_CHECK_EQUAL(this->listFacesByScheme("udp4", ndn::nfd::LINK_TYPE_MULTI_ACCESS).size(), 0);
 }
 
+BOOST_AUTO_TEST_CASE(AllDisabled)
+{
+  const std::string CONFIG = R"CONFIG(
+    face_system
+    {
+      udp
+      {
+        enable_v4 no
+        enable_v6 no
+        mcast no
+      }
+    }
+  )CONFIG";
+
+  BOOST_CHECK_THROW(parseConfig(CONFIG, true), ConfigFile::Error);
+  BOOST_CHECK_THROW(parseConfig(CONFIG, false), ConfigFile::Error);
+}
+
 BOOST_AUTO_TEST_CASE(BadIdleTimeout)
 {
   const std::string CONFIG = R"CONFIG(
@@ -428,7 +448,8 @@
 
 BOOST_AUTO_TEST_CASE(BadMcastGroup)
 {
-  const std::string CONFIG = R"CONFIG(
+  // not an address
+  const std::string CONFIG1 = R"CONFIG(
     face_system
     {
       udp
@@ -438,13 +459,11 @@
     }
   )CONFIG";
 
-  BOOST_CHECK_THROW(parseConfig(CONFIG, true), ConfigFile::Error);
-  BOOST_CHECK_THROW(parseConfig(CONFIG, false), ConfigFile::Error);
-}
+  BOOST_CHECK_THROW(parseConfig(CONFIG1, true), ConfigFile::Error);
+  BOOST_CHECK_THROW(parseConfig(CONFIG1, false), ConfigFile::Error);
 
-BOOST_AUTO_TEST_CASE(BadMcastGroupV4Unicast)
-{
-  const std::string CONFIG = R"CONFIG(
+  // non-multicast address
+  const std::string CONFIG2 = R"CONFIG(
     face_system
     {
       udp
@@ -454,42 +473,51 @@
     }
   )CONFIG";
 
-  BOOST_CHECK_THROW(parseConfig(CONFIG, true), ConfigFile::Error);
-  BOOST_CHECK_THROW(parseConfig(CONFIG, false), ConfigFile::Error);
-}
+  BOOST_CHECK_THROW(parseConfig(CONFIG2, true), ConfigFile::Error);
+  BOOST_CHECK_THROW(parseConfig(CONFIG2, false), ConfigFile::Error);
 
-BOOST_AUTO_TEST_CASE(BadMcastGroupV6)
-{
-  const std::string CONFIG = R"CONFIG(
+  // wrong address family
+  const std::string CONFIG3 = R"CONFIG(
     face_system
     {
       udp
       {
-        mcast_group ff00::1
+        mcast_group ff02::1234
       }
     }
   )CONFIG";
 
-  BOOST_CHECK_THROW(parseConfig(CONFIG, true), ConfigFile::Error);
-  BOOST_CHECK_THROW(parseConfig(CONFIG, false), ConfigFile::Error);
+  BOOST_CHECK_THROW(parseConfig(CONFIG3, true), ConfigFile::Error);
+  BOOST_CHECK_THROW(parseConfig(CONFIG3, false), ConfigFile::Error);
 }
 
-BOOST_AUTO_TEST_CASE(AllDisabled)
+BOOST_AUTO_TEST_CASE(BadMcastPort)
 {
-  const std::string CONFIG = R"CONFIG(
+  const std::string CONFIG1 = R"CONFIG(
     face_system
     {
       udp
       {
-        enable_v4 no
-        enable_v6 no
-        mcast no
+        mcast_port hey
       }
     }
   )CONFIG";
 
-  BOOST_CHECK_THROW(parseConfig(CONFIG, true), ConfigFile::Error);
-  BOOST_CHECK_THROW(parseConfig(CONFIG, false), ConfigFile::Error);
+  BOOST_CHECK_THROW(parseConfig(CONFIG1, true), ConfigFile::Error);
+  BOOST_CHECK_THROW(parseConfig(CONFIG1, false), ConfigFile::Error);
+
+  const std::string CONFIG2 = R"CONFIG(
+    face_system
+    {
+      udp
+      {
+        mcast_port 99999
+      }
+    }
+  )CONFIG";
+
+  BOOST_CHECK_THROW(parseConfig(CONFIG2, true), ConfigFile::Error);
+  BOOST_CHECK_THROW(parseConfig(CONFIG2, false), ConfigFile::Error);
 }
 
 BOOST_AUTO_TEST_CASE(UnknownOption)
@@ -515,37 +543,43 @@
   BOOST_CHECK_EQUAL(factory.getChannels().empty(), true);
 
   std::set<std::string> expected;
-  expected.insert(createChannel("127.0.0.1", "20070")->getUri().toString());
-  expected.insert(createChannel("127.0.0.1", "20071")->getUri().toString());
-  expected.insert(createChannel("::1", "20071")->getUri().toString());
+  expected.insert(createChannel("127.0.0.1", 20070)->getUri().toString());
+  expected.insert(createChannel("127.0.0.1", 20071)->getUri().toString());
+  expected.insert(createChannel("::1", 20071)->getUri().toString());
   checkChannelListEqual(factory, expected);
 }
 
-BOOST_AUTO_TEST_CASE(CreateChannel)
+BOOST_FIXTURE_TEST_CASE(CreateChannel, UdpFactoryMcastFixture)
 {
-  auto channel1 = createChannel("127.0.0.1", "20070");
-  auto channel1a = createChannel("127.0.0.1", "20070");
+  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");
 
-  auto channel2 = createChannel("127.0.0.1", "20071");
+  auto channel2 = createChannel("127.0.0.1", 20071);
   BOOST_CHECK_NE(channel1, channel2);
 
-  auto channel3 = createChannel("::1", "20071");
+  auto channel3 = createChannel("::1", 20071);
   BOOST_CHECK_NE(channel2, channel3);
   BOOST_CHECK_EQUAL(channel3->getUri().toString(), "udp6://[::1]:20071");
 
   // createChannel with multicast address
-  BOOST_CHECK_EXCEPTION(createChannel("224.0.0.1", "20070"), UdpFactory::Error,
+  BOOST_CHECK_EXCEPTION(createChannel("224.0.0.1", 20070), UdpFactory::Error,
                         [] (const UdpFactory::Error& e) {
                           return strcmp(e.what(),
                                         "createChannel is only for unicast channels. The provided endpoint "
                                         "is multicast. Use createMulticastFace to create a multicast face") == 0;
                         });
 
+#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);
+
   // createChannel with a local endpoint that has already been allocated for a UDP multicast face
-  auto multicastFace = factory.createMulticastFace("127.0.0.1", "224.0.0.1", "20072");
-  BOOST_CHECK_EXCEPTION(createChannel("127.0.0.1", "20072"), UdpFactory::Error,
+  auto multicastFace = createMulticastFace("127.0.0.1", "224.0.0.1", 20072);
+  BOOST_CHECK_EXCEPTION(createChannel("127.0.0.1", 20072), UdpFactory::Error,
                         [] (const UdpFactory::Error& e) {
                           return strcmp(e.what(),
                                         "Cannot create the requested UDP unicast channel, local "
@@ -553,24 +587,30 @@
                         });
 }
 
-BOOST_AUTO_TEST_CASE(CreateMulticastFace)
+BOOST_FIXTURE_TEST_CASE(CreateMulticastFace, UdpFactoryMcastFixture)
 {
-  auto multicastFace1  = factory.createMulticastFace("127.0.0.1", "224.0.0.1", "20070");
-  auto multicastFace1a = factory.createMulticastFace("127.0.0.1", "224.0.0.1", "20070");
+#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);
+
+  auto multicastFace1  = createMulticastFace("127.0.0.1", "224.0.0.1", 20070);
+  auto multicastFace1a = createMulticastFace("127.0.0.1", "224.0.0.1", 20070);
   BOOST_CHECK_EQUAL(multicastFace1, multicastFace1a);
 
-  // createMulticastFace with a local endpoint that is already used by a channel
-  auto channel = createChannel("127.0.0.1", "20071");
-  BOOST_CHECK_EXCEPTION(factory.createMulticastFace("127.0.0.1", "224.0.0.1", "20071"), UdpFactory::Error,
+  // createMulticastFace with an IPv4 local endpoint already used by a channel
+  auto channel = createChannel("127.0.0.1", 20071);
+  BOOST_CHECK_EXCEPTION(createMulticastFace("127.0.0.1", "224.0.0.1", 20071), UdpFactory::Error,
                         [] (const UdpFactory::Error& e) {
                           return strcmp(e.what(),
                                         "Cannot create the requested UDP multicast face, local "
                                         "endpoint is already allocated for a UDP unicast channel") == 0;
                         });
 
-  // createMulticastFace with a local endpoint that is already
+  // createMulticastFace with an IPv4 local endpoint already
   // used by a multicast face on a different multicast group
-  BOOST_CHECK_EXCEPTION(factory.createMulticastFace("127.0.0.1", "224.0.0.42", "20070"), UdpFactory::Error,
+  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 the requested UDP multicast face, local "
@@ -578,31 +618,13 @@
                                         "on a different multicast group") == 0;
                         });
 
-  // createMulticastFace with an IPv4 unicast address
-  BOOST_CHECK_EXCEPTION(factory.createMulticastFace("127.0.0.1", "192.168.10.15", "20072"), UdpFactory::Error,
-                        [] (const UdpFactory::Error& e) {
-                          return strcmp(e.what(),
-                                        "Cannot create the requested UDP multicast face, "
-                                        "the multicast group given as input is not a multicast address") == 0;
-                        });
-
   // createMulticastFace with an IPv6 multicast address
-  BOOST_CHECK_EXCEPTION(factory.createMulticastFace("::1", "ff01::114", "20073"), UdpFactory::Error,
+  BOOST_CHECK_EXCEPTION(createMulticastFace("::1", "ff01::114", 20073), UdpFactory::Error,
                         [] (const UdpFactory::Error& e) {
                           return strcmp(e.what(),
                                         "IPv6 multicast is not supported yet. Please provide an IPv4 "
                                         "address") == 0;
                         });
-
-  // createMulticastFace with different local and remote port numbers
-  udp::Endpoint localEndpoint(boost::asio::ip::address_v4::loopback(), 20074);
-  udp::Endpoint multicastEndpoint(boost::asio::ip::address::from_string("224.0.0.1"), 20075);
-  BOOST_CHECK_EXCEPTION(factory.createMulticastFace(localEndpoint, multicastEndpoint), UdpFactory::Error,
-                        [] (const UdpFactory::Error& e) {
-                          return strcmp(e.what(),
-                                        "Cannot create the requested UDP multicast face, "
-                                        "both endpoints should have the same port number. ") == 0;
-                        });
 }
 
 BOOST_AUTO_TEST_CASE(CreateFace)
@@ -615,7 +637,7 @@
              false,
              {CreateFaceExpectedResult::FAILURE, 504, "No channels available to connect"});
 
-  createChannel("127.0.0.1", "20071");
+  createChannel("127.0.0.1", 20071);
 
   createFace(factory,
              FaceUri("udp4://127.0.0.1:6363"),
@@ -641,7 +663,6 @@
              false,
              {CreateFaceExpectedResult::SUCCESS, 0, ""});
 
-
   createFace(factory,
              FaceUri("udp4://127.0.0.1:20073"),
              {},
@@ -653,7 +674,7 @@
 
 BOOST_AUTO_TEST_CASE(UnsupportedCreateFace)
 {
-  createChannel("127.0.0.1", "20071");
+  createChannel("127.0.0.1", 20071);
 
   createFace(factory,
              FaceUri("udp4://127.0.0.1:20072"),