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