face: configuration option to make Ethernet face "ad hoc"

Change-Id: I55aeb2ca38ec34365b2d5d951b9ca2d7b05f46b2
Refs: #4018, #3967
diff --git a/daemon/face/ethernet-factory.cpp b/daemon/face/ethernet-factory.cpp
index 8d3e737..7263118 100644
--- a/daemon/face/ethernet-factory.cpp
+++ b/daemon/face/ethernet-factory.cpp
@@ -52,6 +52,7 @@
   // {
   //   mcast yes
   //   mcast_group 01:00:5E:00:17:AA
+  //   mcast_ad_hoc no
   //   whitelist
   //   {
   //     *
@@ -86,6 +87,10 @@
                                 valueStr + "' is not a multicast address"));
         }
       }
+      else if (key == "mcast_ad_hoc") {
+        bool wantAdHoc = ConfigFile::parseYesNo(pair, "ether");
+        mcastConfig.linkType = wantAdHoc ? ndn::nfd::LINK_TYPE_AD_HOC : ndn::nfd::LINK_TYPE_MULTI_ACCESS;
+      }
       else if (key == "whitelist") {
         mcastConfig.netifPredicate.parseWhitelist(value);
       }
@@ -107,18 +112,21 @@
         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.
+    else if (mcastConfig.isEnabled) {
+      if (m_mcastConfig.linkType != mcastConfig.linkType && !m_mcastFaces.empty()) {
+        NFD_LOG_WARN("Cannot change ad hoc setting on existing faces");
+      }
+      if (m_mcastConfig.group != mcastConfig.group) {
+        NFD_LOG_INFO("changing multicast group from " << m_mcastConfig.group <<
+                     " to " << mcastConfig.group);
+      }
+      if (m_mcastConfig.netifPredicate != mcastConfig.netifPredicate) {
+        NFD_LOG_INFO("changing whitelist/blacklist");
+      }
     }
 
+    // Even if there's no configuration change, we still need to re-apply configuration because
+    // netifs may have changed.
     m_mcastConfig = mcastConfig;
     this->applyConfig(context);
   }
@@ -157,7 +165,7 @@
   opts.allowReassembly = true;
 
   auto linkService = make_unique<face::GenericLinkService>(opts);
-  auto transport = make_unique<face::EthernetTransport>(netif, address);
+  auto transport = make_unique<face::EthernetTransport>(netif, address, m_mcastConfig.linkType);
   auto face = make_shared<Face>(std::move(linkService), std::move(transport));
 
   m_mcastFaces[key] = face;
diff --git a/daemon/face/ethernet-factory.hpp b/daemon/face/ethernet-factory.hpp
index d1b9ee2..9facd71 100644
--- a/daemon/face/ethernet-factory.hpp
+++ b/daemon/face/ethernet-factory.hpp
@@ -80,6 +80,7 @@
   {
     bool isEnabled = false;
     ethernet::Address group = ethernet::getDefaultMulticastAddress();
+    ndn::nfd::LinkType linkType = ndn::nfd::LINK_TYPE_MULTI_ACCESS;
     NetworkInterfacePredicate netifPredicate;
   };
 
diff --git a/daemon/face/ethernet-transport.cpp b/daemon/face/ethernet-transport.cpp
index f47f1b1..4b039c6 100644
--- a/daemon/face/ethernet-transport.cpp
+++ b/daemon/face/ethernet-transport.cpp
@@ -62,7 +62,8 @@
 NFD_LOG_INIT("EthernetTransport");
 
 EthernetTransport::EthernetTransport(const NetworkInterfaceInfo& interface,
-                                     const ethernet::Address& mcastAddress)
+                                     const ethernet::Address& mcastAddress,
+                                     ndn::nfd::LinkType linkType)
   : m_pcap(nullptr, pcap_close)
   , m_socket(getGlobalIoService())
   , m_srcAddress(interface.etherAddress)
@@ -79,7 +80,7 @@
   this->setRemoteUri(FaceUri(mcastAddress));
   this->setScope(ndn::nfd::FACE_SCOPE_NON_LOCAL);
   this->setPersistency(ndn::nfd::FACE_PERSISTENCY_PERMANENT);
-  this->setLinkType(ndn::nfd::LINK_TYPE_MULTI_ACCESS);
+  this->setLinkType(linkType);
 
   NFD_LOG_FACE_INFO("Creating transport");
 
diff --git a/daemon/face/ethernet-transport.hpp b/daemon/face/ethernet-transport.hpp
index d89e280..0777d74 100644
--- a/daemon/face/ethernet-transport.hpp
+++ b/daemon/face/ethernet-transport.hpp
@@ -62,7 +62,8 @@
    * @brief Creates an Ethernet-based transport for multicast communication
    */
   EthernetTransport(const NetworkInterfaceInfo& interface,
-                    const ethernet::Address& mcastAddress);
+                    const ethernet::Address& mcastAddress,
+                    ndn::nfd::LinkType linkType);
 
 protected:
   void
diff --git a/nfd.conf.sample.in b/nfd.conf.sample.in
index 8d6f5d1..b763e0c 100644
--- a/nfd.conf.sample.in
+++ b/nfd.conf.sample.in
@@ -173,6 +173,7 @@
   @IF_HAVE_LIBPCAP@
   @IF_HAVE_LIBPCAP@  mcast yes ; set to 'no' to disable Ethernet multicast, default 'yes'
   @IF_HAVE_LIBPCAP@  mcast_group 01:00:5E:00:17:AA ; Ethernet multicast group
+  @IF_HAVE_LIBPCAP@  mcast_ad_hoc no ; set to 'yes' to make all Ethernet faces 'ad hoc', default 'no'
   @IF_HAVE_LIBPCAP@
   @IF_HAVE_LIBPCAP@  ; Whitelist and blacklist can contain, in no particular order:
   @IF_HAVE_LIBPCAP@  ; interface names, including wildcard patterns (e.g., 'ifname eth0', 'ifname en*', 'ifname wlp?s0'),
diff --git a/tests/daemon/face/ethernet-factory.t.cpp b/tests/daemon/face/ethernet-factory.t.cpp
index 62748cd..56373d0 100644
--- a/tests/daemon/face/ethernet-factory.t.cpp
+++ b/tests/daemon/face/ethernet-factory.t.cpp
@@ -48,15 +48,15 @@
 {
 public:
   std::vector<const Face*>
-  listEtherMcastFaces() const
+  listEtherMcastFaces(ndn::nfd::LinkType linkType = ndn::nfd::LINK_TYPE_MULTI_ACCESS) const
   {
-    return this->listFacesByScheme("ether", ndn::nfd::LINK_TYPE_MULTI_ACCESS);
+    return this->listFacesByScheme("ether", linkType);
   }
 
   size_t
-  countEtherMcastFaces() const
+  countEtherMcastFaces(ndn::nfd::LinkType linkType = ndn::nfd::LINK_TYPE_MULTI_ACCESS) const
   {
-    return this->listEtherMcastFaces().size();
+    return this->listEtherMcastFaces(linkType).size();
   }
 };
 
@@ -73,6 +73,7 @@
       {
         mcast yes
         mcast_group 01:00:5E:00:17:AA
+        mcast_ad_hoc no
         whitelist
         {
           *
@@ -90,18 +91,94 @@
   BOOST_CHECK_EQUAL(this->countEtherMcastFaces(), netifs.size());
 }
 
-BOOST_AUTO_TEST_CASE(Omitted)
+BOOST_AUTO_TEST_CASE(EnableDisableMcast)
 {
-  const std::string CONFIG = R"CONFIG(
+  const std::string CONFIG_WITH_MCAST = R"CONFIG(
     face_system
     {
+      ether
+      {
+        mcast yes
+      }
+    }
+  )CONFIG";
+  const std::string CONFIG_WITHOUT_MCAST = R"CONFIG(
+    face_system
+    {
+      ether
+      {
+        mcast no
+      }
     }
   )CONFIG";
 
-  parseConfig(CONFIG, true);
-  parseConfig(CONFIG, false);
-
+  parseConfig(CONFIG_WITHOUT_MCAST, false);
   BOOST_CHECK_EQUAL(this->countEtherMcastFaces(), 0);
+
+  SKIP_IF_ETHERNET_NETIF_COUNT_LT(1);
+
+  parseConfig(CONFIG_WITH_MCAST, false);
+  g_io.poll();
+  BOOST_CHECK_EQUAL(this->countEtherMcastFaces(), netifs.size());
+
+  parseConfig(CONFIG_WITHOUT_MCAST, false);
+  g_io.poll();
+  BOOST_CHECK_EQUAL(this->countEtherMcastFaces(), 0);
+}
+
+BOOST_AUTO_TEST_CASE(McastAdHoc)
+{
+  SKIP_IF_ETHERNET_NETIF_COUNT_LT(1);
+
+  const std::string CONFIG = R"CONFIG(
+    face_system
+    {
+      ether
+      {
+        mcast_ad_hoc yes
+      }
+    }
+  )CONFIG";
+
+  parseConfig(CONFIG, false);
+  BOOST_CHECK_EQUAL(this->countEtherMcastFaces(ndn::nfd::LINK_TYPE_AD_HOC), netifs.size());
+}
+
+BOOST_AUTO_TEST_CASE(ChangeMcastGroup)
+{
+  SKIP_IF_ETHERNET_NETIF_COUNT_LT(1);
+
+  const std::string CONFIG1 = R"CONFIG(
+    face_system
+    {
+      ether
+      {
+        mcast_group 01:00:00:00:00:01
+      }
+    }
+  )CONFIG";
+  const std::string CONFIG2 = R"CONFIG(
+    face_system
+    {
+      ether
+      {
+        mcast_group 01:00:00:00:00:02
+      }
+    }
+  )CONFIG";
+
+  parseConfig(CONFIG1, false);
+  auto etherMcastFaces = this->listEtherMcastFaces();
+  BOOST_REQUIRE_EQUAL(etherMcastFaces.size(), netifs.size());
+  BOOST_CHECK_EQUAL(etherMcastFaces.front()->getRemoteUri(),
+                    FaceUri(ethernet::Address(0x01, 0x00, 0x00, 0x00, 0x00, 0x01)));
+
+  parseConfig(CONFIG2, false);
+  g_io.poll();
+  etherMcastFaces = this->listEtherMcastFaces();
+  BOOST_REQUIRE_EQUAL(etherMcastFaces.size(), netifs.size());
+  BOOST_CHECK_EQUAL(etherMcastFaces.front()->getRemoteUri(),
+                    FaceUri(ethernet::Address(0x01, 0x00, 0x00, 0x00, 0x00, 0x02)));
 }
 
 BOOST_AUTO_TEST_CASE(Whitelist)
@@ -154,76 +231,18 @@
   }), 0);
 }
 
-BOOST_AUTO_TEST_CASE(EnableDisableMcast)
+BOOST_AUTO_TEST_CASE(Omitted)
 {
-  const std::string CONFIG_WITH_MCAST = R"CONFIG(
+  const std::string CONFIG = R"CONFIG(
     face_system
     {
-      ether
-      {
-        mcast yes
-      }
-    }
-  )CONFIG";
-  const std::string CONFIG_WITHOUT_MCAST = R"CONFIG(
-    face_system
-    {
-      ether
-      {
-        mcast no
-      }
     }
   )CONFIG";
 
-  parseConfig(CONFIG_WITHOUT_MCAST, false);
+  parseConfig(CONFIG, true);
+  parseConfig(CONFIG, false);
+
   BOOST_CHECK_EQUAL(this->countEtherMcastFaces(), 0);
-
-  SKIP_IF_ETHERNET_NETIF_COUNT_LT(1);
-
-  parseConfig(CONFIG_WITH_MCAST, false);
-  g_io.poll();
-  BOOST_CHECK_EQUAL(this->countEtherMcastFaces(), netifs.size());
-
-  parseConfig(CONFIG_WITHOUT_MCAST, false);
-  g_io.poll();
-  BOOST_CHECK_EQUAL(this->countEtherMcastFaces(), 0);
-}
-
-BOOST_AUTO_TEST_CASE(ChangeMcastGroup)
-{
-  SKIP_IF_ETHERNET_NETIF_COUNT_LT(1);
-
-  const std::string CONFIG1 = R"CONFIG(
-    face_system
-    {
-      ether
-      {
-        mcast_group 01:00:00:00:00:01
-      }
-    }
-  )CONFIG";
-  const std::string CONFIG2 = R"CONFIG(
-    face_system
-    {
-      ether
-      {
-        mcast_group 01:00:00:00:00:02
-      }
-    }
-  )CONFIG";
-
-  parseConfig(CONFIG1, false);
-  auto etherMcastFaces = this->listEtherMcastFaces();
-  BOOST_REQUIRE_EQUAL(etherMcastFaces.size(), netifs.size());
-  BOOST_CHECK_EQUAL(etherMcastFaces.front()->getRemoteUri(),
-                    FaceUri(ethernet::Address(0x01, 0x00, 0x00, 0x00, 0x00, 0x01)));
-
-  parseConfig(CONFIG2, false);
-  g_io.poll();
-  etherMcastFaces = this->listEtherMcastFaces();
-  BOOST_REQUIRE_EQUAL(etherMcastFaces.size(), netifs.size());
-  BOOST_CHECK_EQUAL(etherMcastFaces.front()->getRemoteUri(),
-                    FaceUri(ethernet::Address(0x01, 0x00, 0x00, 0x00, 0x00, 0x02)));
 }
 
 BOOST_AUTO_TEST_CASE(BadMcast)
diff --git a/tests/daemon/face/ethernet-fixture.hpp b/tests/daemon/face/ethernet-fixture.hpp
index 4228f79..5769637 100644
--- a/tests/daemon/face/ethernet-fixture.hpp
+++ b/tests/daemon/face/ethernet-fixture.hpp
@@ -43,7 +43,7 @@
     for (const auto& netif : listNetworkInterfaces()) {
       if (!netif.isLoopback() && netif.isUp()) {
         try {
-          EthernetTransport transport(netif, ethernet::getBroadcastAddress());
+          EthernetTransport transport(netif, ethernet::getBroadcastAddress(), ndn::nfd::LINK_TYPE_MULTI_ACCESS);
           netifs.push_back(netif);
         }
         catch (const EthernetTransport::Error&) {
diff --git a/tests/daemon/face/ethernet-transport.t.cpp b/tests/daemon/face/ethernet-transport.t.cpp
index cd46a04..c0d5138 100644
--- a/tests/daemon/face/ethernet-transport.t.cpp
+++ b/tests/daemon/face/ethernet-transport.t.cpp
@@ -42,7 +42,9 @@
   SKIP_IF_ETHERNET_NETIF_COUNT_LT(1);
 
   auto netif = netifs.front();
-  EthernetTransport transport(netif, ethernet::getDefaultMulticastAddress());
+  EthernetTransport transport(netif,
+                              ethernet::getDefaultMulticastAddress(),
+                              ndn::nfd::LINK_TYPE_MULTI_ACCESS);
   checkStaticPropertiesInitialized(transport);
 
   BOOST_CHECK_EQUAL(transport.getLocalUri(), FaceUri::fromDev(netif.name));
@@ -55,7 +57,9 @@
 BOOST_AUTO_TEST_CASE(PersistencyChange)
 {
   SKIP_IF_ETHERNET_NETIF_COUNT_LT(1);
-  EthernetTransport transport(netifs.front(), ethernet::getDefaultMulticastAddress());
+  EthernetTransport transport(netifs.front(),
+                              ethernet::getDefaultMulticastAddress(),
+                              ndn::nfd::LINK_TYPE_MULTI_ACCESS);
 
   BOOST_CHECK_EQUAL(transport.canChangePersistencyTo(ndn::nfd::FACE_PERSISTENCY_ON_DEMAND), false);
   BOOST_CHECK_EQUAL(transport.canChangePersistencyTo(ndn::nfd::FACE_PERSISTENCY_PERSISTENT), false);