tools: add mtu option to 'nfdc face create'

refs #4789

Change-Id: I9e31c5fb460ee99199332ffb6fadc9f0f33125e9
diff --git a/docs/manpages/nfdc-face.rst b/docs/manpages/nfdc-face.rst
index f9dd1fd..c00a6ce 100644
--- a/docs/manpages/nfdc-face.rst
+++ b/docs/manpages/nfdc-face.rst
@@ -9,6 +9,7 @@
 |                  [reliability on|off] [congestion-marking on|off]
 |                  [congestion-marking-interval <MARKING-INTERVAL>]
 |                  [default-congestion-threshold <CONGESTION-THRESHOLD>]
+|                  [mtu <MTU>]
 | nfdc face destroy [face] <FACEID|FACEURI>
 | nfdc channel [list]
 
@@ -37,6 +38,11 @@
 default on all other face types.
 Parameters for this feature can set with the **congestion-marking-interval** option (specified in
 milliseconds) and the **default-congestion-threshold** option (specified in bytes).
+The MTUs of unicast Ethernet and UDP faces may be overridden using the **mtu** parameter (specified
+in bytes).
+However, a face may choose to limit the range of acceptable values for this parameter or disregard
+it altogether when setting its MTU.
+The MTU of an existing face may not be modified using this parameter and will result in an error.
 
 The **nfdc face destroy** command destroys an existing face.
 
@@ -128,6 +134,10 @@
 nfdc face create remote udp://router.example.net congestion-marking off
     Create a face with the specified remote FaceUri and explicitly disable congestion marking.
 
+nfdc face create remote udp://router.example.net mtu 4000
+    Create a face with the specified remote FaceUri and an MTU of 4000 bytes (which may be ignored
+    or limited to within a certain range by the internal logic of the face).
+
 nfdc face destroy 300
     Destroy the face whose FaceId is 300.
 
diff --git a/tests/tools/nfdc/face-module.t.cpp b/tests/tools/nfdc/face-module.t.cpp
index 22de100..429af36 100644
--- a/tests/tools/nfdc/face-module.t.cpp
+++ b/tests/tools/nfdc/face-module.t.cpp
@@ -42,11 +42,11 @@
 
 const std::string NONQUERY_OUTPUT =
   "faceid=134 remote=udp4://233.252.0.4:6363 local=udp4://192.0.2.1:6363"
-    " congestion={base-marking-interval=12345ms default-threshold=54321B}"
+    " congestion={base-marking-interval=12345ms default-threshold=54321B} mtu=1024"
     " counters={in={22562i 22031d 63n 2522915B} out={30121i 20940d 1218n 1353592B}}"
     " flags={non-local permanent multi-access}\n"
   "faceid=745 remote=fd://75 local=unix:///var/run/nfd.sock"
-    " congestion={base-marking-interval=100ms default-threshold=65536B}"
+    " congestion={base-marking-interval=100ms default-threshold=65536B} mtu=8800"
     " counters={in={18998i 26701d 147n 4672308B} out={34779i 17028d 1176n 8957187B}}"
     " flags={local on-demand point-to-point local-fields lp-reliability congestion-marking}\n";
 
@@ -62,6 +62,7 @@
             .setLinkType(ndn::nfd::LINK_TYPE_MULTI_ACCESS)
             .setBaseCongestionMarkingInterval(12345_ms)
             .setDefaultCongestionThreshold(54321)
+            .setMtu(1024)
             .setNInInterests(22562)
             .setNInData(22031)
             .setNInNacks(63)
@@ -79,6 +80,7 @@
             .setLinkType(ndn::nfd::LINK_TYPE_POINT_TO_POINT)
             .setBaseCongestionMarkingInterval(100_ms)
             .setDefaultCongestionThreshold(65536)
+            .setMtu(8800)
             .setFlagBit(ndn::nfd::BIT_LOCAL_FIELDS_ENABLED, true)
             .setFlagBit(ndn::nfd::BIT_LP_RELIABILITY_ENABLED, true)
             .setFlagBit(ndn::nfd::BIT_CONGESTION_MARKING_ENABLED, true)
@@ -101,7 +103,7 @@
 
 const std::string QUERY_OUTPUT =
   "faceid=177 remote=tcp4://53.239.9.114:6363 local=tcp4://164.0.31.106:20396"
-    " congestion={base-marking-interval=555ms default-threshold=10000B}"
+    " congestion={base-marking-interval=555ms default-threshold=10000B} mtu=2000"
     " counters={in={2325i 1110d 79n 4716834B} out={2278i 485d 841n 308108B}}"
     " flags={non-local persistent point-to-point}\n";
 
@@ -126,6 +128,7 @@
            .setLinkType(ndn::nfd::LINK_TYPE_POINT_TO_POINT)
            .setBaseCongestionMarkingInterval(555_ms)
            .setDefaultCongestionThreshold(10000)
+           .setMtu(2000)
            .setNInInterests(2325)
            .setNInData(1110)
            .setNInNacks(79)
@@ -174,6 +177,7 @@
     remote=udp4://84.67.35.111:6363
      local=udp4://79.91.49.215:6363
 congestion={base-marking-interval=123ms default-threshold=10000B}
+       mtu=4000
   counters={in={28975i 28232d 212n 13307258B} out={19525i 30993d 1038n 6231946B}}
      flags={non-local on-demand point-to-point}
 )TEXT").substr(1);
@@ -192,6 +196,7 @@
            .setLocalUri("udp4://79.91.49.215:6363")
            .setFaceScope(ndn::nfd::FACE_SCOPE_NON_LOCAL)
            .setFacePersistency(ndn::nfd::FACE_PERSISTENCY_ON_DEMAND)
+           .setMtu(4000)
            .setLinkType(ndn::nfd::LINK_TYPE_POINT_TO_POINT)
            .setBaseCongestionMarkingInterval(123_ms)
            .setDefaultCongestionThreshold(10000)
@@ -218,6 +223,7 @@
     remote=udp4://84.67.35.111:6363
      local=udp4://79.91.49.215:6363
 congestion={base-marking-interval=123ms}
+       mtu=3000
   counters={in={28975i 28232d 212n 13307258B} out={19525i 30993d 1038n 6231946B}}
      flags={non-local on-demand point-to-point}
 )TEXT").substr(1);
@@ -238,6 +244,7 @@
            .setFacePersistency(ndn::nfd::FACE_PERSISTENCY_ON_DEMAND)
            .setLinkType(ndn::nfd::LINK_TYPE_POINT_TO_POINT)
            .setBaseCongestionMarkingInterval(123_ms)
+           .setMtu(3000)
            .setNInInterests(28975)
            .setNInData(28232)
            .setNInNacks(212)
@@ -261,6 +268,7 @@
     remote=udp4://84.67.35.111:6363
      local=udp4://79.91.49.215:6363
 congestion={default-threshold=10000B}
+       mtu=5000
   counters={in={28975i 28232d 212n 13307258B} out={19525i 30993d 1038n 6231946B}}
      flags={non-local on-demand point-to-point}
 )TEXT").substr(1);
@@ -279,6 +287,7 @@
            .setLocalUri("udp4://79.91.49.215:6363")
            .setFaceScope(ndn::nfd::FACE_SCOPE_NON_LOCAL)
            .setFacePersistency(ndn::nfd::FACE_PERSISTENCY_ON_DEMAND)
+           .setMtu(5000)
            .setLinkType(ndn::nfd::LINK_TYPE_POINT_TO_POINT)
            .setDefaultCongestionThreshold(10000)
            .setNInInterests(28975)
@@ -303,6 +312,7 @@
     faceid=256
     remote=udp4://84.67.35.111:6363
      local=udp4://79.91.49.215:6363
+       mtu=6000
   counters={in={28975i 28232d 212n 13307258B} out={19525i 30993d 1038n 6231946B}}
      flags={non-local on-demand point-to-point}
 )TEXT").substr(1);
@@ -321,6 +331,7 @@
            .setLocalUri("udp4://79.91.49.215:6363")
            .setFaceScope(ndn::nfd::FACE_SCOPE_NON_LOCAL)
            .setFacePersistency(ndn::nfd::FACE_PERSISTENCY_ON_DEMAND)
+           .setMtu(6000)
            .setLinkType(ndn::nfd::LINK_TYPE_POINT_TO_POINT)
            .setNInInterests(28975)
            .setNInData(28232)
@@ -368,7 +379,7 @@
 {
 protected:
   void
-  respond409(const Interest& interest, FacePersistency persistency,
+  respond409(const Interest& interest, FacePersistency persistency, optional<uint64_t> mtu = {},
              bool enableLpReliability = false,
              bool enableCongestionMarking = false)
   {
@@ -379,6 +390,9 @@
         .setLocalUri("udp4://68.62.26.57:24087")
         .setFacePersistency(persistency)
         .setFlags(0);
+    if (mtu) {
+      body.setMtu(*mtu);
+    }
     if (enableLpReliability) {
       body.setFlagBit(ndn::nfd::BIT_LP_RELIABILITY_ENABLED, true, false);
     }
@@ -428,6 +442,12 @@
     BOOST_CHECK_EQUAL(req.getLocalUri(), "udp4://98.68.23.71:6363");
     BOOST_REQUIRE(req.hasFacePersistency());
     BOOST_CHECK_EQUAL(req.getFacePersistency(), FacePersistency::FACE_PERSISTENCY_PERMANENT);
+    BOOST_REQUIRE(req.hasBaseCongestionMarkingInterval());
+    BOOST_CHECK_EQUAL(req.getBaseCongestionMarkingInterval(), 100_ms);
+    BOOST_REQUIRE(req.hasDefaultCongestionThreshold());
+    BOOST_CHECK_EQUAL(req.getDefaultCongestionThreshold(), 65536);
+    BOOST_REQUIRE(req.hasMtu());
+    BOOST_CHECK_EQUAL(req.getMtu(), 10000);
     BOOST_CHECK(req.hasFlags());
     BOOST_CHECK(req.getFlagBit(ndn::nfd::BIT_LP_RELIABILITY_ENABLED));
 
@@ -438,6 +458,7 @@
         .setFacePersistency(FacePersistency::FACE_PERSISTENCY_PERMANENT)
         .setBaseCongestionMarkingInterval(100_ms)
         .setDefaultCongestionThreshold(65536)
+        .setMtu(8800)
         .setFlagBit(ndn::nfd::BIT_LP_RELIABILITY_ENABLED, true, false)
         .setFlagBit(ndn::nfd::BIT_CONGESTION_MARKING_ENABLED, true, false);
     this->succeedCommand(interest, resp);
@@ -445,15 +466,30 @@
 
   this->execute("face create udp://22.91.89.51:19903 permanent local udp://98.68.23.71 reliability on "
                 "congestion-marking on congestion-marking-interval 100 "
-                "default-congestion-threshold 65536");
+                "default-congestion-threshold 65536 mtu 10000");
   BOOST_CHECK_EQUAL(exitCode, 0);
   BOOST_CHECK(out.is_equal("face-created id=301 local=udp4://98.68.23.71:6363 "
                            "remote=udp4://22.91.89.51:19903 persistency=permanent "
                            "reliability=on congestion-marking=on "
-                           "congestion-marking-interval=100ms default-congestion-threshold=65536B\n"));
+                           "congestion-marking-interval=100ms default-congestion-threshold=65536B "
+                           "mtu=8800\n"));
   BOOST_CHECK(err.is_empty());
 }
 
+BOOST_AUTO_TEST_CASE(MtuExistingFace)
+{
+  bool hasUpdateCommand = false;
+  this->processInterest = [this, &hasUpdateCommand] (const Interest& interest) {
+    this->respond409(interest, FacePersistency::FACE_PERSISTENCY_ON_DEMAND, 4000);
+    // no command other than faces/create is expected
+  };
+
+  this->execute("face create udp://100.77.30.65 mtu 5000");
+  BOOST_CHECK_EQUAL(exitCode, 1);
+  BOOST_CHECK(out.is_empty());
+  BOOST_CHECK(err.is_equal("Error 409 when creating face: conflict-409\n"));
+}
+
 BOOST_AUTO_TEST_CASE(UpgradingPersistency)
 {
   bool hasUpdateCommand = false;
@@ -487,6 +523,39 @@
   BOOST_CHECK(err.is_empty());
 }
 
+BOOST_AUTO_TEST_CASE(UpgradingPersistencySameMtu)
+{
+  bool hasUpdateCommand = false;
+  this->processInterest = [this, &hasUpdateCommand] (const Interest& interest) {
+    if (parseCommand(interest, "/localhost/nfd/faces/create")) {
+      this->respond409(interest, FacePersistency::FACE_PERSISTENCY_ON_DEMAND, 8800);
+      return;
+    }
+
+    ControlParameters req = MOCK_NFD_MGMT_REQUIRE_COMMAND_IS("/localhost/nfd/faces/update");
+    hasUpdateCommand = true;
+    BOOST_REQUIRE(req.hasFaceId());
+    BOOST_CHECK_EQUAL(req.getFaceId(), 1172);
+    BOOST_REQUIRE(req.hasFacePersistency());
+    BOOST_CHECK_EQUAL(req.getFacePersistency(), FacePersistency::FACE_PERSISTENCY_PERSISTENT);
+    BOOST_CHECK(!req.hasFlags());
+
+    ControlParameters resp;
+    resp.setFaceId(1172)
+        .setFacePersistency(FacePersistency::FACE_PERSISTENCY_PERSISTENT)
+        .setFlags(0);
+    this->succeedCommand(interest, resp);
+  };
+
+  this->execute("face create udp://100.77.30.65 mtu 8800");
+  BOOST_CHECK(hasUpdateCommand);
+  BOOST_CHECK_EQUAL(exitCode, 0);
+  BOOST_CHECK(out.is_equal("face-updated id=1172 local=udp4://68.62.26.57:24087 "
+                           "remote=udp4://100.77.30.65:6363 persistency=persistent "
+                           "reliability=off congestion-marking=off\n"));
+  BOOST_CHECK(err.is_empty());
+}
+
 BOOST_AUTO_TEST_CASE(NotDowngradingPersistency)
 {
   this->processInterest = [this] (const Interest& interest) {
@@ -553,7 +622,7 @@
   bool hasUpdateCommand = false;
   this->processInterest = [this, &hasUpdateCommand] (const Interest& interest) {
     if (parseCommand(interest, "/localhost/nfd/faces/create")) {
-      this->respond409(interest, FacePersistency::FACE_PERSISTENCY_PERSISTENT, true);
+      this->respond409(interest, FacePersistency::FACE_PERSISTENCY_PERSISTENT, {}, true);
       return;
     }
 
@@ -620,7 +689,7 @@
   bool hasUpdateCommand = false;
   this->processInterest = [this, &hasUpdateCommand] (const Interest& interest) {
     if (parseCommand(interest, "/localhost/nfd/faces/create")) {
-      this->respond409(interest, FacePersistency::FACE_PERSISTENCY_PERSISTENT, false, true);
+      this->respond409(interest, FacePersistency::FACE_PERSISTENCY_PERSISTENT, {}, false, true);
       return;
     }
 
@@ -867,6 +936,7 @@
         <baseMarkingInterval>PT0.100S</baseMarkingInterval>
         <defaultThreshold>65536</defaultThreshold>
       </congestion>
+      <mtu>8800</mtu>
       <flags>
         <localFieldsEnabled/>
         <lpReliabilityEnabled/>
@@ -898,7 +968,7 @@
     " counters={in={22562i 22031d 63n 2522915B} out={30121i 20940d 1218n 1353592B}}"
     " flags={non-local permanent multi-access}\n"
   "  faceid=745 remote=fd://75 local=unix:///var/run/nfd.sock"
-    " congestion={base-marking-interval=100ms default-threshold=65536B}"
+    " congestion={base-marking-interval=100ms default-threshold=65536B} mtu=8800"
     " counters={in={18998i 26701d 147n 4672308B} out={34779i 17028d 1176n 8957187B}}"
     " flags={local on-demand point-to-point local-fields lp-reliability congestion-marking}\n";
 
@@ -929,6 +999,7 @@
           .setLinkType(ndn::nfd::LINK_TYPE_POINT_TO_POINT)
           .setBaseCongestionMarkingInterval(100_ms)
           .setDefaultCongestionThreshold(65536)
+          .setMtu(8800)
           .setFlagBit(ndn::nfd::BIT_LOCAL_FIELDS_ENABLED, true)
           .setFlagBit(ndn::nfd::BIT_LP_RELIABILITY_ENABLED, true)
           .setFlagBit(ndn::nfd::BIT_CONGESTION_MARKING_ENABLED, true)
diff --git a/tools/nfd-status-http-server-files/nfd-status.xsl b/tools/nfd-status-http-server-files/nfd-status.xsl
index 725e53e..d9a996c 100644
--- a/tools/nfd-status-http-server-files/nfd-status.xsl
+++ b/tools/nfd-status-http-server-files/nfd-status.xsl
@@ -165,6 +165,7 @@
         <th>Scope</th>
         <th>Persistency</th>
         <th>LinkType</th>
+        <th>MTU</th>
         <th>Flags</th>
         <th>Expires in</th>
         <th>In Interests</th>
@@ -187,6 +188,16 @@
         <td><xsl:value-of select="nfd:facePersistency"/></td>
         <td><xsl:value-of select="nfd:linkType"/></td>
         <td>
+          <xsl:choose>
+            <xsl:when test="nfd:mtu">
+              <xsl:value-of select="nfd:mtu"/>
+            </xsl:when>
+            <xsl:otherwise>
+              n/a
+            </xsl:otherwise>
+          </xsl:choose>
+        </td>
+        <td>
           <xsl:if test="nfd:flags/nfd:localFieldsEnabled">local-fields </xsl:if>
           <xsl:if test="nfd:flags/nfd:lpReliabilityEnabled">reliability </xsl:if>
           <xsl:if test="nfd:flags/nfd:congestionMarkingEnabled">congestion-marking </xsl:if>
diff --git a/tools/nfdc/face-module.cpp b/tools/nfdc/face-module.cpp
index 23a03db..ae6ba16 100644
--- a/tools/nfdc/face-module.cpp
+++ b/tools/nfdc/face-module.cpp
@@ -57,7 +57,8 @@
     .addArg("reliability", ArgValueType::BOOLEAN, Required::NO, Positional::NO)
     .addArg("congestion-marking", ArgValueType::BOOLEAN, Required::NO, Positional::NO)
     .addArg("congestion-marking-interval", ArgValueType::UNSIGNED, Required::NO, Positional::NO)
-    .addArg("default-congestion-threshold", ArgValueType::UNSIGNED, Required::NO, Positional::NO);
+    .addArg("default-congestion-threshold", ArgValueType::UNSIGNED, Required::NO, Positional::NO)
+    .addArg("mtu", ArgValueType::UNSIGNED, Required::NO, Positional::NO);
   parser.addCommand(defFaceCreate, &FaceModule::create);
 
   CommandDefinition defFaceDestroy("face", "destroy");
@@ -159,6 +160,7 @@
   auto congestionMarking = ctx.args.getTribool("congestion-marking");
   auto baseCongestionMarkingIntervalMs = ctx.args.getOptional<uint64_t>("congestion-marking-interval");
   auto defaultCongestionThreshold = ctx.args.getOptional<uint64_t>("default-congestion-threshold");
+  auto mtu = ctx.args.getOptional<uint64_t>("mtu");
 
   FaceUri canonicalRemote;
   optional<FaceUri> canonicalLocal;
@@ -192,7 +194,11 @@
       return false;
     }
 
-    if (persistencyLessThan(respParams.getFacePersistency(), persistency)) {
+    if (mtu && (!respParams.hasMtu() || respParams.getMtu() != *mtu)) {
+      // Mtu cannot be changed with faces/update
+      return false;
+    }
+    else if (persistencyLessThan(respParams.getFacePersistency(), persistency)) {
       // need to upgrade persistency
       ControlParameters params;
       params.setFaceId(respParams.getFaceId()).setFacePersistency(persistency);
@@ -263,6 +269,9 @@
     if (defaultCongestionThreshold) {
       params.setDefaultCongestionThreshold(*defaultCongestionThreshold);
     }
+    if (mtu) {
+      params.setMtu(*mtu);
+    }
 
     ctx.controller.start<ndn::nfd::FaceCreateCommand>(
       params,
@@ -400,6 +409,10 @@
     os << "</congestion>";
   }
 
+  if (item.hasMtu()) {
+    os << "<mtu>" << item.getMtu() << "</mtu>";
+  }
+
   if (item.getFlags() == 0) {
     os << "<flags/>";
   }
@@ -469,6 +482,10 @@
     os << "}";
   }
 
+  if (item.hasMtu()) {
+    os << ia("mtu") << item.getMtu();
+  }
+
   os << ia("counters")
      << "{in={"
      << item.getNInInterests() << "i "
@@ -512,6 +529,9 @@
   if (resp.hasDefaultCongestionThreshold()) {
     os << ia("default-congestion-threshold") << resp.getDefaultCongestionThreshold() << "B";
   }
+  if (resp.hasMtu()) {
+    os << ia("mtu") << resp.getMtu();
+  }
   os << '\n';
 }