face: deduplicate multicast UDP socket setup
In preparation for adding IPv6 support.
Change-Id: I5a3be2007f7fecc44915a5d6794093143ce9c0f9
Refs: #4222
diff --git a/daemon/face/multicast-udp-transport.cpp b/daemon/face/multicast-udp-transport.cpp
index 7b3fca3..f8ee1a9 100644
--- a/daemon/face/multicast-udp-transport.cpp
+++ b/daemon/face/multicast-udp-transport.cpp
@@ -1,5 +1,5 @@
/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
-/**
+/*
* Copyright (c) 2014-2017, Regents of the University of California,
* Arizona Board of Regents,
* Colorado State University,
@@ -26,6 +26,12 @@
#include "multicast-udp-transport.hpp"
#include "udp-protocol.hpp"
+#ifdef __linux__
+#include <cerrno> // for errno
+#include <cstring> // for std::strerror()
+#include <sys/socket.h> // for setsockopt()
+#endif // __linux__
+
namespace nfd {
namespace face {
@@ -79,6 +85,67 @@
DatagramTransport::doClose();
}
+void
+MulticastUdpTransport::openRxSocket(protocol::socket& sock,
+ const protocol::endpoint& multicastGroup,
+ const boost::asio::ip::address& localAddress,
+ const shared_ptr<const ndn::net::NetworkInterface>& netif)
+{
+ BOOST_ASSERT(!sock.is_open());
+
+ 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.set_option(boost::asio::ip::multicast::join_group(multicastGroup.address().to_v4(),
+ localAddress.to_v4()));
+ }
+ else {
+ // IPv6 multicast is not supported
+ BOOST_ASSERT(false);
+ }
+
+#ifdef __linux__
+ if (netif) {
+ // On Linux, if there is more than one MulticastUdpTransport for the same multicast
+ // group but they are on different network interfaces, each socket needs to be bound
+ // to the corresponding interface using SO_BINDTODEVICE, otherwise the transport will
+ // receive all packets sent to the other interfaces as well.
+ // This is needed only on Linux. On macOS, the boost::asio::ip::multicast::join_group
+ // option is sufficient to obtain the desired behavior.
+ if (::setsockopt(sock.native_handle(), SOL_SOCKET, SO_BINDTODEVICE,
+ netif->getName().data(), netif->getName().size() + 1) < 0) {
+ BOOST_THROW_EXCEPTION(Error("Cannot bind multicast rx socket to " + netif->getName() +
+ ": " + std::strerror(errno)));
+ }
+ }
+#endif // __linux__
+}
+
+void
+MulticastUdpTransport::openTxSocket(protocol::socket& sock,
+ const protocol::endpoint& localEndpoint,
+ bool enableLoopback)
+{
+ BOOST_ASSERT(!sock.is_open());
+
+ 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()) {
+ 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);
+ }
+}
+
template<>
Transport::EndpointId
DatagramTransport<boost::asio::ip::udp, Multicast>::makeEndpointId(const protocol::endpoint& ep)
diff --git a/daemon/face/multicast-udp-transport.hpp b/daemon/face/multicast-udp-transport.hpp
index 9b903d0..b0fb4d4 100644
--- a/daemon/face/multicast-udp-transport.hpp
+++ b/daemon/face/multicast-udp-transport.hpp
@@ -1,5 +1,5 @@
/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
-/**
+/*
* Copyright (c) 2014-2017, Regents of the University of California,
* Arizona Board of Regents,
* Colorado State University,
@@ -28,6 +28,8 @@
#include "datagram-transport.hpp"
+#include <ndn-cxx/net/network-interface.hpp>
+
namespace nfd {
namespace face {
@@ -45,6 +47,16 @@
class MulticastUdpTransport final : public DatagramTransport<boost::asio::ip::udp, Multicast>
{
public:
+ class Error : public std::runtime_error
+ {
+ public:
+ explicit
+ Error(const std::string& what)
+ : std::runtime_error(what)
+ {
+ }
+ };
+
/**
* \brief Creates a UDP-based transport for multicast communication
* \param localEndpoint local endpoint
@@ -59,6 +71,17 @@
protocol::socket&& sendSocket,
ndn::nfd::LinkType linkType);
+ static void
+ openRxSocket(protocol::socket& sock,
+ const protocol::endpoint& multicastGroup,
+ const boost::asio::ip::address& localAddress,
+ const shared_ptr<const ndn::net::NetworkInterface>& netif = nullptr);
+
+ static void
+ openTxSocket(protocol::socket& sock,
+ const protocol::endpoint& localEndpoint,
+ bool enableLoopback = false);
+
private:
void
doSend(Transport::Packet&& packet) final;
diff --git a/daemon/face/udp-factory.cpp b/daemon/face/udp-factory.cpp
index 7b1f4f0..c9cab22 100644
--- a/daemon/face/udp-factory.cpp
+++ b/daemon/face/udp-factory.cpp
@@ -32,12 +32,6 @@
#include <boost/range/adaptor/map.hpp>
#include <boost/range/algorithm/copy.hpp>
-#ifdef __linux__
-#include <cerrno> // for errno
-#include <cstring> // for std::strerror()
-#include <sys/socket.h> // for setsockopt()
-#endif // __linux__
-
namespace nfd {
namespace face {
@@ -294,7 +288,7 @@
shared_ptr<Face>
UdpFactory::createMulticastFace(const udp::Endpoint& localEndpoint,
const udp::Endpoint& multicastEndpoint,
- const net::NetworkInterface& netif)
+ const shared_ptr<const ndn::net::NetworkInterface>& netif)
{
BOOST_ASSERT(multicastEndpoint.address().is_multicast());
BOOST_ASSERT(localEndpoint.port() == multicastEndpoint.port());
@@ -321,42 +315,14 @@
"address"));
}
- ip::udp::socket receiveSocket(getGlobalIoService());
- receiveSocket.open(multicastEndpoint.protocol());
- receiveSocket.set_option(ip::udp::socket::reuse_address(true));
- receiveSocket.bind(multicastEndpoint);
-
- ip::udp::socket sendSocket(getGlobalIoService());
- sendSocket.open(multicastEndpoint.protocol());
- sendSocket.set_option(ip::udp::socket::reuse_address(true));
- sendSocket.set_option(ip::multicast::enable_loopback(false));
- sendSocket.bind(udp::Endpoint(ip::address_v4::any(), multicastEndpoint.port()));
- if (localEndpoint.address() != ip::address_v4::any())
- sendSocket.set_option(ip::multicast::outbound_interface(localEndpoint.address().to_v4()));
-
- sendSocket.set_option(ip::multicast::join_group(multicastEndpoint.address().to_v4(),
- localEndpoint.address().to_v4()));
- receiveSocket.set_option(ip::multicast::join_group(multicastEndpoint.address().to_v4(),
- localEndpoint.address().to_v4()));
-
-#ifdef __linux__
- // On Linux, if there is more than one multicast UDP face for the same multicast
- // group but they are bound to different network interfaces, the socket needs
- // to be bound to the specific interface using SO_BINDTODEVICE, otherwise the
- // face will receive all packets sent to the other interfaces as well.
- // This happens only on Linux. On macOS, the ip::multicast::join_group option
- // is enough to get the desired behaviour.
- if (::setsockopt(receiveSocket.native_handle(), SOL_SOCKET, SO_BINDTODEVICE,
- netif.getName().data(), netif.getName().size() + 1) < 0) {
- BOOST_THROW_EXCEPTION(Error("Cannot bind multicast face to " + netif.getName() +
- ": " + std::strerror(errno)));
- }
-#endif // __linux__
+ ip::udp::socket rxSock(getGlobalIoService());
+ MulticastUdpTransport::openRxSocket(rxSock, multicastEndpoint, localEndpoint.address(), netif);
+ ip::udp::socket txSock(getGlobalIoService());
+ MulticastUdpTransport::openTxSocket(txSock, localEndpoint);
auto linkService = make_unique<GenericLinkService>();
auto transport = make_unique<MulticastUdpTransport>(localEndpoint, multicastEndpoint,
- std::move(receiveSocket),
- std::move(sendSocket),
+ std::move(rxSock), std::move(txSock),
m_mcastConfig.linkType);
auto face = make_shared<Face>(std::move(linkService), std::move(transport));
@@ -411,15 +377,14 @@
}
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);
- // ifname is only used on Linux. It is not required if there is only one multicast-capable netif,
- // but it is always supplied because a new netif can be added at anytime.
+ 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);
}
+
return face;
}
diff --git a/daemon/face/udp-factory.hpp b/daemon/face/udp-factory.hpp
index 9e20be4..3f644c4 100644
--- a/daemon/face/udp-factory.hpp
+++ b/daemon/face/udp-factory.hpp
@@ -116,7 +116,7 @@
shared_ptr<Face>
createMulticastFace(const udp::Endpoint& localEndpoint,
const udp::Endpoint& multicastEndpoint,
- const ndn::net::NetworkInterface& netif);
+ const shared_ptr<const ndn::net::NetworkInterface>& netif);
private:
/** \brief Create UDP multicast face on \p netif if needed by \p m_mcastConfig
diff --git a/tests/daemon/face/multicast-udp-transport-fixture.hpp b/tests/daemon/face/multicast-udp-transport-fixture.hpp
index 7c3ca21..2b86833 100644
--- a/tests/daemon/face/multicast-udp-transport-fixture.hpp
+++ b/tests/daemon/face/multicast-udp-transport-fixture.hpp
@@ -55,12 +55,16 @@
void
initialize(ip::address address)
{
- openMulticastSockets(remoteSockRx, remoteSockTx, multicastEp.port());
+ localEp = udp::endpoint(address, 7001);
+
+ MulticastUdpTransport::openRxSocket(remoteSockRx, multicastEp, ip::address_v4::any());
+ MulticastUdpTransport::openTxSocket(remoteSockTx, udp::endpoint(udp::v4(), 0), true);
udp::socket sockRx(g_io);
udp::socket sockTx(g_io);
- localEp = udp::endpoint(address, 7001);
- openMulticastSockets(sockRx, sockTx, localEp.port());
+ MulticastUdpTransport::openRxSocket(sockRx, udp::endpoint(multicastEp.address(), localEp.port()),
+ ip::address_v4::any());
+ MulticastUdpTransport::openTxSocket(sockTx, udp::endpoint(udp::v4(), 0), true);
face = make_unique<Face>(
make_unique<DummyReceiveLinkService>(),
@@ -73,20 +77,6 @@
}
void
- openMulticastSockets(udp::socket& rx, udp::socket& tx, uint16_t port)
- {
- rx.open(udp::v4());
- rx.set_option(udp::socket::reuse_address(true));
- rx.bind(udp::endpoint(multicastEp.address(), port));
- rx.set_option(ip::multicast::join_group(multicastEp.address()));
-
- tx.open(udp::v4());
- tx.set_option(udp::socket::reuse_address(true));
- tx.set_option(ip::multicast::enable_loopback(true));
- tx.bind(udp::endpoint(ip::address_v4::any(), port));
- }
-
- void
remoteRead(std::vector<uint8_t>& buf, bool needToCheck = true)
{
remoteSockRx.async_receive(boost::asio::buffer(buf),
diff --git a/tests/daemon/face/multicast-udp-transport.t.cpp b/tests/daemon/face/multicast-udp-transport.t.cpp
index 6e4a8cf..51acb08 100644
--- a/tests/daemon/face/multicast-udp-transport.t.cpp
+++ b/tests/daemon/face/multicast-udp-transport.t.cpp
@@ -79,10 +79,7 @@
// remoteSockRx2 unnecessary for this test case - only remoteSockTx2 is needed
udp::socket remoteSockTx2(this->g_io);
- remoteSockTx2.open(udp::v4());
- remoteSockTx2.set_option(udp::socket::reuse_address(true));
- remoteSockTx2.set_option(ip::multicast::enable_loopback(true));
- remoteSockTx2.bind(udp::endpoint(ip::address_v4::any(), 7071));
+ MulticastUdpTransport::openTxSocket(remoteSockTx2, udp::endpoint(udp::v4(), 7071), true);
Block pkt1 = ndn::encoding::makeStringBlock(300, "hello");
ndn::Buffer buf1(pkt1.begin(), pkt1.end());
diff --git a/tests/daemon/face/udp-factory.t.cpp b/tests/daemon/face/udp-factory.t.cpp
index 84d4529..5e7e615 100644
--- a/tests/daemon/face/udp-factory.t.cpp
+++ b/tests/daemon/face/udp-factory.t.cpp
@@ -68,7 +68,7 @@
BOOST_ASSERT(!netifs.empty());
udp::Endpoint localEndpoint(ndn::ip::addressFromString(localIp), mcastPort);
udp::Endpoint mcastEndpoint(ndn::ip::addressFromString(mcastIp), mcastPort);
- return factory.createMulticastFace(localEndpoint, mcastEndpoint, *netifs.front());
+ return factory.createMulticastFace(localEndpoint, mcastEndpoint, netifs.front());
}
std::vector<const Face*>