face: process face_system.udp config section in UdpFactory

refs #3904

Change-Id: I8edf69c152f7c164cbab2b482d6b138cbf89d3e2
diff --git a/daemon/face/udp-factory.cpp b/daemon/face/udp-factory.cpp
index 88ac221..c9b4ac4 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-2016,  Regents of the University of California,
+ * Copyright (c) 2014-2017,  Regents of the University of California,
  *                           Arizona Board of Regents,
  *                           Colorado State University,
  *                           University Pierre & Marie Curie, Sorbonne University,
@@ -27,21 +27,199 @@
 #include "generic-link-service.hpp"
 #include "multicast-udp-transport.hpp"
 #include "core/global-io.hpp"
-#include "core/network-interface.hpp"
+#include <boost/range/adaptors.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
+#endif // __linux__
 
 namespace nfd {
+namespace face {
 
 namespace ip = boost::asio::ip;
 
 NFD_LOG_INIT("UdpFactory");
 
 void
+UdpFactory::processConfig(OptionalConfigSection configSection,
+                          FaceSystem::ConfigContext& context)
+{
+  // udp
+  // {
+  //   port 6363
+  //   enable_v4 yes
+  //   enable_v6 yes
+  //   idle_timeout 600
+  //   keep_alive_interval 25 ; acceptable but ignored
+  //   mcast yes
+  //   mcast_group 224.0.23.170
+  //   mcast_port 56363
+  // }
+
+  uint16_t port = 6363;
+  bool enableV4 = false;
+  bool enableV6 = false;
+  uint32_t idleTimeout = 600;
+  MulticastConfig mcastConfig;
+
+  if (configSection) {
+    // These default to 'yes' but only if face_system.udp section is present
+    enableV4 = enableV6 = mcastConfig.isEnabled = true;
+
+    for (const auto& pair : *configSection) {
+      const std::string& key = pair.first;
+      const ConfigSection& value = pair.second;
+
+      if (key == "port") {
+        port = ConfigFile::parseNumber<uint16_t>(pair, "face_system.udp");
+      }
+      else if (key == "enable_v4") {
+        enableV4 = ConfigFile::parseYesNo(pair, "face_system.udp");
+      }
+      else if (key == "enable_v6") {
+        enableV6 = ConfigFile::parseYesNo(pair, "face_system.udp");
+      }
+      else if (key == "idle_timeout") {
+        idleTimeout = ConfigFile::parseNumber<uint32_t>(pair, "face_system.udp");
+      }
+      else if (key == "keep_alive_interval") {
+        // ignored
+      }
+      else if (key == "mcast") {
+        mcastConfig.isEnabled = ConfigFile::parseYesNo(pair, "face_system.udp");
+      }
+      else if (key == "mcast_group") {
+        const std::string& valueStr = value.get_value<std::string>();
+        boost::system::error_code ec;
+        mcastConfig.group.address(boost::asio::ip::address_v4::from_string(valueStr, ec));
+        if (ec) {
+          BOOST_THROW_EXCEPTION(ConfigFile::Error("face_system.udp.mcast_group: '" +
+                                valueStr + "' cannot be parsed as an IPv4 address"));
+        }
+        else if (!mcastConfig.group.address().is_multicast()) {
+          BOOST_THROW_EXCEPTION(ConfigFile::Error("face_system.udp.mcast_group: '" +
+                                valueStr + "' is not a multicast address"));
+        }
+      }
+      else if (key == "mcast_port") {
+        mcastConfig.group.port(ConfigFile::parseNumber<uint16_t>(pair, "face_system.udp"));
+      }
+      else {
+        BOOST_THROW_EXCEPTION(ConfigFile::Error("Unrecognized option face_system.udp." + key));
+      }
+    }
+
+    if (!enableV4 && !enableV6 && !mcastConfig.isEnabled) {
+      BOOST_THROW_EXCEPTION(ConfigFile::Error(
+        "IPv4 and IPv6 UDP channels and UDP multicast have been disabled. "
+        "Remove face_system.udp section to disable UDP channels or enable at least one of them."));
+    }
+  }
+
+  if (!context.isDryRun) {
+    if (enableV4) {
+      udp::Endpoint endpoint(ip::udp::v4(), port);
+      shared_ptr<UdpChannel> v4Channel = this->createChannel(endpoint, time::seconds(idleTimeout));
+      if (!v4Channel->isListening()) {
+        v4Channel->listen(context.addFace, nullptr);
+      }
+      providedSchemes.insert("udp");
+      providedSchemes.insert("udp4");
+    }
+    else if (providedSchemes.count("udp4") > 0) {
+      NFD_LOG_WARN("Cannot close udp4 channel after its creation");
+    }
+
+    if (enableV6) {
+      udp::Endpoint endpoint(ip::udp::v6(), port);
+      shared_ptr<UdpChannel> v6Channel = this->createChannel(endpoint, time::seconds(idleTimeout));
+      if (!v6Channel->isListening()) {
+        v6Channel->listen(context.addFace, nullptr);
+      }
+      providedSchemes.insert("udp");
+      providedSchemes.insert("udp6");
+    }
+    else if (providedSchemes.count("udp6") > 0) {
+      NFD_LOG_WARN("Cannot close udp6 channel after its creation");
+    }
+
+    if (m_mcastConfig.isEnabled != mcastConfig.isEnabled) {
+      if (mcastConfig.isEnabled) {
+        NFD_LOG_INFO("enabling multicast on " << mcastConfig.group);
+      }
+      else {
+        NFD_LOG_INFO("disabling multicast");
+      }
+    }
+    else if (m_mcastConfig.group != mcastConfig.group) {
+      NFD_LOG_INFO("changing multicast group from " << m_mcastConfig.group <<
+                   " to " << mcastConfig.group);
+    }
+    else {
+      // There's no configuration change, but we still need to re-apply configuration because
+      // netifs may have changed.
+    }
+
+    m_mcastConfig = mcastConfig;
+    this->applyMulticastConfig(context);
+  }
+}
+
+void
+UdpFactory::createFace(const FaceUri& uri,
+                       ndn::nfd::FacePersistency persistency,
+                       bool wantLocalFieldsEnabled,
+                       const FaceCreatedCallback& onCreated,
+                       const FaceCreationFailedCallback& onFailure)
+{
+  BOOST_ASSERT(uri.isCanonical());
+
+  if (persistency == ndn::nfd::FACE_PERSISTENCY_ON_DEMAND) {
+    NFD_LOG_TRACE("createFace does not support FACE_PERSISTENCY_ON_DEMAND");
+    onFailure(406, "Outgoing unicast UDP faces do not support on-demand persistency");
+    return;
+  }
+
+  udp::Endpoint endpoint(ip::address::from_string(uri.getHost()),
+                         boost::lexical_cast<uint16_t>(uri.getPort()));
+
+  if (endpoint.address().is_multicast()) {
+    NFD_LOG_TRACE("createFace does not support multicast faces");
+    onFailure(406, "Cannot create multicast UDP faces");
+    return;
+  }
+
+  if (m_prohibitedEndpoints.find(endpoint) != m_prohibitedEndpoints.end()) {
+    NFD_LOG_TRACE("Requested endpoint is prohibited "
+                  "(reserved by this NFD or disallowed by face management protocol)");
+    onFailure(406, "Requested endpoint is prohibited");
+    return;
+  }
+
+  if (wantLocalFieldsEnabled) {
+    // UDP faces are never local
+    NFD_LOG_TRACE("createFace cannot create non-local face with local fields enabled");
+    onFailure(406, "Local fields can only be enabled on faces with local scope");
+    return;
+  }
+
+  // very simple logic for now
+  for (const auto& i : m_channels) {
+    if ((i.first.address().is_v4() && endpoint.address().is_v4()) ||
+        (i.first.address().is_v6() && endpoint.address().is_v6())) {
+      i.second->connect(endpoint, persistency, onCreated, onFailure);
+      return;
+    }
+  }
+
+  NFD_LOG_TRACE("No channels available to connect to " + boost::lexical_cast<std::string>(endpoint));
+  onFailure(504, "No channels available to connect");
+}
+
+void
 UdpFactory::prohibitEndpoint(const udp::Endpoint& endpoint)
 {
   if (endpoint.address().is_v4() &&
@@ -126,6 +304,22 @@
   return createChannel(endpoint, timeout);
 }
 
+std::vector<shared_ptr<const Channel>>
+UdpFactory::getChannels() const
+{
+  return getChannelsFromMap(m_channels);
+}
+
+shared_ptr<UdpChannel>
+UdpFactory::findChannel(const udp::Endpoint& localEndpoint) const
+{
+  auto i = m_channels.find(localEndpoint);
+  if (i != m_channels.end())
+    return i->second;
+  else
+    return nullptr;
+}
+
 shared_ptr<Face>
 UdpFactory::createMulticastFace(const udp::Endpoint& localEndpoint,
                                 const udp::Endpoint& multicastEndpoint,
@@ -203,16 +397,16 @@
                                   ": " + std::strerror(errno)));
     }
   }
-#endif
+#endif // __linux__
 
-  auto linkService = make_unique<face::GenericLinkService>();
-  auto transport = make_unique<face::MulticastUdpTransport>(localEndpoint, multicastEndpoint,
-                                                            std::move(receiveSocket),
-                                                            std::move(sendSocket));
+  auto linkService = make_unique<GenericLinkService>();
+  auto transport = make_unique<MulticastUdpTransport>(localEndpoint, multicastEndpoint,
+                                                      std::move(receiveSocket),
+                                                      std::move(sendSocket));
   face = make_shared<Face>(std::move(linkService), std::move(transport));
 
-  m_multicastFaces[localEndpoint] = face;
-  connectFaceClosedSignal(*face, [this, localEndpoint] { m_multicastFaces.erase(localEndpoint); });
+  m_mcastFaces[localEndpoint] = face;
+  connectFaceClosedSignal(*face, [this, localEndpoint] { m_mcastFaces.erase(localEndpoint); });
 
   return face;
 }
@@ -230,87 +424,63 @@
   return createMulticastFace(localEndpoint, multicastEndpoint, networkInterfaceName);
 }
 
-void
-UdpFactory::createFace(const FaceUri& uri,
-                       ndn::nfd::FacePersistency persistency,
-                       bool wantLocalFieldsEnabled,
-                       const FaceCreatedCallback& onCreated,
-                       const FaceCreationFailedCallback& onFailure)
-{
-  BOOST_ASSERT(uri.isCanonical());
-
-  if (persistency == ndn::nfd::FACE_PERSISTENCY_ON_DEMAND) {
-    NFD_LOG_TRACE("createFace does not support FACE_PERSISTENCY_ON_DEMAND");
-    onFailure(406, "Outgoing unicast UDP faces do not support on-demand persistency");
-    return;
-  }
-
-  udp::Endpoint endpoint(ip::address::from_string(uri.getHost()),
-                         boost::lexical_cast<uint16_t>(uri.getPort()));
-
-  if (endpoint.address().is_multicast()) {
-    NFD_LOG_TRACE("createFace does not support multicast faces");
-    onFailure(406, "Cannot create multicast UDP faces");
-    return;
-  }
-
-  if (m_prohibitedEndpoints.find(endpoint) != m_prohibitedEndpoints.end()) {
-    NFD_LOG_TRACE("Requested endpoint is prohibited "
-                  "(reserved by this NFD or disallowed by face management protocol)");
-    onFailure(406, "Requested endpoint is prohibited");
-    return;
-  }
-
-  if (wantLocalFieldsEnabled) {
-    // UDP faces are never local
-    NFD_LOG_TRACE("createFace cannot create non-local face with local fields enabled");
-    onFailure(406, "Local fields can only be enabled on faces with local scope");
-    return;
-  }
-
-  // very simple logic for now
-  for (const auto& i : m_channels) {
-    if ((i.first.address().is_v4() && endpoint.address().is_v4()) ||
-        (i.first.address().is_v6() && endpoint.address().is_v6())) {
-      i.second->connect(endpoint, persistency, onCreated, onFailure);
-      return;
-    }
-  }
-
-  NFD_LOG_TRACE("No channels available to connect to " + boost::lexical_cast<std::string>(endpoint));
-  onFailure(504, "No channels available to connect");
-}
-
-std::vector<shared_ptr<const Channel>>
-UdpFactory::getChannels() const
-{
-  std::vector<shared_ptr<const Channel>> channels;
-  channels.reserve(m_channels.size());
-
-  for (const auto& i : m_channels)
-    channels.push_back(i.second);
-
-  return channels;
-}
-
-shared_ptr<UdpChannel>
-UdpFactory::findChannel(const udp::Endpoint& localEndpoint) const
-{
-  auto i = m_channels.find(localEndpoint);
-  if (i != m_channels.end())
-    return i->second;
-  else
-    return nullptr;
-}
-
 shared_ptr<Face>
 UdpFactory::findMulticastFace(const udp::Endpoint& localEndpoint) const
 {
-  auto i = m_multicastFaces.find(localEndpoint);
-  if (i != m_multicastFaces.end())
+  auto i = m_mcastFaces.find(localEndpoint);
+  if (i != m_mcastFaces.end())
     return i->second;
   else
     return nullptr;
 }
 
+void
+UdpFactory::applyMulticastConfig(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()));
+
+  if (m_mcastConfig.isEnabled) {
+    // determine interfaces on which faces should be created or retained
+    auto capableNetifRange = context.listNetifs() |
+                             boost::adaptors::filtered([this] (const NetworkInterfaceInfo& netif) {
+                               return netif.isUp() && netif.isMulticastCapable() &&
+                                      !netif.ipv4Addresses.empty();
+                             });
+
+    bool needIfname = false;
+#ifdef __linux__
+    std::vector<NetworkInterfaceInfo> capableNetifs;
+    boost::copy(capableNetifRange, std::back_inserter(capableNetifs));
+    // on Linux, ifname is needed to create more than one UDP multicast face on the same group
+    needIfname = capableNetifs.size() > 1;
+#else
+    auto& capableNetifs = capableNetifRange;
+#endif // __linux__
+
+    // create faces
+    for (const auto& netif : capableNetifs) {
+      udp::Endpoint localEndpoint(netif.ipv4Addresses.front(), m_mcastConfig.group.port());
+      shared_ptr<Face> face = this->createMulticastFace(localEndpoint, m_mcastConfig.group,
+                                                        needIfname ? netif.name : "");
+      if (face->getId() == INVALID_FACEID) {
+        // new face: register with forwarding
+        context.addFace(face);
+      }
+      else {
+        // existing face: don't destroy
+        oldFaces.erase(face);
+      }
+    }
+  }
+
+  // destroy old faces that are not needed in new configuration
+  for (const auto& face : oldFaces) {
+    face->close();
+  }
+}
+
+} // namespace face
 } // namespace nfd