face: process face_system.ether config section in EthernetFactory

This commit also fixes a potential memory access error in EthernetTransport.

refs #3904

Change-Id: I08296e7c6f1039b59b2859d277fc95326af34f52
diff --git a/daemon/face/ethernet-factory.cpp b/daemon/face/ethernet-factory.cpp
index cfb4b79..53a0815 100644
--- a/daemon/face/ethernet-factory.cpp
+++ b/daemon/face/ethernet-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,
@@ -26,33 +26,93 @@
 #include "ethernet-factory.hpp"
 #include "ethernet-transport.hpp"
 #include "generic-link-service.hpp"
+#include "core/logger.hpp"
+#include <boost/range/adaptors.hpp>
+#include <boost/range/algorithm/copy.hpp>
 
 namespace nfd {
+namespace face {
 
-shared_ptr<Face>
-EthernetFactory::createMulticastFace(const NetworkInterfaceInfo& interface,
-                                     const ethernet::Address& address)
+NFD_LOG_INIT("EthernetFactory");
+
+void
+EthernetFactory::processConfig(OptionalConfigSection configSection,
+                               FaceSystem::ConfigContext& context)
 {
-  if (!address.isMulticast())
-    BOOST_THROW_EXCEPTION(Error(address.toString() + " is not a multicast address"));
+  // ether
+  // {
+  //   mcast yes
+  //   mcast_group 01:00:5E:00:17:AA
+  //   whitelist
+  //   {
+  //     *
+  //   }
+  //   blacklist
+  //   {
+  //   }
+  // }
 
-  auto face = findMulticastFace(interface.name, address);
-  if (face)
-    return face;
+  MulticastConfig mcastConfig;
 
-  face::GenericLinkService::Options opts;
-  opts.allowFragmentation = true;
-  opts.allowReassembly = true;
+  if (configSection) {
+    // face_system.ether.mcast defaults to 'yes' but only if face_system.ether section is present
+    mcastConfig.isEnabled = true;
 
-  auto linkService = make_unique<face::GenericLinkService>(opts);
-  auto transport = make_unique<face::EthernetTransport>(interface, address);
-  face = make_shared<Face>(std::move(linkService), std::move(transport));
+    for (const auto& pair : *configSection) {
+      const std::string& key = pair.first;
+      const ConfigSection& value = pair.second;
 
-  auto key = std::make_pair(interface.name, address);
-  m_multicastFaces[key] = face;
-  connectFaceClosedSignal(*face, [this, key] { m_multicastFaces.erase(key); });
+      if (key == "mcast") {
+        mcastConfig.isEnabled = ConfigFile::parseYesNo(pair, "ether");
+      }
+      else if (key == "mcast_group") {
+        const std::string& valueStr = value.get_value<std::string>();
+        mcastConfig.group = ethernet::Address::fromString(valueStr);
+        if (mcastConfig.group.isNull()) {
+          BOOST_THROW_EXCEPTION(ConfigFile::Error("face_system.ether.mcast_group: '" +
+                                valueStr + "' cannot be parsed as an Ethernet address"));
+        }
+        else if (!mcastConfig.group.isMulticast()) {
+          BOOST_THROW_EXCEPTION(ConfigFile::Error("face_system.ether.mcast_group: '" +
+                                valueStr + "' is not a multicast address"));
+        }
+      }
+      else if (key == "whitelist") {
+        mcastConfig.netifPredicate.parseWhitelist(value);
+      }
+      else if (key == "blacklist") {
+        mcastConfig.netifPredicate.parseBlacklist(value);
+      }
+      else {
+        BOOST_THROW_EXCEPTION(ConfigFile::Error("Unrecognized option face_system.ether." + key));
+      }
+    }
+  }
 
-  return face;
+  if (!context.isDryRun) {
+    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 if (m_mcastConfig.netifPredicate != mcastConfig.netifPredicate) {
+      NFD_LOG_INFO("changing whitelist/blacklist");
+    }
+    else {
+      // There's no configuration change, but we still need to re-apply configuration because
+      // netifs may have changed.
+    }
+
+    m_mcastConfig = mcastConfig;
+    this->applyConfig(context);
+  }
 }
 
 void
@@ -72,14 +132,75 @@
 }
 
 shared_ptr<Face>
-EthernetFactory::findMulticastFace(const std::string& interfaceName,
-                                   const ethernet::Address& address) const
+EthernetFactory::createMulticastFace(const NetworkInterfaceInfo& netif,
+                                     const ethernet::Address& address)
 {
-  auto i = m_multicastFaces.find({interfaceName, address});
-  if (i != m_multicastFaces.end())
-    return i->second;
-  else
-    return nullptr;
+  BOOST_ASSERT(address.isMulticast());
+
+  auto key = std::make_pair(netif.name, address);
+  auto found = m_mcastFaces.find(key);
+  if (found != m_mcastFaces.end()) {
+    return found->second;
+  }
+
+  face::GenericLinkService::Options opts;
+  opts.allowFragmentation = true;
+  opts.allowReassembly = true;
+
+  auto linkService = make_unique<face::GenericLinkService>(opts);
+  auto transport = make_unique<face::EthernetTransport>(netif, address);
+  auto face = make_shared<Face>(std::move(linkService), std::move(transport));
+
+  m_mcastFaces[key] = face;
+  connectFaceClosedSignal(*face, [this, key] { m_mcastFaces.erase(key); });
+
+  return face;
 }
 
+void
+EthernetFactory::applyConfig(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 capableNetifs = context.listNetifs() |
+                         boost::adaptors::filtered([this] (const NetworkInterfaceInfo& netif) {
+                           return netif.isUp() && netif.isMulticastCapable() &&
+                                  m_mcastConfig.netifPredicate(netif);
+                         });
+
+    // create faces
+    for (const auto& netif : capableNetifs) {
+      shared_ptr<Face> face;
+      try {
+        face = this->createMulticastFace(netif, m_mcastConfig.group);
+      }
+      catch (const EthernetTransport::Error& e) {
+        NFD_LOG_ERROR("Cannot create Ethernet multicast face on " << netif.name << ": " <<
+                      e.what() << ", continuing");
+        continue;
+      }
+
+      if (face->getId() == face::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