diff --git a/daemon/face/multicast-udp-transport.cpp b/daemon/face/multicast-udp-transport.cpp index 86324d34..89127599 100644 --- a/daemon/face/multicast-udp-transport.cpp +++ b/daemon/face/multicast-udp-transport.cpp @@ -27,6 +27,8 @@ #include "socket-utils.hpp" #include "udp-protocol.hpp" +#include + #ifdef __linux__ #include // for errno #include // for std::strerror() @@ -39,8 +41,7 @@ namespace face { NFD_LOG_INCLASS_2TEMPLATE_SPECIALIZATION_DEFINE(DatagramTransport, MulticastUdpTransport::protocol, Multicast, "MulticastUdpTransport"); -MulticastUdpTransport::MulticastUdpTransport(const protocol::endpoint& localEndpoint, - const protocol::endpoint& multicastGroup, +MulticastUdpTransport::MulticastUdpTransport(const protocol::endpoint& multicastGroup, protocol::socket&& recvSocket, protocol::socket&& sendSocket, ndn::nfd::LinkType linkType) @@ -48,12 +49,12 @@ MulticastUdpTransport::MulticastUdpTransport(const protocol::endpoint& localEndp , m_multicastGroup(multicastGroup) , m_sendSocket(std::move(sendSocket)) { - this->setLocalUri(FaceUri(localEndpoint)); + this->setLocalUri(FaceUri(m_sendSocket.local_endpoint())); this->setRemoteUri(FaceUri(multicastGroup)); this->setScope(ndn::nfd::FACE_SCOPE_NON_LOCAL); this->setPersistency(ndn::nfd::FACE_PERSISTENCY_PERMANENT); this->setLinkType(linkType); - this->setMtu(udp::computeMtu(localEndpoint)); + this->setMtu(udp::computeMtu(m_sendSocket.local_endpoint())); protocol::socket::send_buffer_size sendBufferSizeOption; boost::system::error_code error; @@ -117,16 +118,25 @@ MulticastUdpTransport::openRxSocket(protocol::socket& sock, sock.open(multicastGroup.protocol()); sock.set_option(protocol::socket::reuse_address(true)); - sock.bind(protocol::endpoint(multicastGroup.protocol(), multicastGroup.port())); if (multicastGroup.address().is_v4()) { BOOST_ASSERT(localAddress.is_v4()); + sock.bind(multicastGroup); sock.set_option(boost::asio::ip::multicast::join_group(multicastGroup.address().to_v4(), localAddress.to_v4())); } else { - // IPv6 multicast is not supported - BOOST_ASSERT(false); + BOOST_ASSERT(localAddress.is_v6()); + sock.set_option(boost::asio::ip::v6_only(true)); +#ifdef WITH_TESTS + // To simplify unit tests, we bind to the "any" IPv6 address if the supplied multicast + // address lacks a scope id. Calling bind() without a scope id would otherwise fail. + if (multicastGroup.address().to_v6().scope_id() == 0) + sock.bind(protocol::endpoint(boost::asio::ip::address_v6::any(), multicastGroup.port())); + else +#endif + sock.bind(multicastGroup); + sock.set_option(boost::asio::ip::multicast::join_group(multicastGroup.address().to_v6())); } #ifdef __linux__ @@ -149,6 +159,7 @@ MulticastUdpTransport::openRxSocket(protocol::socket& sock, void MulticastUdpTransport::openTxSocket(protocol::socket& sock, const protocol::endpoint& localEndpoint, + const shared_ptr& netif, bool enableLoopback) { BOOST_ASSERT(!sock.is_open()); @@ -156,15 +167,17 @@ MulticastUdpTransport::openTxSocket(protocol::socket& sock, sock.open(localEndpoint.protocol()); sock.set_option(protocol::socket::reuse_address(true)); sock.set_option(boost::asio::ip::multicast::enable_loopback(enableLoopback)); - sock.bind(localEndpoint); if (localEndpoint.address().is_v4()) { + sock.bind(localEndpoint); if (!localEndpoint.address().is_unspecified()) sock.set_option(boost::asio::ip::multicast::outbound_interface(localEndpoint.address().to_v4())); } else { - // IPv6 multicast is not supported - BOOST_ASSERT(false); + sock.set_option(boost::asio::ip::v6_only(true)); + sock.bind(localEndpoint); + if (netif) + sock.set_option(boost::asio::ip::multicast::outbound_interface(netif->getIndex())); } } @@ -172,11 +185,17 @@ template<> Transport::EndpointId DatagramTransport::makeEndpointId(const protocol::endpoint& ep) { - // IPv6 multicast is not supported - BOOST_ASSERT(ep.address().is_v4()); - - return (static_cast(ep.port()) << 32) | - static_cast(ep.address().to_v4().to_ulong()); + if (ep.address().is_v4()) { + return (static_cast(ep.port()) << 32) | + static_cast(ep.address().to_v4().to_ulong()); + } + else { + size_t seed = 0; + const auto& addrBytes = ep.address().to_v6().to_bytes(); + boost::hash_range(seed, addrBytes.begin(), addrBytes.end()); + boost::hash_combine(seed, ep.port()); + return seed; + } } } // namespace face diff --git a/daemon/face/multicast-udp-transport.hpp b/daemon/face/multicast-udp-transport.hpp index e655f4a2..ba7bcbec 100644 --- a/daemon/face/multicast-udp-transport.hpp +++ b/daemon/face/multicast-udp-transport.hpp @@ -59,14 +59,12 @@ public: /** * \brief Creates a UDP-based transport for multicast communication - * \param localEndpoint local endpoint * \param multicastGroup multicast group - * \param recvSocket socket used to receive packets + * \param recvSocket socket used to receive multicast packets * \param sendSocket socket used to send to the multicast group - * \param linkType either LINK_TYPE_MULTI_ACCESS or LINK_TYPE_AD_HOC + * \param linkType either `ndn::nfd::LINK_TYPE_MULTI_ACCESS` or `ndn::nfd::LINK_TYPE_AD_HOC` */ - MulticastUdpTransport(const protocol::endpoint& localEndpoint, - const protocol::endpoint& multicastGroup, + MulticastUdpTransport(const protocol::endpoint& multicastGroup, protocol::socket&& recvSocket, protocol::socket&& sendSocket, ndn::nfd::LinkType linkType); @@ -83,6 +81,7 @@ public: static void openTxSocket(protocol::socket& sock, const protocol::endpoint& localEndpoint, + const shared_ptr& netif = nullptr, bool enableLoopback = false); private: diff --git a/daemon/face/udp-factory.cpp b/daemon/face/udp-factory.cpp index c9cab22b..2024cd66 100644 --- a/daemon/face/udp-factory.cpp +++ b/daemon/face/udp-factory.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, @@ -68,6 +68,8 @@ UdpFactory::processConfig(OptionalConfigSection configSection, // mcast yes // mcast_group 224.0.23.170 // mcast_port 56363 + // mcast_group_v6 ff02::1234 + // mcast_port_v6 56363 // mcast_ad_hoc no // whitelist // { @@ -126,6 +128,22 @@ UdpFactory::processConfig(OptionalConfigSection configSection, else if (key == "mcast_port") { mcastConfig.group.port(ConfigFile::parseNumber(pair, "face_system.udp")); } + else if (key == "mcast_group_v6") { + const std::string& valueStr = value.get_value(); + boost::system::error_code ec; + mcastConfig.groupV6.address(ndn::ip::addressV6FromString(valueStr, ec)); + if (ec) { + BOOST_THROW_EXCEPTION(ConfigFile::Error("face_system.udp.mcast_group_v6: '" + + valueStr + "' cannot be parsed as an IPv6 address")); + } + else if (!mcastConfig.groupV6.address().is_multicast()) { + BOOST_THROW_EXCEPTION(ConfigFile::Error("face_system.udp.mcast_group_v6: '" + + valueStr + "' is not a multicast address")); + } + } + else if (key == "mcast_port_v6") { + mcastConfig.groupV6.port(ConfigFile::parseNumber(pair, "face_system.udp")); + } else if (key == "mcast_ad_hoc") { bool wantAdHoc = ConfigFile::parseYesNo(pair, "face_system.udp"); mcastConfig.linkType = wantAdHoc ? ndn::nfd::LINK_TYPE_AD_HOC : ndn::nfd::LINK_TYPE_MULTI_ACCESS; @@ -181,6 +199,7 @@ UdpFactory::processConfig(OptionalConfigSection configSection, if (m_mcastConfig.isEnabled != mcastConfig.isEnabled) { if (mcastConfig.isEnabled) { NFD_LOG_INFO("enabling multicast on " << mcastConfig.group); + NFD_LOG_INFO("enabling multicast on " << mcastConfig.groupV6); } else { NFD_LOG_INFO("disabling multicast"); @@ -191,9 +210,13 @@ UdpFactory::processConfig(OptionalConfigSection configSection, NFD_LOG_WARN("Cannot change ad hoc setting on existing faces"); } if (m_mcastConfig.group != mcastConfig.group) { - NFD_LOG_INFO("changing multicast group from " << m_mcastConfig.group << + NFD_LOG_INFO("changing IPv4 multicast group from " << m_mcastConfig.group << " to " << mcastConfig.group); } + if (m_mcastConfig.groupV6 != mcastConfig.groupV6) { + NFD_LOG_INFO("changing IPv6 multicast group from " << m_mcastConfig.groupV6 << + " to " << mcastConfig.groupV6); + } if (m_mcastConfig.netifPredicate != mcastConfig.netifPredicate) { NFD_LOG_INFO("changing whitelist/blacklist"); } @@ -262,15 +285,11 @@ UdpFactory::createChannel(const udp::Endpoint& localEndpoint, if (it != m_channels.end()) return it->second; - if (localEndpoint.address().is_multicast()) { - BOOST_THROW_EXCEPTION(Error("createChannel is only for unicast channels. The provided endpoint " - "is multicast. Use createMulticastFace to create a multicast face")); - } - // check if the endpoint is already used by a multicast face if (m_mcastFaces.find(localEndpoint) != m_mcastFaces.end()) { - BOOST_THROW_EXCEPTION(Error("Cannot create the requested UDP unicast channel, local " - "endpoint is already allocated for a UDP multicast face")); + BOOST_THROW_EXCEPTION(Error("Cannot create UDP channel on " + + boost::lexical_cast(localEndpoint) + + ", endpoint already allocated for a UDP multicast face")); } auto channel = std::make_shared(localEndpoint, idleTimeout); @@ -286,126 +305,148 @@ UdpFactory::getChannels() const } shared_ptr -UdpFactory::createMulticastFace(const udp::Endpoint& localEndpoint, - const udp::Endpoint& multicastEndpoint, - const shared_ptr& netif) +UdpFactory::createMulticastFace(const shared_ptr& netif, + const ip::address& localAddress, + const udp::Endpoint& multicastEndpoint) { 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); + udp::Endpoint localEp(localAddress, multicastEndpoint.port()); + BOOST_ASSERT(localEp.protocol() == multicastEndpoint.protocol()); + + auto mcastEp = multicastEndpoint; + if (mcastEp.address().is_v6()) { + // in IPv6, a scope id on the multicast address is always required + auto mcastAddress = mcastEp.address().to_v6(); + mcastAddress.scope_id(netif->getIndex()); + mcastEp.address(mcastAddress); + } + + // check if the local endpoint is already used by another multicast face + auto it = m_mcastFaces.find(localEp); if (it != m_mcastFaces.end()) { - if (it->second->getRemoteUri() == FaceUri(multicastEndpoint)) + if (it->second->getRemoteUri() == FaceUri(mcastEp)) return it->second; else - BOOST_THROW_EXCEPTION(Error("Cannot create the requested UDP multicast face, local " - "endpoint is already allocated for a UDP multicast face " - "on a different multicast group")); + BOOST_THROW_EXCEPTION(Error("Cannot create UDP multicast face on " + + boost::lexical_cast(localEp) + + ", endpoint already allocated for a different UDP multicast face")); } // 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")); - } - - if (localEndpoint.address().is_v6() || multicastEndpoint.address().is_v6()) { - BOOST_THROW_EXCEPTION(Error("IPv6 multicast is not supported yet. Please provide an IPv4 " - "address")); + if (m_channels.find(localEp) != m_channels.end()) { + BOOST_THROW_EXCEPTION(Error("Cannot create UDP multicast face on " + + boost::lexical_cast(localEp) + + ", endpoint already allocated for a UDP channel")); } ip::udp::socket rxSock(getGlobalIoService()); - MulticastUdpTransport::openRxSocket(rxSock, multicastEndpoint, localEndpoint.address(), netif); + MulticastUdpTransport::openRxSocket(rxSock, mcastEp, localAddress, netif); ip::udp::socket txSock(getGlobalIoService()); - MulticastUdpTransport::openTxSocket(txSock, localEndpoint); + MulticastUdpTransport::openTxSocket(txSock, udp::Endpoint(localAddress, 0), netif); auto linkService = make_unique(); - auto transport = make_unique(localEndpoint, multicastEndpoint, - std::move(rxSock), std::move(txSock), + auto transport = make_unique(mcastEp, std::move(rxSock), std::move(txSock), m_mcastConfig.linkType); auto face = make_shared(std::move(linkService), std::move(transport)); - m_mcastFaces[localEndpoint] = face; - connectFaceClosedSignal(*face, [this, localEndpoint] { m_mcastFaces.erase(localEndpoint); }); + m_mcastFaces[localEp] = face; + connectFaceClosedSignal(*face, [this, localEp] { m_mcastFaces.erase(localEp); }); return face; } static ndn::optional -getV4Address(const net::NetworkInterface& netif) +pickAddress(const net::NetworkInterface& netif, net::AddressFamily af) { for (const auto& na : netif.getNetworkAddresses()) { - if (na.getFamily() == net::AddressFamily::V4 && na.getScope() != net::AddressScope::NOWHERE) { + if (na.getFamily() == af && + (na.getScope() == net::AddressScope::LINK || na.getScope() == net::AddressScope::GLOBAL)) { return na.getIp(); } } return ndn::nullopt; } -shared_ptr +std::vector> UdpFactory::applyMcastConfigToNetif(const shared_ptr& netif) { BOOST_ASSERT(netif != nullptr); if (!m_mcastConfig.isEnabled) { - return nullptr; + return {}; } if (!netif->isUp()) { - NFD_LOG_DEBUG("Not creating multicast face on " << netif->getName() << ": netif is down"); - return nullptr; + NFD_LOG_DEBUG("Not creating multicast faces on " << netif->getName() << ": netif is down"); + return {}; + } + + if (netif->isLoopback()) { + NFD_LOG_DEBUG("Not creating multicast faces on " << netif->getName() << ": netif is loopback"); + return {}; } if (!netif->canMulticast()) { - NFD_LOG_DEBUG("Not creating multicast face on " << netif->getName() << ": netif cannot multicast"); - return nullptr; - } - - auto address = getV4Address(*netif); - if (!address) { - NFD_LOG_DEBUG("Not creating multicast face on " << netif->getName() << ": no IPv4 address"); - // keep an eye on new addresses - m_netifConns[netif->getIndex()].addrAddConn = - netif->onAddressAdded.connectSingleShot(bind(&UdpFactory::applyMcastConfigToNetif, this, netif)); - return nullptr; + NFD_LOG_DEBUG("Not creating multicast faces on " << netif->getName() << ": netif cannot multicast"); + return {}; } if (!m_mcastConfig.netifPredicate(*netif)) { - NFD_LOG_DEBUG("Not creating multicast face on " << netif->getName() << ": rejected by whitelist/blacklist"); - return nullptr; + NFD_LOG_DEBUG("Not creating multicast faces on " << netif->getName() << ": rejected by whitelist/blacklist"); + return {}; } - NFD_LOG_DEBUG("Creating multicast face on " << netif->getName()); - - udp::Endpoint localEndpoint(*address, m_mcastConfig.group.port()); - auto face = this->createMulticastFace(localEndpoint, m_mcastConfig.group, netif); - if (face->getId() == INVALID_FACEID) { - // new face: register with forwarding - this->addFace(face); + std::vector addrs; + for (auto af : {net::AddressFamily::V4, net::AddressFamily::V6}) { + auto addr = pickAddress(*netif, af); + if (addr) + addrs.push_back(*addr); } - return face; + if (addrs.empty()) { + NFD_LOG_DEBUG("Not creating multicast faces on " << netif->getName() << ": no viable IP address"); + // keep an eye on new addresses + m_netifConns[netif->getIndex()].addrAddConn = + netif->onAddressAdded.connect(bind(&UdpFactory::applyMcastConfigToNetif, this, netif)); + return {}; + } + + NFD_LOG_DEBUG("Creating multicast faces on " << netif->getName()); + + std::vector> faces; + for (const auto& addr : addrs) { + auto face = this->createMulticastFace(netif, addr, + addr.is_v4() ? m_mcastConfig.group : m_mcastConfig.groupV6); + if (face->getId() == INVALID_FACEID) { + // new face: register with forwarding + this->addFace(face); + } + faces.push_back(std::move(face)); + } + + return faces; } void UdpFactory::applyMcastConfig(const FaceSystem::ConfigContext& context) { // collect old faces - std::set> oldFaces; - boost::copy(m_mcastFaces | boost::adaptors::map_values, std::inserter(oldFaces, oldFaces.end())); + std::set> facesToClose; + boost::copy(m_mcastFaces | boost::adaptors::map_values, + std::inserter(facesToClose, facesToClose.end())); // create faces if requested by config for (const auto& netif : netmon->listNetworkInterfaces()) { - auto face = this->applyMcastConfigToNetif(netif); - if (face != nullptr) { + auto facesToKeep = this->applyMcastConfigToNetif(netif); + for (const auto& face : facesToKeep) { // don't destroy face - oldFaces.erase(face); + facesToClose.erase(face); } } // destroy old faces that are not needed in new configuration - for (const auto& face : oldFaces) { + for (const auto& face : facesToClose) { face->close(); } } diff --git a/daemon/face/udp-factory.hpp b/daemon/face/udp-factory.hpp index 3f644c44..1153a7a4 100644 --- a/daemon/face/udp-factory.hpp +++ b/daemon/face/udp-factory.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, @@ -99,30 +99,31 @@ public: * * The face will join the specified multicast group. * - * If this method is called twice with the same pair of endpoints, only one + * If this method is called twice with the same set of arguments, only one * face will be created. The second call will just return the existing face. * * If a UDP channel, unicast face, or multicast face already exists on the - * same local endpoint, the creation fails and an exception is thrown. + * same combination of local address and multicast port, the creation fails + * and an exception is thrown. * - * \param localEndpoint the local endpoint - * \param multicastEndpoint the multicast endpoint * \param netif the network interface to which the face will be bound + * \param localAddress the local IP address to which the face will be bound + * \param multicastEndpoint the multicast endpoint (multicast group and port number) * * \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 - createMulticastFace(const udp::Endpoint& localEndpoint, - const udp::Endpoint& multicastEndpoint, - const shared_ptr& netif); + createMulticastFace(const shared_ptr& netif, + const boost::asio::ip::address& localAddress, + const udp::Endpoint& multicastEndpoint); private: - /** \brief Create UDP multicast face on \p netif if needed by \p m_mcastConfig - * \return new or existing face, or nullptr if no face should be created + /** \brief Create UDP multicast faces on \p netif if needed by \p m_mcastConfig + * \return list of faces (just created or already existing) on \p netif */ - shared_ptr + std::vector> applyMcastConfigToNetif(const shared_ptr& netif); /** \brief Create and destroy UDP multicast faces according to \p m_mcastConfig @@ -137,6 +138,7 @@ private: { bool isEnabled = false; udp::Endpoint group = udp::getDefaultMulticastGroup(); + udp::Endpoint groupV6 = udp::getDefaultMulticastGroupV6(); ndn::nfd::LinkType linkType = ndn::nfd::LINK_TYPE_MULTI_ACCESS; NetworkInterfacePredicate netifPredicate; }; diff --git a/daemon/face/udp-protocol.hpp b/daemon/face/udp-protocol.hpp index f7166586..85a6a5fd 100644 --- a/daemon/face/udp-protocol.hpp +++ b/daemon/face/udp-protocol.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, @@ -38,15 +38,22 @@ typedef boost::asio::ip::udp::endpoint Endpoint; ssize_t computeMtu(const Endpoint& localEndpoint); -/** \return default multicast group: 224.0.23.170:56363 +/** \return default IPv4 multicast group: 224.0.23.170:56363 */ inline Endpoint getDefaultMulticastGroup() { - // 224.0.23.170:56363 return {boost::asio::ip::address_v4(0xE00017AA), 56363}; } +/** \return default IPv6 multicast group: [FF02::1234]:56363 + */ +inline Endpoint +getDefaultMulticastGroupV6() +{ + return {boost::asio::ip::address_v6::from_string("FF02::1234"), 56363}; +} + } // namespace udp } // namespace nfd diff --git a/nfd.conf.sample.in b/nfd.conf.sample.in index a960b7d3..2ae5f84a 100644 --- a/nfd.conf.sample.in +++ b/nfd.conf.sample.in @@ -127,8 +127,10 @@ face_system ; sudo setcap cap_net_raw=eip /path/to/nfd ; mcast yes ; set to 'no' to disable UDP multicast, default 'yes' - mcast_group 224.0.23.170 ; UDP multicast group (IPv4 only) - mcast_port 56363 ; UDP multicast port number + mcast_group 224.0.23.170 ; UDP multicast group (IPv4) + mcast_port 56363 ; UDP multicast port number (IPv4) + mcast_group_v6 ff02::1234 ; UDP multicast group (IPv6) + mcast_port_v6 56363 ; UDP multicast port number (IPv6) mcast_ad_hoc no ; set to 'yes' to make all UDP multicast faces "ad hoc", default 'no' ; Whitelist and blacklist can contain, in no particular order: diff --git a/tests/daemon/face/datagram-transport.t.cpp b/tests/daemon/face/datagram-transport.t.cpp index 8cfb6227..d5ce2a86 100644 --- a/tests/daemon/face/datagram-transport.t.cpp +++ b/tests/daemon/face/datagram-transport.t.cpp @@ -39,7 +39,9 @@ BOOST_AUTO_TEST_SUITE(TestDatagramTransport) using DatagramTransportFixtures = boost::mpl::vector< GENERATE_IP_TRANSPORT_FIXTURE_INSTANTIATIONS(UnicastUdpTransportFixture), - IpTransportFixture + IpTransportFixture, + IpTransportFixture, + IpTransportFixture >; 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 2b868330..59598215 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 @@ class MulticastUdpTransportFixture : public BaseFixture 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 @@ protected: 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( make_unique(), - make_unique(localEp, multicastEp, std::move(sockRx), - std::move(sockTx), ndn::nfd::LINK_TYPE_MULTI_ACCESS)); + make_unique(mcastEp, std::move(sockRx), std::move(sockTx), + ndn::nfd::LINK_TYPE_MULTI_ACCESS)); transport = static_cast(face->getTransport()); receivedPackets = &static_cast(face->getLinkService())->receivedPackets; @@ -92,24 +103,31 @@ protected: void remoteWrite(const std::vector& 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& 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* receivedPackets; private: unique_ptr 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 de6d30f9..d8e7a60c 100644 --- a/tests/daemon/face/multicast-udp-transport.t.cpp +++ b/tests/daemon/face/multicast-udp-transport.t.cpp @@ -36,17 +36,15 @@ namespace tests { BOOST_AUTO_TEST_SUITE(Face) using MulticastUdpTransportFixtureWithAddress = - // TODO: change to AddressFamily::Any after IPv6 support is implemented - IpTransportFixture; BOOST_FIXTURE_TEST_SUITE(TestMulticastUdpTransport, MulticastUdpTransportFixtureWithAddress) using MulticastUdpTransportFixtures = boost::mpl::vector< - IpTransportFixture - // TODO: IPv6 not supported yet - //IpTransportFixture, - //IpTransportFixture + IpTransportFixture, + IpTransportFixture, + IpTransportFixture >; BOOST_FIXTURE_TEST_CASE_TEMPLATE(StaticProperties, T, MulticastUdpTransportFixtures, T) @@ -55,8 +53,8 @@ BOOST_FIXTURE_TEST_CASE_TEMPLATE(StaticProperties, T, MulticastUdpTransportFixtu 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 @@ BOOST_FIXTURE_TEST_CASE_TEMPLATE(ReceiveMultipleRemoteEndpoints, T, MulticastUdp { 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_FIXTURE_TEST_CASE_TEMPLATE(ReceiveMultipleRemoteEndpoints, T, MulticastUdp 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 5e7e6154..01a24a00 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 -#include #include namespace nfd { @@ -53,22 +52,54 @@ protected: 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 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 @@ -77,7 +108,13 @@ protected: return this->listFacesByScheme("udp4", linkType); } - /** \brief determine whether \p netif has at least one address of the given family + std::vector + 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 @@ protected: /** \brief determine whether a UDP multicast face is created on \p netif */ static bool - isFaceOnNetif(const Face& face, const shared_ptr& 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> netifs; + + /** \brief MulticastUdpTransport-capable network interfaces (IPv4 only) + */ + std::vector> netifsV4; + + /** \brief MulticastUdpTransport-capable network interfaces (IPv6 only) + */ + std::vector> netifsV6; }; #define SKIP_IF_UDP_MCAST_NETIF_COUNT_LT(n) \ @@ -111,6 +158,24 @@ protected: } \ } 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_AUTO_TEST_CASE(ChannelV6) 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 @@ BOOST_FIXTURE_TEST_CASE(EnableDisableMcast, UdpFactoryMcastFixture) 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 @@ BOOST_FIXTURE_TEST_CASE(McastAdHoc, UdpFactoryMcastFixture) )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 @@ BOOST_FIXTURE_TEST_CASE(ChangeMcastEndpoint, UdpFactoryMcastFixture) 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_FIXTURE_TEST_CASE(Whitelist, UdpFactoryMcastFixture) 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_FIXTURE_TEST_CASE(Blacklist, UdpFactoryMcastFixture) 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_FIXTURE_TEST_CASE(ChangePredicate, UdpFactoryMcastFixture) 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_AUTO_TEST_CASE(Omitted) 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_AUTO_TEST_CASE(BadMcast) 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_AUTO_TEST_CASE(BadMcastGroup) 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_AUTO_TEST_CASE(BadMcastPort) 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_FIXTURE_TEST_CASE(CreateChannel, UdpFactoryMcastFixture) 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; }); }