udp-factory.cpp
Go to the documentation of this file.
1 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
2 /*
3  * Copyright (c) 2014-2017, Regents of the University of California,
4  * Arizona Board of Regents,
5  * Colorado State University,
6  * University Pierre & Marie Curie, Sorbonne University,
7  * Washington University in St. Louis,
8  * Beijing Institute of Technology,
9  * The University of Memphis.
10  *
11  * This file is part of NFD (Named Data Networking Forwarding Daemon).
12  * See AUTHORS.md for complete list of NFD authors and contributors.
13  *
14  * NFD is free software: you can redistribute it and/or modify it under the terms
15  * of the GNU General Public License as published by the Free Software Foundation,
16  * either version 3 of the License, or (at your option) any later version.
17  *
18  * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
19  * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
20  * PURPOSE. See the GNU General Public License for more details.
21  *
22  * You should have received a copy of the GNU General Public License along with
23  * NFD, e.g., in COPYING.md file. If not, see <http://www.gnu.org/licenses/>.
24  */
25 
26 #include "udp-factory.hpp"
27 #include "generic-link-service.hpp"
29 #include "core/global-io.hpp"
30 
31 #include <ndn-cxx/net/address-converter.hpp>
32 #include <boost/range/adaptors.hpp>
33 #include <boost/range/algorithm/copy.hpp>
34 
35 #ifdef __linux__
36 #include <cerrno> // for errno
37 #include <cstring> // for std::strerror()
38 #include <sys/socket.h> // for setsockopt()
39 #endif // __linux__
40 
41 namespace nfd {
42 namespace face {
43 
44 namespace ip = boost::asio::ip;
45 
46 NFD_LOG_INIT("UdpFactory");
48 
49 const std::string&
51 {
52  static std::string id("udp");
53  return id;
54 }
55 
57  : ProtocolFactory(params)
58 {
59  m_netifAddConn = netmon->onInterfaceAdded.connect(bind(&UdpFactory::applyMcastConfigToNetif, this, _1));
60 }
61 
62 void
65 {
66  // udp
67  // {
68  // port 6363
69  // enable_v4 yes
70  // enable_v6 yes
71  // idle_timeout 600
72  // keep_alive_interval 25 ; acceptable but ignored
73  // mcast yes
74  // mcast_group 224.0.23.170
75  // mcast_port 56363
76  // mcast_ad_hoc no
77  // whitelist
78  // {
79  // *
80  // }
81  // blacklist
82  // {
83  // }
84  // }
85 
86  uint16_t port = 6363;
87  bool enableV4 = false;
88  bool enableV6 = false;
89  uint32_t idleTimeout = 600;
90  MulticastConfig mcastConfig;
91 
92  if (configSection) {
93  // These default to 'yes' but only if face_system.udp section is present
94  enableV4 = enableV6 = mcastConfig.isEnabled = true;
95 
96  for (const auto& pair : *configSection) {
97  const std::string& key = pair.first;
98  const ConfigSection& value = pair.second;
99 
100  if (key == "port") {
101  port = ConfigFile::parseNumber<uint16_t>(pair, "face_system.udp");
102  }
103  else if (key == "enable_v4") {
104  enableV4 = ConfigFile::parseYesNo(pair, "face_system.udp");
105  }
106  else if (key == "enable_v6") {
107  enableV6 = ConfigFile::parseYesNo(pair, "face_system.udp");
108  }
109  else if (key == "idle_timeout") {
110  idleTimeout = ConfigFile::parseNumber<uint32_t>(pair, "face_system.udp");
111  }
112  else if (key == "keep_alive_interval") {
113  // ignored
114  }
115  else if (key == "mcast") {
116  mcastConfig.isEnabled = ConfigFile::parseYesNo(pair, "face_system.udp");
117  }
118  else if (key == "mcast_group") {
119  const std::string& valueStr = value.get_value<std::string>();
120  boost::system::error_code ec;
121  mcastConfig.group.address(boost::asio::ip::address_v4::from_string(valueStr, ec));
122  if (ec) {
123  BOOST_THROW_EXCEPTION(ConfigFile::Error("face_system.udp.mcast_group: '" +
124  valueStr + "' cannot be parsed as an IPv4 address"));
125  }
126  else if (!mcastConfig.group.address().is_multicast()) {
127  BOOST_THROW_EXCEPTION(ConfigFile::Error("face_system.udp.mcast_group: '" +
128  valueStr + "' is not a multicast address"));
129  }
130  }
131  else if (key == "mcast_port") {
132  mcastConfig.group.port(ConfigFile::parseNumber<uint16_t>(pair, "face_system.udp"));
133  }
134  else if (key == "mcast_ad_hoc") {
135  bool wantAdHoc = ConfigFile::parseYesNo(pair, "face_system.udp");
136  mcastConfig.linkType = wantAdHoc ? ndn::nfd::LINK_TYPE_AD_HOC : ndn::nfd::LINK_TYPE_MULTI_ACCESS;
137  }
138  else if (key == "whitelist") {
139  mcastConfig.netifPredicate.parseWhitelist(value);
140  }
141  else if (key == "blacklist") {
142  mcastConfig.netifPredicate.parseBlacklist(value);
143  }
144  else {
145  BOOST_THROW_EXCEPTION(ConfigFile::Error("Unrecognized option face_system.udp." + key));
146  }
147  }
148 
149  if (!enableV4 && !enableV6 && !mcastConfig.isEnabled) {
150  BOOST_THROW_EXCEPTION(ConfigFile::Error(
151  "IPv4 and IPv6 UDP channels and UDP multicast have been disabled. "
152  "Remove face_system.udp section to disable UDP channels or enable at least one of them."));
153  }
154  }
155 
156  if (context.isDryRun) {
157  return;
158  }
159 
160  if (enableV4) {
161  udp::Endpoint endpoint(ip::udp::v4(), port);
162  shared_ptr<UdpChannel> v4Channel = this->createChannel(endpoint, time::seconds(idleTimeout));
163  if (!v4Channel->isListening()) {
164  v4Channel->listen(this->addFace, nullptr);
165  }
166  providedSchemes.insert("udp");
167  providedSchemes.insert("udp4");
168  }
169  else if (providedSchemes.count("udp4") > 0) {
170  NFD_LOG_WARN("Cannot close udp4 channel after its creation");
171  }
172 
173  if (enableV6) {
174  udp::Endpoint endpoint(ip::udp::v6(), port);
175  shared_ptr<UdpChannel> v6Channel = this->createChannel(endpoint, time::seconds(idleTimeout));
176  if (!v6Channel->isListening()) {
177  v6Channel->listen(this->addFace, nullptr);
178  }
179  providedSchemes.insert("udp");
180  providedSchemes.insert("udp6");
181  }
182  else if (providedSchemes.count("udp6") > 0) {
183  NFD_LOG_WARN("Cannot close udp6 channel after its creation");
184  }
185 
186  if (m_mcastConfig.isEnabled != mcastConfig.isEnabled) {
187  if (mcastConfig.isEnabled) {
188  NFD_LOG_INFO("enabling multicast on " << mcastConfig.group);
189  }
190  else {
191  NFD_LOG_INFO("disabling multicast");
192  }
193  }
194  else if (mcastConfig.isEnabled) {
195  if (m_mcastConfig.linkType != mcastConfig.linkType && !m_mcastFaces.empty()) {
196  NFD_LOG_WARN("Cannot change ad hoc setting on existing faces");
197  }
198  if (m_mcastConfig.group != mcastConfig.group) {
199  NFD_LOG_INFO("changing multicast group from " << m_mcastConfig.group <<
200  " to " << mcastConfig.group);
201  }
202  if (m_mcastConfig.netifPredicate != mcastConfig.netifPredicate) {
203  NFD_LOG_INFO("changing whitelist/blacklist");
204  }
205  }
206 
207  // Even if there's no configuration change, we still need to re-apply configuration because
208  // netifs may have changed.
209  m_mcastConfig = mcastConfig;
210  this->applyMcastConfig(context);
211 }
212 
213 void
215  const FaceCreatedCallback& onCreated,
216  const FaceCreationFailedCallback& onFailure)
217 {
218  BOOST_ASSERT(params.remoteUri.isCanonical());
219 
220  if (params.localUri) {
221  NFD_LOG_TRACE("Cannot create unicast UDP face with LocalUri");
222  onFailure(406, "Unicast UDP faces cannot be created with a LocalUri");
223  return;
224  }
225 
226  if (params.persistency == ndn::nfd::FACE_PERSISTENCY_ON_DEMAND) {
227  NFD_LOG_TRACE("createFace does not support FACE_PERSISTENCY_ON_DEMAND");
228  onFailure(406, "Outgoing UDP faces do not support on-demand persistency");
229  return;
230  }
231 
232  udp::Endpoint endpoint(ndn::ip::addressFromString(params.remoteUri.getHost()),
233  boost::lexical_cast<uint16_t>(params.remoteUri.getPort()));
234 
235  if (endpoint.address().is_multicast()) {
236  NFD_LOG_TRACE("createFace does not support multicast faces");
237  onFailure(406, "Cannot create multicast UDP faces");
238  return;
239  }
240 
241  if (params.wantLocalFields) {
242  // UDP faces are never local
243  NFD_LOG_TRACE("createFace cannot create non-local face with local fields enabled");
244  onFailure(406, "Local fields can only be enabled on faces with local scope");
245  return;
246  }
247 
248  // very simple logic for now
249  for (const auto& i : m_channels) {
250  if ((i.first.address().is_v4() && endpoint.address().is_v4()) ||
251  (i.first.address().is_v6() && endpoint.address().is_v6())) {
252  i.second->connect(endpoint, params.persistency, params.wantLpReliability,
253  onCreated, onFailure);
254  return;
255  }
256  }
257 
258  NFD_LOG_TRACE("No channels available to connect to " << endpoint);
259  onFailure(504, "No channels available to connect");
260 }
261 
262 shared_ptr<UdpChannel>
264  time::nanoseconds idleTimeout)
265 {
266  auto it = m_channels.find(localEndpoint);
267  if (it != m_channels.end())
268  return it->second;
269 
270  if (localEndpoint.address().is_multicast()) {
271  BOOST_THROW_EXCEPTION(Error("createChannel is only for unicast channels. The provided endpoint "
272  "is multicast. Use createMulticastFace to create a multicast face"));
273  }
274 
275  // check if the endpoint is already used by a multicast face
276  if (m_mcastFaces.find(localEndpoint) != m_mcastFaces.end()) {
277  BOOST_THROW_EXCEPTION(Error("Cannot create the requested UDP unicast channel, local "
278  "endpoint is already allocated for a UDP multicast face"));
279  }
280 
281  auto channel = std::make_shared<UdpChannel>(localEndpoint, idleTimeout);
282  m_channels[localEndpoint] = channel;
283 
284  return channel;
285 }
286 
287 std::vector<shared_ptr<const Channel>>
289 {
290  return getChannelsFromMap(m_channels);
291 }
292 
293 shared_ptr<Face>
295  const udp::Endpoint& multicastEndpoint,
296  const std::string& networkInterfaceName)
297 {
298  // checking if the local and multicast endpoints are already in use for a multicast face
299  auto it = m_mcastFaces.find(localEndpoint);
300  if (it != m_mcastFaces.end()) {
301  if (it->second->getRemoteUri() == FaceUri(multicastEndpoint))
302  return it->second;
303  else
304  BOOST_THROW_EXCEPTION(Error("Cannot create the requested UDP multicast face, local "
305  "endpoint is already allocated for a UDP multicast face "
306  "on a different multicast group"));
307  }
308 
309  // checking if the local endpoint is already in use for a unicast channel
310  if (m_channels.find(localEndpoint) != m_channels.end()) {
311  BOOST_THROW_EXCEPTION(Error("Cannot create the requested UDP multicast face, local "
312  "endpoint is already allocated for a UDP unicast channel"));
313  }
314 
315  if (localEndpoint.address().is_v6() || multicastEndpoint.address().is_v6()) {
316  BOOST_THROW_EXCEPTION(Error("IPv6 multicast is not supported yet. Please provide an IPv4 "
317  "address"));
318  }
319 
320  if (localEndpoint.port() != multicastEndpoint.port()) {
321  BOOST_THROW_EXCEPTION(Error("Cannot create the requested UDP multicast face, "
322  "both endpoints should have the same port number. "));
323  }
324 
325  if (!multicastEndpoint.address().is_multicast()) {
326  BOOST_THROW_EXCEPTION(Error("Cannot create the requested UDP multicast face, "
327  "the multicast group given as input is not a multicast address"));
328  }
329 
330  ip::udp::socket receiveSocket(getGlobalIoService());
331  receiveSocket.open(multicastEndpoint.protocol());
332  receiveSocket.set_option(ip::udp::socket::reuse_address(true));
333  receiveSocket.bind(multicastEndpoint);
334 
335  ip::udp::socket sendSocket(getGlobalIoService());
336  sendSocket.open(multicastEndpoint.protocol());
337  sendSocket.set_option(ip::udp::socket::reuse_address(true));
338  sendSocket.set_option(ip::multicast::enable_loopback(false));
339  sendSocket.bind(udp::Endpoint(ip::address_v4::any(), multicastEndpoint.port()));
340  if (localEndpoint.address() != ip::address_v4::any())
341  sendSocket.set_option(ip::multicast::outbound_interface(localEndpoint.address().to_v4()));
342 
343  sendSocket.set_option(ip::multicast::join_group(multicastEndpoint.address().to_v4(),
344  localEndpoint.address().to_v4()));
345  receiveSocket.set_option(ip::multicast::join_group(multicastEndpoint.address().to_v4(),
346  localEndpoint.address().to_v4()));
347 
348 #ifdef __linux__
349  // On Linux, if there is more than one multicast UDP face for the same multicast
350  // group but they are bound to different network interfaces, the socket needs
351  // to be bound to the specific interface using SO_BINDTODEVICE, otherwise the
352  // face will receive all packets sent to the other interfaces as well.
353  // This happens only on Linux. On macOS, the ip::multicast::join_group option
354  // is enough to get the desired behaviour.
355  if (!networkInterfaceName.empty()) {
356  if (::setsockopt(receiveSocket.native_handle(), SOL_SOCKET, SO_BINDTODEVICE,
357  networkInterfaceName.c_str(), networkInterfaceName.size() + 1) < 0) {
358  BOOST_THROW_EXCEPTION(Error("Cannot bind multicast face to " + networkInterfaceName +
359  ": " + std::strerror(errno)));
360  }
361  }
362 #endif // __linux__
363 
364  auto linkService = make_unique<GenericLinkService>();
365  auto transport = make_unique<MulticastUdpTransport>(localEndpoint, multicastEndpoint,
366  std::move(receiveSocket),
367  std::move(sendSocket),
368  m_mcastConfig.linkType);
369  auto face = make_shared<Face>(std::move(linkService), std::move(transport));
370 
371  m_mcastFaces[localEndpoint] = face;
372  connectFaceClosedSignal(*face, [this, localEndpoint] { m_mcastFaces.erase(localEndpoint); });
373 
374  return face;
375 }
376 
377 shared_ptr<Face>
378 UdpFactory::createMulticastFace(const std::string& localIp,
379  const std::string& multicastIp,
380  const std::string& multicastPort,
381  const std::string& networkInterfaceName)
382 {
383  udp::Endpoint localEndpoint(ndn::ip::addressFromString(localIp),
384  boost::lexical_cast<uint16_t>(multicastPort));
385  udp::Endpoint multicastEndpoint(ndn::ip::addressFromString(multicastIp),
386  boost::lexical_cast<uint16_t>(multicastPort));
387  return createMulticastFace(localEndpoint, multicastEndpoint, networkInterfaceName);
388 }
389 
390 static ndn::optional<ndn::net::NetworkAddress>
391 getV4Address(const ndn::net::NetworkInterface& netif)
392 {
393  using namespace ndn::net;
394  for (const NetworkAddress& na : netif.getNetworkAddresses()) {
395  if (na.getFamily() == AddressFamily::V4 && na.getScope() != AddressScope::NOWHERE) {
396  return na;
397  }
398  }
399  return ndn::nullopt;
400 }
401 
402 shared_ptr<Face>
403 UdpFactory::applyMcastConfigToNetif(const shared_ptr<const ndn::net::NetworkInterface>& netif)
404 {
405  if (!m_mcastConfig.isEnabled) {
406  return nullptr;
407  }
408 
409  if (!netif->isUp()) {
410  NFD_LOG_DEBUG("Not creating multicast face on " << netif->getName() << ": netif is down");
411  return nullptr;
412  }
413 
414  if (!netif->canMulticast()) {
415  NFD_LOG_DEBUG("Not creating multicast face on " << netif->getName() << ": netif cannot multicast");
416  return nullptr;
417  }
418 
419  auto address = getV4Address(*netif);
420  if (!address) {
421  NFD_LOG_DEBUG("Not creating multicast face on " << netif->getName() << ": no IPv4 address");
422  // keep an eye on new addresses
423  m_netifConns[netif->getIndex()].addrAddConn =
424  netif->onAddressAdded.connectSingleShot(bind(&UdpFactory::applyMcastConfigToNetif, this, netif));
425  return nullptr;
426  }
427 
428  if (!m_mcastConfig.netifPredicate(*netif)) {
429  NFD_LOG_DEBUG("Not creating multicast face on " << netif->getName() << ": rejected by whitelist/blacklist");
430  return nullptr;
431  }
432 
433  NFD_LOG_DEBUG("Creating multicast face on " << netif->getName());
434  udp::Endpoint localEndpoint(address->getIp(), m_mcastConfig.group.port());
435  auto face = this->createMulticastFace(localEndpoint, m_mcastConfig.group, netif->getName());
436  // ifname is only used on Linux. It is not required if there is only one multicast-capable netif,
437  // but it is always supplied because a new netif can be added at anytime.
438 
439  if (face->getId() == INVALID_FACEID) {
440  // new face: register with forwarding
441  this->addFace(face);
442  }
443  return face;
444 }
445 
446 void
447 UdpFactory::applyMcastConfig(const FaceSystem::ConfigContext& context)
448 {
449  // collect old faces
450  std::set<shared_ptr<Face>> oldFaces;
451  boost::copy(m_mcastFaces | boost::adaptors::map_values, std::inserter(oldFaces, oldFaces.end()));
452 
453  // create faces if requested by config
454  for (const auto& netif : netmon->listNetworkInterfaces()) {
455  auto face = this->applyMcastConfigToNetif(netif);
456  if (face != nullptr) {
457  // don't destroy face
458  oldFaces.erase(face);
459  }
460  }
461 
462  // destroy old faces that are not needed in new configuration
463  for (const auto& face : oldFaces) {
464  face->close();
465  }
466 }
467 
468 } // namespace face
469 } // namespace nfd
void createFace(const CreateFaceParams &params, const FaceCreatedCallback &onCreated, const FaceCreationFailedCallback &onFailure) override
Try to create face using the supplied parameters.
UdpFactory(const CtorParams &params)
Definition: udp-factory.cpp:56
FaceCreatedCallback addFace
callback when a new face is created
#define NFD_LOG_DEBUG(expression)
Definition: logger.hpp:161
std::set< std::string > providedSchemes
FaceUri schemes provided by this ProtocolFactory.
std::vector< shared_ptr< const Channel > > getChannels() const override
static bool parseYesNo(const ConfigSection &node, const std::string &key, const std::string &sectionName)
parse a config option that can be either "yes" or "no"
Definition: config-file.cpp:59
shared_ptr< ndn::net::NetworkMonitor > netmon
NetworkMonitor for listing available network interfaces and monitoring their changes.
shared_ptr< UdpChannel > createChannel(const udp::Endpoint &localEndpoint, time::nanoseconds idleTimeout)
Create UDP-based channel using udp::Endpoint.
#define NFD_REGISTER_PROTOCOL_FACTORY(PF)
registers a protocol factory
Exception of UdpFactory.
Definition: udp-factory.hpp:46
void connectFaceClosedSignal(Face &face, const std::function< void()> &f)
invokes a callback when the face is closed
Definition: channel.cpp:40
#define NFD_LOG_WARN(expression)
Definition: logger.hpp:163
context for processing a config section in ProtocolFactory
Definition: face-system.hpp:85
static std::vector< shared_ptr< const Channel > > getChannelsFromMap(const ChannelMap &channelMap)
#define NFD_LOG_INFO(expression)
Definition: logger.hpp:162
Copyright (c) 2014-2015, Regents of the University of California, Arizona Board of Regents...
Definition: algorithm.hpp:32
boost::optional< const ConfigSection & > OptionalConfigSection
an optional config file section
Definition: config-file.hpp:41
boost::property_tree::ptree ConfigSection
a config file section
Definition: config-file.hpp:37
function< void(uint32_t status, const std::string &reason)> FaceCreationFailedCallback
Prototype for the callback that is invoked when a face fails to be created.
Definition: channel.hpp:44
boost::asio::ip::udp::endpoint Endpoint
Provides support for an underlying protocol.
static ndn::optional< ndn::net::NetworkAddress > getV4Address(const ndn::net::NetworkInterface &netif)
void processConfig(OptionalConfigSection configSection, FaceSystem::ConfigContext &context) override
process face_system.udp config section
Definition: udp-factory.cpp:63
Parameters to ProtocolFactory constructor.
static const std::string & getId()
Definition: udp-factory.cpp:50
#define NFD_LOG_INIT(name)
Definition: logger.hpp:122
#define NFD_LOG_TRACE(expression)
Definition: logger.hpp:160
Parameters to ProtocolFactory::createFace.
const FaceId INVALID_FACEID
indicates an invalid FaceId
Definition: face.hpp:42
shared_ptr< Face > createMulticastFace(const udp::Endpoint &localEndpoint, const udp::Endpoint &multicastEndpoint, const std::string &networkInterfaceName="")
Create multicast UDP face using udp::Endpoint.
function< void(const shared_ptr< Face > &newFace)> FaceCreatedCallback
Prototype for the callback that is invoked when a face is created (in response to an incoming connect...
Definition: channel.hpp:35
boost::asio::io_service & getGlobalIoService()
Definition: global-io.cpp:41