face: implement IPv6 UDP multicast transport

Change-Id: Ib6ab956354dbbba00694c7949fa9ee4639579879
Refs: #4222
diff --git a/tests/daemon/face/datagram-transport.t.cpp b/tests/daemon/face/datagram-transport.t.cpp
index 8cfb622..d5ce2a8 100644
--- a/tests/daemon/face/datagram-transport.t.cpp
+++ b/tests/daemon/face/datagram-transport.t.cpp
@@ -39,7 +39,9 @@
 
 using DatagramTransportFixtures = boost::mpl::vector<
   GENERATE_IP_TRANSPORT_FIXTURE_INSTANTIATIONS(UnicastUdpTransportFixture),
-  IpTransportFixture<MulticastUdpTransportFixture, AddressFamily::V4, AddressScope::Global, MulticastInterface::Yes>
+  IpTransportFixture<MulticastUdpTransportFixture, AddressFamily::V4, AddressScope::Global, MulticastInterface::Yes>,
+  IpTransportFixture<MulticastUdpTransportFixture, AddressFamily::V6, AddressScope::LinkLocal, MulticastInterface::Yes>,
+  IpTransportFixture<MulticastUdpTransportFixture, AddressFamily::V6, AddressScope::Global, MulticastInterface::Yes>
 >;
 
 BOOST_FIXTURE_TEST_CASE_TEMPLATE(Send, T, DatagramTransportFixtures, T)
diff --git a/tests/daemon/face/multicast-udp-transport-fixture.hpp b/tests/daemon/face/multicast-udp-transport-fixture.hpp
index 2b86833..5959821 100644
--- a/tests/daemon/face/multicast-udp-transport-fixture.hpp
+++ b/tests/daemon/face/multicast-udp-transport-fixture.hpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /*
- * Copyright (c) 2014-2017,  Regents of the University of California,
+ * Copyright (c) 2014-2018,  Regents of the University of California,
  *                           Arizona Board of Regents,
  *                           Colorado State University,
  *                           University Pierre & Marie Curie, Sorbonne University,
@@ -45,7 +45,7 @@
 protected:
   MulticastUdpTransportFixture()
     : transport(nullptr)
-    , multicastEp(ip::address::from_string("230.15.19.47"), 7070)
+    , txPort(7001)
     , receivedPackets(nullptr)
     , remoteSockRx(g_io)
     , remoteSockTx(g_io)
@@ -55,21 +55,32 @@
   void
   initialize(ip::address address)
   {
-    localEp = udp::endpoint(address, 7001);
+    ip::address mcastAddr;
+    if (address.is_v4()) {
+      // the administratively scoped group 224.0.0.254 is reserved for experimentation (RFC 4727)
+      mcastAddr = ip::address_v4(0xE00000FE);
+    }
+    else {
+      // the group FF0X::114 is reserved for experimentation at all scope levels (RFC 4727)
+      auto v6Addr = ip::address_v6::from_string("FF01::114");
+      v6Addr.scope_id(address.to_v6().scope_id());
+      mcastAddr = v6Addr;
+    }
+    mcastEp = udp::endpoint(mcastAddr, 7373);
+    remoteMcastEp = udp::endpoint(mcastAddr, 8383);
 
-    MulticastUdpTransport::openRxSocket(remoteSockRx, multicastEp, ip::address_v4::any());
-    MulticastUdpTransport::openTxSocket(remoteSockTx, udp::endpoint(udp::v4(), 0), true);
+    MulticastUdpTransport::openRxSocket(remoteSockRx, mcastEp, address);
+    MulticastUdpTransport::openTxSocket(remoteSockTx, udp::endpoint(address, 0), nullptr, true);
 
     udp::socket sockRx(g_io);
     udp::socket sockTx(g_io);
-    MulticastUdpTransport::openRxSocket(sockRx, udp::endpoint(multicastEp.address(), localEp.port()),
-                                        ip::address_v4::any());
-    MulticastUdpTransport::openTxSocket(sockTx, udp::endpoint(udp::v4(), 0), true);
+    MulticastUdpTransport::openRxSocket(sockRx, remoteMcastEp, address);
+    MulticastUdpTransport::openTxSocket(sockTx, udp::endpoint(address, txPort), nullptr, true);
 
     face = make_unique<Face>(
              make_unique<DummyReceiveLinkService>(),
-             make_unique<MulticastUdpTransport>(localEp, multicastEp, std::move(sockRx),
-                                                std::move(sockTx), ndn::nfd::LINK_TYPE_MULTI_ACCESS));
+             make_unique<MulticastUdpTransport>(mcastEp, std::move(sockRx), std::move(sockTx),
+                                                ndn::nfd::LINK_TYPE_MULTI_ACCESS));
     transport = static_cast<MulticastUdpTransport*>(face->getTransport());
     receivedPackets = &static_cast<DummyReceiveLinkService*>(face->getLinkService())->receivedPackets;
 
@@ -92,24 +103,31 @@
   void
   remoteWrite(const std::vector<uint8_t>& buf, bool needToCheck = true)
   {
-    remoteSockTx.async_send_to(boost::asio::buffer(buf), udp::endpoint(multicastEp.address(), 7001),
+    sendToGroup(remoteSockTx, buf, needToCheck);
+    limitedIo.defer(time::seconds(1));
+  }
+
+  void
+  sendToGroup(udp::socket& sock, const std::vector<uint8_t>& buf, bool needToCheck = true) const
+  {
+    sock.async_send_to(boost::asio::buffer(buf), remoteMcastEp,
       [needToCheck] (const boost::system::error_code& error, size_t) {
         if (needToCheck) {
           BOOST_REQUIRE_EQUAL(error, boost::system::errc::success);
         }
       });
-    limitedIo.defer(time::seconds(1));
   }
 
 protected:
   LimitedIo limitedIo;
   MulticastUdpTransport* transport;
-  udp::endpoint localEp;
-  udp::endpoint multicastEp;
+  udp::endpoint mcastEp;
+  uint16_t txPort;
   std::vector<Transport::Packet>* receivedPackets;
 
 private:
   unique_ptr<Face> face;
+  udp::endpoint remoteMcastEp;
   udp::socket remoteSockRx;
   udp::socket remoteSockTx;
 };
diff --git a/tests/daemon/face/multicast-udp-transport.t.cpp b/tests/daemon/face/multicast-udp-transport.t.cpp
index de6d30f..d8e7a60 100644
--- a/tests/daemon/face/multicast-udp-transport.t.cpp
+++ b/tests/daemon/face/multicast-udp-transport.t.cpp
@@ -36,17 +36,15 @@
 BOOST_AUTO_TEST_SUITE(Face)
 
 using MulticastUdpTransportFixtureWithAddress =
-  // TODO: change to AddressFamily::Any after IPv6 support is implemented
-  IpTransportFixture<MulticastUdpTransportFixture, AddressFamily::V4,
+  IpTransportFixture<MulticastUdpTransportFixture, AddressFamily::Any,
                      AddressScope::Global, MulticastInterface::Yes>;
 
 BOOST_FIXTURE_TEST_SUITE(TestMulticastUdpTransport, MulticastUdpTransportFixtureWithAddress)
 
 using MulticastUdpTransportFixtures = boost::mpl::vector<
-  IpTransportFixture<MulticastUdpTransportFixture, AddressFamily::V4, AddressScope::Global, MulticastInterface::Yes>
-  // TODO: IPv6 not supported yet
-  //IpTransportFixture<MulticastUdpTransportFixture, AddressFamily::V6, AddressScope::LinkLocal, MulticastInterface::Yes>,
-  //IpTransportFixture<MulticastUdpTransportFixture, AddressFamily::V6, AddressScope::Global, MulticastInterface::Yes>
+  IpTransportFixture<MulticastUdpTransportFixture, AddressFamily::V4, AddressScope::Global, MulticastInterface::Yes>,
+  IpTransportFixture<MulticastUdpTransportFixture, AddressFamily::V6, AddressScope::LinkLocal, MulticastInterface::Yes>,
+  IpTransportFixture<MulticastUdpTransportFixture, AddressFamily::V6, AddressScope::Global, MulticastInterface::Yes>
 >;
 
 BOOST_FIXTURE_TEST_CASE_TEMPLATE(StaticProperties, T, MulticastUdpTransportFixtures, T)
@@ -55,8 +53,8 @@
 
   checkStaticPropertiesInitialized(*this->transport);
 
-  BOOST_CHECK_EQUAL(this->transport->getLocalUri(), FaceUri(udp::endpoint(this->address, this->localEp.port())));
-  BOOST_CHECK_EQUAL(this->transport->getRemoteUri(), FaceUri(this->multicastEp));
+  BOOST_CHECK_EQUAL(this->transport->getLocalUri(), FaceUri(udp::endpoint(this->address, this->txPort)));
+  BOOST_CHECK_EQUAL(this->transport->getRemoteUri(), FaceUri(this->mcastEp));
   BOOST_CHECK_EQUAL(this->transport->getScope(), ndn::nfd::FACE_SCOPE_NON_LOCAL);
   BOOST_CHECK_EQUAL(this->transport->getPersistency(), ndn::nfd::FACE_PERSISTENCY_PERMANENT);
   BOOST_CHECK_EQUAL(this->transport->getLinkType(), ndn::nfd::LINK_TYPE_MULTI_ACCESS);
@@ -78,9 +76,9 @@
 {
   TRANSPORT_TEST_INIT();
 
-  // remoteSockRx2 unnecessary for this test case - only remoteSockTx2 is needed
+  // we need a second remote tx socket for this test case
   udp::socket remoteSockTx2(this->g_io);
-  MulticastUdpTransport::openTxSocket(remoteSockTx2, udp::endpoint(udp::v4(), 7071), true);
+  MulticastUdpTransport::openTxSocket(remoteSockTx2, udp::endpoint(this->address, 0), nullptr, true);
 
   Block pkt1 = ndn::encoding::makeStringBlock(300, "hello");
   ndn::Buffer buf1(pkt1.begin(), pkt1.end());
@@ -98,15 +96,8 @@
   BOOST_CHECK_EQUAL(this->receivedPackets->at(0).remoteEndpoint,
                     this->receivedPackets->at(1).remoteEndpoint);
 
-  udp::endpoint destEp(this->multicastEp.address(), this->localEp.port());
-  remoteSockTx2.async_send_to(boost::asio::buffer(buf1), destEp,
-    [] (const boost::system::error_code& error, size_t) {
-      BOOST_REQUIRE_EQUAL(error, boost::system::errc::success);
-    });
-  remoteSockTx2.async_send_to(boost::asio::buffer(buf2), destEp,
-    [] (const boost::system::error_code& error, size_t) {
-      BOOST_REQUIRE_EQUAL(error, boost::system::errc::success);
-    });
+  this->sendToGroup(remoteSockTx2, buf1);
+  this->sendToGroup(remoteSockTx2, buf2);
   this->limitedIo.defer(time::seconds(1));
 
   BOOST_CHECK_EQUAL(this->transport->getCounters().nInPackets, 4);
diff --git a/tests/daemon/face/udp-factory.t.cpp b/tests/daemon/face/udp-factory.t.cpp
index 5e7e615..01a24a0 100644
--- a/tests/daemon/face/udp-factory.t.cpp
+++ b/tests/daemon/face/udp-factory.t.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /*
- * Copyright (c) 2014-2017,  Regents of the University of California,
+ * Copyright (c) 2014-2018,  Regents of the University of California,
  *                           Arizona Board of Regents,
  *                           Colorado State University,
  *                           University Pierre & Marie Curie, Sorbonne University,
@@ -29,7 +29,6 @@
 #include "factory-test-common.hpp"
 
 #include <boost/algorithm/string/replace.hpp>
-#include <boost/range/algorithm/count_if.hpp>
 #include <ndn-cxx/net/address-converter.hpp>
 
 namespace nfd {
@@ -53,22 +52,54 @@
   UdpFactoryMcastFixture()
   {
     for (const auto& netif : collectNetworkInterfaces()) {
-      if (netif->isUp() && netif->canMulticast() &&
-          hasAddressFamily(*netif, ndn::net::AddressFamily::V4)) {
-        netifs.push_back(netif);
+      // same filtering logic as UdpFactory::applyMcastConfigToNetif()
+      if (netif->isUp() && !netif->isLoopback() && netif->canMulticast()) {
+        bool hasValidIpAddress = false;
+        if (hasAddressFamily(*netif, ndn::net::AddressFamily::V4)) {
+          hasValidIpAddress = true;
+          netifsV4.push_back(netif);
+        }
+        if (hasAddressFamily(*netif, ndn::net::AddressFamily::V6)) {
+          hasValidIpAddress = true;
+          netifsV6.push_back(netif);
+        }
+        if (hasValidIpAddress) {
+          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);
+    auto localAddress = ndn::ip::addressFromString(localIp);
     udp::Endpoint mcastEndpoint(ndn::ip::addressFromString(mcastIp), mcastPort);
-    return factory.createMulticastFace(localEndpoint, mcastEndpoint, netifs.front());
+
+    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*>
@@ -77,7 +108,13 @@
     return this->listFacesByScheme("udp4", linkType);
   }
 
-  /** \brief determine whether \p netif has at least one address of the given family
+  std::vector<const Face*>
+  listUdp6McastFaces(ndn::nfd::LinkType linkType = ndn::nfd::LINK_TYPE_MULTI_ACCESS) const
+  {
+    return this->listFacesByScheme("udp6", linkType);
+  }
+
+  /** \brief determine whether \p netif has at least one IP address of the given family
    */
   static bool
   hasAddressFamily(const NetworkInterface& netif, ndn::net::AddressFamily af)
@@ -89,17 +126,27 @@
   /** \brief determine whether a UDP multicast face is created on \p netif
    */
   static bool
-  isFaceOnNetif(const Face& face, const shared_ptr<const NetworkInterface>& netif)
+  isFaceOnNetif(const Face& face, 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(),
+    auto ip = ndn::ip::addressFromString(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
+  /** \brief MulticastUdpTransport-capable network interfaces (IPv4 + IPv6)
+   *
+   *  This should be used in test cases that do not depend on a specific address family
    */
   std::vector<shared_ptr<const NetworkInterface>> netifs;
+
+  /** \brief MulticastUdpTransport-capable network interfaces (IPv4 only)
+   */
+  std::vector<shared_ptr<const NetworkInterface>> netifsV4;
+
+  /** \brief MulticastUdpTransport-capable network interfaces (IPv6 only)
+   */
+  std::vector<shared_ptr<const NetworkInterface>> netifsV6;
 };
 
 #define SKIP_IF_UDP_MCAST_NETIF_COUNT_LT(n) \
@@ -111,6 +158,24 @@
     } \
   } while (false)
 
+#define SKIP_IF_UDP_MCAST_V4_NETIF_COUNT_LT(n) \
+  do { \
+    if (this->netifsV4.size() < (n)) { \
+      BOOST_WARN_MESSAGE(false, "skipping assertions that require " #n \
+                                " or more IPv4 MulticastUdpTransport-capable network interfaces"); \
+      return; \
+    } \
+  } while (false)
+
+#define SKIP_IF_UDP_MCAST_V6_NETIF_COUNT_LT(n) \
+  do { \
+    if (this->netifsV6.size() < (n)) { \
+      BOOST_WARN_MESSAGE(false, "skipping assertions that require " #n \
+                                " or more IPv6 MulticastUdpTransport-capable network interfaces"); \
+      return; \
+    } \
+  } while (false)
+
 BOOST_AUTO_TEST_SUITE(Face)
 BOOST_FIXTURE_TEST_SUITE(TestUdpFactory, UdpFactoryFixture)
 
@@ -184,11 +249,6 @@
 
 BOOST_FIXTURE_TEST_CASE(EnableDisableMcast, UdpFactoryMcastFixture)
 {
-#ifdef __linux__
-  // need superuser privilege for creating multicast faces on Linux
-  SKIP_IF_NOT_SUPERUSER();
-#endif // __linux__
-
   const std::string CONFIG_WITH_MCAST = R"CONFIG(
     face_system
     {
@@ -210,22 +270,28 @@
 
   parseConfig(CONFIG_WITHOUT_MCAST, false);
   BOOST_CHECK_EQUAL(this->listUdp4McastFaces().size(), 0);
+  BOOST_CHECK_EQUAL(this->listUdp6McastFaces().size(), 0);
 
-  SKIP_IF_UDP_MCAST_NETIF_COUNT_LT(1);
+#ifdef __linux__
+  // need superuser privileges to create multicast faces on Linux
+  SKIP_IF_NOT_SUPERUSER();
+#endif // __linux__
 
   parseConfig(CONFIG_WITH_MCAST, false);
   g_io.poll();
-  BOOST_CHECK_EQUAL(this->listUdp4McastFaces().size(), netifs.size());
+  BOOST_CHECK_EQUAL(this->listUdp4McastFaces().size(), netifsV4.size());
+  BOOST_CHECK_EQUAL(this->listUdp6McastFaces().size(), netifsV6.size());
 
   parseConfig(CONFIG_WITHOUT_MCAST, false);
   g_io.poll();
   BOOST_CHECK_EQUAL(this->listUdp4McastFaces().size(), 0);
+  BOOST_CHECK_EQUAL(this->listUdp6McastFaces().size(), 0);
 }
 
 BOOST_FIXTURE_TEST_CASE(McastAdHoc, UdpFactoryMcastFixture)
 {
 #ifdef __linux__
-  // need superuser privilege for creating multicast faces on Linux
+  // need superuser privileges to create multicast faces on Linux
   SKIP_IF_NOT_SUPERUSER();
 #endif // __linux__
   SKIP_IF_UDP_MCAST_NETIF_COUNT_LT(1);
@@ -241,16 +307,17 @@
   )CONFIG";
 
   parseConfig(CONFIG, false);
-  BOOST_CHECK_EQUAL(this->listUdp4McastFaces(ndn::nfd::LINK_TYPE_AD_HOC).size(), netifs.size());
+  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(ChangeMcastEndpoint, UdpFactoryMcastFixture)
+BOOST_FIXTURE_TEST_CASE(ChangeMcastEndpointV4, UdpFactoryMcastFixture)
 {
 #ifdef __linux__
-  // need superuser privilege for creating multicast faces on Linux
+  // need superuser privileges to create multicast faces on Linux
   SKIP_IF_NOT_SUPERUSER();
 #endif // __linux__
-  SKIP_IF_UDP_MCAST_NETIF_COUNT_LT(1);
+  SKIP_IF_UDP_MCAST_V4_NETIF_COUNT_LT(1);
 
   const std::string CONFIG1 = R"CONFIG(
     face_system
@@ -275,22 +342,65 @@
 
   parseConfig(CONFIG1, false);
   auto udpMcastFaces = this->listUdp4McastFaces();
-  BOOST_REQUIRE_EQUAL(udpMcastFaces.size(), netifs.size());
-  BOOST_CHECK_EQUAL(udpMcastFaces.front()->getRemoteUri(),
-                    FaceUri("udp4://239.66.30.1:7011"));
+  BOOST_REQUIRE_EQUAL(udpMcastFaces.size(), netifsV4.size());
+  BOOST_CHECK_EQUAL(udpMcastFaces.front()->getRemoteUri(), FaceUri("udp4://239.66.30.1:7011"));
 
   parseConfig(CONFIG2, false);
   g_io.poll();
   udpMcastFaces = this->listUdp4McastFaces();
-  BOOST_REQUIRE_EQUAL(udpMcastFaces.size(), netifs.size());
-  BOOST_CHECK_EQUAL(udpMcastFaces.front()->getRemoteUri(),
-                    FaceUri("udp4://239.66.30.2:7012"));
+  BOOST_REQUIRE_EQUAL(udpMcastFaces.size(), netifsV4.size());
+  BOOST_CHECK_EQUAL(udpMcastFaces.front()->getRemoteUri(), FaceUri("udp4://239.66.30.2:7012"));
+}
+
+BOOST_FIXTURE_TEST_CASE(ChangeMcastEndpointV6, UdpFactoryMcastFixture)
+{
+#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);
+
+  const std::string CONFIG1 = R"CONFIG(
+    face_system
+    {
+      udp
+      {
+        mcast_group_v6 ff02::1101
+        mcast_port_v6 7011
+      }
+    }
+  )CONFIG";
+  const std::string CONFIG2 = R"CONFIG(
+    face_system
+    {
+      udp
+      {
+        mcast_group_v6 ff02::1102
+        mcast_port_v6 7012
+      }
+    }
+  )CONFIG";
+
+  parseConfig(CONFIG1, false);
+  auto udpMcastFaces = this->listUdp6McastFaces();
+  BOOST_REQUIRE_EQUAL(udpMcastFaces.size(), netifsV6.size());
+  auto expectedAddr = boost::asio::ip::address_v6::from_string("ff02::1101");
+  expectedAddr.scope_id(netifsV6.front()->getIndex());
+  BOOST_CHECK_EQUAL(udpMcastFaces.front()->getRemoteUri(), FaceUri(udp::Endpoint(expectedAddr, 7011)));
+
+  parseConfig(CONFIG2, false);
+  g_io.poll();
+  udpMcastFaces = this->listUdp6McastFaces();
+  BOOST_REQUIRE_EQUAL(udpMcastFaces.size(), netifsV6.size());
+  expectedAddr = boost::asio::ip::address_v6::from_string("ff02::1102");
+  expectedAddr.scope_id(netifsV6.front()->getIndex());
+  BOOST_CHECK_EQUAL(udpMcastFaces.front()->getRemoteUri(), FaceUri(udp::Endpoint(expectedAddr, 7012)));
 }
 
 BOOST_FIXTURE_TEST_CASE(Whitelist, UdpFactoryMcastFixture)
 {
 #ifdef __linux__
-  // need superuser privilege for creating multicast faces on Linux
+  // need superuser privileges to create multicast faces on Linux
   SKIP_IF_NOT_SUPERUSER();
 #endif // __linux__
   SKIP_IF_UDP_MCAST_NETIF_COUNT_LT(1);
@@ -310,15 +420,21 @@
   boost::replace_first(CONFIG, "%ifname", netifs.front()->getName());
 
   parseConfig(CONFIG, false);
+
   auto udpMcastFaces = this->listUdp4McastFaces();
-  BOOST_REQUIRE_EQUAL(udpMcastFaces.size(), 1);
-  BOOST_CHECK(isFaceOnNetif(*udpMcastFaces.front(), netifs.front()));
+  BOOST_CHECK_LE(udpMcastFaces.size(), 1);
+  auto udpMcastFacesV6 = this->listUdp6McastFaces();
+  BOOST_CHECK_LE(udpMcastFacesV6.size(), 1);
+  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()); }));
 }
 
 BOOST_FIXTURE_TEST_CASE(Blacklist, UdpFactoryMcastFixture)
 {
 #ifdef __linux__
-  // need superuser privilege for creating multicast faces on Linux
+  // need superuser privileges to create multicast faces on Linux
   SKIP_IF_NOT_SUPERUSER();
 #endif // __linux__
   SKIP_IF_UDP_MCAST_NETIF_COUNT_LT(1);
@@ -338,17 +454,21 @@
   boost::replace_first(CONFIG, "%ifname", netifs.front()->getName());
 
   parseConfig(CONFIG, false);
+
   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_CHECK_GE(udpMcastFaces.size(), netifsV4.size() - 1);
+  auto udpMcastFacesV6 = this->listUdp6McastFaces();
+  BOOST_CHECK_GE(udpMcastFacesV6.size(), netifsV6.size() - 1);
+  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()); }));
 }
 
 BOOST_FIXTURE_TEST_CASE(ChangePredicate, UdpFactoryMcastFixture)
 {
 #ifdef __linux__
-  // need superuser privilege for creating multicast faces on Linux
+  // need superuser privileges to create multicast faces on Linux
   SKIP_IF_NOT_SUPERUSER();
 #endif // __linux__
   SKIP_IF_UDP_MCAST_NETIF_COUNT_LT(2);
@@ -370,15 +490,23 @@
   boost::replace_first(CONFIG2, "%ifname", netifs.back()->getName());
 
   parseConfig(CONFIG1, false);
+
   auto udpMcastFaces = this->listUdp4McastFaces();
-  BOOST_REQUIRE_EQUAL(udpMcastFaces.size(), 1);
-  BOOST_CHECK(isFaceOnNetif(*udpMcastFaces.front(), netifs.front()));
+  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()); }));
 
   parseConfig(CONFIG2, false);
   g_io.poll();
+
   udpMcastFaces = this->listUdp4McastFaces();
-  BOOST_REQUIRE_EQUAL(udpMcastFaces.size(), 1);
-  BOOST_CHECK(isFaceOnNetif(*udpMcastFaces.front(), netifs.back()));
+  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.back()); }));
 }
 
 BOOST_AUTO_TEST_CASE(Omitted)
@@ -394,6 +522,7 @@
 
   BOOST_CHECK_EQUAL(factory.getChannels().size(), 0);
   BOOST_CHECK_EQUAL(this->listFacesByScheme("udp4", ndn::nfd::LINK_TYPE_MULTI_ACCESS).size(), 0);
+  BOOST_CHECK_EQUAL(this->listFacesByScheme("udp6", ndn::nfd::LINK_TYPE_MULTI_ACCESS).size(), 0);
 }
 
 BOOST_AUTO_TEST_CASE(AllDisabled)
@@ -446,7 +575,7 @@
   BOOST_CHECK_THROW(parseConfig(CONFIG, false), ConfigFile::Error);
 }
 
-BOOST_AUTO_TEST_CASE(BadMcastGroup)
+BOOST_AUTO_TEST_CASE(BadMcastGroupV4)
 {
   // not an address
   const std::string CONFIG1 = R"CONFIG(
@@ -491,7 +620,52 @@
   BOOST_CHECK_THROW(parseConfig(CONFIG3, false), ConfigFile::Error);
 }
 
-BOOST_AUTO_TEST_CASE(BadMcastPort)
+BOOST_AUTO_TEST_CASE(BadMcastGroupV6)
+{
+  // not an address
+  const std::string CONFIG1 = R"CONFIG(
+    face_system
+    {
+      udp
+      {
+        mcast_group_v6 foo
+      }
+    }
+  )CONFIG";
+
+  BOOST_CHECK_THROW(parseConfig(CONFIG1, true), ConfigFile::Error);
+  BOOST_CHECK_THROW(parseConfig(CONFIG1, false), ConfigFile::Error);
+
+  // non-multicast address
+  const std::string CONFIG2 = R"CONFIG(
+    face_system
+    {
+      udp
+      {
+        mcast_group_v6 fe80::1234
+      }
+    }
+  )CONFIG";
+
+  BOOST_CHECK_THROW(parseConfig(CONFIG2, true), ConfigFile::Error);
+  BOOST_CHECK_THROW(parseConfig(CONFIG2, false), ConfigFile::Error);
+
+  // wrong address family
+  const std::string CONFIG3 = R"CONFIG(
+    face_system
+    {
+      udp
+      {
+        mcast_group_v6 224.0.23.170
+      }
+    }
+  )CONFIG";
+
+  BOOST_CHECK_THROW(parseConfig(CONFIG3, true), ConfigFile::Error);
+  BOOST_CHECK_THROW(parseConfig(CONFIG3, false), ConfigFile::Error);
+}
+
+BOOST_AUTO_TEST_CASE(BadMcastPortV4)
 {
   const std::string CONFIG1 = R"CONFIG(
     face_system
@@ -520,6 +694,35 @@
   BOOST_CHECK_THROW(parseConfig(CONFIG2, false), ConfigFile::Error);
 }
 
+BOOST_AUTO_TEST_CASE(BadMcastPortV6)
+{
+  const std::string CONFIG1 = R"CONFIG(
+    face_system
+    {
+      udp
+      {
+        mcast_port_v6 bar
+      }
+    }
+  )CONFIG";
+
+  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_v6 99999
+      }
+    }
+  )CONFIG";
+
+  BOOST_CHECK_THROW(parseConfig(CONFIG2, true), ConfigFile::Error);
+  BOOST_CHECK_THROW(parseConfig(CONFIG2, false), ConfigFile::Error);
+}
+
 BOOST_AUTO_TEST_CASE(UnknownOption)
 {
   const std::string CONFIG = R"CONFIG(
@@ -563,67 +766,107 @@
   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,
-                        [] (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
+  // 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 for a UDP multicast face
-  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 "
-                                        "endpoint is already allocated for a UDP multicast face") == 0;
-                        });
+  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 for a UDP multicast face") == 0;
+                          });
+  }
+  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 for a UDP multicast face") == 0;
+                          });
+  }
 }
 
-BOOST_FIXTURE_TEST_CASE(CreateMulticastFace, UdpFactoryMcastFixture)
+BOOST_FIXTURE_TEST_CASE(CreateMulticastFaceV4, UdpFactoryMcastFixture)
 {
 #ifdef __linux__
-  // need superuser privilege for creating multicast faces on Linux
+  // need superuser privileges to create multicast faces on Linux
   SKIP_IF_NOT_SUPERUSER();
 #endif // __linux__
-  SKIP_IF_UDP_MCAST_NETIF_COUNT_LT(1);
+  SKIP_IF_UDP_MCAST_V4_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);
+  auto multicastFace1  = createMulticastFace("127.0.0.1", "224.0.0.254", 20070);
+  auto multicastFace1a = createMulticastFace("127.0.0.1", "224.0.0.254", 20070);
+  auto multicastFace2  = createMulticastFace("127.0.0.1", "224.0.0.254", 20030);
   BOOST_CHECK_EQUAL(multicastFace1, multicastFace1a);
+  BOOST_CHECK_NE(multicastFace1, multicastFace2);
 
-  // createMulticastFace with an IPv4 local endpoint already used by a channel
+  auto address = findNonLoopbackAddressForMulticastFace(ndn::net::AddressFamily::V4);
+  if (!address.is_unspecified()) {
+    auto multicastFace3  = createMulticastFace(address.to_string(), "224.0.0.254", 20070);
+    BOOST_CHECK_NE(multicastFace1, multicastFace3);
+    BOOST_CHECK_NE(multicastFace2, multicastFace3);
+  }
+
+  // create with a 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,
+  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 the requested UDP multicast face, local "
-                                        "endpoint is already allocated for a UDP unicast channel") == 0;
+                                        "Cannot create UDP multicast face on 127.0.0.1:20071, "
+                                        "endpoint already allocated for a UDP channel") == 0;
                         });
 
-  // createMulticastFace with an IPv4 local endpoint already
-  // used by a multicast face on a different multicast group
+  // 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 the requested UDP multicast face, local "
-                                        "endpoint is already allocated for a UDP multicast face "
-                                        "on a different multicast group") == 0;
+                                        "Cannot create UDP multicast face on 127.0.0.1:20070, "
+                                        "endpoint already allocated for a different UDP multicast face") == 0;
                         });
+}
 
-  // createMulticastFace with an IPv6 multicast address
-  BOOST_CHECK_EXCEPTION(createMulticastFace("::1", "ff01::114", 20073), UdpFactory::Error,
+BOOST_FIXTURE_TEST_CASE(CreateMulticastFaceV6, UdpFactoryMcastFixture)
+{
+#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);
+
+  auto multicastFace1  = createMulticastFace("::1", "ff02::114", 20070);
+  auto multicastFace1a = createMulticastFace("::1", "ff02::114", 20070);
+  auto multicastFace2  = createMulticastFace("::1", "ff02::114", 20030);
+  BOOST_CHECK_EQUAL(multicastFace1, multicastFace1a);
+  BOOST_CHECK_NE(multicastFace1, multicastFace2);
+
+  auto address = findNonLoopbackAddressForMulticastFace(ndn::net::AddressFamily::V6);
+  if (!address.is_unspecified()) {
+    auto multicastFace3  = createMulticastFace(address.to_string(), "ff02::114", 20070);
+    BOOST_CHECK_NE(multicastFace1, multicastFace3);
+    BOOST_CHECK_NE(multicastFace2, multicastFace3);
+  }
+
+  // create with a local endpoint already used by a channel
+  auto channel = createChannel("::1", 20071);
+  BOOST_CHECK_EXCEPTION(createMulticastFace("::1", "ff02::114", 20071), UdpFactory::Error,
                         [] (const UdpFactory::Error& e) {
                           return strcmp(e.what(),
-                                        "IPv6 multicast is not supported yet. Please provide an IPv4 "
-                                        "address") == 0;
+                                        "Cannot create UDP multicast face on [::1]:20071, "
+                                        "endpoint already allocated for a UDP channel") == 0;
+                        });
+
+  // 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 for a different UDP multicast face") == 0;
                         });
 }