face: allow setting default UDP face MTU in config

refs #5138

Change-Id: Ibb3767b27aec2b046d1f41292f3d61001866f8c0
diff --git a/daemon/common/config-file.hpp b/daemon/common/config-file.hpp
index 09ac1a2..c58de29 100644
--- a/daemon/common/config-file.hpp
+++ b/daemon/common/config-file.hpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /*
- * Copyright (c) 2014-2020,  Regents of the University of California,
+ * Copyright (c) 2014-2021,  Regents of the University of California,
  *                           Arizona Board of Regents,
  *                           Colorado State University,
  *                           University Pierre & Marie Curie, Sorbonne University,
@@ -117,13 +117,30 @@
                     "' for option '" + key + "' in section '" + sectionName + "'"));
   }
 
-  template <typename T>
+  template<typename T>
   static T
   parseNumber(const ConfigSection::value_type& option, const std::string& sectionName)
   {
     return parseNumber<T>(option.second, option.first, sectionName);
   }
 
+  /**
+   * \brief check that a value is within the inclusive range [min, max]
+   * \throw Error the value is out of the acceptable range
+   */
+  template<typename T>
+  static void
+  checkRange(T value, T min, T max, const std::string& key, const std::string& sectionName)
+  {
+    static_assert(std::is_integral<T>::value, "T must be an integral type");
+
+    if (value < min || value > max) {
+      NDN_THROW(Error("Invalid value '" + to_string(value) + "' for option '" + key +
+                      "' in section '" + sectionName + "': out of acceptable range [" +
+                      to_string(min) + ", " + to_string(max) + "]"));
+    }
+  }
+
 public: // setup and parsing
   /// \brief setup notification of configuration file sections
   void
diff --git a/daemon/face/channel.cpp b/daemon/face/channel.cpp
index 8b1333e..dd3b118 100644
--- a/daemon/face/channel.cpp
+++ b/daemon/face/channel.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-2021,  Regents of the University of California,
  *                           Arizona Board of Regents,
  *                           Colorado State University,
  *                           University Pierre & Marie Curie, Sorbonne University,
@@ -38,6 +38,12 @@
 }
 
 void
+Channel::setDefaultMtu(size_t mtu)
+{
+  m_defaultMtu = mtu;
+}
+
+void
 connectFaceClosedSignal(Face& face, std::function<void()> f)
 {
   face.afterStateChange.connect([f = std::move(f)] (auto, FaceState newState) {
diff --git a/daemon/face/channel.hpp b/daemon/face/channel.hpp
index ffd06d7..f486feb 100644
--- a/daemon/face/channel.hpp
+++ b/daemon/face/channel.hpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /*
- * Copyright (c) 2014-2020,  Regents of the University of California,
+ * Copyright (c) 2014-2021,  Regents of the University of California,
  *                           Arizona Board of Regents,
  *                           Colorado State University,
  *                           University Pierre & Marie Curie, Sorbonne University,
@@ -50,6 +50,14 @@
     return m_uri;
   }
 
+  /** \brief Returns the default MTU for all faces created by this channel
+   */
+  size_t
+  getDefaultMtu() const
+  {
+    return m_defaultMtu;
+  }
+
   /** \brief Returns whether the channel is listening
    */
   virtual bool
@@ -64,8 +72,12 @@
   void
   setUri(const FaceUri& uri);
 
+  void
+  setDefaultMtu(size_t mtu);
+
 private:
   FaceUri m_uri;
+  size_t m_defaultMtu = ndn::MAX_NDN_PACKET_SIZE;
 };
 
 /** \brief Prototype for the callback that is invoked when a face is created
diff --git a/daemon/face/udp-channel.cpp b/daemon/face/udp-channel.cpp
index f2ccb9c..a5d9dee 100644
--- a/daemon/face/udp-channel.cpp
+++ b/daemon/face/udp-channel.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /*
- * Copyright (c) 2014-2020,  Regents of the University of California,
+ * Copyright (c) 2014-2021,  Regents of the University of California,
  *                           Arizona Board of Regents,
  *                           Colorado State University,
  *                           University Pierre & Marie Curie, Sorbonne University,
@@ -38,13 +38,15 @@
 
 UdpChannel::UdpChannel(const udp::Endpoint& localEndpoint,
                        time::nanoseconds idleTimeout,
-                       bool wantCongestionMarking)
+                       bool wantCongestionMarking,
+                       size_t defaultMtu)
   : m_localEndpoint(localEndpoint)
   , m_socket(getGlobalIoService())
   , m_idleFaceTimeout(idleTimeout)
   , m_wantCongestionMarking(wantCongestionMarking)
 {
   setUri(FaceUri(m_localEndpoint));
+  setDefaultMtu(defaultMtu);
   NFD_LOG_CHAN_INFO("Creating channel");
 }
 
@@ -122,6 +124,7 @@
   try {
     FaceParams params;
     params.persistency = ndn::nfd::FACE_PERSISTENCY_ON_DEMAND;
+    params.mtu = getDefaultMtu();
     std::tie(isCreated, face) = createFace(m_remoteEndpoint, params);
   }
   catch (const boost::system::system_error& e) {
@@ -180,9 +183,7 @@
     options.defaultCongestionThreshold = *params.defaultCongestionThreshold;
   }
 
-  if (params.mtu) {
-    options.overrideMtu = *params.mtu;
-  }
+  options.overrideMtu = params.mtu.value_or(getDefaultMtu());
 
   auto linkService = make_unique<GenericLinkService>(options);
   auto transport = make_unique<UnicastUdpTransport>(std::move(socket), params.persistency,
diff --git a/daemon/face/udp-channel.hpp b/daemon/face/udp-channel.hpp
index 89540de..c3f696d 100644
--- a/daemon/face/udp-channel.hpp
+++ b/daemon/face/udp-channel.hpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /*
- * Copyright (c) 2014-2018,  Regents of the University of California,
+ * Copyright (c) 2014-2021,  Regents of the University of California,
  *                           Arizona Board of Regents,
  *                           Colorado State University,
  *                           University Pierre & Marie Curie, Sorbonne University,
@@ -49,7 +49,8 @@
    */
   UdpChannel(const udp::Endpoint& localEndpoint,
              time::nanoseconds idleTimeout,
-             bool wantCongestionMarking);
+             bool wantCongestionMarking,
+             size_t defaultMtu);
 
   bool
   isListening() const override
diff --git a/daemon/face/udp-factory.cpp b/daemon/face/udp-factory.cpp
index 993f61a..9eabd42 100644
--- a/daemon/face/udp-factory.cpp
+++ b/daemon/face/udp-factory.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /*
- * Copyright (c) 2014-2020,  Regents of the University of California,
+ * Copyright (c) 2014-2021,  Regents of the University of California,
  *                           Arizona Board of Regents,
  *                           Colorado State University,
  *                           University Pierre & Marie Curie, Sorbonne University,
@@ -66,6 +66,7 @@
   //   enable_v4 yes
   //   enable_v6 yes
   //   idle_timeout 600
+  //   unicast_mtu 8800
   //   mcast yes
   //   mcast_group 224.0.23.170
   //   mcast_port 56363
@@ -88,6 +89,7 @@
   bool enableV4 = false;
   bool enableV6 = false;
   uint32_t idleTimeout = 600;
+  size_t unicastMtu = ndn::MAX_NDN_PACKET_SIZE;
   MulticastConfig mcastConfig;
 
   if (configSection) {
@@ -113,6 +115,11 @@
       else if (key == "idle_timeout") {
         idleTimeout = ConfigFile::parseNumber<uint32_t>(pair, "face_system.udp");
       }
+      else if (key == "unicast_mtu") {
+        unicastMtu = ConfigFile::parseNumber<size_t>(pair, "face_system.udp");
+        ConfigFile::checkRange(unicastMtu, static_cast<size_t>(MIN_MTU), ndn::MAX_NDN_PACKET_SIZE,
+                               "unicast_mtu", "face_system.udp");
+      }
       else if (key == "keep_alive_interval") {
         // ignored
       }
@@ -177,6 +184,8 @@
     return;
   }
 
+  m_defaultUnicastMtu = unicastMtu;
+
   if (enableV4) {
     udp::Endpoint endpoint(ip::udp::v4(), port);
     shared_ptr<UdpChannel> v4Channel = this->createChannel(endpoint, time::seconds(idleTimeout));
@@ -302,7 +311,8 @@
                     ", endpoint already allocated to a UDP multicast face"));
   }
 
-  auto channel = std::make_shared<UdpChannel>(localEndpoint, idleTimeout, m_wantCongestionMarking);
+  auto channel = std::make_shared<UdpChannel>(localEndpoint, idleTimeout,
+                                              m_wantCongestionMarking, m_defaultUnicastMtu);
   m_channels[localEndpoint] = channel;
   return channel;
 }
diff --git a/daemon/face/udp-factory.hpp b/daemon/face/udp-factory.hpp
index a57cb25..a0f5b6d 100644
--- a/daemon/face/udp-factory.hpp
+++ b/daemon/face/udp-factory.hpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /*
- * Copyright (c) 2014-2018,  Regents of the University of California,
+ * Copyright (c) 2014-2021,  Regents of the University of California,
  *                           Arizona Board of Regents,
  *                           Colorado State University,
  *                           University Pierre & Marie Curie, Sorbonne University,
@@ -123,6 +123,7 @@
 
 private:
   bool m_wantCongestionMarking = false;
+  size_t m_defaultUnicastMtu = ndn::MAX_NDN_PACKET_SIZE;
   std::map<udp::Endpoint, shared_ptr<UdpChannel>> m_channels;
 
   struct MulticastConfig
diff --git a/nfd.conf.sample.in b/nfd.conf.sample.in
index 61ff71b..9e8d22b 100644
--- a/nfd.conf.sample.in
+++ b/nfd.conf.sample.in
@@ -135,6 +135,14 @@
     ; The default is 600 (10 minutes).
     idle_timeout 600
 
+    ; Maximum payload size for outgoing packets on unicast faces, used in NDNLPv2 fragmentation.
+    ; This must be between 64 and 8800. The default is 8800.
+    ; This value excludes IPv4/IPv6/UDP headers. On an Ethernet link of MTU=1500, setting this
+    ; to 1452 would leave enough room for IP+UDP headers and prevent IP fragmentation.
+    ; This option is not changable during runtime configuration reload, but the MTU of an
+    ; individual face can be updated via NFD Management Protocol or the 'nfdc' tool.
+    unicast_mtu 8800
+
     ; UDP multicast settings.
     ; By default, NFD creates one UDP multicast face per NIC.
     ;
diff --git a/tests/daemon/common/config-file.t.cpp b/tests/daemon/common/config-file.t.cpp
index 2a2cff4..c24e335 100644
--- a/tests/daemon/common/config-file.t.cpp
+++ b/tests/daemon/common/config-file.t.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /*
- * Copyright (c) 2014-2020,  Regents of the University of California,
+ * Copyright (c) 2014-2021,  Regents of the University of California,
  *                           Arizona Board of Regents,
  *                           Colorado State University,
  *                           University Pierre & Marie Curie, Sorbonne University,
@@ -375,6 +375,58 @@
                     ConfigFile::Error);
 }
 
+BOOST_AUTO_TEST_CASE(CheckRange)
+{
+  {
+    uint32_t value = 8000;
+    uint32_t min = 8000;
+    uint32_t max = 8999;
+    ConfigFile::checkRange(value, min, max, "key", "section");
+  }
+
+  {
+    size_t value = 8999;
+    size_t min = 8000;
+    size_t max = 8999;
+    ConfigFile::checkRange(value, min, max, "key", "section");
+  }
+
+  {
+    int64_t value = -7000;
+    int64_t min = -7999;
+    int64_t max = 1000;
+    ConfigFile::checkRange(value, min, max, "key", "section");
+  }
+
+  {
+    uint32_t value = 7999;
+    uint32_t min = 8000;
+    uint32_t max = 8999;
+    BOOST_CHECK_THROW(ConfigFile::checkRange(value, min, max, "key", "section"), ConfigFile::Error);
+  }
+
+  {
+    int16_t value = 9000;
+    int16_t min = 8000;
+    int16_t max = 8999;
+    BOOST_CHECK_THROW(ConfigFile::checkRange(value, min, max, "key", "section"), ConfigFile::Error);
+  }
+
+  {
+    int32_t value = -8000;
+    int32_t min = -7999;
+    int32_t max = 1000;
+    BOOST_CHECK_THROW(ConfigFile::checkRange(value, min, max, "key", "section"), ConfigFile::Error);
+  }
+
+  {
+    int64_t value = 0x1001;
+    int64_t min = -0x7fff;
+    int64_t max = 0x1000;
+    BOOST_CHECK_THROW(ConfigFile::checkRange(value, min, max, "key", "section"), ConfigFile::Error);
+  }
+}
+
 BOOST_AUTO_TEST_SUITE_END() // TestConfigFile
 
 } // namespace tests
diff --git a/tests/daemon/face/channel-fixture.hpp b/tests/daemon/face/channel-fixture.hpp
index 58b5ee4..31a9439 100644
--- a/tests/daemon/face/channel-fixture.hpp
+++ b/tests/daemon/face/channel-fixture.hpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /*
- * Copyright (c) 2014-2020,  Regents of the University of California,
+ * Copyright (c) 2014-2021,  Regents of the University of California,
  *                           Arizona Board of Regents,
  *                           Colorado State University,
  *                           University Pierre & Marie Curie, Sorbonne University,
@@ -71,17 +71,17 @@
   }
 
   virtual shared_ptr<ChannelT>
-  makeChannel(const boost::asio::ip::address&, uint16_t port = 0)
+  makeChannel(const boost::asio::ip::address&, uint16_t port = 0, optional<size_t> mtu = nullopt)
   {
     BOOST_FAIL("Unimplemented");
     return nullptr;
   }
 
   void
-  listen(const boost::asio::ip::address& addr)
+  listen(const boost::asio::ip::address& addr, optional<size_t> mtu = nullopt)
   {
     listenerEp = EndpointT{addr, 7030};
-    listenerChannel = makeChannel(addr, 7030);
+    listenerChannel = makeChannel(addr, 7030, mtu);
     listenerChannel->listen(
       [this] (const shared_ptr<Face>& newFace) {
         BOOST_REQUIRE(newFace != nullptr);
diff --git a/tests/daemon/face/tcp-channel-fixture.hpp b/tests/daemon/face/tcp-channel-fixture.hpp
index 41f17d5..ca0f28e 100644
--- a/tests/daemon/face/tcp-channel-fixture.hpp
+++ b/tests/daemon/face/tcp-channel-fixture.hpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /*
- * Copyright (c) 2014-2020,  Regents of the University of California,
+ * Copyright (c) 2014-2021,  Regents of the University of California,
  *                           Arizona Board of Regents,
  *                           Colorado State University,
  *                           University Pierre & Marie Curie, Sorbonne University,
@@ -44,7 +44,7 @@
   }
 
   shared_ptr<TcpChannel>
-  makeChannel(const boost::asio::ip::address& addr, uint16_t port = 0) final
+  makeChannel(const boost::asio::ip::address& addr, uint16_t port = 0, optional<size_t> mtu = nullopt) final
   {
     if (port == 0)
       port = getNextPort();
diff --git a/tests/daemon/face/udp-channel-fixture.hpp b/tests/daemon/face/udp-channel-fixture.hpp
index 93c5a86..6f57b90 100644
--- a/tests/daemon/face/udp-channel-fixture.hpp
+++ b/tests/daemon/face/udp-channel-fixture.hpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /*
- * Copyright (c) 2014-2020,  Regents of the University of California,
+ * Copyright (c) 2014-2021,  Regents of the University of California,
  *                           Arizona Board of Regents,
  *                           Colorado State University,
  *                           University Pierre & Marie Curie, Sorbonne University,
@@ -39,12 +39,12 @@
 {
 protected:
   shared_ptr<UdpChannel>
-  makeChannel(const boost::asio::ip::address& addr, uint16_t port = 0) final
+  makeChannel(const boost::asio::ip::address& addr, uint16_t port = 0, optional<size_t> mtu = nullopt) final
   {
     if (port == 0)
       port = getNextPort();
 
-    return std::make_shared<UdpChannel>(udp::Endpoint(addr, port), 2_s, false);
+    return std::make_shared<UdpChannel>(udp::Endpoint(addr, port), 2_s, false, mtu.value_or(ndn::MAX_NDN_PACKET_SIZE));
   }
 
   void
diff --git a/tests/daemon/face/udp-channel.t.cpp b/tests/daemon/face/udp-channel.t.cpp
new file mode 100644
index 0000000..b61e5af
--- /dev/null
+++ b/tests/daemon/face/udp-channel.t.cpp
@@ -0,0 +1,69 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright (c) 2014-2021,  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 "udp-channel-fixture.hpp"
+
+#include "test-ip.hpp"
+#include <boost/mpl/vector.hpp>
+
+namespace nfd {
+namespace face {
+namespace tests {
+
+BOOST_AUTO_TEST_SUITE(Face)
+BOOST_FIXTURE_TEST_SUITE(TestUdpChannel, UdpChannelFixture)
+
+using AddressFamilies = boost::mpl::vector<
+  std::integral_constant<AddressFamily, AddressFamily::V4>,
+  std::integral_constant<AddressFamily, AddressFamily::V6>>;
+
+BOOST_AUTO_TEST_CASE_TEMPLATE(DefaultMtu, F, AddressFamilies)
+{
+  auto address = getTestIp(F::value, AddressScope::Loopback);
+  SKIP_IF_IP_UNAVAILABLE(address);
+  this->listen(address,1452);
+
+  auto ch1 = this->makeChannel(typename IpAddressFromFamily<F::value>::type(), 0, 1232);
+  connect(*ch1);
+
+  BOOST_CHECK_EQUAL(this->limitedIo.run(2, 1_s), LimitedIo::EXCEED_OPS);
+  BOOST_CHECK_EQUAL(this->listenerChannel->size(), 1);
+  BOOST_CHECK_EQUAL(ch1->size(), 1);
+  BOOST_CHECK_EQUAL(ch1->isListening(), false);
+
+  for (const auto& face : this->listenerFaces) {
+    BOOST_CHECK_EQUAL(face->getMtu(), 1452);
+  }
+  for (const auto& face : this->clientFaces) {
+    BOOST_CHECK_EQUAL(face->getMtu(), 1232);
+  }
+}
+
+BOOST_AUTO_TEST_SUITE_END() // TestUdpChannel
+BOOST_AUTO_TEST_SUITE_END() // Face
+
+} // namespace tests
+} // namespace face
+} // namespace nfd
diff --git a/tests/daemon/face/udp-factory.t.cpp b/tests/daemon/face/udp-factory.t.cpp
index 0bf7e03..e2788f5 100644
--- a/tests/daemon/face/udp-factory.t.cpp
+++ b/tests/daemon/face/udp-factory.t.cpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /*
- * Copyright (c) 2014-2020,  Regents of the University of California,
+ * Copyright (c) 2014-2021,  Regents of the University of California,
  *                           Arizona Board of Regents,
  *                           Colorado State University,
  *                           University Pierre & Marie Curie, Sorbonne University,
@@ -195,9 +195,10 @@
   parseConfig(CONFIG, false);
 
   checkChannelListEqual(factory, {"udp4://0.0.0.0:6363", "udp6://[::]:6363"});
-  auto channels = factory.getChannels();
-  BOOST_CHECK(std::all_of(channels.begin(), channels.end(),
-                          [] (const auto& ch) { return ch->isListening(); }));
+  for (const auto& ch : factory.getChannels()) {
+    BOOST_CHECK(ch->isListening());
+    BOOST_CHECK_EQUAL(ch->getDefaultMtu(), ndn::MAX_NDN_PACKET_SIZE);
+  }
 }
 
 BOOST_AUTO_TEST_CASE(DisableListen)
@@ -218,9 +219,9 @@
   parseConfig(CONFIG, false);
 
   checkChannelListEqual(factory, {"udp4://0.0.0.0:7001", "udp6://[::]:7001"});
-  auto channels = factory.getChannels();
-  BOOST_CHECK(std::none_of(channels.begin(), channels.end(),
-                           [] (const auto& ch) { return ch->isListening(); }));
+  for (const auto& ch : factory.getChannels()) {
+    BOOST_CHECK(!ch->isListening());
+  }
 }
 
 BOOST_AUTO_TEST_CASE(DisableV4)
@@ -233,6 +234,7 @@
         port 7001
         enable_v4 no
         enable_v6 yes
+        unicast_mtu 1452
         mcast no
       }
     }
@@ -242,6 +244,9 @@
   parseConfig(CONFIG, false);
 
   checkChannelListEqual(factory, {"udp6://[::]:7001"});
+  for (const auto& ch : factory.getChannels()) {
+    BOOST_CHECK_EQUAL(ch->getDefaultMtu(), 1452);
+  }
 }
 
 BOOST_AUTO_TEST_CASE(DisableV6)
@@ -254,6 +259,7 @@
         port 7001
         enable_v4 yes
         enable_v6 no
+        unicast_mtu 1452
         mcast no
       }
     }
@@ -263,6 +269,9 @@
   parseConfig(CONFIG, false);
 
   checkChannelListEqual(factory, {"udp4://0.0.0.0:7001"});
+  for (const auto& ch : factory.getChannels()) {
+    BOOST_CHECK_EQUAL(ch->getDefaultMtu(), 1452);
+  }
 }
 
 BOOST_FIXTURE_TEST_CASE(EnableDisableMcast, UdpFactoryMcastFixture)
@@ -666,6 +675,51 @@
   BOOST_CHECK_THROW(parseConfig(CONFIG2, false), ConfigFile::Error);
 }
 
+BOOST_AUTO_TEST_CASE(BadMtu)
+{
+  // not a number
+  const std::string CONFIG1 = R"CONFIG(
+    face_system
+    {
+      udp
+      {
+        unicast_mtu hello
+      }
+    }
+  )CONFIG";
+
+  BOOST_CHECK_THROW(parseConfig(CONFIG1, true), ConfigFile::Error);
+  BOOST_CHECK_THROW(parseConfig(CONFIG1, false), ConfigFile::Error);
+
+  // underflow
+  const std::string CONFIG2 = R"CONFIG(
+    face_system
+    {
+      udp
+      {
+        unicast_mtu 63
+      }
+    }
+  )CONFIG";
+
+  BOOST_CHECK_THROW(parseConfig(CONFIG2, true), ConfigFile::Error);
+  BOOST_CHECK_THROW(parseConfig(CONFIG2, false), ConfigFile::Error);
+
+  // underflow
+  const std::string CONFIG3 = R"CONFIG(
+    face_system
+    {
+      udp
+      {
+        unicast_mtu 8801
+      }
+    }
+  )CONFIG";
+
+  BOOST_CHECK_THROW(parseConfig(CONFIG3, true), ConfigFile::Error);
+  BOOST_CHECK_THROW(parseConfig(CONFIG3, false), ConfigFile::Error);
+}
+
 BOOST_AUTO_TEST_CASE(BadMcast)
 {
   const std::string CONFIG = R"CONFIG(
diff --git a/tests/daemon/face/websocket-channel-fixture.hpp b/tests/daemon/face/websocket-channel-fixture.hpp
index f28459f..69b2e9b 100644
--- a/tests/daemon/face/websocket-channel-fixture.hpp
+++ b/tests/daemon/face/websocket-channel-fixture.hpp
@@ -1,6 +1,6 @@
 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /*
- * Copyright (c) 2014-2020,  Regents of the University of California,
+ * Copyright (c) 2014-2021,  Regents of the University of California,
  *                           Arizona Board of Regents,
  *                           Colorado State University,
  *                           University Pierre & Marie Curie, Sorbonne University,
@@ -38,7 +38,7 @@
 {
 protected:
   shared_ptr<WebSocketChannel>
-  makeChannel(const boost::asio::ip::address& addr, uint16_t port = 0) final
+  makeChannel(const boost::asio::ip::address& addr, uint16_t port = 0, optional<size_t> mtu = nullopt) final
   {
     if (port == 0)
       port = getNextPort();
diff --git a/tests/other/face-benchmark.cpp b/tests/other/face-benchmark.cpp
index 9ee179f..28af8dc 100644
--- a/tests/other/face-benchmark.cpp
+++ b/tests/other/face-benchmark.cpp
@@ -47,7 +47,7 @@
     : m_terminationSignalSet{getGlobalIoService()}
     , m_tcpChannel{tcp::Endpoint{boost::asio::ip::tcp::v4(), 6363}, false,
                    bind([] { return ndn::nfd::FACE_SCOPE_NON_LOCAL; })}
-    , m_udpChannel{udp::Endpoint{boost::asio::ip::udp::v4(), 6363}, 10_min, false}
+    , m_udpChannel{udp::Endpoint{boost::asio::ip::udp::v4(), 6363}, 10_min, false, ndn::MAX_NDN_PACKET_SIZE}
   {
     m_terminationSignalSet.add(SIGINT);
     m_terminationSignalSet.add(SIGTERM);