face: UnicastEthernetTransport

Change-Id: I4d19f52835d9268f2ea63fd4e0b1a0a5aed95c47
Refs: #4011
diff --git a/daemon/face/ethernet-factory.cpp b/daemon/face/ethernet-factory.cpp
index 7263118..1b348fc 100644
--- a/daemon/face/ethernet-factory.cpp
+++ b/daemon/face/ethernet-factory.cpp
@@ -24,8 +24,8 @@
  */
 
 #include "ethernet-factory.hpp"
-#include "ethernet-transport.hpp"
 #include "generic-link-service.hpp"
+#include "multicast-ethernet-transport.hpp"
 #include "core/logger.hpp"
 #include <boost/range/adaptors.hpp>
 #include <boost/range/algorithm/copy.hpp>
@@ -165,7 +165,7 @@
   opts.allowReassembly = true;
 
   auto linkService = make_unique<face::GenericLinkService>(opts);
-  auto transport = make_unique<face::EthernetTransport>(netif, address, m_mcastConfig.linkType);
+  auto transport = make_unique<face::MulticastEthernetTransport>(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-transport.cpp b/daemon/face/ethernet-transport.cpp
index 4b039c6..be44e0c 100644
--- a/daemon/face/ethernet-transport.cpp
+++ b/daemon/face/ethernet-transport.cpp
@@ -33,27 +33,11 @@
 #include <arpa/inet.h>    // for htons() and ntohs()
 #include <net/ethernet.h> // for struct ether_header
 #include <net/if.h>       // for struct ifreq
-#include <stdio.h>        // for snprintf()
 #include <sys/ioctl.h>    // for ioctl()
 #include <unistd.h>       // for dup()
 
-#if defined(__linux__)
-#include <netpacket/packet.h> // for struct packet_mreq
-#include <sys/socket.h>       // for setsockopt()
-#endif
-
-#ifdef SIOCADDMULTI
-#if defined(__APPLE__) || defined(__FreeBSD__)
-#include <net/if_dl.h>    // for struct sockaddr_dl
-#endif
-#endif
-
 #if !defined(PCAP_NETMASK_UNKNOWN)
-/*
- * Value to pass to pcap_compile() as the netmask if you don't know what
- * the netmask is.
- */
-#define PCAP_NETMASK_UNKNOWN    0xffffffff
+#define PCAP_NETMASK_UNKNOWN  0xffffffff
 #endif
 
 namespace nfd {
@@ -61,29 +45,20 @@
 
 NFD_LOG_INIT("EthernetTransport");
 
-EthernetTransport::EthernetTransport(const NetworkInterfaceInfo& interface,
-                                     const ethernet::Address& mcastAddress,
-                                     ndn::nfd::LinkType linkType)
+EthernetTransport::EthernetTransport(const NetworkInterfaceInfo& localEndpoint,
+                                     const ethernet::Address& remoteEndpoint)
   : m_pcap(nullptr, pcap_close)
   , m_socket(getGlobalIoService())
-  , m_srcAddress(interface.etherAddress)
-  , m_destAddress(mcastAddress)
-  , m_interfaceName(interface.name)
+  , m_srcAddress(localEndpoint.etherAddress)
+  , m_destAddress(remoteEndpoint)
+  , m_interfaceName(localEndpoint.name)
 #if defined(__linux__)
-  , m_interfaceIndex(interface.index)
+  , m_interfaceIndex(localEndpoint.index)
 #endif
 #ifdef _DEBUG
   , m_nDropped(0)
 #endif
 {
-  this->setLocalUri(FaceUri::fromDev(interface.name));
-  this->setRemoteUri(FaceUri(mcastAddress));
-  this->setScope(ndn::nfd::FACE_SCOPE_NON_LOCAL);
-  this->setPersistency(ndn::nfd::FACE_PERSISTENCY_PERMANENT);
-  this->setLinkType(linkType);
-
-  NFD_LOG_FACE_INFO("Creating transport");
-
   pcapInit();
 
   int fd = pcap_get_selectable_fd(m_pcap.get());
@@ -98,37 +73,22 @@
   // do this after assigning m_socket because getInterfaceMtu uses it
   this->setMtu(getInterfaceMtu());
 
-  char filter[110];
-  // note #1: we cannot use std::snprintf because it's not available
-  //          on some platforms (see #2299)
-  // note #2: "not vlan" must appear last in the filter expression, or the
-  //          rest of the filter won't work as intended (see pcap-filter(7))
-  snprintf(filter, sizeof(filter),
-           "(ether proto 0x%x) && (ether dst %s) && (not ether src %s) && (not vlan)",
-           ethernet::ETHERTYPE_NDN,
-           m_destAddress.toString().c_str(),
-           m_srcAddress.toString().c_str());
-  setPacketFilter(filter);
-
-  if (!m_destAddress.isBroadcast() && !joinMulticastGroup()) {
-    NFD_LOG_FACE_WARN("Falling back to promiscuous mode");
-    pcap_set_promisc(m_pcap.get(), 1);
-  }
-
   m_socket.async_read_some(boost::asio::null_buffers(),
                            bind(&EthernetTransport::handleRead, this,
                                 boost::asio::placeholders::error,
                                 boost::asio::placeholders::bytes_transferred));
 }
 
-void EthernetTransport::doSend(Transport::Packet&& packet)
+void
+EthernetTransport::doSend(Transport::Packet&& packet)
 {
   NFD_LOG_FACE_TRACE(__func__);
 
   sendPacket(packet.packet);
 }
 
-void EthernetTransport::doClose()
+void
+EthernetTransport::doClose()
 {
   NFD_LOG_FACE_TRACE(__func__);
 
@@ -158,7 +118,7 @@
 
 #ifdef HAVE_PCAP_SET_IMMEDIATE_MODE
   // Enable "immediate mode", effectively disabling any read buffering in the kernel.
-  // This corresponds to the BIOCIMMEDIATE ioctl on BSD-like systems (including OS X)
+  // This corresponds to the BIOCIMMEDIATE ioctl on BSD-like systems (including macOS)
   // where libpcap uses a BPF device. On Linux this forces libpcap not to use TPACKET_V3,
   // even if the kernel supports it, thus preventing bug #1511.
   pcap_set_immediate_mode(m_pcap.get(), 1);
@@ -188,72 +148,6 @@
     BOOST_THROW_EXCEPTION(Error("pcap_setfilter: " + std::string(pcap_geterr(m_pcap.get()))));
 }
 
-bool
-EthernetTransport::joinMulticastGroup()
-{
-#if defined(__linux__)
-  packet_mreq mr{};
-  mr.mr_ifindex = m_interfaceIndex;
-  mr.mr_type = PACKET_MR_MULTICAST;
-  mr.mr_alen = m_destAddress.size();
-  std::memcpy(mr.mr_address, m_destAddress.data(), m_destAddress.size());
-
-  if (::setsockopt(m_socket.native_handle(), SOL_PACKET,
-                   PACKET_ADD_MEMBERSHIP, &mr, sizeof(mr)) == 0)
-    return true; // success
-
-  NFD_LOG_FACE_WARN("setsockopt(PACKET_ADD_MEMBERSHIP) failed: " << std::strerror(errno));
-#endif
-
-#if defined(SIOCADDMULTI)
-  ifreq ifr{};
-  std::strncpy(ifr.ifr_name, m_interfaceName.c_str(), sizeof(ifr.ifr_name) - 1);
-
-#if defined(__APPLE__) || defined(__FreeBSD__)
-  // see bug #2327
-  using boost::asio::ip::udp;
-  udp::socket sock(getGlobalIoService(), udp::v4());
-  int fd = sock.native_handle();
-
-  /*
-   * Differences between Linux and the BSDs (including OS X):
-   *   o BSD does not have ifr_hwaddr; use ifr_addr instead.
-   *   o While OS X seems to accept both AF_LINK and AF_UNSPEC as the address
-   *     family, FreeBSD explicitly requires AF_LINK, so we have to use AF_LINK
-   *     and sockaddr_dl instead of the generic sockaddr structure.
-   *   o BSD's sockaddr (and sockaddr_dl in particular) contains an additional
-   *     field, sa_len (sdl_len), which must be set to the total length of the
-   *     structure, including the length field itself.
-   *   o We do not specify the interface name, thus sdl_nlen is left at 0 and
-   *     LLADDR is effectively the same as sdl_data.
-   */
-  sockaddr_dl* sdl = reinterpret_cast<sockaddr_dl*>(&ifr.ifr_addr);
-  sdl->sdl_len = sizeof(ifr.ifr_addr);
-  sdl->sdl_family = AF_LINK;
-  sdl->sdl_alen = m_destAddress.size();
-  std::memcpy(LLADDR(sdl), m_destAddress.data(), m_destAddress.size());
-
-  static_assert(sizeof(ifr.ifr_addr) >= offsetof(sockaddr_dl, sdl_data) + ethernet::ADDR_LEN,
-                "ifr_addr in struct ifreq is too small on this platform");
-#else
-  int fd = m_socket.native_handle();
-
-  ifr.ifr_hwaddr.sa_family = AF_UNSPEC;
-  std::memcpy(ifr.ifr_hwaddr.sa_data, m_destAddress.data(), m_destAddress.size());
-
-  static_assert(sizeof(ifr.ifr_hwaddr.sa_data) >= ethernet::ADDR_LEN,
-                "ifr_hwaddr in struct ifreq is too small on this platform");
-#endif
-
-  if (::ioctl(fd, SIOCADDMULTI, &ifr) == 0)
-    return true; // success
-
-  NFD_LOG_FACE_WARN("ioctl(SIOCADDMULTI) failed: " << std::strerror(errno));
-#endif
-
-  return false;
-}
-
 void
 EthernetTransport::sendPacket(const ndn::Block& block)
 {
@@ -340,7 +234,7 @@
 
   // check that our BPF filter is working correctly
   BOOST_ASSERT_MSG(ethernet::Address(eh->ether_dhost) == m_destAddress,
-                   "Received frame addressed to a different multicast group");
+                   "Received frame addressed to another host or multicast group");
   BOOST_ASSERT_MSG(sourceAddress != m_srcAddress,
                    "Received frame sent by this host");
 
diff --git a/daemon/face/ethernet-transport.hpp b/daemon/face/ethernet-transport.hpp
index 0777d74..8164a33 100644
--- a/daemon/face/ethernet-transport.hpp
+++ b/daemon/face/ethernet-transport.hpp
@@ -43,9 +43,9 @@
 namespace face {
 
 /**
- * \brief A multicast Transport that uses raw Ethernet II frames
+ * @brief Base class for Ethernet-based Transports
  */
-class EthernetTransport final : public Transport
+class EthernetTransport : public Transport
 {
 public:
   class Error : public std::runtime_error
@@ -58,42 +58,29 @@
     }
   };
 
-  /**
-   * @brief Creates an Ethernet-based transport for multicast communication
-   */
-  EthernetTransport(const NetworkInterfaceInfo& interface,
-                    const ethernet::Address& mcastAddress,
-                    ndn::nfd::LinkType linkType);
-
 protected:
+  EthernetTransport(const NetworkInterfaceInfo& localEndpoint,
+                    const ethernet::Address& remoteEndpoint);
+
   void
   doClose() final;
 
-private:
+  /**
+   * @brief Installs a BPF filter on the receiving socket
+   * @param filterString string containing the BPF program source
+   */
   void
-  doSend(Transport::Packet&& packet) final;
+  setPacketFilter(const char* filterString);
 
+private:
   /**
    * @brief Allocates and initializes a libpcap context for live capture
    */
   void
   pcapInit();
 
-  /**
-   * @brief Installs a BPF filter on the receiving socket
-   *
-   * @param filterString string containing the source BPF program
-   */
   void
-  setPacketFilter(const char* filterString);
-
-  /**
-   * @brief Enables receiving frames addressed to our MAC multicast group
-   *
-   * @return true if successful, false otherwise
-   */
-  bool
-  joinMulticastGroup();
+  doSend(Transport::Packet&& packet) final;
 
   /**
    * @brief Sends the specified TLV block on the network wrapped in an Ethernet frame
@@ -130,7 +117,7 @@
   size_t
   getInterfaceMtu();
 
-private:
+protected:
   unique_ptr<pcap_t, void(*)(pcap_t*)> m_pcap;
   boost::asio::posix::stream_descriptor m_socket;
 
@@ -141,6 +128,7 @@
   int m_interfaceIndex;
 #endif
 
+private:
 #ifdef _DEBUG
   /// number of packets dropped by the kernel, as reported by libpcap
   unsigned int m_nDropped;
diff --git a/daemon/face/multicast-ethernet-transport.cpp b/daemon/face/multicast-ethernet-transport.cpp
new file mode 100644
index 0000000..7e7f343
--- /dev/null
+++ b/daemon/face/multicast-ethernet-transport.cpp
@@ -0,0 +1,150 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2017,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "multicast-ethernet-transport.hpp"
+#include "core/global-io.hpp"
+
+#include <pcap/pcap.h>
+
+#include <cerrno>         // for errno
+#include <cstring>        // for memcpy(), strerror(), strncpy()
+#include <net/if.h>       // for struct ifreq
+#include <stdio.h>        // for snprintf()
+#include <sys/ioctl.h>    // for ioctl()
+
+#if defined(__linux__)
+#include <netpacket/packet.h> // for struct packet_mreq
+#include <sys/socket.h>       // for setsockopt()
+#endif
+
+#ifdef SIOCADDMULTI
+#if defined(__APPLE__) || defined(__FreeBSD__)
+#include <net/if_dl.h>    // for struct sockaddr_dl
+#endif
+#endif
+
+namespace nfd {
+namespace face {
+
+NFD_LOG_INIT("MulticastEthernetTransport");
+
+MulticastEthernetTransport::MulticastEthernetTransport(const NetworkInterfaceInfo& localEndpoint,
+                                                       const ethernet::Address& mcastAddress,
+                                                       ndn::nfd::LinkType linkType)
+  : EthernetTransport(localEndpoint, mcastAddress)
+{
+  this->setLocalUri(FaceUri::fromDev(m_interfaceName));
+  this->setRemoteUri(FaceUri(m_destAddress));
+  this->setScope(ndn::nfd::FACE_SCOPE_NON_LOCAL);
+  this->setPersistency(ndn::nfd::FACE_PERSISTENCY_PERMANENT);
+  this->setLinkType(linkType);
+
+  NFD_LOG_FACE_INFO("Creating transport");
+
+  char filter[110];
+  // note #1: we cannot use std::snprintf because it's not available
+  //          on some platforms (see #2299)
+  // note #2: "not vlan" must appear last in the filter expression, or the
+  //          rest of the filter won't work as intended (see pcap-filter(7))
+  snprintf(filter, sizeof(filter),
+           "(ether proto 0x%x) && (ether dst %s) && (not ether src %s) && (not vlan)",
+           ethernet::ETHERTYPE_NDN,
+           m_destAddress.toString().c_str(),
+           m_srcAddress.toString().c_str());
+  setPacketFilter(filter);
+
+  if (!m_destAddress.isBroadcast() && !joinMulticastGroup()) {
+    NFD_LOG_FACE_WARN("Falling back to promiscuous mode");
+    pcap_set_promisc(m_pcap.get(), 1);
+  }
+}
+
+bool
+MulticastEthernetTransport::joinMulticastGroup()
+{
+#if defined(__linux__)
+  packet_mreq mr{};
+  mr.mr_ifindex = m_interfaceIndex;
+  mr.mr_type = PACKET_MR_MULTICAST;
+  mr.mr_alen = m_destAddress.size();
+  std::memcpy(mr.mr_address, m_destAddress.data(), m_destAddress.size());
+
+  if (::setsockopt(m_socket.native_handle(), SOL_PACKET,
+                   PACKET_ADD_MEMBERSHIP, &mr, sizeof(mr)) == 0)
+    return true; // success
+
+  NFD_LOG_FACE_WARN("setsockopt(PACKET_ADD_MEMBERSHIP) failed: " << std::strerror(errno));
+#endif
+
+#if defined(SIOCADDMULTI)
+  ifreq ifr{};
+  std::strncpy(ifr.ifr_name, m_interfaceName.c_str(), sizeof(ifr.ifr_name) - 1);
+
+#if defined(__APPLE__) || defined(__FreeBSD__)
+  // see bug #2327
+  using boost::asio::ip::udp;
+  udp::socket sock(getGlobalIoService(), udp::v4());
+  int fd = sock.native_handle();
+
+  // Differences between Linux and the BSDs (including macOS):
+  //   o BSD does not have ifr_hwaddr; use ifr_addr instead.
+  //   o While macOS seems to accept both AF_LINK and AF_UNSPEC as the address
+  //     family, FreeBSD explicitly requires AF_LINK, so we have to use AF_LINK
+  //     and sockaddr_dl instead of the generic sockaddr structure.
+  //   o BSD's sockaddr (and sockaddr_dl in particular) contains an additional
+  //     field, sa_len (sdl_len), which must be set to the total length of the
+  //     structure, including the length field itself.
+  //   o We do not specify the interface name, thus sdl_nlen is left at 0 and
+  //     LLADDR is effectively the same as sdl_data.
+
+  sockaddr_dl* sdl = reinterpret_cast<sockaddr_dl*>(&ifr.ifr_addr);
+  sdl->sdl_len = sizeof(ifr.ifr_addr);
+  sdl->sdl_family = AF_LINK;
+  sdl->sdl_alen = m_destAddress.size();
+  std::memcpy(LLADDR(sdl), m_destAddress.data(), m_destAddress.size());
+
+  static_assert(sizeof(ifr.ifr_addr) >= offsetof(sockaddr_dl, sdl_data) + ethernet::ADDR_LEN,
+                "ifr_addr in struct ifreq is too small on this platform");
+#else
+  int fd = m_socket.native_handle();
+
+  ifr.ifr_hwaddr.sa_family = AF_UNSPEC;
+  std::memcpy(ifr.ifr_hwaddr.sa_data, m_destAddress.data(), m_destAddress.size());
+
+  static_assert(sizeof(ifr.ifr_hwaddr.sa_data) >= ethernet::ADDR_LEN,
+                "ifr_hwaddr in struct ifreq is too small on this platform");
+#endif
+
+  if (::ioctl(fd, SIOCADDMULTI, &ifr) == 0)
+    return true; // success
+
+  NFD_LOG_FACE_WARN("ioctl(SIOCADDMULTI) failed: " << std::strerror(errno));
+#endif
+
+  return false;
+}
+
+} // namespace face
+} // namespace nfd
diff --git a/daemon/face/multicast-ethernet-transport.hpp b/daemon/face/multicast-ethernet-transport.hpp
new file mode 100644
index 0000000..6d25b41
--- /dev/null
+++ b/daemon/face/multicast-ethernet-transport.hpp
@@ -0,0 +1,59 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2017,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef NFD_DAEMON_FACE_MULTICAST_ETHERNET_TRANSPORT_HPP
+#define NFD_DAEMON_FACE_MULTICAST_ETHERNET_TRANSPORT_HPP
+
+#include "ethernet-transport.hpp"
+
+namespace nfd {
+namespace face {
+
+/**
+ * @brief A multicast Transport that uses raw Ethernet II frames
+ */
+class MulticastEthernetTransport final : public EthernetTransport
+{
+public:
+  /**
+   * @brief Creates an Ethernet-based transport for multicast communication
+   */
+  MulticastEthernetTransport(const NetworkInterfaceInfo& localEndpoint,
+                             const ethernet::Address& mcastAddress,
+                             ndn::nfd::LinkType linkType);
+
+private:
+  /**
+   * @brief Enables receiving frames addressed to our MAC multicast group
+   * @return true if successful, false otherwise
+   */
+  bool
+  joinMulticastGroup();
+};
+
+} // namespace face
+} // namespace nfd
+
+#endif // NFD_DAEMON_FACE_MULTICAST_ETHERNET_TRANSPORT_HPP
diff --git a/daemon/face/unicast-ethernet-transport.cpp b/daemon/face/unicast-ethernet-transport.cpp
new file mode 100644
index 0000000..e21a793
--- /dev/null
+++ b/daemon/face/unicast-ethernet-transport.cpp
@@ -0,0 +1,66 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2017,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "unicast-ethernet-transport.hpp"
+
+#include <stdio.h>  // for snprintf()
+
+namespace nfd {
+namespace face {
+
+NFD_LOG_INIT("UnicastEthernetTransport");
+
+UnicastEthernetTransport::UnicastEthernetTransport(const NetworkInterfaceInfo& localEndpoint,
+                                                   const ethernet::Address& remoteEndpoint,
+                                                   ndn::nfd::FacePersistency persistency,
+                                                   time::nanoseconds idleTimeout)
+  : EthernetTransport(localEndpoint, remoteEndpoint)
+  , m_idleTimeout(idleTimeout)
+{
+  this->setLocalUri(FaceUri::fromDev(m_interfaceName));
+  this->setRemoteUri(FaceUri(m_destAddress));
+  this->setScope(ndn::nfd::FACE_SCOPE_NON_LOCAL);
+  this->setPersistency(persistency);
+  this->setLinkType(ndn::nfd::LINK_TYPE_POINT_TO_POINT);
+
+  NFD_LOG_FACE_INFO("Creating transport");
+
+  char filter[110];
+  // note #1: we cannot use std::snprintf because it's not available
+  //          on some platforms (see #2299)
+  // note #2: "not vlan" must appear last in the filter expression, or the
+  //          rest of the filter won't work as intended (see pcap-filter(7))
+  snprintf(filter, sizeof(filter),
+           "(ether proto 0x%x) && (ether src %s) && (ether dst %s) && (not vlan)",
+           ethernet::ETHERTYPE_NDN,
+           m_destAddress.toString().c_str(),
+           m_srcAddress.toString().c_str());
+  setPacketFilter(filter);
+
+  // TODO: implement close on idle and persistency change
+}
+
+} // namespace face
+} // namespace nfd
diff --git a/daemon/face/unicast-ethernet-transport.hpp b/daemon/face/unicast-ethernet-transport.hpp
new file mode 100644
index 0000000..b1a856b
--- /dev/null
+++ b/daemon/face/unicast-ethernet-transport.hpp
@@ -0,0 +1,55 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2017,  Regents of the University of California,
+ *                           Arizona Board of Regents,
+ *                           Colorado State University,
+ *                           University Pierre & Marie Curie, Sorbonne University,
+ *                           Washington University in St. Louis,
+ *                           Beijing Institute of Technology,
+ *                           The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef NFD_DAEMON_FACE_UNICAST_ETHERNET_TRANSPORT_HPP
+#define NFD_DAEMON_FACE_UNICAST_ETHERNET_TRANSPORT_HPP
+
+#include "ethernet-transport.hpp"
+
+namespace nfd {
+namespace face {
+
+/**
+ * @brief A unicast Transport that uses raw Ethernet II frames
+ */
+class UnicastEthernetTransport final : public EthernetTransport
+{
+public:
+  /**
+   * @brief Creates an Ethernet-based transport for unicast communication
+   */
+  UnicastEthernetTransport(const NetworkInterfaceInfo& localEndpoint,
+                           const ethernet::Address& remoteEndpoint,
+                           ndn::nfd::FacePersistency persistency,
+                           time::nanoseconds idleTimeout);
+
+private:
+  const time::nanoseconds m_idleTimeout;
+};
+
+} // namespace face
+} // namespace nfd
+
+#endif // NFD_DAEMON_FACE_UNICAST_ETHERNET_TRANSPORT_HPP