mgmt+face: allow MTU of datagram faces to be overridden

refs #4005

Change-Id: I01d98b88cdee41b633f6fb9a5600088efe8de749
diff --git a/daemon/face/channel.hpp b/daemon/face/channel.hpp
index db97cc8..6d2c221 100644
--- a/daemon/face/channel.hpp
+++ b/daemon/face/channel.hpp
@@ -89,6 +89,7 @@
   ndn::nfd::FacePersistency persistency = ndn::nfd::FACE_PERSISTENCY_PERSISTENT;
   optional<time::nanoseconds> baseCongestionMarkingInterval;
   optional<uint64_t> defaultCongestionThreshold;
+  optional<ssize_t> mtu;
   bool wantLocalFields = false;
   bool wantLpReliability = false;
   boost::logic::tribool wantCongestionMarking = boost::logic::indeterminate;
diff --git a/daemon/face/ethernet-channel.cpp b/daemon/face/ethernet-channel.cpp
index 69c6f45..691b40b 100644
--- a/daemon/face/ethernet-channel.cpp
+++ b/daemon/face/ethernet-channel.cpp
@@ -203,7 +203,8 @@
 
   auto linkService = make_unique<GenericLinkService>(options);
   auto transport = make_unique<UnicastEthernetTransport>(*m_localEndpoint, remoteEndpoint,
-                                                         params.persistency, m_idleFaceTimeout);
+                                                         params.persistency, m_idleFaceTimeout,
+                                                         params.mtu);
   auto face = make_shared<Face>(std::move(linkService), std::move(transport));
 
   m_channelFaces[remoteEndpoint] = face;
diff --git a/daemon/face/ethernet-factory.cpp b/daemon/face/ethernet-factory.cpp
index f9767df..aab8aa4 100644
--- a/daemon/face/ethernet-factory.cpp
+++ b/daemon/face/ethernet-factory.cpp
@@ -200,6 +200,13 @@
     return;
   }
 
+  if (req.params.mtu && *req.params.mtu < Transport::MIN_MTU) {
+    // The specified MTU must be greater than the minimum possible
+    NFD_LOG_TRACE("createFace cannot create a face with an MTU less than " << Transport::MIN_MTU);
+    onFailure(406, "MTU cannot be less than " + to_string(Transport::MIN_MTU));
+    return;
+  }
+
   for (const auto& i : m_channels) {
     if (i.first == localEndpoint) {
       i.second->connect(remoteEndpoint, req.params, onCreated, onFailure);
diff --git a/daemon/face/tcp-factory.cpp b/daemon/face/tcp-factory.cpp
index 369af43..5b56839 100644
--- a/daemon/face/tcp-factory.cpp
+++ b/daemon/face/tcp-factory.cpp
@@ -179,6 +179,12 @@
     return;
   }
 
+  if (req.params.mtu) {
+    NFD_LOG_TRACE("createFace cannot create a TCP face with an overridden MTU");
+    onFailure(406, "TCP faces do not support MTU overrides");
+    return;
+  }
+
   // very simple logic for now
   for (const auto& i : m_channels) {
     if ((i.first.address().is_v4() && endpoint.address().is_v4()) ||
diff --git a/daemon/face/transport.cpp b/daemon/face/transport.cpp
index f615a10..457e162 100644
--- a/daemon/face/transport.cpp
+++ b/daemon/face/transport.cpp
@@ -126,12 +126,6 @@
   m_service->receivePacket(std::move(packet));
 }
 
-ssize_t
-Transport::getSendQueueLength()
-{
-  return QUEUE_UNSUPPORTED;
-}
-
 bool
 Transport::canChangePersistencyTo(ndn::nfd::FacePersistency newPersistency) const
 {
diff --git a/daemon/face/transport.hpp b/daemon/face/transport.hpp
index a9768e5..a0a97a0 100644
--- a/daemon/face/transport.hpp
+++ b/daemon/face/transport.hpp
@@ -202,13 +202,6 @@
   void
   send(Packet&& packet);
 
-protected: // upper interface to be invoked by subclass
-  /** \brief receive a link-layer packet
-   *  \warning undefined behavior if packet size exceeds MTU limit
-   */
-  void
-  receive(Packet&& packet);
-
 public: // static properties
   /** \return a FaceUri representing local endpoint
    */
@@ -291,7 +284,17 @@
    *  \retval QUEUE_ERROR transport was unable to retrieve the queue length
    */
   virtual ssize_t
-  getSendQueueLength();
+  getSendQueueLength()
+  {
+    return QUEUE_UNSUPPORTED;
+  }
+
+protected: // upper interface to be invoked by subclass
+  /** \brief receive a link-layer packet
+   *  \warning undefined behavior if packet size exceeds MTU limit
+   */
+  void
+  receive(Packet&& packet);
 
 protected: // properties to be set by subclass
   void
@@ -363,6 +366,13 @@
   virtual void
   doSend(Packet&& packet) = 0;
 
+public:
+  /** \brief minimum MTU that may be set on a transport
+   *
+   *  This is done to ensure the NDNLPv2 fragmentation feature functions properly.
+   */
+  static constexpr ssize_t MIN_MTU = 64;
+
 private:
   Face* m_face;
   LinkService* m_service;
diff --git a/daemon/face/udp-channel.cpp b/daemon/face/udp-channel.cpp
index b87e697..eb4337f 100644
--- a/daemon/face/udp-channel.cpp
+++ b/daemon/face/udp-channel.cpp
@@ -160,6 +160,8 @@
   socket.connect(remoteEndpoint);
 
   GenericLinkService::Options options;
+  options.allowFragmentation = true;
+  options.allowReassembly = true;
   options.reliabilityOptions.isEnabled = params.wantLpReliability;
 
   if (boost::logic::indeterminate(params.wantCongestionMarking)) {
@@ -178,7 +180,8 @@
   }
 
   auto linkService = make_unique<GenericLinkService>(options);
-  auto transport = make_unique<UnicastUdpTransport>(std::move(socket), params.persistency, m_idleFaceTimeout);
+  auto transport = make_unique<UnicastUdpTransport>(std::move(socket), params.persistency,
+                                                    m_idleFaceTimeout, params.mtu);
   auto face = make_shared<Face>(std::move(linkService), std::move(transport));
 
   m_channelFaces[remoteEndpoint] = face;
diff --git a/daemon/face/udp-factory.cpp b/daemon/face/udp-factory.cpp
index 3660db5..e649871 100644
--- a/daemon/face/udp-factory.cpp
+++ b/daemon/face/udp-factory.cpp
@@ -270,6 +270,13 @@
     return;
   }
 
+  if (req.params.mtu && *req.params.mtu < Transport::MIN_MTU) {
+    // The specified MTU must be greater than the minimum possible
+    NFD_LOG_TRACE("createFace cannot create a face with MTU less than " << Transport::MIN_MTU);
+    onFailure(406, "MTU cannot be less than " + to_string(Transport::MIN_MTU));
+    return;
+  }
+
   // very simple logic for now
   for (const auto& i : m_channels) {
     if ((i.first.address().is_v4() && endpoint.address().is_v4()) ||
diff --git a/daemon/face/unicast-ethernet-transport.cpp b/daemon/face/unicast-ethernet-transport.cpp
index e28fa93..60059bb 100644
--- a/daemon/face/unicast-ethernet-transport.cpp
+++ b/daemon/face/unicast-ethernet-transport.cpp
@@ -35,7 +35,8 @@
 UnicastEthernetTransport::UnicastEthernetTransport(const ndn::net::NetworkInterface& localEndpoint,
                                                    const ethernet::Address& remoteEndpoint,
                                                    ndn::nfd::FacePersistency persistency,
-                                                   time::nanoseconds idleTimeout)
+                                                   time::nanoseconds idleTimeout,
+                                                   optional<ssize_t> overrideMtu)
   : EthernetTransport(localEndpoint, remoteEndpoint)
   , m_idleTimeout(idleTimeout)
 {
@@ -44,7 +45,13 @@
   this->setScope(ndn::nfd::FACE_SCOPE_NON_LOCAL);
   this->setPersistency(persistency);
   this->setLinkType(ndn::nfd::LINK_TYPE_POINT_TO_POINT);
-  this->setMtu(localEndpoint.getMtu());
+
+  if (overrideMtu) {
+    this->setMtu(std::min<ssize_t>(localEndpoint.getMtu(), *overrideMtu));
+  }
+  else {
+    this->setMtu(localEndpoint.getMtu());
+  }
 
   NFD_LOG_FACE_INFO("Creating transport");
 
diff --git a/daemon/face/unicast-ethernet-transport.hpp b/daemon/face/unicast-ethernet-transport.hpp
index 19617e3..5a148c9 100644
--- a/daemon/face/unicast-ethernet-transport.hpp
+++ b/daemon/face/unicast-ethernet-transport.hpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /*
- * Copyright (c) 2014-2017,  Regents of the University of California,
+ * Copyright (c) 2014-2018,  Regents of the University of California,
  *                           Arizona Board of Regents,
  *                           Colorado State University,
  *                           University Pierre & Marie Curie, Sorbonne University,
@@ -44,7 +44,8 @@
   UnicastEthernetTransport(const ndn::net::NetworkInterface& localEndpoint,
                            const ethernet::Address& remoteEndpoint,
                            ndn::nfd::FacePersistency persistency,
-                           time::nanoseconds idleTimeout);
+                           time::nanoseconds idleTimeout,
+                           optional<ssize_t> overrideMtu = {});
 
 protected:
   bool
diff --git a/daemon/face/unicast-udp-transport.cpp b/daemon/face/unicast-udp-transport.cpp
index 8e3d7d8..062c924 100644
--- a/daemon/face/unicast-udp-transport.cpp
+++ b/daemon/face/unicast-udp-transport.cpp
@@ -40,7 +40,8 @@
 
 UnicastUdpTransport::UnicastUdpTransport(protocol::socket&& socket,
                                          ndn::nfd::FacePersistency persistency,
-                                         time::nanoseconds idleTimeout)
+                                         time::nanoseconds idleTimeout,
+                                         optional<ssize_t> overrideMtu)
   : DatagramTransport(std::move(socket))
   , m_idleTimeout(idleTimeout)
 {
@@ -49,7 +50,14 @@
   this->setScope(ndn::nfd::FACE_SCOPE_NON_LOCAL);
   this->setPersistency(persistency);
   this->setLinkType(ndn::nfd::LINK_TYPE_POINT_TO_POINT);
-  this->setMtu(udp::computeMtu(m_socket.local_endpoint()));
+
+  if (overrideMtu) {
+    this->setMtu(std::min(udp::computeMtu(m_socket.local_endpoint()), *overrideMtu));
+  }
+  else {
+    this->setMtu(udp::computeMtu(m_socket.local_endpoint()));
+  }
+  BOOST_ASSERT(this->getMtu() >= MIN_MTU);
 
   NFD_LOG_FACE_INFO("Creating transport");
 
diff --git a/daemon/face/unicast-udp-transport.hpp b/daemon/face/unicast-udp-transport.hpp
index b1eb672..03f1c86 100644
--- a/daemon/face/unicast-udp-transport.hpp
+++ b/daemon/face/unicast-udp-transport.hpp
@@ -42,7 +42,8 @@
 public:
   UnicastUdpTransport(protocol::socket&& socket,
                       ndn::nfd::FacePersistency persistency,
-                      time::nanoseconds idleTimeout);
+                      time::nanoseconds idleTimeout,
+                      optional<ssize_t> overrideMtu = {});
 
 protected:
   bool
diff --git a/daemon/mgmt/face-manager.cpp b/daemon/mgmt/face-manager.cpp
index 1d53eab..b1e934f 100644
--- a/daemon/mgmt/face-manager.cpp
+++ b/daemon/mgmt/face-manager.cpp
@@ -122,6 +122,9 @@
   if (parameters.hasDefaultCongestionThreshold()) {
     faceParams.defaultCongestionThreshold = parameters.getDefaultCongestionThreshold();
   }
+  if (parameters.hasMtu()) {
+    faceParams.mtu = parameters.getMtu();
+  }
   faceParams.wantLocalFields = parameters.hasFlagBit(ndn::nfd::BIT_LOCAL_FIELDS_ENABLED) &&
                                parameters.getFlagBit(ndn::nfd::BIT_LOCAL_FIELDS_ENABLED);
   faceParams.wantLpReliability = parameters.hasFlagBit(ndn::nfd::BIT_LP_RELIABILITY_ENABLED) &&
@@ -243,6 +246,7 @@
 
   // Set ControlResponse fields
   response = collectFaceProperties(*face, false);
+  response.unsetMtu(); // This parameter is only included with the response to faces/create and FaceStatus
 
   done(ControlResponse(200, "OK").setBody(response.wireEncode()));
 }
@@ -294,6 +298,9 @@
   BOOST_ASSERT(linkService != nullptr);
   auto options = linkService->getOptions();
 
+  auto transport = face.getTransport();
+  BOOST_ASSERT(transport != nullptr);
+
   ControlParameters params;
   params.setFaceId(face.getId())
         .setFacePersistency(face.getPersistency())
@@ -302,6 +309,14 @@
         .setFlagBit(ndn::nfd::BIT_LOCAL_FIELDS_ENABLED, options.allowLocalFields, false)
         .setFlagBit(ndn::nfd::BIT_LP_RELIABILITY_ENABLED, options.reliabilityOptions.isEnabled, false)
         .setFlagBit(ndn::nfd::BIT_CONGESTION_MARKING_ENABLED, options.allowCongestionMarking, false);
+
+  if (transport->getMtu() > 0) {
+    params.setMtu(std::min<uint64_t>(transport->getMtu(), ndn::MAX_NDN_PACKET_SIZE));
+  }
+  else if (transport->getMtu() == face::MTU_UNLIMITED) {
+    params.setMtu(ndn::MAX_NDN_PACKET_SIZE);
+  }
+
   if (wantUris) {
     params.setUri(face.getRemoteUri().toString())
           .setLocalUri(face.getLocalUri().toString());