face: implement IPv6 UDP multicast transport
Change-Id: Ib6ab956354dbbba00694c7949fa9ee4639579879
Refs: #4222
diff --git a/daemon/face/multicast-udp-transport.cpp b/daemon/face/multicast-udp-transport.cpp
index 86324d3..8912759 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 <boost/functional/hash.hpp>
+
#ifdef __linux__
#include <cerrno> // for errno
#include <cstring> // for std::strerror()
@@ -39,8 +41,7 @@
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 @@
, 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 @@
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 @@
void
MulticastUdpTransport::openTxSocket(protocol::socket& sock,
const protocol::endpoint& localEndpoint,
+ const shared_ptr<const ndn::net::NetworkInterface>& netif,
bool enableLoopback)
{
BOOST_ASSERT(!sock.is_open());
@@ -156,15 +167,17 @@
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 @@
Transport::EndpointId
DatagramTransport<boost::asio::ip::udp, Multicast>::makeEndpointId(const protocol::endpoint& ep)
{
- // IPv6 multicast is not supported
- BOOST_ASSERT(ep.address().is_v4());
-
- return (static_cast<uint64_t>(ep.port()) << 32) |
- static_cast<uint64_t>(ep.address().to_v4().to_ulong());
+ if (ep.address().is_v4()) {
+ return (static_cast<uint64_t>(ep.port()) << 32) |
+ static_cast<uint64_t>(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 e655f4a..ba7bcbe 100644
--- a/daemon/face/multicast-udp-transport.hpp
+++ b/daemon/face/multicast-udp-transport.hpp
@@ -59,14 +59,12 @@
/**
* \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 @@
static void
openTxSocket(protocol::socket& sock,
const protocol::endpoint& localEndpoint,
+ const shared_ptr<const ndn::net::NetworkInterface>& netif = nullptr,
bool enableLoopback = false);
private:
diff --git a/daemon/face/udp-factory.cpp b/daemon/face/udp-factory.cpp
index c9cab22..2024cd6 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 @@
// 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 @@
else if (key == "mcast_port") {
mcastConfig.group.port(ConfigFile::parseNumber<uint16_t>(pair, "face_system.udp"));
}
+ else if (key == "mcast_group_v6") {
+ const std::string& valueStr = value.get_value<std::string>();
+ 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<uint16_t>(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 @@
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 @@
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 @@
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<std::string>(localEndpoint) +
+ ", endpoint already allocated for a UDP multicast face"));
}
auto channel = std::make_shared<UdpChannel>(localEndpoint, idleTimeout);
@@ -286,126 +305,148 @@
}
shared_ptr<Face>
-UdpFactory::createMulticastFace(const udp::Endpoint& localEndpoint,
- const udp::Endpoint& multicastEndpoint,
- const shared_ptr<const ndn::net::NetworkInterface>& netif)
+UdpFactory::createMulticastFace(const shared_ptr<const net::NetworkInterface>& 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<std::string>(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<std::string>(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<GenericLinkService>();
- auto transport = make_unique<MulticastUdpTransport>(localEndpoint, multicastEndpoint,
- std::move(rxSock), std::move(txSock),
+ auto transport = make_unique<MulticastUdpTransport>(mcastEp, std::move(rxSock), std::move(txSock),
m_mcastConfig.linkType);
auto face = make_shared<Face>(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<ip::address>
-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<Face>
+std::vector<shared_ptr<Face>>
UdpFactory::applyMcastConfigToNetif(const shared_ptr<const net::NetworkInterface>& 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<ip::address> 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<shared_ptr<Face>> 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<shared_ptr<Face>> oldFaces;
- boost::copy(m_mcastFaces | boost::adaptors::map_values, std::inserter(oldFaces, oldFaces.end()));
+ std::set<shared_ptr<Face>> 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 3f644c4..1153a7a 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 @@
*
* 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<Face>
- createMulticastFace(const udp::Endpoint& localEndpoint,
- const udp::Endpoint& multicastEndpoint,
- const shared_ptr<const ndn::net::NetworkInterface>& netif);
+ createMulticastFace(const shared_ptr<const ndn::net::NetworkInterface>& 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<Face>
+ std::vector<shared_ptr<Face>>
applyMcastConfigToNetif(const shared_ptr<const ndn::net::NetworkInterface>& netif);
/** \brief Create and destroy UDP multicast faces according to \p m_mcastConfig
@@ -137,6 +138,7 @@
{
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 f716658..85a6a5f 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 @@
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