face: EthernetTransport

This commit replaces NDNLPv1 fragmentation and reassembly
with NDNLPv2 fragmentation and reassembly.

Change-Id: I10751157fcced94d441167ce470aaed79c12bb54
Refs: #3170
diff --git a/daemon/face/ethernet-factory.cpp b/daemon/face/ethernet-factory.cpp
index 3d88590..3592530 100644
--- a/daemon/face/ethernet-factory.cpp
+++ b/daemon/face/ethernet-factory.cpp
@@ -24,16 +24,16 @@
  */
 
 #include "ethernet-factory.hpp"
-#include "face/ethernet-face.hpp"
-
-#include "core/logger.hpp"
+#include "ethernet-transport.hpp"
+#include "generic-link-service.hpp"
+#include "lp-face-wrapper.hpp"
 #include "core/global-io.hpp"
 
 namespace nfd {
 
-shared_ptr<EthernetFace>
+shared_ptr<face::LpFaceWrapper>
 EthernetFactory::createMulticastFace(const NetworkInterfaceInfo& interface,
-                                     const ethernet::Address &address)
+                                     const ethernet::Address& address)
 {
   if (!address.isMulticast())
     BOOST_THROW_EXCEPTION(Error(address.toString() + " is not a multicast address"));
@@ -42,8 +42,14 @@
   if (face)
     return face;
 
-  face = make_shared<EthernetFace>(boost::asio::posix::stream_descriptor(getGlobalIoService()),
-                                   interface, address);
+  face::GenericLinkService::Options opts;
+  opts.allowFragmentation = true;
+  opts.allowReassembly = true;
+
+  auto linkService = make_unique<face::GenericLinkService>(opts);
+  auto transport = make_unique<face::EthernetTransport>(interface, address);
+  auto lpFace = make_unique<face::LpFace>(std::move(linkService), std::move(transport));
+  face = make_shared<face::LpFaceWrapper>(std::move(lpFace));
 
   auto key = std::make_pair(interface.name, address);
   face->onFail.connectSingleShot([this, key] (const std::string& reason) {
@@ -69,7 +75,7 @@
   return {};
 }
 
-shared_ptr<EthernetFace>
+shared_ptr<face::LpFaceWrapper>
 EthernetFactory::findMulticastFace(const std::string& interfaceName,
                                    const ethernet::Address& address) const
 {
diff --git a/daemon/face/ethernet-factory.hpp b/daemon/face/ethernet-factory.hpp
index c14e093..c0a951d 100644
--- a/daemon/face/ethernet-factory.hpp
+++ b/daemon/face/ethernet-factory.hpp
@@ -31,7 +31,9 @@
 
 namespace nfd {
 
-class EthernetFace;
+namespace face {
+class LpFaceWrapper;
+} // namespace face
 
 class EthernetFactory : public ProtocolFactory
 {
@@ -50,7 +52,7 @@
   };
 
   typedef std::map<std::pair<std::string, ethernet::Address>,
-                   shared_ptr<EthernetFace>> MulticastFaceMap;
+                   shared_ptr<face::LpFaceWrapper>> MulticastFaceMap;
 
   /**
    * \brief Create an EthernetFace to communicate with the given multicast group
@@ -65,9 +67,9 @@
    * \returns always a valid shared pointer to an EthernetFace object,
    *          an exception will be thrown if the creation fails
    *
-   * \throws EthernetFactory::Error or EthernetFace::Error
+   * \throws EthernetFactory::Error or EthernetTransport::Error
    */
-  shared_ptr<EthernetFace>
+  shared_ptr<face::LpFaceWrapper>
   createMulticastFace(const NetworkInterfaceInfo& interface,
                       const ethernet::Address& address);
 
@@ -94,7 +96,7 @@
    * \returns shared pointer to the existing EthernetFace object
    *          or nullptr when such face does not exist
    */
-  shared_ptr<EthernetFace>
+  shared_ptr<face::LpFaceWrapper>
   findMulticastFace(const std::string& interfaceName,
                     const ethernet::Address& address) const;
 
diff --git a/daemon/face/ethernet-face.cpp b/daemon/face/ethernet-transport.cpp
similarity index 62%
rename from daemon/face/ethernet-face.cpp
rename to daemon/face/ethernet-transport.cpp
index f31a8f6..5318097 100644
--- a/daemon/face/ethernet-face.cpp
+++ b/daemon/face/ethernet-transport.cpp
@@ -23,17 +23,17 @@
  * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-#include "ethernet-face.hpp"
+#include "ethernet-transport.hpp"
 #include "core/global-io.hpp"
 
 #include <pcap/pcap.h>
 
 #include <cerrno>         // for errno
-#include <cstring>        // for std::strerror() and std::strncpy()
-#include <stdio.h>        // for snprintf()
+#include <cstring>        // for memcpy(), strerror(), strncpy()
 #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()
 
@@ -57,28 +57,32 @@
 #endif
 
 namespace nfd {
+namespace face {
 
-NFD_LOG_INIT("EthernetFace");
+NFD_LOG_INIT("EthernetTransport");
 
-const time::nanoseconds EthernetFace::REASSEMBLER_LIFETIME = time::seconds(60);
-
-EthernetFace::EthernetFace(boost::asio::posix::stream_descriptor socket,
-                           const NetworkInterfaceInfo& interface,
-                           const ethernet::Address& address)
-  : Face(FaceUri(address), FaceUri::fromDev(interface.name), false, true)
-  , m_pcap(nullptr, pcap_close)
-  , m_socket(std::move(socket))
+EthernetTransport::EthernetTransport(const NetworkInterfaceInfo& interface,
+                                     const ethernet::Address& mcastAddress)
+  : m_pcap(nullptr, pcap_close)
+  , m_socket(getGlobalIoService())
+  , m_srcAddress(interface.etherAddress)
+  , m_destAddress(mcastAddress)
+  , m_interfaceName(interface.name)
 #if defined(__linux__)
   , m_interfaceIndex(interface.index)
 #endif
-  , m_interfaceName(interface.name)
-  , m_srcAddress(interface.etherAddress)
-  , m_destAddress(address)
 #ifdef _DEBUG
   , m_nDropped(0)
 #endif
 {
-  NFD_LOG_FACE_INFO("Creating face on " << m_interfaceName << "/" << m_srcAddress);
+  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(ndn::nfd::LINK_TYPE_MULTI_ACCESS);
+
+  NFD_LOG_FACE_INFO("Creating transport");
+
   pcapInit();
 
   int fd = pcap_get_selectable_fd(m_pcap.get());
@@ -90,10 +94,8 @@
   // same fd and one of them will fail
   m_socket.assign(::dup(fd));
 
-  m_interfaceMtu = getInterfaceMtu();
-  NFD_LOG_FACE_DEBUG("Interface MTU is: " << m_interfaceMtu);
-
-  m_slicer.reset(new ndnlp::Slicer(m_interfaceMtu));
+  // do this after assigning m_socket because getInterfaceMtu uses it
+  this->setMtu(getInterfaceMtu());
 
   char filter[100];
   // std::snprintf not found in some environments
@@ -105,62 +107,54 @@
            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);
-    }
+  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(&EthernetFace::handleRead, this,
+                           bind(&EthernetTransport::handleRead, this,
                                 boost::asio::placeholders::error,
                                 boost::asio::placeholders::bytes_transferred));
 }
 
-void
-EthernetFace::sendInterest(const Interest& interest)
+void EthernetTransport::beforeChangePersistency(ndn::nfd::FacePersistency newPersistency)
 {
-  NFD_LOG_FACE_TRACE(__func__);
-
-  this->emitSignal(onSendInterest, interest);
-
-  ndnlp::PacketArray pa = m_slicer->slice(interest.wireEncode());
-  for (const auto& packet : *pa) {
-    sendPacket(packet);
+  if (newPersistency != ndn::nfd::FACE_PERSISTENCY_PERMANENT) {
+    BOOST_THROW_EXCEPTION(
+      std::invalid_argument("EthernetTransport supports only FACE_PERSISTENCY_PERMANENT"));
   }
 }
 
-void
-EthernetFace::sendData(const Data& data)
+void EthernetTransport::doSend(Transport::Packet&& packet)
 {
   NFD_LOG_FACE_TRACE(__func__);
 
-  this->emitSignal(onSendData, data);
-
-  ndnlp::PacketArray pa = m_slicer->slice(data.wireEncode());
-  for (const auto& packet : *pa) {
-    sendPacket(packet);
-  }
+  sendPacket(packet.packet);
 }
 
-void
-EthernetFace::close()
+void EthernetTransport::doClose()
 {
-  if (!m_pcap)
-    return;
+  NFD_LOG_FACE_TRACE(__func__);
 
-  NFD_LOG_FACE_INFO("Closing face");
-
-  boost::system::error_code error;
-  m_socket.cancel(error); // ignore errors
-  m_socket.close(error);  // ignore errors
+  if (m_socket.is_open()) {
+    // Cancel all outstanding operations and close the socket.
+    // Use the non-throwing variants and ignore errors, if any.
+    boost::system::error_code error;
+    m_socket.cancel(error);
+    m_socket.close(error);
+  }
   m_pcap.reset();
 
-  fail("Face closed");
+  // Ensure that the Transport stays alive at least
+  // until all pending handlers are dispatched
+  getGlobalIoService().post([this] {
+    this->setState(TransportState::CLOSED);
+  });
 }
 
 void
-EthernetFace::pcapInit()
+EthernetTransport::pcapInit()
 {
   char errbuf[PCAP_ERRBUF_SIZE] = {};
   m_pcap.reset(pcap_create(m_interfaceName.c_str(), errbuf));
@@ -187,7 +181,7 @@
 }
 
 void
-EthernetFace::setPacketFilter(const char* filterString)
+EthernetTransport::setPacketFilter(const char* filterString)
 {
   bpf_program filter;
   if (pcap_compile(m_pcap.get(), &filter, filterString, 1, PCAP_NETMASK_UNKNOWN) < 0)
@@ -200,14 +194,14 @@
 }
 
 bool
-EthernetFace::joinMulticastGroup()
+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::copy(m_destAddress.begin(), m_destAddress.end(), mr.mr_address);
+  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)
@@ -223,7 +217,7 @@
 #if defined(__APPLE__) || defined(__FreeBSD__)
   // see bug #2327
   using boost::asio::ip::udp;
-  udp::socket sock(ref(getGlobalIoService()), udp::v4());
+  udp::socket sock(getGlobalIoService(), udp::v4());
   int fd = sock.native_handle();
 
   /*
@@ -242,7 +236,7 @@
   sdl->sdl_len = sizeof(ifr.ifr_addr);
   sdl->sdl_family = AF_LINK;
   sdl->sdl_alen = m_destAddress.size();
-  std::copy(m_destAddress.begin(), m_destAddress.end(), LLADDR(sdl));
+  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");
@@ -250,9 +244,9 @@
   int fd = m_socket.native_handle();
 
   ifr.ifr_hwaddr.sa_family = AF_UNSPEC;
-  std::copy(m_destAddress.begin(), m_destAddress.end(), ifr.ifr_hwaddr.sa_data);
+  std::memcpy(ifr.ifr_hwaddr.sa_data, m_destAddress.data(), m_destAddress.size());
 
-  static_assert(sizeof(ifr.ifr_hwaddr) >= offsetof(sockaddr, sa_data) + ethernet::ADDR_LEN,
+  static_assert(sizeof(ifr.ifr_hwaddr.sa_data) >= ethernet::ADDR_LEN,
                 "ifr_hwaddr in struct ifreq is too small on this platform");
 #endif
 
@@ -266,26 +260,17 @@
 }
 
 void
-EthernetFace::sendPacket(const ndn::Block& block)
+EthernetTransport::sendPacket(const ndn::Block& block)
 {
-  if (!m_pcap)
-    {
-      NFD_LOG_FACE_WARN("Trying to send on closed face");
-      return fail("Face closed");
-    }
-
-  BOOST_ASSERT(block.size() <= m_interfaceMtu);
-
   /// \todo Right now there is no reserve when packet is received, but
   ///       we should reserve some space at the beginning and at the end
   ndn::EncodingBuffer buffer(block);
 
   // pad with zeroes if the payload is too short
-  if (block.size() < ethernet::MIN_DATA_LEN)
-    {
-      static const uint8_t padding[ethernet::MIN_DATA_LEN] = {};
-      buffer.appendByteArray(padding, ethernet::MIN_DATA_LEN - block.size());
-    }
+  if (block.size() < ethernet::MIN_DATA_LEN) {
+    static const uint8_t padding[ethernet::MIN_DATA_LEN] = {};
+    buffer.appendByteArray(padding, ethernet::MIN_DATA_LEN - block.size());
+  }
 
   // construct and prepend the ethernet header
   static uint16_t ethertype = htons(ethernet::ETHERTYPE_NDN);
@@ -296,66 +281,53 @@
   // send the packet
   int sent = pcap_inject(m_pcap.get(), buffer.buf(), buffer.size());
   if (sent < 0)
-    {
-      return fail("pcap_inject: " + std::string(pcap_geterr(m_pcap.get())));
-    }
+    NFD_LOG_FACE_ERROR("pcap_inject failed: " << pcap_geterr(m_pcap.get()));
   else if (static_cast<size_t>(sent) < buffer.size())
-    {
-      return fail("Failed to inject frame");
-    }
-
-  NFD_LOG_FACE_TRACE("Successfully sent: " << block.size() << " bytes");
-  this->getMutableCounters().getNOutBytes() += block.size();
+    NFD_LOG_FACE_ERROR("Failed to send the full frame: bufsize=" << buffer.size() << " sent=" << sent);
+  else
+    // print block size because we don't want to count the padding in buffer
+    NFD_LOG_FACE_TRACE("Successfully sent: " << block.size() << " bytes");
 }
 
 void
-EthernetFace::handleRead(const boost::system::error_code& error, size_t)
+EthernetTransport::handleRead(const boost::system::error_code& error, size_t)
 {
-  if (!m_pcap)
-    return fail("Face closed");
-
   if (error)
     return processErrorCode(error);
 
   pcap_pkthdr* header;
   const uint8_t* packet;
+
+  // read the pcap header and packet data
   int ret = pcap_next_ex(m_pcap.get(), &header, &packet);
   if (ret < 0)
-    {
-      return fail("pcap_next_ex: " + std::string(pcap_geterr(m_pcap.get())));
-    }
+    NFD_LOG_FACE_ERROR("pcap_next_ex failed: " << pcap_geterr(m_pcap.get()));
   else if (ret == 0)
-    {
-      NFD_LOG_FACE_WARN("Read timeout");
-    }
+    NFD_LOG_FACE_WARN("Read timeout");
   else
-    {
-      processIncomingPacket(header, packet);
-    }
+    processIncomingPacket(header, packet);
 
 #ifdef _DEBUG
   pcap_stat ps{};
   ret = pcap_stats(m_pcap.get(), &ps);
-  if (ret < 0)
-    {
-      NFD_LOG_FACE_DEBUG("pcap_stats failed: " << pcap_geterr(m_pcap.get()));
-    }
-  else if (ret == 0)
-    {
-      if (ps.ps_drop - m_nDropped > 0)
-        NFD_LOG_FACE_DEBUG("Detected " << ps.ps_drop - m_nDropped << " dropped packet(s)");
-      m_nDropped = ps.ps_drop;
-    }
+  if (ret < 0) {
+    NFD_LOG_FACE_DEBUG("pcap_stats failed: " << pcap_geterr(m_pcap.get()));
+  }
+  else if (ret == 0) {
+    if (ps.ps_drop - m_nDropped > 0)
+      NFD_LOG_FACE_DEBUG("Detected " << ps.ps_drop - m_nDropped << " dropped packet(s)");
+    m_nDropped = ps.ps_drop;
+  }
 #endif
 
   m_socket.async_read_some(boost::asio::null_buffers(),
-                           bind(&EthernetFace::handleRead, this,
+                           bind(&EthernetTransport::handleRead, this,
                                 boost::asio::placeholders::error,
                                 boost::asio::placeholders::bytes_transferred));
 }
 
 void
-EthernetFace::processIncomingPacket(const pcap_pkthdr* header, const uint8_t* packet)
+EthernetTransport::processIncomingPacket(const pcap_pkthdr* header, const uint8_t* packet)
 {
   size_t length = header->caplen;
   if (length < ethernet::HDR_LEN + ethernet::MIN_DATA_LEN) {
@@ -377,77 +349,46 @@
   packet += ethernet::HDR_LEN;
   length -= ethernet::HDR_LEN;
 
-  /// \todo Reserve space in front and at the back of the underlying buffer
   bool isOk = false;
-  Block fragmentBlock;
-  std::tie(isOk, fragmentBlock) = Block::fromBuffer(packet, length);
+  Block element;
+  std::tie(isOk, element) = Block::fromBuffer(packet, length);
   if (!isOk) {
-    NFD_LOG_FACE_WARN("Block received from " << sourceAddress.toString()
-                      << " is invalid or too large to process");
+    NFD_LOG_FACE_WARN("Received invalid packet from " << sourceAddress.toString());
     return;
   }
 
-  NFD_LOG_FACE_TRACE("Received: " << fragmentBlock.size() << " bytes from "
-                     << sourceAddress.toString());
-  this->getMutableCounters().getNInBytes() += fragmentBlock.size();
+  NFD_LOG_FACE_TRACE("Received: " << element.size() << " bytes from " << sourceAddress.toString());
 
-  Reassembler& reassembler = m_reassemblers[sourceAddress];
-  if (!reassembler.pms) {
-    // new sender, setup a PartialMessageStore for it
-    reassembler.pms.reset(new ndnlp::PartialMessageStore);
-    reassembler.pms->onReceive.connect(
-      [this, sourceAddress] (const Block& block) {
-        NFD_LOG_FACE_TRACE("All fragments received from " << sourceAddress.toString());
-        if (!decodeAndDispatchInput(block))
-          NFD_LOG_FACE_WARN("Received unrecognized TLV block of type " << block.type()
-                            << " from " << sourceAddress.toString());
-      });
-  }
-
-  scheduler::cancel(reassembler.expireEvent);
-  reassembler.expireEvent = scheduler::schedule(REASSEMBLER_LIFETIME,
-    [this, sourceAddress] {
-      BOOST_VERIFY(m_reassemblers.erase(sourceAddress) == 1);
-    });
-
-  ndnlp::NdnlpData fragment;
-  std::tie(isOk, fragment) = ndnlp::NdnlpData::fromBlock(fragmentBlock);
-  if (!isOk) {
-    NFD_LOG_FACE_WARN("Received invalid NDNLP fragment from " << sourceAddress.toString());
-    return;
-  }
-
-  reassembler.pms->receive(fragment);
+  Transport::Packet tp(std::move(element));
+  static_assert(sizeof(tp.remoteEndpoint) >= ethernet::ADDR_LEN,
+                "Transport::Packet::remoteEndpoint is too small");
+  std::memcpy(&tp.remoteEndpoint, sourceAddress.data(), sourceAddress.size());
+  this->receive(std::move(tp));
 }
 
 void
-EthernetFace::processErrorCode(const boost::system::error_code& error)
+EthernetTransport::processErrorCode(const boost::system::error_code& error)
 {
-  if (error == boost::asio::error::operation_aborted)
-    // cancel() has been called on the socket
+  NFD_LOG_FACE_TRACE(__func__);
+
+  if (getState() == TransportState::CLOSING ||
+      getState() == TransportState::FAILED ||
+      getState() == TransportState::CLOSED ||
+      error == boost::asio::error::operation_aborted)
+    // transport is shutting down, ignore any errors
     return;
 
-  std::string msg;
-  if (error == boost::asio::error::eof)
-    {
-      msg = "Face closed";
-    }
-  else
-    {
-      msg = "Receive operation failed: " + error.message();
-      NFD_LOG_FACE_WARN(msg);
-    }
-  fail(msg);
+  NFD_LOG_FACE_WARN("Receive operation failed: " << error.message());
 }
 
 size_t
-EthernetFace::getInterfaceMtu()
+EthernetTransport::getInterfaceMtu()
 {
 #ifdef SIOCGIFMTU
 #if defined(__APPLE__) || defined(__FreeBSD__)
   // see bug #2328
   using boost::asio::ip::udp;
-  udp::socket sock(ref(getGlobalIoService()), udp::v4());
+  udp::socket sock(getGlobalIoService(), udp::v4());
   int fd = sock.native_handle();
 #else
   int fd = m_socket.native_handle();
@@ -456,13 +397,17 @@
   ifreq ifr{};
   std::strncpy(ifr.ifr_name, m_interfaceName.c_str(), sizeof(ifr.ifr_name) - 1);
 
-  if (::ioctl(fd, SIOCGIFMTU, &ifr) == 0)
+  if (::ioctl(fd, SIOCGIFMTU, &ifr) == 0) {
+    NFD_LOG_FACE_DEBUG("Interface MTU is " << ifr.ifr_mtu);
     return static_cast<size_t>(ifr.ifr_mtu);
+  }
 
   NFD_LOG_FACE_WARN("Failed to get interface MTU: " << std::strerror(errno));
 #endif
 
+  NFD_LOG_FACE_DEBUG("Assuming default MTU of " << ethernet::MAX_DATA_LEN);
   return ethernet::MAX_DATA_LEN;
 }
 
+} // namespace face
 } // namespace nfd
diff --git a/daemon/face/ethernet-face.hpp b/daemon/face/ethernet-transport.hpp
similarity index 71%
rename from daemon/face/ethernet-face.hpp
rename to daemon/face/ethernet-transport.hpp
index 2bbd781..1d41008 100644
--- a/daemon/face/ethernet-face.hpp
+++ b/daemon/face/ethernet-transport.hpp
@@ -23,17 +23,13 @@
  * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-#ifndef NFD_DAEMON_FACE_ETHERNET_FACE_HPP
-#define NFD_DAEMON_FACE_ETHERNET_FACE_HPP
+#ifndef NFD_DAEMON_FACE_ETHERNET_TRANSPORT_HPP
+#define NFD_DAEMON_FACE_ETHERNET_TRANSPORT_HPP
 
 #include "common.hpp"
-#include "face.hpp"
-#include "ndnlp-partial-message-store.hpp"
-#include "ndnlp-slicer.hpp"
+#include "transport.hpp"
 #include "core/network-interface.hpp"
 
-#include <unordered_map>
-
 #ifndef HAVE_LIBPCAP
 #error "Cannot include this file when libpcap is not available"
 #endif
@@ -44,43 +40,41 @@
 struct pcap_pkthdr;
 
 namespace nfd {
+namespace face {
 
 /**
- * @brief Implementation of Face abstraction that uses raw
- *        Ethernet frames as underlying transport mechanism
+ * \brief A multicast Transport that uses raw Ethernet II frames
  */
-class EthernetFace : public Face
+class EthernetTransport DECL_CLASS_FINAL : public Transport
 {
 public:
-  /**
-   * @brief EthernetFace-related error
-   */
-  struct Error : public Face::Error
+  class Error : public std::runtime_error
   {
-    Error(const std::string& what) : Face::Error(what) {}
+  public:
+    explicit
+    Error(const std::string& what)
+      : std::runtime_error(what)
+    {
+    }
   };
 
-  EthernetFace(boost::asio::posix::stream_descriptor socket,
-               const NetworkInterfaceInfo& interface,
-               const ethernet::Address& address);
-
-  /// send an Interest
-  void
-  sendInterest(const Interest& interest) DECL_OVERRIDE;
-
-  /// send a Data
-  void
-  sendData(const Data& data) DECL_OVERRIDE;
-
   /**
-   * @brief Closes the face
-   *
-   * This terminates all communication on the face and triggers the onFail() event.
+   * @brief Creates an Ethernet-based transport for multicast communication
    */
-  void
-  close() DECL_OVERRIDE;
+  EthernetTransport(const NetworkInterfaceInfo& interface,
+                    const ethernet::Address& mcastAddress);
+
+protected:
+  virtual void
+  beforeChangePersistency(ndn::nfd::FacePersistency newPersistency) DECL_FINAL;
+
+  virtual void
+  doClose() DECL_FINAL;
 
 private:
+  virtual void
+  doSend(Transport::Packet&& packet) DECL_FINAL;
+
   /**
    * @brief Allocates and initializes a libpcap context for live capture
    */
@@ -139,26 +133,15 @@
   getInterfaceMtu();
 
 private:
-  struct Reassembler
-  {
-    unique_ptr<ndnlp::PartialMessageStore> pms;
-    scheduler::EventId expireEvent;
-  };
-
   unique_ptr<pcap_t, void(*)(pcap_t*)> m_pcap;
   boost::asio::posix::stream_descriptor m_socket;
 
+  ethernet::Address m_srcAddress;
+  ethernet::Address m_destAddress;
+  std::string m_interfaceName;
 #if defined(__linux__)
   int m_interfaceIndex;
 #endif
-  std::string m_interfaceName;
-  ethernet::Address m_srcAddress;
-  ethernet::Address m_destAddress;
-
-  size_t m_interfaceMtu;
-  unique_ptr<ndnlp::Slicer> m_slicer;
-  std::unordered_map<ethernet::Address, Reassembler> m_reassemblers;
-  static const time::nanoseconds REASSEMBLER_LIFETIME;
 
 #ifdef _DEBUG
   /// number of packets dropped by the kernel, as reported by libpcap
@@ -166,6 +149,7 @@
 #endif
 };
 
+} // namespace face
 } // namespace nfd
 
-#endif // NFD_DAEMON_FACE_ETHERNET_FACE_HPP
+#endif // NFD_DAEMON_FACE_ETHERNET_TRANSPORT_HPP
diff --git a/daemon/face/ndnlp-data.cpp b/daemon/face/ndnlp-data.cpp
deleted file mode 100644
index 7cfacac..0000000
--- a/daemon/face/ndnlp-data.cpp
+++ /dev/null
@@ -1,108 +0,0 @@
-/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
-/**
- * Copyright (c) 2014-2015,  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 "ndnlp-data.hpp"
-
-namespace nfd {
-namespace ndnlp {
-
-std::tuple<bool, NdnlpData>
-NdnlpData::fromBlock(const Block& wire)
-{
-  if (wire.type() != tlv::NdnlpData) {
-    // top element is not NdnlpData
-    return std::make_tuple(false, NdnlpData());
-  }
-  wire.parse();
-  const Block::element_container& elements = wire.elements();
-  if (elements.size() < 2) {
-    // NdnlpData element has incorrect number of children
-    return std::make_tuple(false, NdnlpData());
-  }
-
-  NdnlpData parsed;
-
-  const Block& sequenceElement = elements.front();
-  if (sequenceElement.type() != tlv::NdnlpSequence) {
-    // NdnlpSequence element is missing
-    return std::make_tuple(false, NdnlpData());
-  }
-  if (sequenceElement.value_size() != sizeof(uint64_t)) {
-    // NdnlpSequence element has incorrect length
-    return std::make_tuple(false, NdnlpData());
-  }
-  parsed.seq = be64toh(*reinterpret_cast<const uint64_t*>(&*sequenceElement.value_begin()));
-
-  const Block& payloadElement = elements.back();
-  if (payloadElement.type() != tlv::NdnlpPayload) {
-    // NdnlpPayload element is missing
-    return std::make_tuple(false, NdnlpData());
-  }
-  parsed.payload = payloadElement;
-
-  if (elements.size() == 2) { // single wire packet
-    parsed.fragIndex = 0;
-    parsed.fragCount = 1;
-    return std::make_tuple(true, parsed);
-  }
-  if (elements.size() != 4) {
-    // NdnlpData element has incorrect number of children
-    return std::make_tuple(false, NdnlpData());
-  }
-
-  const Block& fragIndexElement = elements.at(1);
-  if (fragIndexElement.type() != tlv::NdnlpFragIndex) {
-    // NdnlpFragIndex element is missing
-    return std::make_tuple(false, NdnlpData());
-  }
-  uint64_t fragIndex = ndn::readNonNegativeInteger(fragIndexElement);
-  if (fragIndex > std::numeric_limits<uint16_t>::max()) {
-    // NdnlpFragIndex is too large
-    return std::make_tuple(false, NdnlpData());
-  }
-  parsed.fragIndex = static_cast<uint16_t>(fragIndex);
-
-  const Block& fragCountElement = elements.at(2);
-  if (fragCountElement.type() != tlv::NdnlpFragCount) {
-    // NdnlpFragCount element is missing
-    return std::make_tuple(false, NdnlpData());
-  }
-  uint64_t fragCount = ndn::readNonNegativeInteger(fragCountElement);
-  if (fragCount > std::numeric_limits<uint16_t>::max()) {
-    // NdnlpFragCount is too large
-    return std::make_tuple(false, NdnlpData());
-  }
-  parsed.fragCount = static_cast<uint16_t>(fragCount);
-
-  if (parsed.fragIndex >= parsed.fragCount) {
-    // NdnlpFragIndex must be less than NdnlpFragCount
-    return std::make_tuple(false, NdnlpData());
-  }
-
-  return std::make_tuple(true, parsed);
-}
-
-} // namespace ndnlp
-} // namespace nfd
diff --git a/daemon/face/ndnlp-data.hpp b/daemon/face/ndnlp-data.hpp
deleted file mode 100644
index fd921e2..0000000
--- a/daemon/face/ndnlp-data.hpp
+++ /dev/null
@@ -1,62 +0,0 @@
-/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
-/**
- * Copyright (c) 2014-2015,  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_NDNLP_PARSE_HPP
-#define NFD_DAEMON_FACE_NDNLP_PARSE_HPP
-
-#include "common.hpp"
-#include "ndnlp-tlv.hpp"
-
-namespace nfd {
-namespace ndnlp {
-
-/** \brief represents a NdnlpData packet
- *
- *  NdnlpData ::= NDNLP-DATA-TYPE TLV-LENGTH
- *                  NdnlpSequence
- *                  NdnlpFragIndex?
- *                  NdnlpFragCount?
- *                  NdnlpPayload
- */
-class NdnlpData
-{
-public:
-  /** \brief parse a NdnlpData packet
-   *  \return whether \p wire has a valid NdnlpData packet, and the parsed packet
-   */
-  static std::tuple<bool, NdnlpData>
-  fromBlock(const Block& wire);
-
-public:
-  uint64_t seq;
-  uint16_t fragIndex;
-  uint16_t fragCount;
-  Block payload;
-};
-
-} // namespace ndnlp
-} // namespace nfd
-
-#endif // NFD_DAEMON_FACE_NDNLP_PARSE_HPP
diff --git a/daemon/face/ndnlp-partial-message-store.cpp b/daemon/face/ndnlp-partial-message-store.cpp
deleted file mode 100644
index 252e77d..0000000
--- a/daemon/face/ndnlp-partial-message-store.cpp
+++ /dev/null
@@ -1,152 +0,0 @@
-/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
-/**
- * Copyright (c) 2014-2015,  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 "ndnlp-partial-message-store.hpp"
-#include "core/logger.hpp"
-
-namespace nfd {
-namespace ndnlp {
-
-NFD_LOG_INIT("NdnlpPartialMessageStore");
-
-PartialMessage::PartialMessage()
-  : m_fragCount(0)
-  , m_received(0)
-  , m_totalLength(0)
-{
-}
-
-bool
-PartialMessage::add(uint16_t fragIndex, uint16_t fragCount, const Block& payload)
-{
-  if (m_received == 0) { // first packet
-    m_fragCount = fragCount;
-    m_payloads.resize(fragCount);
-  }
-
-  if (m_fragCount != fragCount || fragIndex >= m_fragCount) {
-    return false;
-  }
-
-  if (!m_payloads[fragIndex].empty()) { // duplicate
-    return false;
-  }
-
-  m_payloads[fragIndex] = payload;
-  ++m_received;
-  m_totalLength += payload.value_size();
-  return true;
-}
-
-bool
-PartialMessage::isComplete() const
-{
-  return m_received == m_fragCount;
-}
-
-std::tuple<bool, Block>
-PartialMessage::reassemble()
-{
-  BOOST_ASSERT(this->isComplete());
-
-  ndn::BufferPtr buffer = make_shared<ndn::Buffer>(m_totalLength);
-  ndn::Buffer::iterator buf = buffer->begin();
-  for (const Block& payload : m_payloads) {
-    buf = std::copy(payload.value_begin(), payload.value_end(), buf);
-  }
-  BOOST_ASSERT(buf == buffer->end());
-
-  return Block::fromBuffer(buffer, 0);
-}
-
-std::tuple<bool, Block>
-PartialMessage::reassembleSingle(const NdnlpData& fragment)
-{
-  BOOST_ASSERT(fragment.fragCount == 1);
-
-  try {
-    return std::make_tuple(true, fragment.payload.blockFromValue());
-  }
-  catch (tlv::Error&) {
-    return std::make_tuple(false, Block());
-  }
-}
-
-PartialMessageStore::PartialMessageStore(const time::nanoseconds& idleDuration)
-  : m_idleDuration(idleDuration)
-{
-}
-
-void
-PartialMessageStore::receive(const NdnlpData& pkt)
-{
-  bool isReassembled = false;
-  Block reassembled;
-
-  if (pkt.fragCount == 1) { // single fragment
-    std::tie(isReassembled, reassembled) = PartialMessage::reassembleSingle(pkt);
-  }
-  else {
-    uint64_t messageIdentifier = pkt.seq - pkt.fragIndex;
-    PartialMessage& pm = m_partialMessages[messageIdentifier];
-    this->scheduleCleanup(messageIdentifier, pm);
-
-    pm.add(pkt.fragIndex, pkt.fragCount, pkt.payload);
-
-    if (pm.isComplete()) {
-      std::tie(isReassembled, reassembled) = pm.reassemble();
-      m_partialMessages.erase(messageIdentifier);
-    }
-    else {
-      return;
-    }
-  }
-
-  if (!isReassembled) {
-    NFD_LOG_TRACE(pkt.seq << " reassemble error");
-    return;
-  }
-
-  NFD_LOG_TRACE(pkt.seq << " deliver");
-  this->onReceive(reassembled);
-}
-
-void
-PartialMessageStore::scheduleCleanup(uint64_t messageIdentifier,
-                                     PartialMessage& partialMessage)
-{
-  partialMessage.expiry = scheduler::schedule(m_idleDuration,
-    bind(&PartialMessageStore::cleanup, this, messageIdentifier));
-}
-
-void
-PartialMessageStore::cleanup(uint64_t messageIdentifier)
-{
-  NFD_LOG_TRACE(messageIdentifier << " cleanup");
-  m_partialMessages.erase(messageIdentifier);
-}
-
-} // namespace ndnlp
-} // namespace nfd
diff --git a/daemon/face/ndnlp-partial-message-store.hpp b/daemon/face/ndnlp-partial-message-store.hpp
deleted file mode 100644
index 25afcd8..0000000
--- a/daemon/face/ndnlp-partial-message-store.hpp
+++ /dev/null
@@ -1,117 +0,0 @@
-/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
-/**
- * Copyright (c) 2014-2015,  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_NDNLP_PARTIAL_MESSAGE_STORE_HPP
-#define NFD_DAEMON_FACE_NDNLP_PARTIAL_MESSAGE_STORE_HPP
-
-#include "ndnlp-data.hpp"
-#include "core/scheduler.hpp"
-
-namespace nfd {
-namespace ndnlp {
-
-/** \brief represents a partially received message
- */
-class PartialMessage
-{
-public:
-  PartialMessage();
-
-  PartialMessage(const PartialMessage&) = delete;
-
-  PartialMessage&
-  operator=(const PartialMessage&) = delete;
-
-  PartialMessage(PartialMessage&&) = default;
-
-  PartialMessage&
-  operator=(PartialMessage&&) = default;
-
-  bool
-  add(uint16_t fragIndex, uint16_t fragCount, const Block& payload);
-
-  bool
-  isComplete() const;
-
-  /** \brief reassemble network layer packet
-   *  \pre isComplete() == true
-   *  \return whether success, network layer packet
-   */
-  std::tuple<bool, Block>
-  reassemble();
-
-  /** \brief reassemble network layer packet from a single fragment
-   *  \pre fragment.fragCount == 1
-   *  \return whether success, network layer packet
-   */
-  static std::tuple<bool, Block>
-  reassembleSingle(const NdnlpData& fragment);
-
-public:
-  scheduler::ScopedEventId expiry;
-
-private:
-  size_t m_fragCount;
-  size_t m_received;
-  std::vector<Block> m_payloads;
-  size_t m_totalLength;
-};
-
-/** \brief provides reassembly feature at receiver
- */
-class PartialMessageStore : noncopyable
-{
-public:
-  explicit
-  PartialMessageStore(const time::nanoseconds& idleDuration = time::milliseconds(100));
-
-  /** \brief receive a NdnlpData packet
-   *
-   *  Reassembly errors will be ignored.
-   */
-  void
-  receive(const NdnlpData& pkt);
-
-  /** \brief fires when network layer packet is received
-   */
-  signal::Signal<PartialMessageStore, Block> onReceive;
-
-private:
-  void
-  scheduleCleanup(uint64_t messageIdentifier, PartialMessage& partialMessage);
-
-  void
-  cleanup(uint64_t messageIdentifier);
-
-private:
-  std::unordered_map<uint64_t, PartialMessage> m_partialMessages;
-
-  time::nanoseconds m_idleDuration;
-};
-
-} // namespace ndnlp
-} // namespace nfd
-
-#endif // NFD_DAEMON_FACE_NDNLP_PARTIAL_MESSAGE_STORE_HPP
diff --git a/daemon/face/ndnlp-sequence-generator.cpp b/daemon/face/ndnlp-sequence-generator.cpp
deleted file mode 100644
index 842a319..0000000
--- a/daemon/face/ndnlp-sequence-generator.cpp
+++ /dev/null
@@ -1,50 +0,0 @@
-/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
-/**
- * Copyright (c) 2014  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
- *
- * 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 "ndnlp-sequence-generator.hpp"
-
-namespace nfd {
-namespace ndnlp {
-
-SequenceBlock::SequenceBlock(uint64_t start, size_t count)
-  : m_start(start)
-  , m_count(count)
-{
-}
-
-SequenceGenerator::SequenceGenerator()
-  : m_next(0)
-{
-}
-
-SequenceBlock
-SequenceGenerator::nextBlock(size_t count)
-{
-  SequenceBlock sb(m_next, count);
-  m_next += count;
-  return sb;
-}
-
-} // namespace ndnlp
-} // namespace nfd
diff --git a/daemon/face/ndnlp-sequence-generator.hpp b/daemon/face/ndnlp-sequence-generator.hpp
deleted file mode 100644
index 3019933..0000000
--- a/daemon/face/ndnlp-sequence-generator.hpp
+++ /dev/null
@@ -1,90 +0,0 @@
-/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
-/**
- * Copyright (c) 2014  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
- *
- * 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_NDNLP_SEQUENCE_GENERATOR_HPP
-#define NFD_DAEMON_FACE_NDNLP_SEQUENCE_GENERATOR_HPP
-
-#include "common.hpp"
-
-namespace nfd {
-namespace ndnlp {
-
-/** \brief represents a block of sequence numbers
- */
-class SequenceBlock
-{
-public:
-  SequenceBlock(uint64_t start, size_t count);
-
-  /** \return{ the pos-th sequence number }
-   */
-  uint64_t
-  operator[](size_t pos) const;
-
-  size_t
-  count() const;
-
-private:
-  uint64_t m_start;
-  size_t m_count;
-};
-
-inline uint64_t
-SequenceBlock::operator[](size_t pos) const
-{
-  if (pos >= m_count)
-    BOOST_THROW_EXCEPTION(std::out_of_range("pos"));
-  return m_start + static_cast<uint64_t>(pos);
-}
-
-inline size_t
-SequenceBlock::count() const
-{
-  return m_count;
-}
-
-/** \class SequenceGenerator
- *  \brief generates sequence numbers
- */
-class SequenceGenerator : noncopyable
-{
-public:
-  SequenceGenerator();
-
-  /** \brief generates a block of consecutive sequence numbers
-   *
-   *  This block must not overlap with a recent block.
-   */
-  SequenceBlock
-  nextBlock(size_t count);
-
-private:
-  /// next sequence number
-  uint64_t m_next;
-};
-
-} // namespace ndnlp
-} // namespace nfd
-
-#endif // NFD_DAEMON_FACE_NDNLP_SEQUENCE_GENERATOR_HPP
diff --git a/daemon/face/ndnlp-slicer.cpp b/daemon/face/ndnlp-slicer.cpp
deleted file mode 100644
index 03b1454..0000000
--- a/daemon/face/ndnlp-slicer.cpp
+++ /dev/null
@@ -1,135 +0,0 @@
-/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
-/**
- * Copyright (c) 2014-2015,  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 "ndnlp-slicer.hpp"
-
-#include <ndn-cxx/encoding/encoding-buffer.hpp>
-
-namespace nfd {
-namespace ndnlp {
-
-Slicer::Slicer(size_t mtu)
-  : m_mtu(mtu)
-{
-  this->estimateOverhead();
-}
-
-Slicer::~Slicer()
-{
-}
-
-template<bool T>
-size_t
-Slicer::encodeFragment(ndn::EncodingImpl<T>& blk,
-                       uint64_t seq, uint16_t fragIndex, uint16_t fragCount,
-                       const uint8_t* payload, size_t payloadSize)
-{
-  size_t totalLength = 0;
-
-  // NdnlpPayload
-  size_t payloadLength = blk.prependByteArray(payload, payloadSize);
-  totalLength += payloadLength;
-  totalLength += blk.prependVarNumber(payloadLength);
-  totalLength += blk.prependVarNumber(tlv::NdnlpPayload);
-
-  bool needFragIndexAndCount = fragCount > 1;
-  if (needFragIndexAndCount) {
-    // NdnlpFragCount
-    size_t fragCountLength = blk.prependNonNegativeInteger(fragCount);
-    totalLength += fragCountLength;
-    totalLength += blk.prependVarNumber(fragCountLength);
-    totalLength += blk.prependVarNumber(tlv::NdnlpFragCount);
-
-    // NdnlpFragIndex
-    size_t fragIndexLength = blk.prependNonNegativeInteger(fragIndex);
-    totalLength += fragIndexLength;
-    totalLength += blk.prependVarNumber(fragIndexLength);
-    totalLength += blk.prependVarNumber(tlv::NdnlpFragIndex);
-  }
-
-  // NdnlpSequence
-  uint64_t sequenceBE = htobe64(seq);
-  size_t sequenceLength = blk.prependByteArray(
-    reinterpret_cast<uint8_t*>(&sequenceBE), sizeof(sequenceBE));
-  totalLength += sequenceLength;
-  totalLength += blk.prependVarNumber(sequenceLength);
-  totalLength += blk.prependVarNumber(tlv::NdnlpSequence);
-
-  // NdnlpData
-  totalLength += blk.prependVarNumber(totalLength);
-  totalLength += blk.prependVarNumber(tlv::NdnlpData);
-
-  return totalLength;
-}
-
-void
-Slicer::estimateOverhead()
-{
-  // estimate fragment size with all header fields at largest possible size
-  ndn::EncodingEstimator estimator;
-  size_t estimatedSize = this->encodeFragment(estimator,
-                                              std::numeric_limits<uint64_t>::max(),
-                                              std::numeric_limits<uint16_t>::max() - 1,
-                                              std::numeric_limits<uint16_t>::max(),
-                                              nullptr, m_mtu);
-
-  size_t overhead = estimatedSize - m_mtu; // minus payload length in estimation
-  m_maxPayload = m_mtu - overhead;
-}
-
-PacketArray
-Slicer::slice(const Block& block)
-{
-  BOOST_ASSERT(block.hasWire());
-  const uint8_t* networkPacket = block.wire();
-  size_t networkPacketSize = block.size();
-
-  uint16_t fragCount = static_cast<uint16_t>(
-                         (networkPacketSize / m_maxPayload) +
-                         (networkPacketSize % m_maxPayload == 0 ? 0 : 1)
-                       );
-  PacketArray pa = make_shared<std::vector<Block>>();
-  pa->reserve(fragCount);
-  SequenceBlock seqBlock = m_seqgen.nextBlock(fragCount);
-
-  for (uint16_t fragIndex = 0; fragIndex < fragCount; ++fragIndex) {
-    size_t payloadOffset = fragIndex * m_maxPayload;
-    const uint8_t* payload = networkPacket + payloadOffset;
-    size_t payloadSize = std::min(m_maxPayload, networkPacketSize - payloadOffset);
-
-    ndn::EncodingBuffer buffer(m_mtu, 0);
-    size_t pktSize = this->encodeFragment(buffer,
-      seqBlock[fragIndex], fragIndex, fragCount, payload, payloadSize);
-
-    BOOST_VERIFY(pktSize <= m_mtu);
-
-    pa->push_back(buffer.block());
-  }
-
-  return pa;
-}
-
-} // namespace ndnlp
-} // namespace nfd
diff --git a/daemon/face/ndnlp-slicer.hpp b/daemon/face/ndnlp-slicer.hpp
deleted file mode 100644
index 1e2985a..0000000
--- a/daemon/face/ndnlp-slicer.hpp
+++ /dev/null
@@ -1,79 +0,0 @@
-/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
-/**
- * Copyright (c) 2014-2015,  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_NDNLP_SLICER_HPP
-#define NFD_DAEMON_FACE_NDNLP_SLICER_HPP
-
-#include "ndnlp-tlv.hpp"
-#include "ndnlp-sequence-generator.hpp"
-
-namespace nfd {
-namespace ndnlp {
-
-typedef shared_ptr<std::vector<Block>> PacketArray;
-
-/** \brief provides fragmentation feature at sender
- */
-class Slicer : noncopyable
-{
-public:
-  /** \param mtu maximum size of NDNLP header and payload
-   *  \note If NDNLP packets are to be encapsulated in an additional header
-   *        (eg. in UDP packets), the caller must deduct such overhead.
-   */
-  explicit
-  Slicer(size_t mtu);
-
-  virtual
-  ~Slicer();
-
-  PacketArray
-  slice(const Block& block);
-
-private:
-  template<bool T>
-  size_t
-  encodeFragment(ndn::EncodingImpl<T>& blk,
-                 uint64_t seq, uint16_t fragIndex, uint16_t fragCount,
-                 const uint8_t* payload, size_t payloadSize);
-
-  /// estimate the size of NDNLP header and maximum payload size per packet
-  void
-  estimateOverhead();
-
-private:
-  SequenceGenerator m_seqgen;
-
-  /// maximum packet size
-  size_t m_mtu;
-
-  /// maximum payload size
-  size_t m_maxPayload;
-};
-
-} // namespace ndnlp
-} // namespace nfd
-
-#endif // NFD_DAEMON_FACE_NDNLP_SLICER_HPP
diff --git a/daemon/face/ndnlp-tlv.hpp b/daemon/face/ndnlp-tlv.hpp
deleted file mode 100644
index 0c41f52..0000000
--- a/daemon/face/ndnlp-tlv.hpp
+++ /dev/null
@@ -1,43 +0,0 @@
-/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
-/**
- * Copyright (c) 2014  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
- *
- * 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_NDNLP_TLV_HPP
-#define NFD_DAEMON_FACE_NDNLP_TLV_HPP
-
-namespace nfd {
-namespace tlv {
-
-enum
-{
-  NdnlpData      = 80,
-  NdnlpSequence  = 81,
-  NdnlpFragIndex = 82,
-  NdnlpFragCount = 83,
-  NdnlpPayload   = 84
-};
-
-} // namespace tlv
-} // namespace nfd
-
-#endif // NFD_DAEMON_FACE_NDNLP_TLV_HPP
diff --git a/daemon/mgmt/face-manager.cpp b/daemon/mgmt/face-manager.cpp
index 1b68a3d..1bc1701 100644
--- a/daemon/mgmt/face-manager.cpp
+++ b/daemon/mgmt/face-manager.cpp
@@ -42,7 +42,7 @@
 
 #ifdef HAVE_LIBPCAP
 #include "face/ethernet-factory.hpp"
-#include "face/ethernet-face.hpp"
+#include "face/ethernet-transport.hpp"
 #endif // HAVE_LIBPCAP
 
 #ifdef HAVE_WEBSOCKET
@@ -806,7 +806,7 @@
       m_factories.insert(std::make_pair("ether", factory));
     }
 
-    std::set<shared_ptr<EthernetFace>> multicastFacesToRemove;
+    std::set<shared_ptr<face::LpFaceWrapper>> multicastFacesToRemove;
     for (const auto& i : factory->getMulticastFaces()) {
       multicastFacesToRemove.insert(i.second);
     }
@@ -822,7 +822,7 @@
           catch (const EthernetFactory::Error& factoryError) {
             NFD_LOG_ERROR(factoryError.what() << ", continuing");
           }
-          catch (const EthernetFace::Error& faceError) {
+          catch (const face::EthernetTransport::Error& faceError) {
             NFD_LOG_ERROR(faceError.what() << ", continuing");
           }
         }
diff --git a/tests/daemon/face/ethernet-transport.t.cpp b/tests/daemon/face/ethernet-transport.t.cpp
new file mode 100644
index 0000000..85c799d
--- /dev/null
+++ b/tests/daemon/face/ethernet-transport.t.cpp
@@ -0,0 +1,60 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2015,  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 "face/ethernet-transport.hpp"
+#include "transport-properties.hpp"
+
+#include "network-interface-fixture.hpp"
+
+namespace nfd {
+namespace face {
+namespace tests {
+
+using namespace nfd::tests;
+
+BOOST_AUTO_TEST_SUITE(Face)
+BOOST_FIXTURE_TEST_SUITE(TestEthernetTransport, NetworkInterfaceFixture)
+
+BOOST_AUTO_TEST_CASE(StaticProperties)
+{
+  SKIP_IF_NETWORK_INTERFACE_COUNT_LT(1);
+
+  auto netif = m_interfaces.front();
+  EthernetTransport transport(netif, ethernet::getDefaultMulticastAddress());
+  checkStaticPropertiesInitialized(transport);
+
+  BOOST_CHECK_EQUAL(transport.getLocalUri(), FaceUri::fromDev(netif.name));
+  BOOST_CHECK_EQUAL(transport.getRemoteUri(), FaceUri(ethernet::getDefaultMulticastAddress()));
+  BOOST_CHECK_EQUAL(transport.getScope(), ndn::nfd::FACE_SCOPE_NON_LOCAL);
+  BOOST_CHECK_EQUAL(transport.getPersistency(), ndn::nfd::FACE_PERSISTENCY_PERMANENT);
+  BOOST_CHECK_EQUAL(transport.getLinkType(), ndn::nfd::LINK_TYPE_MULTI_ACCESS);
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestEthernetTransport
+BOOST_AUTO_TEST_SUITE_END() // Face
+
+} // namespace tests
+} // namespace face
+} // namespace nfd
diff --git a/tests/daemon/face/ethernet.t.cpp b/tests/daemon/face/ethernet.t.cpp
index 7d97016..0098436 100644
--- a/tests/daemon/face/ethernet.t.cpp
+++ b/tests/daemon/face/ethernet.t.cpp
@@ -23,11 +23,11 @@
  * NFD, e.g., in COPYING.md file.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-#include "face/ethernet-face.hpp"
 #include "face/ethernet-factory.hpp"
+#include "face/ethernet-transport.hpp"
 
-#include "core/network-interface.hpp"
-#include "tests/test-common.hpp"
+#include "face/lp-face-wrapper.hpp"
+#include "network-interface-fixture.hpp"
 
 #include <pcap/pcap.h>
 
@@ -35,36 +35,10 @@
 namespace tests {
 
 BOOST_AUTO_TEST_SUITE(Face)
+BOOST_FIXTURE_TEST_SUITE(TestEthernet, NetworkInterfaceFixture)
 
 using nfd::Face;
 
-class InterfacesFixture : protected BaseFixture
-{
-protected:
-  InterfacesFixture()
-  {
-    EthernetFactory factory;
-
-    for (const auto& netif : listNetworkInterfaces()) {
-      if (!netif.isLoopback() && netif.isUp()) {
-        try {
-          factory.createMulticastFace(netif, ethernet::getBroadcastAddress());
-        }
-        catch (Face::Error&) {
-          continue;
-        }
-
-        m_interfaces.push_back(netif);
-      }
-    }
-  }
-
-protected:
-  std::vector<NetworkInterfaceInfo> m_interfaces;
-};
-
-BOOST_FIXTURE_TEST_SUITE(TestEthernet, InterfacesFixture)
-
 BOOST_AUTO_TEST_CASE(GetChannels)
 {
   EthernetFactory factory;
@@ -75,31 +49,19 @@
 
 BOOST_AUTO_TEST_CASE(MulticastFacesMap)
 {
-  if (m_interfaces.empty()) {
-    BOOST_WARN_MESSAGE(false, "No interfaces available for pcap, "
-                              "cannot perform MulticastFacesMap test");
-    return;
-  }
+  SKIP_IF_NETWORK_INTERFACE_COUNT_LT(1);
 
   EthernetFactory factory;
-  shared_ptr<EthernetFace> face1 = factory.createMulticastFace(m_interfaces.front(),
-                                                               ethernet::getBroadcastAddress());
-  shared_ptr<EthernetFace> face1bis = factory.createMulticastFace(m_interfaces.front(),
-                                                                  ethernet::getBroadcastAddress());
+  auto face1 = factory.createMulticastFace(m_interfaces.front(), ethernet::getBroadcastAddress());
+  auto face1bis = factory.createMulticastFace(m_interfaces.front(), ethernet::getBroadcastAddress());
   BOOST_CHECK_EQUAL(face1, face1bis);
 
-  if (m_interfaces.size() > 1) {
-    shared_ptr<EthernetFace> face2 = factory.createMulticastFace(m_interfaces.back(),
-                                                                 ethernet::getBroadcastAddress());
-    BOOST_CHECK_NE(face1, face2);
-  }
-  else {
-    BOOST_WARN_MESSAGE(false, "Only one interface available for pcap, "
-                              "cannot test second EthernetFace creation");
-  }
+  auto face2 = factory.createMulticastFace(m_interfaces.front(), ethernet::getDefaultMulticastAddress());
+  BOOST_CHECK_NE(face1, face2);
 
-  shared_ptr<EthernetFace> face3 = factory.createMulticastFace(m_interfaces.front(),
-                                     ethernet::getDefaultMulticastAddress());
+  SKIP_IF_NETWORK_INTERFACE_COUNT_LT(2);
+
+  auto face3 = factory.createMulticastFace(m_interfaces.back(), ethernet::getBroadcastAddress());
   BOOST_CHECK_NE(face1, face3);
 }
 
@@ -128,19 +90,14 @@
 
 BOOST_AUTO_TEST_CASE(SendPacket)
 {
-  if (m_interfaces.empty()) {
-    BOOST_WARN_MESSAGE(false, "No interfaces available for pcap, "
-                              "cannot perform SendPacket test");
-    return;
-  }
+  SKIP_IF_NETWORK_INTERFACE_COUNT_LT(1);
 
   EthernetFactory factory;
-  shared_ptr<EthernetFace> face = factory.createMulticastFace(m_interfaces.front(),
-                                    ethernet::getDefaultMulticastAddress());
+  auto face = factory.createMulticastFace(m_interfaces.front(), ethernet::getDefaultMulticastAddress());
 
   BOOST_REQUIRE(static_cast<bool>(face));
   BOOST_CHECK_EQUAL(face->isLocal(), false);
-  BOOST_CHECK_EQUAL(face->getPersistency(), ndn::nfd::FACE_PERSISTENCY_PERSISTENT);
+  BOOST_CHECK_EQUAL(face->getPersistency(), ndn::nfd::FACE_PERSISTENCY_PERMANENT);
   BOOST_CHECK_EQUAL(face->isMultiAccess(), true);
   BOOST_CHECK_EQUAL(face->getRemoteUri().toString(),
                     "ether://[" + ethernet::getDefaultMulticastAddress().toString() + "]");
@@ -162,40 +119,23 @@
   face->sendData    (*data2    );
 
   BOOST_CHECK_EQUAL(face->getCounters().nOutBytes,
-                    14 * 4 + // 4 NDNLP headers
                     interest1->wireEncode().size() +
                     data1->wireEncode().size() +
                     interest2->wireEncode().size() +
                     data2->wireEncode().size());
-
-//  m_ioRemaining = 4;
-//  m_ioService.run();
-//  m_ioService.reset();
-
-//  BOOST_REQUIRE_EQUAL(m_face1_receivedInterests.size(), 1);
-//  BOOST_REQUIRE_EQUAL(m_face1_receivedDatas    .size(), 1);
-//  BOOST_REQUIRE_EQUAL(m_face2_receivedInterests.size(), 1);
-//  BOOST_REQUIRE_EQUAL(m_face2_receivedDatas    .size(), 1);
-
-//  BOOST_CHECK_EQUAL(m_face1_receivedInterests[0].getName(), interest2.getName());
-//  BOOST_CHECK_EQUAL(m_face1_receivedDatas    [0].getName(), data2.getName());
-//  BOOST_CHECK_EQUAL(m_face2_receivedInterests[0].getName(), interest1.getName());
-//  BOOST_CHECK_EQUAL(m_face2_receivedDatas    [0].getName(), data1.getName());
 }
 
 BOOST_AUTO_TEST_CASE(ProcessIncomingPacket)
 {
-  if (m_interfaces.empty()) {
-    BOOST_WARN_MESSAGE(false, "No interfaces available for pcap, "
-                              "cannot perform ProcessIncomingPacket test");
-    return;
-  }
+  SKIP_IF_NETWORK_INTERFACE_COUNT_LT(1);
 
   EthernetFactory factory;
-  shared_ptr<EthernetFace> face = factory.createMulticastFace(m_interfaces.front(),
-                                    ethernet::getDefaultMulticastAddress());
+  auto face = factory.createMulticastFace(m_interfaces.front(), ethernet::getDefaultMulticastAddress());
   BOOST_REQUIRE(static_cast<bool>(face));
 
+  auto transport = dynamic_cast<face::EthernetTransport*>(face->getLpFace()->getTransport());
+  BOOST_REQUIRE(transport != nullptr);
+
   std::vector<Interest> recInterests;
   std::vector<Data>     recDatas;
 
@@ -206,7 +146,7 @@
 
   // check that packet data is not accessed if pcap didn't capture anything (caplen == 0)
   static const pcap_pkthdr zeroHeader{};
-  face->processIncomingPacket(&zeroHeader, nullptr);
+  transport->processIncomingPacket(&zeroHeader, nullptr);
   BOOST_CHECK_EQUAL(face->getCounters().nInBytes, 0);
   BOOST_CHECK_EQUAL(recInterests.size(), 0);
   BOOST_CHECK_EQUAL(recDatas.size(), 0);
@@ -215,7 +155,7 @@
   pcap_pkthdr runtHeader{};
   runtHeader.caplen = ethernet::HDR_LEN + 6;
   static const uint8_t packet2[ethernet::HDR_LEN + 6]{};
-  face->processIncomingPacket(&runtHeader, packet2);
+  transport->processIncomingPacket(&runtHeader, packet2);
   BOOST_CHECK_EQUAL(face->getCounters().nInBytes, 0);
   BOOST_CHECK_EQUAL(recInterests.size(), 0);
   BOOST_CHECK_EQUAL(recDatas.size(), 0);
@@ -227,10 +167,10 @@
     0x01, 0x00, 0x5e, 0x00, 0x17, 0xaa, // destination address
     0x02, 0x00, 0x00, 0x00, 0x00, 0x02, // source address
     0x86, 0x24,       // NDN ethertype
-    tlv::NdnlpData,   // TLV type
+    tlv::Interest,    // TLV type
     0xfd, 0xff, 0xff  // TLV length (invalid because greater than buffer size)
   };
-  face->processIncomingPacket(&validHeader, packet3);
+  transport->processIncomingPacket(&validHeader, packet3);
   BOOST_CHECK_EQUAL(face->getCounters().nInBytes, 0);
   BOOST_CHECK_EQUAL(recInterests.size(), 0);
   BOOST_CHECK_EQUAL(recDatas.size(), 0);
@@ -243,44 +183,41 @@
     0x00,             // TLV type (invalid)
     0x00              // TLV length
   };
-  face->processIncomingPacket(&validHeader, packet4);
+  transport->processIncomingPacket(&validHeader, packet4);
   BOOST_CHECK_EQUAL(face->getCounters().nInBytes, 2);
   BOOST_CHECK_EQUAL(recInterests.size(), 0);
   BOOST_CHECK_EQUAL(recDatas.size(), 0);
 
-  // valid frame and valid NDNLP header, but invalid payload
+  // valid frame and valid NDNLPv2 packet, but invalid network-layer packet
   static const uint8_t packet5[ethernet::HDR_LEN + ethernet::MIN_DATA_LEN]{
     0x01, 0x00, 0x5e, 0x00, 0x17, 0xaa, // destination address
     0x02, 0x00, 0x00, 0x00, 0x00, 0x02, // source address
-    0x86, 0x24,                   // NDN ethertype
-    tlv::NdnlpData,     0x0e,     // NDNLP header
-    tlv::NdnlpSequence, 0x08,
-    0, 0, 0, 0, 0, 0, 0, 0,
-    tlv::NdnlpPayload,  0x02,
-    0x00,             // NDN TLV type (invalid)
-    0x00              // NDN TLV length
+    0x86, 0x24,                         // NDN ethertype
+    lp::tlv::LpPacket, 0x04,            // start of NDNLPv2 packet
+    lp::tlv::Fragment, 0x02,            // single fragment
+    0x00,             // TLV type (invalid)
+    0x00              // TLV length
   };
-  face->processIncomingPacket(&validHeader, packet5);
-  BOOST_CHECK_EQUAL(face->getCounters().nInBytes, 18);
+  transport->processIncomingPacket(&validHeader, packet5);
+  BOOST_CHECK_EQUAL(face->getCounters().nInBytes, 8);
   BOOST_CHECK_EQUAL(recInterests.size(), 0);
   BOOST_CHECK_EQUAL(recDatas.size(), 0);
 
-  // valid frame, valid NDNLP header, and valid NDN (interest) packet
+  // valid frame, valid NDNLPv2 packet, and valid NDN (interest) packet
   static const uint8_t packet6[ethernet::HDR_LEN + ethernet::MIN_DATA_LEN]{
     0x01, 0x00, 0x5e, 0x00, 0x17, 0xaa, // destination address
     0x02, 0x00, 0x00, 0x00, 0x00, 0x02, // source address
     0x86, 0x24,                         // NDN ethertype
-    tlv::NdnlpData, 0x24,               // NDNLP TLV type and length
-    0x51, 0x08, 0x00, 0x00, 0x00, 0x00, // rest of NDNLP header
-    0x00, 0x00, 0x00, 0x00, 0x54, 0x18,
-    tlv::Interest, 0x16,                // NDN TLV type and length
+    lp::tlv::LpPacket, 0x1a,            // start of NDNLPv2 packet
+    lp::tlv::Fragment, 0x18,            // single fragment
+    tlv::Interest, 0x16,                // start of NDN packet
     0x07, 0x0e, 0x08, 0x07, 0x65, 0x78, // payload
     0x61, 0x6d, 0x70, 0x6c, 0x65, 0x08,
     0x03, 0x66, 0x6f, 0x6f, 0x0a, 0x04,
     0x03, 0xef, 0xe9, 0x7c
   };
-  face->processIncomingPacket(&validHeader, packet6);
-  BOOST_CHECK_EQUAL(face->getCounters().nInBytes, 56);
+  transport->processIncomingPacket(&validHeader, packet6);
+  BOOST_CHECK_EQUAL(face->getCounters().nInBytes, 36);
   BOOST_CHECK_EQUAL(recInterests.size(), 1);
   BOOST_CHECK_EQUAL(recDatas.size(), 0);
 }
diff --git a/tests/daemon/face/ndnlp.t.cpp b/tests/daemon/face/ndnlp.t.cpp
deleted file mode 100644
index b372769..0000000
--- a/tests/daemon/face/ndnlp.t.cpp
+++ /dev/null
@@ -1,306 +0,0 @@
-/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
-/**
- * Copyright (c) 2014-2015,  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 "face/ndnlp-sequence-generator.hpp"
-#include "face/ndnlp-slicer.hpp"
-#include "face/ndnlp-partial-message-store.hpp"
-
-#include "tests/test-common.hpp"
-
-#include <boost/scoped_array.hpp>
-
-namespace nfd {
-namespace ndnlp {
-namespace tests {
-
-using namespace nfd::tests;
-
-BOOST_FIXTURE_TEST_SUITE(FaceNdnlp, BaseFixture)
-
-BOOST_AUTO_TEST_CASE(SequenceBlock)
-{
-  ndnlp::SequenceBlock sb(0x8000, 2);
-  BOOST_CHECK_EQUAL(sb.count(), 2);
-  BOOST_CHECK_EQUAL(sb[0], 0x8000);
-  BOOST_CHECK_EQUAL(sb[1], 0x8001);
-  BOOST_CHECK_THROW(sb[2], std::out_of_range);
-}
-
-// sequence number can safely wrap around
-BOOST_AUTO_TEST_CASE(SequenceBlockWrap)
-{
-  ndnlp::SequenceBlock sb(std::numeric_limits<uint64_t>::max(), 2);
-  BOOST_CHECK_EQUAL(sb[0], std::numeric_limits<uint64_t>::max());
-  BOOST_CHECK_EQUAL(sb[1], std::numeric_limits<uint64_t>::min());
-  BOOST_CHECK_EQUAL(sb[1] - sb[0], 1);
-}
-
-BOOST_AUTO_TEST_CASE(SequenceGenerator)
-{
-  ndnlp::SequenceGenerator seqgen;
-
-  ndnlp::SequenceBlock sb1 = seqgen.nextBlock(2);
-  BOOST_CHECK_EQUAL(sb1.count(), 2);
-
-  ndnlp::SequenceBlock sb2 = seqgen.nextBlock(1);
-  BOOST_CHECK_NE(sb1[0], sb2[0]);
-  BOOST_CHECK_NE(sb1[1], sb2[0]);
-}
-
-// slice a Block to one fragment
-BOOST_AUTO_TEST_CASE(Slice1)
-{
-  uint8_t blockValue[60];
-  memset(blockValue, 0xcc, sizeof(blockValue));
-  Block block = ndn::dataBlock(0x01, blockValue, sizeof(blockValue));
-
-  ndnlp::Slicer slicer(9000);
-  ndnlp::PacketArray pa = slicer.slice(block);
-
-  BOOST_REQUIRE_EQUAL(pa->size(), 1);
-
-  const Block& pkt = pa->at(0);
-  BOOST_CHECK_EQUAL(pkt.type(), static_cast<uint32_t>(tlv::NdnlpData));
-  pkt.parse();
-
-  const Block::element_container& elements = pkt.elements();
-  BOOST_REQUIRE_EQUAL(elements.size(), 2);
-
-  const Block& sequenceElement = elements[0];
-  BOOST_CHECK_EQUAL(sequenceElement.type(), static_cast<uint32_t>(tlv::NdnlpSequence));
-  BOOST_REQUIRE_EQUAL(sequenceElement.value_size(), sizeof(uint64_t));
-
-  const Block& payloadElement = elements[1];
-  BOOST_CHECK_EQUAL(payloadElement.type(), static_cast<uint32_t>(tlv::NdnlpPayload));
-  size_t payloadSize = payloadElement.value_size();
-  BOOST_CHECK_EQUAL(payloadSize, block.size());
-
-  BOOST_CHECK_EQUAL_COLLECTIONS(payloadElement.value_begin(), payloadElement.value_end(),
-                                block.begin(),                block.end());
-}
-
-// slice a Block to four fragments
-BOOST_AUTO_TEST_CASE(Slice4)
-{
-  uint8_t blockValue[5050];
-  memset(blockValue, 0xcc, sizeof(blockValue));
-  Block block = ndn::dataBlock(0x01, blockValue, sizeof(blockValue));
-
-  ndnlp::Slicer slicer(1500);
-  ndnlp::PacketArray pa = slicer.slice(block);
-
-  BOOST_REQUIRE_EQUAL(pa->size(), 4);
-
-  uint64_t seq0 = 0xdddd;
-
-  size_t totalPayloadSize = 0;
-
-  for (size_t i = 0; i < 4; ++i) {
-    const Block& pkt = pa->at(i);
-    BOOST_CHECK_EQUAL(pkt.type(), static_cast<uint32_t>(tlv::NdnlpData));
-    pkt.parse();
-
-    const Block::element_container& elements = pkt.elements();
-    BOOST_REQUIRE_EQUAL(elements.size(), 4);
-
-    const Block& sequenceElement = elements[0];
-    BOOST_CHECK_EQUAL(sequenceElement.type(), static_cast<uint32_t>(tlv::NdnlpSequence));
-    BOOST_REQUIRE_EQUAL(sequenceElement.value_size(), sizeof(uint64_t));
-    uint64_t seq = be64toh(*reinterpret_cast<const uint64_t*>(
-                             &*sequenceElement.value_begin()));
-    if (i == 0) {
-      seq0 = seq;
-    }
-    BOOST_CHECK_EQUAL(seq, seq0 + i);
-
-    const Block& fragIndexElement = elements[1];
-    BOOST_CHECK_EQUAL(fragIndexElement.type(), static_cast<uint32_t>(tlv::NdnlpFragIndex));
-    uint64_t fragIndex = ndn::readNonNegativeInteger(fragIndexElement);
-    BOOST_CHECK_EQUAL(fragIndex, i);
-
-    const Block& fragCountElement = elements[2];
-    BOOST_CHECK_EQUAL(fragCountElement.type(), static_cast<uint32_t>(tlv::NdnlpFragCount));
-    uint64_t fragCount = ndn::readNonNegativeInteger(fragCountElement);
-    BOOST_CHECK_EQUAL(fragCount, 4);
-
-    const Block& payloadElement = elements[3];
-    BOOST_CHECK_EQUAL(payloadElement.type(), static_cast<uint32_t>(tlv::NdnlpPayload));
-    size_t payloadSize = payloadElement.value_size();
-    totalPayloadSize += payloadSize;
-  }
-
-  BOOST_CHECK_EQUAL(totalPayloadSize, block.size());
-}
-
-class ReassembleFixture : protected UnitTestTimeFixture
-{
-protected:
-  ReassembleFixture()
-    : slicer(1500)
-    , pms(time::milliseconds(100))
-  {
-    pms.onReceive.connect([this] (const Block& block) {
-      received.push_back(block);
-    });
-  }
-
-  Block
-  makeBlock(size_t valueLength)
-  {
-    boost::scoped_array<uint8_t> blockValue(new uint8_t[valueLength]);
-    memset(blockValue.get(), 0xcc, valueLength);
-    return ndn::dataBlock(0x01, blockValue.get(), valueLength);
-  }
-
-  void
-  receiveNdnlpData(const Block& block)
-  {
-    bool isOk = false;
-    ndnlp::NdnlpData pkt;
-    std::tie(isOk, pkt) = ndnlp::NdnlpData::fromBlock(block);
-    BOOST_REQUIRE(isOk);
-    pms.receive(pkt);
-  }
-
-protected:
-  ndnlp::Slicer slicer;
-  ndnlp::PartialMessageStore pms;
-
-  static const time::nanoseconds IDLE_DURATION;
-
-  // received network layer packets
-  std::vector<Block> received;
-};
-
-// reassemble one fragment into one Block
-BOOST_FIXTURE_TEST_CASE(Reassemble1, ReassembleFixture)
-{
-  Block block = makeBlock(60);
-  ndnlp::PacketArray pa = slicer.slice(block);
-  BOOST_REQUIRE_EQUAL(pa->size(), 1);
-
-  BOOST_CHECK_EQUAL(received.size(), 0);
-  this->receiveNdnlpData(pa->at(0));
-
-  BOOST_REQUIRE_EQUAL(received.size(), 1);
-  BOOST_CHECK_EQUAL_COLLECTIONS(received.at(0).begin(), received.at(0).end(),
-                                block.begin(),          block.end());
-}
-
-// reassemble four and two fragments into two Blocks
-BOOST_FIXTURE_TEST_CASE(Reassemble4and2, ReassembleFixture)
-{
-  Block block = makeBlock(5050);
-  ndnlp::PacketArray pa = slicer.slice(block);
-  BOOST_REQUIRE_EQUAL(pa->size(), 4);
-
-  Block block2 = makeBlock(2000);
-  ndnlp::PacketArray pa2 = slicer.slice(block2);
-  BOOST_REQUIRE_EQUAL(pa2->size(), 2);
-
-  BOOST_CHECK_EQUAL(received.size(), 0);
-  this->receiveNdnlpData(pa->at(0));
-  BOOST_CHECK_EQUAL(received.size(), 0);
-  this->advanceClocks(time::milliseconds(40));
-
-  this->receiveNdnlpData(pa->at(1));
-  BOOST_CHECK_EQUAL(received.size(), 0);
-  this->advanceClocks(time::milliseconds(40));
-
-  this->receiveNdnlpData(pa2->at(1));
-  BOOST_CHECK_EQUAL(received.size(), 0);
-  this->advanceClocks(time::milliseconds(40));
-
-  this->receiveNdnlpData(pa->at(1));
-  BOOST_CHECK_EQUAL(received.size(), 0);
-  this->advanceClocks(time::milliseconds(40));
-
-  this->receiveNdnlpData(pa2->at(0));
-  BOOST_CHECK_EQUAL(received.size(), 1);
-  this->advanceClocks(time::milliseconds(40));
-
-  this->receiveNdnlpData(pa->at(3));
-  BOOST_CHECK_EQUAL(received.size(), 1);
-  this->advanceClocks(time::milliseconds(40));
-
-  this->receiveNdnlpData(pa->at(2));
-
-  BOOST_REQUIRE_EQUAL(received.size(), 2);
-  BOOST_CHECK_EQUAL_COLLECTIONS(received.at(1).begin(), received.at(1).end(),
-                                block.begin(),          block.end());
-  BOOST_CHECK_EQUAL_COLLECTIONS(received.at(0).begin(), received.at(0).end(),
-                                block2.begin(),         block2.end());
-}
-
-// reassemble four fragments into one Block, but another two fragments are expired
-BOOST_FIXTURE_TEST_CASE(ReassembleTimeout, ReassembleFixture)
-{
-  Block block = makeBlock(5050);
-  ndnlp::PacketArray pa = slicer.slice(block);
-  BOOST_REQUIRE_EQUAL(pa->size(), 4);
-
-  Block block2 = makeBlock(2000);
-  ndnlp::PacketArray pa2 = slicer.slice(block2);
-  BOOST_REQUIRE_EQUAL(pa2->size(), 2);
-
-  BOOST_CHECK_EQUAL(received.size(), 0);
-  this->receiveNdnlpData(pa->at(0));
-  BOOST_CHECK_EQUAL(received.size(), 0);
-  this->advanceClocks(time::milliseconds(40));
-
-  this->receiveNdnlpData(pa->at(1));
-  BOOST_CHECK_EQUAL(received.size(), 0);
-  this->advanceClocks(time::milliseconds(40));
-
-  this->receiveNdnlpData(pa2->at(1));
-  BOOST_CHECK_EQUAL(received.size(), 0);
-  this->advanceClocks(time::milliseconds(40));
-
-  this->receiveNdnlpData(pa->at(1));
-  BOOST_CHECK_EQUAL(received.size(), 0);
-  this->advanceClocks(time::milliseconds(40));
-
-  this->receiveNdnlpData(pa->at(3));
-  BOOST_CHECK_EQUAL(received.size(), 0);
-  this->advanceClocks(time::milliseconds(40));
-
-  this->receiveNdnlpData(pa->at(2));
-  BOOST_CHECK_EQUAL(received.size(), 1);
-  this->advanceClocks(time::milliseconds(40));
-
-  this->receiveNdnlpData(pa2->at(0)); // last fragment was received 160ms ago, expired
-  BOOST_CHECK_EQUAL(received.size(), 1);
-  this->advanceClocks(time::milliseconds(40));
-
-  BOOST_REQUIRE_EQUAL(received.size(), 1);
-  BOOST_CHECK_EQUAL_COLLECTIONS(received.at(0).begin(), received.at(0).end(),
-                                block.begin(),          block.end());
-}
-
-BOOST_AUTO_TEST_SUITE_END()
-
-} // namespace tests
-} // namespace ndnlp
-} // namespace nfd
diff --git a/tests/daemon/face/network-interface-fixture.hpp b/tests/daemon/face/network-interface-fixture.hpp
new file mode 100644
index 0000000..3fb7af8
--- /dev/null
+++ b/tests/daemon/face/network-interface-fixture.hpp
@@ -0,0 +1,71 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2015,  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_TESTS_NETWORK_INTERFACE_FIXTURE_HPP
+#define NFD_TESTS_NETWORK_INTERFACE_FIXTURE_HPP
+
+#include "core/network-interface.hpp"
+#include "face/ethernet-transport.hpp"
+
+#include "test-common.hpp"
+
+namespace nfd {
+namespace tests {
+
+class NetworkInterfaceFixture : protected BaseFixture
+{
+protected:
+  NetworkInterfaceFixture()
+  {
+    for (const auto& netif : listNetworkInterfaces()) {
+      if (!netif.isLoopback() && netif.isUp()) {
+        try {
+          face::EthernetTransport transport(netif, ethernet::getBroadcastAddress());
+          m_interfaces.push_back(netif);
+        }
+        catch (const face::EthernetTransport::Error&) {
+          // pass
+        }
+      }
+    }
+  }
+
+protected:
+  std::vector<NetworkInterfaceInfo> m_interfaces;
+};
+
+#define SKIP_IF_NETWORK_INTERFACE_COUNT_LT(n) \
+  do {                                        \
+    if (this->m_interfaces.size() < (n)) {    \
+      BOOST_WARN_MESSAGE(false, "skipping assertions that require " \
+                                #n " or more network interfaces");  \
+      return;                                 \
+    }                                         \
+  } while (false)
+
+} // namespace tests
+} // namespace nfd
+
+#endif // NFD_TESTS_NETWORK_INTERFACE_FIXTURE_HPP
diff --git a/tests/daemon/mgmt/face-manager-process-config.t.cpp b/tests/daemon/mgmt/face-manager-process-config.t.cpp
index 5dedb91..b5a7312 100644
--- a/tests/daemon/mgmt/face-manager-process-config.t.cpp
+++ b/tests/daemon/mgmt/face-manager-process-config.t.cpp
@@ -47,7 +47,6 @@
     m_manager.setConfigFile(m_config);
   }
 
-public:
   void
   parseConfig(const std::string& type, bool isDryRun)
   {
@@ -411,7 +410,6 @@
                        "no Ethernet multicast faces are available");
     return;
   }
-  BOOST_CHECK_GT(factory->getMulticastFaces().size(), 0);
 
   const std::string CONFIG_WITHOUT_MCAST =
     "face_system\n"
@@ -422,6 +420,7 @@
     "  }\n"
     "}\n";
   BOOST_CHECK_NO_THROW(parseConfig(CONFIG_WITHOUT_MCAST, false));
+  BOOST_REQUIRE_NO_THROW(g_io.poll());
   BOOST_CHECK_EQUAL(factory->getMulticastFaces().size(), 0);
 }
 
diff --git a/tests/daemon/mgmt/manager-common-fixture.hpp b/tests/daemon/mgmt/manager-common-fixture.hpp
index ffdb579..539d56a 100644
--- a/tests/daemon/mgmt/manager-common-fixture.hpp
+++ b/tests/daemon/mgmt/manager-common-fixture.hpp
@@ -105,7 +105,7 @@
     WRONG_TEXT,
     WRONG_BODY_SIZE,
     WRONG_BODY_VALUE
-   };
+  };
 
   /**
    * @brief check a specified response data with the expected ControlResponse