face: unicast support in EthernetFactory

Change-Id: I1886a87d79a7194b3320a5417404b17a7290fa5d
Refs: #4012
diff --git a/daemon/face/ethernet-factory.cpp b/daemon/face/ethernet-factory.cpp
index 44282c0..20fda6d 100644
--- a/daemon/face/ethernet-factory.cpp
+++ b/daemon/face/ethernet-factory.cpp
@@ -49,6 +49,8 @@
 {
   // ether
   // {
+  //   listen yes
+  //   idle_timeout 600
   //   mcast yes
   //   mcast_group 01:00:5E:00:17:AA
   //   mcast_ad_hoc no
@@ -61,6 +63,8 @@
   //   }
   // }
 
+  bool wantListen = true;
+  uint32_t idleTimeout = 600;
   MulticastConfig mcastConfig;
 
   if (configSection) {
@@ -71,8 +75,14 @@
       const std::string& key = pair.first;
       const ConfigSection& value = pair.second;
 
-      if (key == "mcast") {
-        mcastConfig.isEnabled = ConfigFile::parseYesNo(pair, "ether");
+      if (key == "listen") {
+        wantListen = ConfigFile::parseYesNo(pair, "face_system.ether");
+      }
+      else if (key == "idle_timeout") {
+        idleTimeout = ConfigFile::parseNumber<uint32_t>(pair, "face_system.ether");
+      }
+      else if (key == "mcast") {
+        mcastConfig.isEnabled = ConfigFile::parseYesNo(pair, "face_system.ether");
       }
       else if (key == "mcast_group") {
         const std::string& valueStr = value.get_value<std::string>();
@@ -87,7 +97,7 @@
         }
       }
       else if (key == "mcast_ad_hoc") {
-        bool wantAdHoc = ConfigFile::parseYesNo(pair, "ether");
+        bool wantAdHoc = ConfigFile::parseYesNo(pair, "face_system.ether");
         mcastConfig.linkType = wantAdHoc ? ndn::nfd::LINK_TYPE_AD_HOC : ndn::nfd::LINK_TYPE_MULTI_ACCESS;
       }
       else if (key == "whitelist") {
@@ -103,6 +113,32 @@
   }
 
   if (!context.isDryRun) {
+    if (configSection) {
+      providedSchemes.insert("ether");
+
+      // determine the interfaces on which channels should be created
+      auto netifs = context.listNetifs() |
+                    boost::adaptors::filtered([this] (const NetworkInterfaceInfo& netif) {
+                      return netif.isUp() && !netif.isLoopback();
+                    });
+
+      // create channels
+      for (const auto& netif : netifs) {
+        auto channel = this->createChannel(netif, time::seconds(idleTimeout));
+        if (wantListen && !channel->isListening()) {
+          try {
+            channel->listen(context.addFace, nullptr);
+          }
+          catch (const EthernetChannel::Error& e) {
+            NFD_LOG_WARN("Cannot listen on " << netif.name << ": " << e.what());
+          }
+        }
+      }
+    }
+    else if (!m_channels.empty()) {
+      NFD_LOG_WARN("Cannot disable dev channels after initialization");
+    }
+
     if (m_mcastConfig.isEnabled != mcastConfig.isEnabled) {
       if (mcastConfig.isEnabled) {
         NFD_LOG_INFO("enabling multicast on " << mcastConfig.group);
@@ -139,13 +175,66 @@
                             const FaceCreatedCallback& onCreated,
                             const FaceCreationFailedCallback& onFailure)
 {
-  onFailure(406, "Unsupported protocol");
+  BOOST_ASSERT(remoteUri.isCanonical());
+
+  if (!localUri || localUri->getScheme() != "dev") {
+    NFD_LOG_TRACE("Cannot create unicast Ethernet face without dev:// LocalUri");
+    onFailure(406, "Creation of unicast Ethernet faces requires a LocalUri with dev:// scheme");
+    return;
+  }
+  BOOST_ASSERT(localUri->isCanonical());
+
+  if (persistency == ndn::nfd::FACE_PERSISTENCY_ON_DEMAND) {
+    NFD_LOG_TRACE("createFace does not support FACE_PERSISTENCY_ON_DEMAND");
+    onFailure(406, "Outgoing Ethernet faces do not support on-demand persistency");
+    return;
+  }
+
+  ethernet::Address remoteEndpoint(ethernet::Address::fromString(remoteUri.getHost()));
+  std::string localEndpoint(localUri->getHost());
+
+  if (remoteEndpoint.isMulticast()) {
+    NFD_LOG_TRACE("createFace does not support multicast faces");
+    onFailure(406, "Cannot create multicast Ethernet faces");
+    return;
+  }
+
+  if (wantLocalFieldsEnabled) {
+    // Ethernet 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;
+  }
+
+  for (const auto& i : m_channels) {
+    if (i.first == localEndpoint) {
+      i.second->connect(remoteEndpoint, persistency, onCreated, onFailure);
+      return;
+    }
+  }
+
+  NFD_LOG_TRACE("No channels available to connect to " << remoteEndpoint);
+  onFailure(504, "No channels available to connect");
+}
+
+shared_ptr<EthernetChannel>
+EthernetFactory::createChannel(const NetworkInterfaceInfo& localEndpoint,
+                               time::nanoseconds idleTimeout)
+{
+  auto it = m_channels.find(localEndpoint.name);
+  if (it != m_channels.end())
+    return it->second;
+
+  auto channel = std::make_shared<EthernetChannel>(localEndpoint, idleTimeout);
+  m_channels[localEndpoint.name] = channel;
+
+  return channel;
 }
 
 std::vector<shared_ptr<const Channel>>
 EthernetFactory::getChannels() const
 {
-  return {};
+  return getChannelsFromMap(m_channels);
 }
 
 shared_ptr<Face>
@@ -197,8 +286,7 @@
         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");
+        NFD_LOG_WARN("Cannot create Ethernet multicast face on " << netif.name << ": " << e.what());
         continue;
       }
 
diff --git a/daemon/face/ethernet-factory.hpp b/daemon/face/ethernet-factory.hpp
index 856116f..1cbe4e9 100644
--- a/daemon/face/ethernet-factory.hpp
+++ b/daemon/face/ethernet-factory.hpp
@@ -27,13 +27,12 @@
 #define NFD_DAEMON_FACE_ETHERNET_FACTORY_HPP
 
 #include "protocol-factory.hpp"
+#include "ethernet-channel.hpp"
 
 namespace nfd {
 namespace face {
 
 /** \brief protocol factory for Ethernet
- *
- *  Currently, only Ethernet multicast is supported.
  */
 class EthernetFactory : public ProtocolFactory
 {
@@ -47,8 +46,6 @@
   processConfig(OptionalConfigSection configSection,
                 FaceSystem::ConfigContext& context) override;
 
-  /** \brief unicast face creation is not supported and will always fail
-   */
   void
   createFace(const FaceUri& remoteUri,
              const ndn::optional<FaceUri>& localUri,
@@ -57,26 +54,45 @@
              const FaceCreatedCallback& onCreated,
              const FaceCreationFailedCallback& onFailure) override;
 
-  /** \return empty container, because Ethernet unicast is not supported
+  /**
+   * \brief Create Ethernet-based channel on the specified network interface
+   *
+   * If this method is called twice with the same endpoint, only one channel
+   * will be created. The second call will just return the existing channel.
+   *
+   * \return always a valid pointer to a EthernetChannel object, an exception
+   *         is thrown if it cannot be created.
+   * \throw PcapHelper::Error
    */
+  shared_ptr<EthernetChannel>
+  createChannel(const NetworkInterfaceInfo& localEndpoint,
+                time::nanoseconds idleTimeout);
+
   std::vector<shared_ptr<const Channel>>
   getChannels() const override;
 
-private:
-  /** \brief Create a face to communicate on the given Ethernet multicast group
-   *  \param netif local network interface
-   *  \param group multicast group address
-   *  \note Calling this method again with same arguments returns the existing face on the given
-   *        interface and multicast group rather than creating a new one.
-   *  \throw EthernetTransport::Error transport creation fails
+  /**
+   * \brief Create a face to communicate on the given Ethernet multicast group
+   *
+   * If this method is called twice with the same arguments, only one face
+   * will be created. The second call will just return the existing face.
+   *
+   * \param netif local network interface
+   * \param group multicast group address
+   *
+   * \throw EthernetTransport::Error transport creation fails
    */
   shared_ptr<Face>
-  createMulticastFace(const NetworkInterfaceInfo& netif, const ethernet::Address& group);
+  createMulticastFace(const NetworkInterfaceInfo& netif,
+                      const ethernet::Address& group);
 
+private:
   void
   applyConfig(const FaceSystem::ConfigContext& context);
 
 private:
+  std::map<std::string, shared_ptr<EthernetChannel>> m_channels; ///< ifname => channel
+
   struct MulticastConfig
   {
     bool isEnabled = false;
diff --git a/daemon/face/tcp-factory.cpp b/daemon/face/tcp-factory.cpp
index 49f1307..3132796 100644
--- a/daemon/face/tcp-factory.cpp
+++ b/daemon/face/tcp-factory.cpp
@@ -144,11 +144,8 @@
   tcp::Endpoint endpoint(ip::address::from_string(remoteUri.getHost()),
                          boost::lexical_cast<uint16_t>(remoteUri.getPort()));
 
-  if (endpoint.address().is_multicast()) {
-    NFD_LOG_TRACE("createFace does not support multicast faces");
-    onFailure(406, "Cannot create multicast TCP faces");
-    return;
-  }
+  // a canonical tcp4/tcp6 FaceUri cannot have a multicast address
+  BOOST_ASSERT(!endpoint.address().is_multicast());
 
   if (m_prohibitedEndpoints.find(endpoint) != m_prohibitedEndpoints.end()) {
     NFD_LOG_TRACE("Requested endpoint is prohibited "
@@ -172,7 +169,7 @@
     }
   }
 
-  NFD_LOG_TRACE("No channels available to connect to " + boost::lexical_cast<std::string>(endpoint));
+  NFD_LOG_TRACE("No channels available to connect to " << endpoint);
   onFailure(504, "No channels available to connect");
 }
 
diff --git a/daemon/face/tcp-factory.hpp b/daemon/face/tcp-factory.hpp
index 35a51d7..4576c65 100644
--- a/daemon/face/tcp-factory.hpp
+++ b/daemon/face/tcp-factory.hpp
@@ -59,15 +59,11 @@
    *
    * tcp::Endpoint is really an alias for boost::asio::ip::tcp::endpoint.
    *
-   * If this method called twice with the same endpoint, only one channel
-   * will be created.  The second call will just retrieve the existing
-   * channel.
+   * If this method is called twice with the same endpoint, only one channel
+   * will be created. The second call will just return the existing channel.
    *
-   * \returns always a valid pointer to a TcpChannel object, an exception
-   *          is thrown if it cannot be created.
-   *
-   * \see http://www.boost.org/doc/libs/1_42_0/doc/html/boost_asio/reference/ip__tcp/endpoint.html
-   *      for details on ways to create tcp::Endpoint
+   * \return always a valid pointer to a TcpChannel object, an exception
+   *         is thrown if it cannot be created.
    */
   shared_ptr<TcpChannel>
   createChannel(const tcp::Endpoint& localEndpoint);
@@ -75,10 +71,11 @@
   /**
    * \brief Create TCP-based channel using specified IP address and port number
    *
-   * This method is just a helper that converts a string representation of localIp and port to
-   * tcp::Endpoint and calls the other createChannel overload.
+   * This method is just a helper that converts the string representation of \p localIp
+   * and \p localPort to tcp::Endpoint and calls the other createChannel overload.
    *
-   * \throws std::runtime_error
+   * \return always a valid pointer to a UdpChannel object, an exception
+   *         is thrown if it cannot be created.
    */
   shared_ptr<TcpChannel>
   createChannel(const std::string& localIp, const std::string& localPort);
@@ -100,8 +97,8 @@
   /**
    * \brief Look up TcpChannel using specified local endpoint
    *
-   * \returns shared pointer to the existing TcpChannel object
-   *          or empty shared pointer when such channel does not exist
+   * \return shared pointer to the existing TcpChannel object
+   *         or empty shared pointer when such channel does not exist
    */
   shared_ptr<TcpChannel>
   findChannel(const tcp::Endpoint& localEndpoint) const;
diff --git a/daemon/face/udp-factory.cpp b/daemon/face/udp-factory.cpp
index a544edf..5b68061 100644
--- a/daemon/face/udp-factory.cpp
+++ b/daemon/face/udp-factory.cpp
@@ -219,7 +219,7 @@
 
   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");
+    onFailure(406, "Outgoing UDP faces do not support on-demand persistency");
     return;
   }
 
@@ -255,7 +255,7 @@
     }
   }
 
-  NFD_LOG_TRACE("No channels available to connect to " + boost::lexical_cast<std::string>(endpoint));
+  NFD_LOG_TRACE("No channels available to connect to " << endpoint);
   onFailure(504, "No channels available to connect");
 }
 
@@ -307,38 +307,38 @@
 }
 
 shared_ptr<UdpChannel>
-UdpFactory::createChannel(const udp::Endpoint& endpoint,
-                          const time::seconds& timeout)
+UdpFactory::createChannel(const udp::Endpoint& localEndpoint,
+                          time::nanoseconds idleTimeout)
 {
-  auto channel = findChannel(endpoint);
-  if (channel)
-    return channel;
+  auto it = m_channels.find(localEndpoint);
+  if (it != m_channels.end())
+    return it->second;
 
-  if (endpoint.address().is_multicast()) {
+  if (localEndpoint.address().is_multicast()) {
     BOOST_THROW_EXCEPTION(Error("createChannel is only for unicast channels. The provided endpoint "
                                 "is multicast. Use createMulticastFace to create a multicast face"));
   }
 
   // check if the endpoint is already used by a multicast face
-  auto face = findMulticastFace(endpoint);
-  if (face) {
+  if (m_mcastFaces.find(localEndpoint) != m_mcastFaces.end()) {
     BOOST_THROW_EXCEPTION(Error("Cannot create the requested UDP unicast channel, local "
                                 "endpoint is already allocated for a UDP multicast face"));
   }
 
-  channel = std::make_shared<UdpChannel>(endpoint, timeout);
-  m_channels[endpoint] = channel;
-  prohibitEndpoint(endpoint);
+  auto channel = std::make_shared<UdpChannel>(localEndpoint, idleTimeout);
+  m_channels[localEndpoint] = channel;
+  prohibitEndpoint(localEndpoint);
+
   return channel;
 }
 
 shared_ptr<UdpChannel>
 UdpFactory::createChannel(const std::string& localIp, const std::string& localPort,
-                          const time::seconds& timeout)
+                          time::nanoseconds idleTimeout)
 {
   udp::Endpoint endpoint(ip::address::from_string(localIp),
                          boost::lexical_cast<uint16_t>(localPort));
-  return createChannel(endpoint, timeout);
+  return createChannel(endpoint, idleTimeout);
 }
 
 std::vector<shared_ptr<const Channel>>
@@ -347,26 +347,16 @@
   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,
-                                const std::string& networkInterfaceName/* = ""*/)
+                                const std::string& networkInterfaceName)
 {
   // checking if the local and multicast endpoints are already in use for a multicast face
-  auto face = findMulticastFace(localEndpoint);
-  if (face) {
-    if (face->getRemoteUri() == FaceUri(multicastEndpoint))
-      return face;
+  auto it = m_mcastFaces.find(localEndpoint);
+  if (it != m_mcastFaces.end()) {
+    if (it->second->getRemoteUri() == FaceUri(multicastEndpoint))
+      return it->second;
     else
       BOOST_THROW_EXCEPTION(Error("Cannot create the requested UDP multicast face, local "
                                   "endpoint is already allocated for a UDP multicast face "
@@ -374,8 +364,7 @@
   }
 
   // checking if the local endpoint is already in use for a unicast channel
-  auto unicastCh = findChannel(localEndpoint);
-  if (unicastCh) {
+  if (m_channels.find(localEndpoint) != m_channels.end()) {
     BOOST_THROW_EXCEPTION(Error("Cannot create the requested UDP multicast face, local "
                                 "endpoint is already allocated for a UDP unicast channel"));
   }
@@ -441,7 +430,7 @@
                                                       std::move(receiveSocket),
                                                       std::move(sendSocket),
                                                       m_mcastConfig.linkType);
-  face = make_shared<Face>(std::move(linkService), std::move(transport));
+  auto face = make_shared<Face>(std::move(linkService), std::move(transport));
 
   m_mcastFaces[localEndpoint] = face;
   connectFaceClosedSignal(*face, [this, localEndpoint] { m_mcastFaces.erase(localEndpoint); });
@@ -453,7 +442,7 @@
 UdpFactory::createMulticastFace(const std::string& localIp,
                                 const std::string& multicastIp,
                                 const std::string& multicastPort,
-                                const std::string& networkInterfaceName/* = ""*/)
+                                const std::string& networkInterfaceName)
 {
   udp::Endpoint localEndpoint(ip::address::from_string(localIp),
                               boost::lexical_cast<uint16_t>(multicastPort));
@@ -462,16 +451,6 @@
   return createMulticastFace(localEndpoint, multicastEndpoint, networkInterfaceName);
 }
 
-shared_ptr<Face>
-UdpFactory::findMulticastFace(const udp::Endpoint& localEndpoint) const
-{
-  auto i = m_mcastFaces.find(localEndpoint);
-  if (i != m_mcastFaces.end())
-    return i->second;
-  else
-    return nullptr;
-}
-
 void
 UdpFactory::applyMulticastConfig(const FaceSystem::ConfigContext& context)
 {
diff --git a/daemon/face/udp-factory.hpp b/daemon/face/udp-factory.hpp
index ce6d2e5..3ebf0a3 100644
--- a/daemon/face/udp-factory.hpp
+++ b/daemon/face/udp-factory.hpp
@@ -75,46 +75,37 @@
    *
    * udp::Endpoint is really an alias for boost::asio::ip::udp::endpoint.
    *
-   * If this method called twice with the same endpoint, only one channel
-   * will be created.  The second call will just retrieve the existing
-   * channel.
+   * If this method is called twice with the same endpoint, only one channel
+   * will be created. The second call will just return the existing channel.
    *
    * If a multicast face is already active on the same local endpoint,
-   * the creation fails and an exception is thrown
+   * the creation fails and an exception is thrown.
    *
-   * Once a face is created, if it doesn't send/receive anything for
-   * a period of time equal to timeout, it will be destroyed
-   * @todo this funcionality has to be implemented
-   *
-   * \returns always a valid pointer to a UdpChannel object, an exception
-   *          is thrown if it cannot be created.
-   *
-   * \throws UdpFactory::Error
-   *
-   * \see http://www.boost.org/doc/libs/1_42_0/doc/html/boost_asio/reference/ip__udp/endpoint.html
-   *      for details on ways to create udp::Endpoint
+   * \return always a valid pointer to a UdpChannel object, an exception
+   *         is thrown if it cannot be created.
+   * \throw UdpFactory::Error
    */
   shared_ptr<UdpChannel>
   createChannel(const udp::Endpoint& localEndpoint,
-                const time::seconds& timeout = time::seconds(600));
+                time::nanoseconds idleTimeout);
 
   /**
    * \brief Create UDP-based channel using specified IP address and port number
    *
-   * This method is just a helper that converts a string representation of localIp and port to
-   * udp::Endpoint and calls the other createChannel overload.
+   * This method is just a helper that converts the string representation of \p localIp
+   * and \p localPort to udp::Endpoint and calls the other createChannel overload.
    *
-   * If localHost is a IPv6 address of a specific device, it must be in the form:
-   * ip address%interface name
-   * Example: fe80::5e96:9dff:fe7d:9c8d%en1
-   * Otherwise, you can use ::
+   * If \p localIp is an IPv6 address of a specific device, it must be in the form
+   * <tt>[ip address]%[interface name]</tt>, e.g. <tt>"fe80::5e96:9dff:fe7d:9c8d%en1"</tt>.
+   * Otherwise, you can use <tt>"::"</tt>.
    *
-   * \throws UdpChannel::Error if the bind on the socket fails
-   * \throws UdpFactory::Error
+   * \return always a valid pointer to a UdpChannel object, an exception
+   *         is thrown if it cannot be created.
+   * \throw UdpFactory::Error
    */
   shared_ptr<UdpChannel>
   createChannel(const std::string& localIp, const std::string& localPort,
-                const time::seconds& timeout = time::seconds(600));
+                time::nanoseconds idleTimeout = time::seconds(600));
 
   std::vector<shared_ptr<const Channel>>
   getChannels() const override;
@@ -124,14 +115,13 @@
    *
    * udp::Endpoint is really an alias for boost::asio::ip::udp::endpoint.
    *
-   * The face will join the multicast group
+   * The face will join the specified multicast group.
    *
-   * If this method called twice with the same endpoint and group, only one face
-   * will be created.  The second call will just retrieve the existing
-   * channel.
+   * If this method is called twice with the same pair of endpoints, only one
+   * face will be created. The second call will just return the existing face.
    *
    * If an unicast face is already active on the same local NIC and port, the
-   * creation fails and an exception is thrown
+   * creation fails and an exception is thrown.
    *
    * \param localEndpoint local endpoint
    * \param multicastEndpoint multicast endpoint
@@ -141,14 +131,9 @@
    *        An empty string can be provided in other system or in linux machine with only one
    *        MulticastUdpFace per multicast group
    *
-   *
-   * \returns always a valid pointer to a MulticastUdpFace object, an exception
-   *          is thrown if it cannot be created.
-   *
-   * \throws UdpFactory::Error
-   *
-   * \see http://www.boost.org/doc/libs/1_42_0/doc/html/boost_asio/reference/ip__udp/endpoint.html
-   *      for details on ways to create udp::Endpoint
+   * \return always a valid pointer to a MulticastUdpFace object, an exception
+   *         is thrown if it cannot be created.
+   * \throw UdpFactory::Error
    */
   shared_ptr<Face>
   createMulticastFace(const udp::Endpoint& localEndpoint,
@@ -172,24 +157,6 @@
   prohibitAllIpv6Endpoints(uint16_t port);
 
 private:
-  /**
-   * \brief Look up UdpChannel using specified local endpoint
-   *
-   * \returns shared pointer to the existing UdpChannel object
-   *          or nullptr when such channel does not exist
-   */
-  shared_ptr<UdpChannel>
-  findChannel(const udp::Endpoint& localEndpoint) const;
-
-  /**
-   * \brief Look up multicast UdpFace using specified local endpoint
-   *
-   * \returns shared pointer to the existing multicast UdpFace object
-   *          or nullptr when such face does not exist
-   */
-  shared_ptr<Face>
-  findMulticastFace(const udp::Endpoint& localEndpoint) const;
-
   void
   applyMulticastConfig(const FaceSystem::ConfigContext& context);
 
diff --git a/nfd.conf.sample.in b/nfd.conf.sample.in
index b763e0c..7e4c940 100644
--- a/nfd.conf.sample.in
+++ b/nfd.conf.sample.in
@@ -81,13 +81,13 @@
 face_system
 {
   ; The unix section contains settings of Unix stream faces and channels.
-  ; Unix channel is always listening; delete unix section to disable
+  ; A Unix channel is always listening; delete the unix section to disable
   ; Unix stream faces and channels.
   ;
-  ; The ndn-cxx library expects unix:///var/run/nfd.sock
-  ; to be used as the default transport option. Please change
-  ; the "transport" field in client.conf to an appropriate tcp4 FaceUri
-  ; if you need to disable unix sockets.
+  ; The ndn-cxx library expects unix:///var/run/nfd.sock to be used as
+  ; the default transport option. Please change the "transport" field
+  ; in client.conf to an appropriate tcp4 FaceUri if you want to
+  ; disable Unix sockets and use TCP instead.
   unix
   {
     path /var/run/nfd.sock ; Unix stream listener path
@@ -103,37 +103,40 @@
   }
 
   ; The udp section contains settings of UDP faces and channels.
-  ; UDP channel is always listening; delete udp section to disable UDP
   udp
   {
+    ; UDP unicast settings.
+    ; UDP channels are always listening; delete the udp section to disable them
     port 6363 ; UDP unicast port number
     enable_v4 yes ; set to 'no' to disable IPv4 channels, default 'yes'
     enable_v6 yes ; set to 'no' to disable IPv6 channels, default 'yes'
 
-    ; idle time (seconds) before closing a UDP unicast face, the actual timeout would be
-    ; anywhere within [idle_timeout, 2*idle_timeout), default is 600
+    ; Time (in seconds) before closing an idle UDP unicast face.
+    ; The actual timeout will occur anytime between idle_timeout and 2*idle_timeout.
+    ; The default is 600 (10 minutes).
     idle_timeout 600
 
     keep_alive_interval 25; interval (seconds) between keep-alive refreshes
 
-    ; UDP multicast settings
-    ; By default, NFD creates one UDP multicast face per NIC
+    ; UDP multicast settings.
+    ; By default, NFD creates one UDP multicast face per NIC.
     ;
     ; In multi-homed Linux machines these settings will NOT work without
-    ; root or settings the appropriate permissions:
+    ; root or setting the appropriate permissions:
     ;
-    ;    sudo setcap cap_net_raw=eip /full/path/nfd
+    ;    sudo setcap cap_net_raw=eip /path/to/nfd
     ;
     mcast yes ; set to 'no' to disable UDP multicast, default 'yes'
     mcast_group 224.0.23.170 ; UDP multicast group (IPv4 only)
     mcast_port 56363 ; UDP multicast port number
-    mcast_ad_hoc no ; set 'yes' to make all UDP multicast faces "ad hoc", default 'no'
+    mcast_ad_hoc no ; set to 'yes' to make all UDP multicast faces "ad hoc", default 'no'
 
     ; Whitelist and blacklist can contain, in no particular order:
-    ; interface names, including wildcard patterns (e.g., 'ifname eth0', 'ifname en*', 'ifname wlp?s0'),
-    ; mac addresses (e.g., 'ether 85:3b:4d:d3:5f:c2'),
-    ; subnets (e.g., 'subnet 192.0.2.0/24', note that only IPv4 is supported here),
-    ; or a single asterisk ('*') that matches all interfaces.
+    ; - interface names, including wildcard patterns (e.g., 'ifname eth0', 'ifname en*', 'ifname wlp?s0')
+    ; - mac addresses (e.g., 'ether 85:3b:4d:d3:5f:c2')
+    ; - subnets (e.g., 'subnet 192.0.2.0/24', note that only IPv4 is supported here)
+    ; - a single asterisk ('*') that matches all interfaces
+    ; By default, all interfaces are whitelisted.
     whitelist
     {
       *
@@ -147,7 +150,7 @@
   ; These settings will NOT work without root or setting the appropriate
   ; permissions:
   ;
-  ;    sudo setcap cap_net_raw,cap_net_admin=eip /full/path/nfd
+  ;    sudo setcap cap_net_raw,cap_net_admin=eip /path/to/nfd
   ;
   ; You may need to install a package to use setcap:
   ;
@@ -168,18 +171,26 @@
   ;
   @IF_HAVE_LIBPCAP@ether
   @IF_HAVE_LIBPCAP@{
-  @IF_HAVE_LIBPCAP@  ; Ethernet multicast settings
-  @IF_HAVE_LIBPCAP@  ; By default, NFD creates one Ethernet multicast face per NIC
+  @IF_HAVE_LIBPCAP@  ; Ethernet unicast settings.
+  @IF_HAVE_LIBPCAP@  listen yes ; set to 'no' to disable Ethernet listener, default 'yes'
   @IF_HAVE_LIBPCAP@
+  @IF_HAVE_LIBPCAP@  ; Time (in seconds) before closing an idle Ethernet unicast face.
+  @IF_HAVE_LIBPCAP@  ; The actual timeout will occur anytime between idle_timeout and 2*idle_timeout.
+  @IF_HAVE_LIBPCAP@  ; The default is 600 (10 minutes).
+  @IF_HAVE_LIBPCAP@  idle_timeout 600
+  @IF_HAVE_LIBPCAP@
+  @IF_HAVE_LIBPCAP@  ; Ethernet multicast settings.
+  @IF_HAVE_LIBPCAP@  ; By default, NFD creates one Ethernet multicast face per NIC.
   @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@  mcast_ad_hoc no ; set to 'yes' to make all Ethernet multicast 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'),
-  @IF_HAVE_LIBPCAP@  ; mac addresses (e.g., 'ether 85:3b:4d:d3:5f:c2'),
-  @IF_HAVE_LIBPCAP@  ; subnets (e.g., 'subnet 192.0.2.0/24', note that only IPv4 is supported here),
-  @IF_HAVE_LIBPCAP@  ; or a single asterisk ('*') that matches all interfaces.
+  @IF_HAVE_LIBPCAP@  ; - interface names, including wildcard patterns (e.g., 'ifname eth0', 'ifname en*', 'ifname wlp?s0')
+  @IF_HAVE_LIBPCAP@  ; - mac addresses (e.g., 'ether 85:3b:4d:d3:5f:c2')
+  @IF_HAVE_LIBPCAP@  ; - subnets (e.g., 'subnet 192.0.2.0/24', note that only IPv4 is supported here)
+  @IF_HAVE_LIBPCAP@  ; - a single asterisk ('*') that matches all interfaces
+  @IF_HAVE_LIBPCAP@  ; By default, all interfaces are whitelisted.
   @IF_HAVE_LIBPCAP@  whitelist
   @IF_HAVE_LIBPCAP@  {
   @IF_HAVE_LIBPCAP@    *
diff --git a/tests/daemon/face/ethernet-factory.t.cpp b/tests/daemon/face/ethernet-factory.t.cpp
index 5e7c6d2..953a35f 100644
--- a/tests/daemon/face/ethernet-factory.t.cpp
+++ b/tests/daemon/face/ethernet-factory.t.cpp
@@ -326,25 +326,44 @@
   EthernetFactory factory;
 
   createFace(factory,
-             FaceUri("ether://[08:00:27:01:01:01]"),
-             {},
-             ndn::nfd::FACE_PERSISTENCY_PERMANENT,
-             false,
-             {CreateFaceExpectedResult::FAILURE, 406, "Unsupported protocol"});
-
-  createFace(factory,
-             FaceUri("ether://[08:00:27:01:01:01]"),
-             {},
-             ndn::nfd::FACE_PERSISTENCY_ON_DEMAND,
-             false,
-             {CreateFaceExpectedResult::FAILURE, 406, "Unsupported protocol"});
-
-  createFace(factory,
-             FaceUri("ether://[08:00:27:01:01:01]"),
+             FaceUri("ether://[00:00:5e:00:53:5e]"),
              {},
              ndn::nfd::FACE_PERSISTENCY_PERSISTENT,
              false,
-             {CreateFaceExpectedResult::FAILURE, 406, "Unsupported protocol"});
+             {CreateFaceExpectedResult::FAILURE, 406,
+              "Creation of unicast Ethernet faces requires a LocalUri with dev:// scheme"});
+
+  createFace(factory,
+             FaceUri("ether://[00:00:5e:00:53:5e]"),
+             FaceUri("udp4://127.0.0.1:20071"),
+             ndn::nfd::FACE_PERSISTENCY_PERSISTENT,
+             false,
+             {CreateFaceExpectedResult::FAILURE, 406,
+              "Creation of unicast Ethernet faces requires a LocalUri with dev:// scheme"});
+
+  createFace(factory,
+             FaceUri("ether://[00:00:5e:00:53:5e]"),
+             FaceUri("dev://eth0"),
+             ndn::nfd::FACE_PERSISTENCY_ON_DEMAND,
+             false,
+             {CreateFaceExpectedResult::FAILURE, 406,
+              "Outgoing Ethernet faces do not support on-demand persistency"});
+
+  createFace(factory,
+             FaceUri("ether://[01:00:5e:90:10:5e]"),
+             FaceUri("dev://eth0"),
+             ndn::nfd::FACE_PERSISTENCY_PERSISTENT,
+             false,
+             {CreateFaceExpectedResult::FAILURE, 406,
+              "Cannot create multicast Ethernet faces"});
+
+  createFace(factory,
+             FaceUri("ether://[00:00:5e:00:53:5e]"),
+             FaceUri("dev://eth0"),
+             ndn::nfd::FACE_PERSISTENCY_PERSISTENT,
+             true,
+             {CreateFaceExpectedResult::FAILURE, 406,
+              "Local fields can only be enabled on faces with local scope"});
 }
 
 BOOST_AUTO_TEST_SUITE_END() // TestEthernetFactory
diff --git a/tests/daemon/face/tcp-factory.t.cpp b/tests/daemon/face/tcp-factory.t.cpp
index d3f5fc9..7f93d78 100644
--- a/tests/daemon/face/tcp-factory.t.cpp
+++ b/tests/daemon/face/tcp-factory.t.cpp
@@ -204,25 +204,39 @@
 BOOST_AUTO_TEST_CASE(UnsupportedFaceCreate)
 {
   TcpFactory factory;
-
   factory.createChannel("127.0.0.1", "20071");
-  factory.createChannel("127.0.0.1", "20072");
 
   createFace(factory,
+             FaceUri("tcp4://127.0.0.1:20072"),
              FaceUri("tcp4://127.0.0.1:20071"),
+             ndn::nfd::FACE_PERSISTENCY_PERSISTENT,
+             false,
+             {CreateFaceExpectedResult::FAILURE, 406,
+              "Unicast TCP faces cannot be created with a LocalUri"});
+
+  createFace(factory,
+             FaceUri("tcp4://127.0.0.1:20072"),
              {},
              ndn::nfd::FACE_PERSISTENCY_ON_DEMAND,
              false,
              {CreateFaceExpectedResult::FAILURE, 406,
-               "Outgoing TCP faces do not support on-demand persistency"});
+              "Outgoing TCP faces do not support on-demand persistency"});
 
   createFace(factory,
-             FaceUri("tcp4://127.0.0.1:20072"),
-             FaceUri("tcp4://127.0.0.1:20073"),
+             FaceUri("tcp4://127.0.0.1:20071"),
+             {},
              ndn::nfd::FACE_PERSISTENCY_PERSISTENT,
              false,
              {CreateFaceExpectedResult::FAILURE, 406,
-               "Unicast TCP faces cannot be created with a LocalUri"});
+              "Requested endpoint is prohibited"});
+
+  createFace(factory,
+             FaceUri("tcp4://198.51.100.100:6363"),
+             {},
+             ndn::nfd::FACE_PERSISTENCY_PERSISTENT,
+             true,
+             {CreateFaceExpectedResult::FAILURE, 406,
+              "Local fields can only be enabled on faces with local scope"});
 }
 
 class FaceCreateTimeoutFixture : public BaseFixture
diff --git a/tests/daemon/face/udp-factory.t.cpp b/tests/daemon/face/udp-factory.t.cpp
index 3270ad6..9677abe 100644
--- a/tests/daemon/face/udp-factory.t.cpp
+++ b/tests/daemon/face/udp-factory.t.cpp
@@ -635,25 +635,47 @@
 BOOST_AUTO_TEST_CASE(UnsupportedFaceCreate)
 {
   UdpFactory factory;
-
   factory.createChannel("127.0.0.1", "20071");
-  factory.createChannel("127.0.0.1", "20072");
 
   createFace(factory,
+             FaceUri("udp4://127.0.0.1:20072"),
              FaceUri("udp4://127.0.0.1:20071"),
+             ndn::nfd::FACE_PERSISTENCY_PERSISTENT,
+             false,
+             {CreateFaceExpectedResult::FAILURE, 406,
+              "Unicast UDP faces cannot be created with a LocalUri"});
+
+  createFace(factory,
+             FaceUri("udp4://127.0.0.1:20072"),
              {},
              ndn::nfd::FACE_PERSISTENCY_ON_DEMAND,
              false,
              {CreateFaceExpectedResult::FAILURE, 406,
-               "Outgoing unicast UDP faces do not support on-demand persistency"});
+              "Outgoing UDP faces do not support on-demand persistency"});
 
   createFace(factory,
-             FaceUri("udp4://127.0.0.1:20072"),
-             FaceUri("udp4://127.0.0.1:20073"),
+             FaceUri("udp4://233.252.0.1:23252"),
+             {},
              ndn::nfd::FACE_PERSISTENCY_PERSISTENT,
              false,
              {CreateFaceExpectedResult::FAILURE, 406,
-               "Unicast UDP faces cannot be created with a LocalUri"});
+              "Cannot create multicast UDP faces"});
+
+  createFace(factory,
+             FaceUri("udp4://127.0.0.1:20071"),
+             {},
+             ndn::nfd::FACE_PERSISTENCY_PERSISTENT,
+             false,
+             {CreateFaceExpectedResult::FAILURE, 406,
+              "Requested endpoint is prohibited"});
+
+  createFace(factory,
+             FaceUri("udp4://127.0.0.1:20072"),
+             {},
+             ndn::nfd::FACE_PERSISTENCY_PERSISTENT,
+             true,
+             {CreateFaceExpectedResult::FAILURE, 406,
+              "Local fields can only be enabled on faces with local scope"});
 }
 
 class FakeNetworkInterfaceFixture : public BaseFixture