face: support runtime MTU changes on Ethernet faces

refs #3257

Change-Id: I160fe444b889b8f7be19022a2bd52bbc9f7a21aa
diff --git a/daemon/face/ethernet-transport.cpp b/daemon/face/ethernet-transport.cpp
index 6b419bf..01e175b 100644
--- a/daemon/face/ethernet-transport.cpp
+++ b/daemon/face/ethernet-transport.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /*
- * Copyright (c) 2014-2019,  Regents of the University of California,
+ * Copyright (c) 2014-2020,  Regents of the University of California,
  *                           Arizona Board of Regents,
  *                           Colorado State University,
  *                           University Pierre & Marie Curie, Sorbonne University,
@@ -58,11 +58,16 @@
     NDN_THROW_NESTED(Error(e.what()));
   }
 
-  m_netifStateConn = localEndpoint.onStateChanged.connect(
-    [=] (ndn::net::InterfaceState, ndn::net::InterfaceState newState) {
+  m_netifStateChangedConn = localEndpoint.onStateChanged.connect(
+    [this] (ndn::net::InterfaceState, ndn::net::InterfaceState newState) {
       handleNetifStateChange(newState);
     });
 
+  m_netifMtuChangedConn = localEndpoint.onMtuChanged.connect(
+    [this] (uint32_t, uint32_t mtu) {
+      setMtu(mtu);
+    });
+
   asyncRead();
 }
 
diff --git a/daemon/face/ethernet-transport.hpp b/daemon/face/ethernet-transport.hpp
index 8258285..a88b95b 100644
--- a/daemon/face/ethernet-transport.hpp
+++ b/daemon/face/ethernet-transport.hpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /*
- * Copyright (c) 2014-2019,  Regents of the University of California,
+ * Copyright (c) 2014-2020,  Regents of the University of California,
  *                           Arizona Board of Regents,
  *                           Colorado State University,
  *                           University Pierre & Marie Curie, Sorbonne University,
@@ -106,7 +106,8 @@
   std::string m_interfaceName;
 
 private:
-  signal::ScopedConnection m_netifStateConn;
+  signal::ScopedConnection m_netifStateChangedConn;
+  signal::ScopedConnection m_netifMtuChangedConn;
   bool m_hasRecentlyReceived;
 #ifdef _DEBUG
   /// number of frames dropped by the kernel, as reported by libpcap
diff --git a/daemon/face/generic-link-service.cpp b/daemon/face/generic-link-service.cpp
index 601c041..b3b7d96 100644
--- a/daemon/face/generic-link-service.cpp
+++ b/daemon/face/generic-link-service.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /*
- * Copyright (c) 2014-2019,  Regents of the University of California,
+ * Copyright (c) 2014-2020,  Regents of the University of California,
  *                           Arizona Board of Regents,
  *                           Colorado State University,
  *                           University Pierre & Marie Curie, Sorbonne University,
@@ -171,7 +171,8 @@
     mtu -= CONGESTION_MARK_SIZE;
   }
 
-  BOOST_ASSERT(mtu == MTU_UNLIMITED || mtu > 0);
+  // An MTU of 0 is allowed but will cause all packets to be dropped before transmission
+  BOOST_ASSERT(mtu == MTU_UNLIMITED || mtu >= 0);
 
   if (m_options.allowFragmentation && mtu != MTU_UNLIMITED) {
     bool isOk = false;
diff --git a/daemon/face/transport.cpp b/daemon/face/transport.cpp
index 7b1bb18..18d8608 100644
--- a/daemon/face/transport.cpp
+++ b/daemon/face/transport.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /*
- * Copyright (c) 2014-2019,  Regents of the University of California,
+ * Copyright (c) 2014-2020,  Regents of the University of California,
  *                           Arizona Board of Regents,
  *                           Colorado State University,
  *                           University Pierre & Marie Curie, Sorbonne University,
@@ -124,6 +124,22 @@
   m_service->receivePacket(packet, endpoint);
 }
 
+void
+Transport::setMtu(ssize_t mtu)
+{
+  BOOST_ASSERT(mtu == MTU_UNLIMITED || mtu >= 0);
+
+  if (mtu == m_mtu) {
+    return;
+  }
+
+  if (m_mtu != MTU_INVALID) {
+    NFD_LOG_FACE_INFO("setMtu " << m_mtu << " -> " << mtu);
+  }
+
+  m_mtu = mtu;
+}
+
 bool
 Transport::canChangePersistencyTo(ndn::nfd::FacePersistency newPersistency) const
 {
diff --git a/daemon/face/transport.hpp b/daemon/face/transport.hpp
index dd6bb41..211fefc 100644
--- a/daemon/face/transport.hpp
+++ b/daemon/face/transport.hpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /*
- * Copyright (c) 2014-2019,  Regents of the University of California,
+ * Copyright (c) 2014-2020,  Regents of the University of California,
  *                           Arizona Board of Regents,
  *                           Colorado State University,
  *                           University Pierre & Marie Curie, Sorbonne University,
@@ -444,13 +444,6 @@
   return m_mtu;
 }
 
-inline void
-Transport::setMtu(ssize_t mtu)
-{
-  BOOST_ASSERT(mtu == MTU_UNLIMITED || mtu > 0);
-  m_mtu = mtu;
-}
-
 inline ssize_t
 Transport::getSendQueueCapacity() const
 {
diff --git a/tests/daemon/face/generic-link-service.t.cpp b/tests/daemon/face/generic-link-service.t.cpp
index 8006fbb..693d393 100644
--- a/tests/daemon/face/generic-link-service.t.cpp
+++ b/tests/daemon/face/generic-link-service.t.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /*
- * Copyright (c) 2014-2019,  Regents of the University of California,
+ * Copyright (c) 2014-2020,  Regents of the University of California,
  *                           Arizona Board of Regents,
  *                           Colorado State University,
  *                           University Pierre & Marie Curie, Sorbonne University,
@@ -282,6 +282,24 @@
   BOOST_CHECK_EQUAL(service->getCounters().nOutOverMtu, 1);
 }
 
+// It is possible for some interfaces (e.g., virtual Ethernet) to have their MTU set to zero
+// This test case ensures that packets are dropped if the MTU is zero
+BOOST_AUTO_TEST_CASE(FragmentationDisabledZeroMtuDrop)
+{
+  // Initialize with Options that disable fragmentation
+  GenericLinkService::Options options;
+  options.allowFragmentation = false;
+  initialize(options);
+
+  transport->setMtu(0);
+
+  auto data = makeData("/test/data/123456789/987654321/123456789");
+  face->sendData(*data, 0);
+
+  BOOST_CHECK_EQUAL(transport->sentPackets.size(), 0);
+  BOOST_CHECK_EQUAL(service->getCounters().nOutOverMtu, 1);
+}
+
 BOOST_AUTO_TEST_CASE(FragmentationUnlimitedMtu)
 {
   // Initialize with Options that enable fragmentation
@@ -327,6 +345,24 @@
   BOOST_CHECK_GT(transport->sentPackets.size(), 1);
 }
 
+// It is possible for some interfaces (e.g., virtual Ethernet) to have their MTU set to zero
+// This test case ensures that packets are dropped if the MTU is zero
+BOOST_AUTO_TEST_CASE(FragmentationZeroMtuDrop)
+{
+  // Initialize with Options that enable fragmentation
+  GenericLinkService::Options options;
+  options.allowFragmentation = true;
+  initialize(options);
+
+  transport->setMtu(0);
+
+  auto data = makeData("/test/data/123456789/987654321/123456789");
+  face->sendData(*data, 0);
+
+  BOOST_CHECK_EQUAL(transport->sentPackets.size(), 0);
+  BOOST_CHECK_EQUAL(service->getCounters().nFragmentationErrors, 1);
+}
+
 BOOST_AUTO_TEST_CASE(ReassembleFragments)
 {
   // Initialize with Options that enables reassembly
diff --git a/tests/daemon/face/multicast-ethernet-transport.t.cpp b/tests/daemon/face/multicast-ethernet-transport.t.cpp
index 8dde05f..4e0df3c 100644
--- a/tests/daemon/face/multicast-ethernet-transport.t.cpp
+++ b/tests/daemon/face/multicast-ethernet-transport.t.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /*
- * Copyright (c) 2014-2019,  Regents of the University of California,
+ * Copyright (c) 2014-2020,  Regents of the University of California,
  *                           Arizona Board of Regents,
  *                           Colorado State University,
  *                           University Pierre & Marie Curie, Sorbonne University,
@@ -89,6 +89,25 @@
   BOOST_CHECK_EQUAL(transport->getState(), TransportState::UP);
 }
 
+BOOST_AUTO_TEST_CASE(NetifMtuChange)
+{
+  SKIP_IF_ETHERNET_NETIF_COUNT_LT(1);
+  initializeMulticast();
+  BOOST_CHECK_EQUAL(transport->getMtu(), netif->getMtu());
+
+  // netif changes MTU from initial value to 1024
+  netif->setMtu(1024);
+  BOOST_CHECK_EQUAL(transport->getMtu(), 1024);
+
+  // netif changes MTU from 1024 to 4000
+  netif->setMtu(4000);
+  BOOST_CHECK_EQUAL(transport->getMtu(), 4000);
+
+  // netif changes MTU from 4000 to 0
+  netif->setMtu(0);
+  BOOST_CHECK_EQUAL(transport->getMtu(), 0);
+}
+
 BOOST_AUTO_TEST_CASE(Close)
 {
   SKIP_IF_ETHERNET_NETIF_COUNT_LT(1);
diff --git a/tests/daemon/face/unicast-ethernet-transport.t.cpp b/tests/daemon/face/unicast-ethernet-transport.t.cpp
index 355a3ea..634a119 100644
--- a/tests/daemon/face/unicast-ethernet-transport.t.cpp
+++ b/tests/daemon/face/unicast-ethernet-transport.t.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /*
- * Copyright (c) 2014-2019,  Regents of the University of California,
+ * Copyright (c) 2014-2020,  Regents of the University of California,
  *                           Arizona Board of Regents,
  *                           Colorado State University,
  *                           University Pierre & Marie Curie, Sorbonne University,
@@ -58,6 +58,25 @@
   BOOST_CHECK_EQUAL(transport->canChangePersistencyTo(ndn::nfd::FACE_PERSISTENCY_PERMANENT), true);
 }
 
+BOOST_AUTO_TEST_CASE(NetifMtuChange)
+{
+  SKIP_IF_ETHERNET_NETIF_COUNT_LT(1);
+  initializeUnicast();
+  BOOST_CHECK_EQUAL(transport->getMtu(), netif->getMtu());
+
+  // netif changes MTU from initial value to 1024
+  netif->setMtu(1024);
+  BOOST_CHECK_EQUAL(transport->getMtu(), 1024);
+
+  // netif changes MTU from 1024 to 4000
+  netif->setMtu(4000);
+  BOOST_CHECK_EQUAL(transport->getMtu(), 4000);
+
+  // netif changes MTU from 4000 to 0
+  netif->setMtu(0);
+  BOOST_CHECK_EQUAL(transport->getMtu(), 0);
+}
+
 BOOST_AUTO_TEST_CASE(ExpirationTime)
 {
   SKIP_IF_ETHERNET_NETIF_COUNT_LT(1);