mgmt: add support for enabling/disabling IPv4 or v6 channels

refs: #1461

Change-Id: I00bda4098c9a7722bce9d498acce339809af3f01
diff --git a/daemon/mgmt/face-manager.cpp b/daemon/mgmt/face-manager.cpp
index 95b01e0..855bc85 100644
--- a/daemon/mgmt/face-manager.cpp
+++ b/daemon/mgmt/face-manager.cpp
@@ -253,6 +253,8 @@
 
   std::string port = "6363";
   bool needToListen = true;
+  bool enableV4 = true;
+  bool enableV6 = true;
 
   for (ConfigSection::const_iterator i = configSection.begin();
        i != configSection.end();
@@ -269,41 +271,64 @@
           catch (const std::bad_cast& error)
             {
               throw ConfigFile::Error("Invalid value for option " +
-                                      i->first + "\" in \"udp\" section");
+                                      i->first + "\" in \"tcp\" section");
             }
         }
       else if (i->first == "listen")
         {
           needToListen = parseYesNo(i, i->first, "tcp");
         }
+      else if (i->first == "enable_v4")
+        {
+          enableV4 = parseYesNo(i, i->first, "tcp");
+        }
+      else if (i->first == "enable_v6")
+        {
+          enableV6 = parseYesNo(i, i->first, "tcp");
+        }
       else
         {
           throw ConfigFile::Error("Unrecognized option \"" + i->first + "\" in \"tcp\" section");
         }
     }
 
+  if (!enableV4 && !enableV6)
+    {
+      throw ConfigFile::Error("IPv4 and IPv6 channels have been disabled."
+                              " Remove \"tcp\" section to disable TCP channels or"
+                              " re-enable at least one channel type.");
+    }
+
   if (!isDryRun)
     {
       shared_ptr<TcpFactory> factory = make_shared<TcpFactory>(boost::cref(port));
+      m_factories.insert(std::make_pair("tcp", factory));
 
-      using namespace boost::asio::ip;
-
-      shared_ptr<TcpChannel> ipv4Channel = factory->createChannel("0.0.0.0", port);
-      shared_ptr<TcpChannel> ipv6Channel = factory->createChannel("::", port);
-
-      if (needToListen)
+      if (enableV4)
         {
-          // Should acceptFailed callback be used somehow?
+          shared_ptr<TcpChannel> ipv4Channel = factory->createChannel("0.0.0.0", port);
+          if (needToListen)
+            {
+              // Should acceptFailed callback be used somehow?
+              ipv4Channel->listen(bind(&FaceTable::add, &m_faceTable, _1),
+                                  TcpChannel::ConnectFailedCallback());
+            }
 
-          ipv4Channel->listen(bind(&FaceTable::add, &m_faceTable, _1),
-                              TcpChannel::ConnectFailedCallback());
-          ipv6Channel->listen(bind(&FaceTable::add, &m_faceTable, _1),
-                              TcpChannel::ConnectFailedCallback());
+          m_factories.insert(std::make_pair("tcp4", factory));
         }
 
-      m_factories.insert(std::make_pair("tcp", factory));
-      m_factories.insert(std::make_pair("tcp4", factory));
-      m_factories.insert(std::make_pair("tcp6", factory));
+      if (enableV6)
+        {
+          shared_ptr<TcpChannel> ipv6Channel = factory->createChannel("::", port);
+          if (needToListen)
+            {
+              // Should acceptFailed callback be used somehow?
+              ipv6Channel->listen(bind(&FaceTable::add, &m_faceTable, _1),
+                                  TcpChannel::ConnectFailedCallback());
+            }
+
+          m_factories.insert(std::make_pair("tcp6", factory));
+        }
     }
 }
 
@@ -326,6 +351,8 @@
   // }
 
   std::string port = "6363";
+  bool enableV4 = true;
+  bool enableV6 = true;
   size_t timeout = 30;
   size_t keepAliveInterval = 25;
   bool useMcast = true;
@@ -351,6 +378,14 @@
                                       i->first + "\" in \"udp\" section");
             }
         }
+      else if (i->first == "enable_v4")
+        {
+          enableV4 = parseYesNo(i, i->first, "udp");
+        }
+      else if (i->first == "enable_v6")
+        {
+          enableV6 = parseYesNo(i, i->first, "udp");
+        }
       else if (i->first == "idle_timeout")
         {
           try
@@ -421,29 +456,47 @@
         }
     }
 
+  if (!enableV4 && !enableV6)
+    {
+      throw ConfigFile::Error("IPv4 and IPv6 channels have been disabled."
+                              " Remove \"udp\" section to disable UDP channels or"
+                              " re-enable at least one channel type.");
+    }
+  else if (useMcast && !enableV4)
+    {
+      throw ConfigFile::Error("IPv4 multicast requested, but IPv4 channels"
+                              " have been disabled (conflicting configuration options set)");
+    }
+
   /// \todo what is keep alive interval used for?
 
   if (!isDryRun)
     {
       shared_ptr<UdpFactory> factory = make_shared<UdpFactory>(boost::cref(port));
-
-      shared_ptr<UdpChannel> v4Channel =
-        factory->createChannel("0.0.0.0", port, time::seconds(timeout));
-
-      shared_ptr<UdpChannel> v6Channel =
-        factory->createChannel("::", port, time::seconds(timeout));
-
-      v4Channel->listen(bind(&FaceTable::add, &m_faceTable, _1),
-                        UdpChannel::ConnectFailedCallback());
-
-      v6Channel->listen(bind(&FaceTable::add, &m_faceTable, _1),
-                        UdpChannel::ConnectFailedCallback());
-
       m_factories.insert(std::make_pair("udp", factory));
-      m_factories.insert(std::make_pair("udp4", factory));
-      m_factories.insert(std::make_pair("udp6", factory));
 
-      if (useMcast)
+      if (enableV4)
+        {
+          shared_ptr<UdpChannel> v4Channel =
+            factory->createChannel("0.0.0.0", port, time::seconds(timeout));
+
+          v4Channel->listen(bind(&FaceTable::add, &m_faceTable, _1),
+                            UdpChannel::ConnectFailedCallback());
+
+          m_factories.insert(std::make_pair("udp4", factory));
+        }
+
+      if (enableV6)
+        {
+          shared_ptr<UdpChannel> v6Channel =
+            factory->createChannel("::", port, time::seconds(timeout));
+
+          v6Channel->listen(bind(&FaceTable::add, &m_faceTable, _1),
+                            UdpChannel::ConnectFailedCallback());
+          m_factories.insert(std::make_pair("udp6", factory));
+        }
+
+      if (useMcast && enableV4)
         {
           for (std::list<shared_ptr<NetworkInterfaceInfo> >::const_iterator i = nicList.begin();
                i != nicList.end();
diff --git a/nfd.conf.sample.in b/nfd.conf.sample.in
index b825cca..11cd009 100644
--- a/nfd.conf.sample.in
+++ b/nfd.conf.sample.in
@@ -50,12 +50,17 @@
   {
     listen yes ; set to 'no' to disable TCP listener, default 'yes'
     port 6363 ; TCP listener port number
+    enable_v4 yes ; set to 'no' to disable IPv4 channels, default 'yes'
+    enable_v6 yes ; set to 'no' to disable IPv6 channels, default 'yes'
   }
 
   ; The udp section contains settings of UDP faces and channels.
+  ; UDP channel is always listening; delete udp section to disable UDP
   udp
   {
     port 6363 ; UDP unicast port number
+    enable_v4 yes ; set to 'no' to disable IPv4 channels, default 'yes'
+    enable_v6 yes ; set to 'no' to disable IPv6 channels, default 'yes'
     idle_timeout 600 ; idle time (seconds) before closing a UDP unicast face
     keep_alive_interval 25; interval (seconds) between keep-alive refreshes
 
diff --git a/tests/mgmt/face-manager.cpp b/tests/mgmt/face-manager.cpp
index 3b07274..c410878 100644
--- a/tests/mgmt/face-manager.cpp
+++ b/tests/mgmt/face-manager.cpp
@@ -400,6 +400,8 @@
     "  {\n"
     "    listen yes\n"
     "    port 6363\n"
+    "    enable_v4 yes\n"
+    "    enable_v6 yes\n"
     "  }\n"
     "}\n";
   try
@@ -425,6 +427,8 @@
     "  {\n"
     "    listen yes\n"
     "    port 6363\n"
+    "    enable_v4 yes\n"
+    "    enable_v6 yes\n"
     "  }\n"
     "}\n";
   BOOST_CHECK_NO_THROW(parseConfig(CONFIG, true));
@@ -445,6 +449,25 @@
                              "Invalid value for option \"listen\" in \"tcp\" section"));
 }
 
+BOOST_AUTO_TEST_CASE(TestProcessSectionTcpChannelsDisabled)
+{
+  const std::string CONFIG =
+    "face_system\n"
+    "{\n"
+    "  tcp\n"
+    "  {\n"
+    "    port 6363\n"
+    "    enable_v4 no\n"
+    "    enable_v6 no\n"
+    "  }\n"
+    "}\n";
+  BOOST_CHECK_EXCEPTION(parseConfig(CONFIG, false), ConfigFile::Error,
+                        bind(&isExpectedException, _1,
+                             "IPv4 and IPv6 channels have been disabled."
+                             " Remove \"tcp\" section to disable TCP channels or"
+                             " re-enable at least one channel type."));
+}
+
 BOOST_AUTO_TEST_CASE(TestProcessSectionTcpUnknownOption)
 {
   const std::string CONFIG =
@@ -468,6 +491,8 @@
     "  udp\n"
     "  {\n"
     "    port 6363\n"
+    "    enable_v4 yes\n"
+    "    enable_v6 yes\n"
     "    idle_timeout 30\n"
     "    keep_alive_interval 25\n"
     "    mcast yes\n"
@@ -564,6 +589,55 @@
                              "Invalid value for option \"mcast_group\" in \"udp\" section"));
 }
 
+BOOST_AUTO_TEST_CASE(TestProcessSectionUdpChannelsDisabled)
+{
+  const std::string CONFIG =
+    "face_system\n"
+    "{\n"
+    "  udp\n"
+    "  {\n"
+    "    port 6363\n"
+    "    enable_v4 no\n"
+    "    enable_v6 no\n"
+    "    idle_timeout 30\n"
+    "    keep_alive_interval 25\n"
+    "    mcast yes\n"
+    "    mcast_port 56363\n"
+    "    mcast_group 224.0.23.170\n"
+    "  }\n"
+    "}\n";
+  BOOST_CHECK_EXCEPTION(parseConfig(CONFIG, false), ConfigFile::Error,
+                        bind(&isExpectedException, _1,
+                             "IPv4 and IPv6 channels have been disabled."
+                             " Remove \"udp\" section to disable UDP channels or"
+                             " re-enable at least one channel type."));
+}
+
+BOOST_AUTO_TEST_CASE(TestProcessSectionUdpConflictingMcast)
+{
+  const std::string CONFIG =
+    "face_system\n"
+    "{\n"
+    "  udp\n"
+    "  {\n"
+    "    port 6363\n"
+    "    enable_v4 no\n"
+    "    enable_v6 yes\n"
+    "    idle_timeout 30\n"
+    "    keep_alive_interval 25\n"
+    "    mcast yes\n"
+    "    mcast_port 56363\n"
+    "    mcast_group 224.0.23.170\n"
+    "  }\n"
+    "}\n";
+  BOOST_CHECK_EXCEPTION(parseConfig(CONFIG, false), ConfigFile::Error,
+                        bind(&isExpectedException, _1,
+                             "IPv4 multicast requested, but IPv4 channels"
+                             " have been disabled (conflicting configuration options set)"));
+}
+
+
+
 BOOST_AUTO_TEST_CASE(TestProcessSectionUdpUnknownOption)
 {
   const std::string CONFIG =