face: Prevent infinite loop in TcpFactory and UdpFactory

Change-Id: Idd694bc08033c524f3c0e569ed74341aa33fce31
Refs: #2292
diff --git a/core/network-interface.cpp b/core/network-interface.cpp
index d7eebbd..1aad63e 100644
--- a/core/network-interface.cpp
+++ b/core/network-interface.cpp
@@ -54,9 +54,25 @@
               "NetworkInterfaceInfo must provide a default constructor");
 #endif
 
+#ifdef WITH_TESTS
+static shared_ptr<std::vector<NetworkInterfaceInfo>> s_debugNetworkInterfaces = nullptr;
+
+void
+setDebugNetworkInterfaces(shared_ptr<std::vector<NetworkInterfaceInfo>> interfaces)
+{
+  s_debugNetworkInterfaces = interfaces;
+}
+#endif
+
 std::vector<NetworkInterfaceInfo>
 listNetworkInterfaces()
 {
+#ifdef WITH_TESTS
+  if (s_debugNetworkInterfaces != nullptr) {
+    return *s_debugNetworkInterfaces;
+  }
+#endif
+
   using namespace boost::asio::ip;
   using std::strerror;
 
diff --git a/core/network-interface.hpp b/core/network-interface.hpp
index 8519db1..e9be083 100644
--- a/core/network-interface.hpp
+++ b/core/network-interface.hpp
@@ -88,9 +88,19 @@
   return (flags & IFF_UP) != 0;
 }
 
+/** \brief List configured network interfaces on the system and their info
+ */
 std::vector<NetworkInterfaceInfo>
 listNetworkInterfaces();
 
+#ifdef WITH_TESTS
+/** \brief Set a list of network interfaces to be returned by subsequent listNetworkInterfaces call
+ *  \note To reset to normal behavior, use `setDebugNetworkInterfaces(nullptr);`
+ */
+void
+setDebugNetworkInterfaces(shared_ptr<std::vector<NetworkInterfaceInfo>> interfaces);
+#endif
+
 } // namespace nfd
 
 #endif // NFD_CORE_NETWORK_INTERFACE_HPP
diff --git a/daemon/face/tcp-factory.cpp b/daemon/face/tcp-factory.cpp
index bdc8c24..598a2c4 100644
--- a/daemon/face/tcp-factory.cpp
+++ b/daemon/face/tcp-factory.cpp
@@ -32,6 +32,12 @@
 
 namespace nfd {
 
+static const boost::asio::ip::address_v4 ALL_V4_ENDPOINT(
+  boost::asio::ip::address_v4::from_string("0.0.0.0"));
+
+static const boost::asio::ip::address_v6 ALL_V6_ENDPOINT(
+  boost::asio::ip::address_v6::from_string("::"));
+
 TcpFactory::TcpFactory(const std::string& defaultPort/* = "6363"*/)
   : m_defaultPort(defaultPort)
 {
@@ -42,9 +48,6 @@
 {
   using namespace boost::asio::ip;
 
-  static const address_v4 ALL_V4_ENDPOINT(address_v4::from_string("0.0.0.0"));
-  static const address_v6 ALL_V6_ENDPOINT(address_v6::from_string("::"));
-
   const address& address = endpoint.address();
 
   if (address.is_v4() && address == ALL_V4_ENDPOINT)
@@ -56,8 +59,7 @@
       prohibitAllIpv6Endpoints(endpoint.port());
     }
 
-  NFD_LOG_TRACE("prohibiting TCP " <<
-                endpoint.address().to_string() << ":" << endpoint.port());
+  NFD_LOG_TRACE("prohibiting TCP " << endpoint);
 
   m_prohibitedEndpoints.insert(endpoint);
 }
@@ -69,7 +71,9 @@
 
   for (const NetworkInterfaceInfo& nic : listNetworkInterfaces()) {
     for (const address_v4& addr : nic.ipv4Addresses) {
-      prohibitEndpoint(tcp::Endpoint(addr, port));
+      if (addr != ALL_V4_ENDPOINT) {
+        prohibitEndpoint(tcp::Endpoint(addr, port));
+      }
     }
   }
 }
@@ -81,7 +85,9 @@
 
   for (const NetworkInterfaceInfo& nic : listNetworkInterfaces()) {
     for (const address_v6& addr : nic.ipv6Addresses) {
-      prohibitEndpoint(tcp::Endpoint(addr, port));
+      if (addr != ALL_V6_ENDPOINT) {
+        prohibitEndpoint(tcp::Endpoint(addr, port));
+      }
     }
   }
 }
diff --git a/daemon/face/tcp-factory.hpp b/daemon/face/tcp-factory.hpp
index fd57fe5..e2ebda5 100644
--- a/daemon/face/tcp-factory.hpp
+++ b/daemon/face/tcp-factory.hpp
@@ -88,7 +88,7 @@
   virtual std::list<shared_ptr<const Channel> >
   getChannels() const;
 
-private:
+PUBLIC_WITH_TESTS_ELSE_PRIVATE:
 
   void
   prohibitEndpoint(const tcp::Endpoint& endpoint);
@@ -115,7 +115,7 @@
                                  const FaceCreatedCallback& onCreated,
                                  const FaceConnectFailedCallback& onConnectFailed);
 
-private:
+PUBLIC_WITH_TESTS_ELSE_PRIVATE:
   typedef std::map< tcp::Endpoint, shared_ptr<TcpChannel> > ChannelMap;
   ChannelMap m_channels;
 
diff --git a/daemon/face/udp-factory.cpp b/daemon/face/udp-factory.cpp
index dcb819d..458d4fb 100644
--- a/daemon/face/udp-factory.cpp
+++ b/daemon/face/udp-factory.cpp
@@ -38,6 +38,12 @@
 
 NFD_LOG_INIT("UdpFactory");
 
+static const boost::asio::ip::address_v4 ALL_V4_ENDPOINT(
+  boost::asio::ip::address_v4::from_string("0.0.0.0"));
+
+static const boost::asio::ip::address_v6 ALL_V6_ENDPOINT(
+  boost::asio::ip::address_v6::from_string("::"));
+
 UdpFactory::UdpFactory(const std::string& defaultPort/* = "6363"*/)
   : m_defaultPort(defaultPort)
 {
@@ -48,9 +54,6 @@
 {
   using namespace boost::asio::ip;
 
-  static const address_v4 ALL_V4_ENDPOINT(address_v4::from_string("0.0.0.0"));
-  static const address_v6 ALL_V6_ENDPOINT(address_v6::from_string("::"));
-
   const address& address = endpoint.address();
 
   if (address.is_v4() && address == ALL_V4_ENDPOINT)
@@ -62,8 +65,7 @@
       prohibitAllIpv6Endpoints(endpoint.port());
     }
 
-  NFD_LOG_TRACE("prohibiting UDP " <<
-                endpoint.address().to_string() << ":" << endpoint.port());
+  NFD_LOG_TRACE("prohibiting UDP " << endpoint);
 
   m_prohibitedEndpoints.insert(endpoint);
 }
@@ -73,14 +75,14 @@
 {
   using namespace boost::asio::ip;
 
-  static const address_v4 INVALID_BROADCAST(address_v4::from_string("0.0.0.0"));
-
   for (const NetworkInterfaceInfo& nic : listNetworkInterfaces()) {
     for (const address_v4& addr : nic.ipv4Addresses) {
-      prohibitEndpoint(udp::Endpoint(addr, port));
+      if (addr != ALL_V4_ENDPOINT) {
+        prohibitEndpoint(udp::Endpoint(addr, port));
+      }
     }
 
-    if (nic.isBroadcastCapable() && nic.broadcastAddress != INVALID_BROADCAST)
+    if (nic.isBroadcastCapable() && nic.broadcastAddress != ALL_V4_ENDPOINT)
     {
       NFD_LOG_TRACE("prohibiting broadcast address: " << nic.broadcastAddress.to_string());
       prohibitEndpoint(udp::Endpoint(nic.broadcastAddress, port));
@@ -97,7 +99,9 @@
 
   for (const NetworkInterfaceInfo& nic : listNetworkInterfaces()) {
     for (const address_v6& addr : nic.ipv6Addresses) {
-      prohibitEndpoint(udp::Endpoint(addr, port));
+      if (addr != ALL_V6_ENDPOINT) {
+        prohibitEndpoint(udp::Endpoint(addr, port));
+      }
     }
   }
 }
diff --git a/daemon/face/udp-factory.hpp b/daemon/face/udp-factory.hpp
index 5d83541..1f7f7de 100644
--- a/daemon/face/udp-factory.hpp
+++ b/daemon/face/udp-factory.hpp
@@ -160,7 +160,7 @@
   const MulticastFaceMap&
   getMulticastFaces() const;
 
-private:
+PUBLIC_WITH_TESTS_ELSE_PRIVATE:
 
   void
   prohibitEndpoint(const udp::Endpoint& endpoint);
@@ -202,7 +202,7 @@
                                  const FaceCreatedCallback& onCreated,
                                  const FaceConnectFailedCallback& onConnectFailed);
 
-private:
+PUBLIC_WITH_TESTS_ELSE_PRIVATE:
   typedef std::map< udp::Endpoint, shared_ptr<UdpChannel> > ChannelMap;
 
   ChannelMap m_channels;
diff --git a/tests/core/network-interface.cpp b/tests/core/network-interface.cpp
index 5ac5a31..c26d46a 100644
--- a/tests/core/network-interface.cpp
+++ b/tests/core/network-interface.cpp
@@ -30,7 +30,7 @@
 
 BOOST_FIXTURE_TEST_SUITE(CoreNetworkInterface, BaseFixture)
 
-BOOST_AUTO_TEST_CASE(ListNetworkInterfaces)
+BOOST_AUTO_TEST_CASE(ListRealNetworkInterfaces)
 {
   std::vector<NetworkInterfaceInfo> netifs;
   BOOST_CHECK_NO_THROW(netifs = listNetworkInterfaces());
@@ -48,6 +48,64 @@
   }
 }
 
+class FakeNetworkInterfaceFixture : public BaseFixture
+{
+public:
+  FakeNetworkInterfaceFixture()
+  {
+    using namespace boost::asio::ip;
+
+    auto fakeInterfaces = make_shared<std::vector<NetworkInterfaceInfo>>();
+
+    fakeInterfaces->push_back(
+      NetworkInterfaceInfo {0, "lo0",
+        ethernet::Address(),
+        {address_v4::from_string("127.0.0.1")},
+        {address_v6::from_string("fe80::1")},
+        address_v4::from_string("127.255.255.255"),
+        IFF_LOOPBACK | IFF_UP});
+    fakeInterfaces->push_back(
+      NetworkInterfaceInfo {1, "eth0",
+        ethernet::Address::fromString("3e:15:c2:8b:65:00"),
+        {address_v4::from_string("192.168.2.1")},
+        {},
+        address_v4::from_string("192.168.2.255"),
+        0});
+    fakeInterfaces->push_back(
+      NetworkInterfaceInfo {2, "eth1",
+        ethernet::Address::fromString("3e:15:c2:8b:65:00"),
+        {address_v4::from_string("198.51.100.1")},
+        {address_v6::from_string("2001:db8::1")},
+        address_v4::from_string("198.51.100.255"),
+        IFF_MULTICAST | IFF_BROADCAST | IFF_UP});
+
+    setDebugNetworkInterfaces(fakeInterfaces);
+  }
+
+  ~FakeNetworkInterfaceFixture()
+  {
+    setDebugNetworkInterfaces(nullptr);
+  }
+};
+
+BOOST_FIXTURE_TEST_CASE(ListFakeNetworkInterfaces, FakeNetworkInterfaceFixture)
+{
+  std::vector<NetworkInterfaceInfo> netifs;
+  BOOST_CHECK_NO_THROW(netifs = listNetworkInterfaces());
+
+  BOOST_REQUIRE_EQUAL(netifs.size(), 3);
+
+  BOOST_CHECK_EQUAL(netifs[0].index, 0);
+  BOOST_CHECK_EQUAL(netifs[1].index, 1);
+  BOOST_CHECK_EQUAL(netifs[2].index, 2);
+
+  BOOST_CHECK_EQUAL(netifs[0].name, "lo0");
+  BOOST_CHECK_EQUAL(netifs[1].name, "eth0");
+  BOOST_CHECK_EQUAL(netifs[2].name, "eth1");
+
+  // no real value of testing other parameters
+}
+
 BOOST_AUTO_TEST_SUITE_END()
 
 } // namespace tests
diff --git a/tests/daemon/face/tcp.cpp b/tests/daemon/face/tcp.cpp
index 99c2d5b..7c8e33b 100644
--- a/tests/daemon/face/tcp.cpp
+++ b/tests/daemon/face/tcp.cpp
@@ -685,6 +685,88 @@
   BOOST_TEST_MESSAGE("Unexpected assertion test passed");
 }
 
+class FakeNetworkInterfaceFixture : public BaseFixture
+{
+public:
+  FakeNetworkInterfaceFixture()
+  {
+    using namespace boost::asio::ip;
+
+    auto fakeInterfaces = make_shared<std::vector<NetworkInterfaceInfo>>();
+
+    fakeInterfaces->push_back(
+      NetworkInterfaceInfo {0, "eth0",
+        ethernet::Address::fromString("3e:15:c2:8b:65:00"),
+        {address_v4::from_string("0.0.0.0")},
+        {address_v6::from_string("::")},
+        address_v4(),
+        IFF_UP});
+    fakeInterfaces->push_back(
+      NetworkInterfaceInfo {1, "eth0",
+        ethernet::Address::fromString("3e:15:c2:8b:65:00"),
+        {address_v4::from_string("192.168.2.1"), address_v4::from_string("192.168.2.2")},
+        {},
+        address_v4::from_string("192.168.2.255"),
+        0});
+    fakeInterfaces->push_back(
+      NetworkInterfaceInfo {2, "eth1",
+        ethernet::Address::fromString("3e:15:c2:8b:65:00"),
+        {address_v4::from_string("198.51.100.1")},
+        {address_v6::from_string("2001:db8::2"), address_v6::from_string("2001:db8::3")},
+        address_v4::from_string("198.51.100.255"),
+        IFF_MULTICAST | IFF_BROADCAST | IFF_UP});
+
+    setDebugNetworkInterfaces(fakeInterfaces);
+  }
+
+  ~FakeNetworkInterfaceFixture()
+  {
+    setDebugNetworkInterfaces(nullptr);
+  }
+};
+
+BOOST_FIXTURE_TEST_CASE(Bug2292, FakeNetworkInterfaceFixture)
+{
+  using namespace boost::asio::ip;
+
+  TcpFactory factory;
+  factory.prohibitEndpoint(tcp::Endpoint(address_v4::from_string("192.168.2.1"), 1024));
+  BOOST_REQUIRE_EQUAL(factory.m_prohibitedEndpoints.size(), 1);
+  BOOST_CHECK((factory.m_prohibitedEndpoints ==
+               std::set<tcp::Endpoint> {
+                 tcp::Endpoint(address_v4::from_string("192.168.2.1"), 1024),
+               }));
+
+  factory.m_prohibitedEndpoints.clear();
+  factory.prohibitEndpoint(tcp::Endpoint(address_v6::from_string("2001:db8::1"), 2048));
+  BOOST_REQUIRE_EQUAL(factory.m_prohibitedEndpoints.size(), 1);
+  BOOST_CHECK((factory.m_prohibitedEndpoints ==
+               std::set<tcp::Endpoint> {
+                 tcp::Endpoint(address_v6::from_string("2001:db8::1"), 2048)
+               }));
+
+  factory.m_prohibitedEndpoints.clear();
+  factory.prohibitEndpoint(tcp::Endpoint(address_v4(), 1024));
+  BOOST_REQUIRE_EQUAL(factory.m_prohibitedEndpoints.size(), 4);
+  BOOST_CHECK((factory.m_prohibitedEndpoints ==
+               std::set<tcp::Endpoint> {
+                 tcp::Endpoint(address_v4::from_string("192.168.2.1"), 1024),
+                 tcp::Endpoint(address_v4::from_string("192.168.2.2"), 1024),
+                 tcp::Endpoint(address_v4::from_string("198.51.100.1"), 1024),
+                 tcp::Endpoint(address_v4::from_string("0.0.0.0"), 1024)
+               }));
+
+  factory.m_prohibitedEndpoints.clear();
+  factory.prohibitEndpoint(tcp::Endpoint(address_v6(), 2048));
+  BOOST_REQUIRE_EQUAL(factory.m_prohibitedEndpoints.size(), 3);
+  BOOST_CHECK((factory.m_prohibitedEndpoints ==
+               std::set<tcp::Endpoint> {
+                 tcp::Endpoint(address_v6::from_string("2001:db8::2"), 2048),
+                 tcp::Endpoint(address_v6::from_string("2001:db8::3"), 2048),
+                 tcp::Endpoint(address_v6::from_string("::"), 2048)
+               }));
+}
+
 BOOST_AUTO_TEST_SUITE_END()
 
 } // namespace tests
diff --git a/tests/daemon/face/udp.cpp b/tests/daemon/face/udp.cpp
index 87ee8f0..abab320 100644
--- a/tests/daemon/face/udp.cpp
+++ b/tests/daemon/face/udp.cpp
@@ -24,6 +24,7 @@
  */
 
 #include "face/udp-factory.hpp"
+#include "core/network-interface.hpp"
 
 #include "tests/test-common.hpp"
 #include "tests/limited-io.hpp"
@@ -988,6 +989,90 @@
   BOOST_CHECK_EQUAL(channel2->size(), 1);
 }
 
+class FakeNetworkInterfaceFixture : public BaseFixture
+{
+public:
+  FakeNetworkInterfaceFixture()
+  {
+    using namespace boost::asio::ip;
+
+    auto fakeInterfaces = make_shared<std::vector<NetworkInterfaceInfo>>();
+
+    fakeInterfaces->push_back(
+      NetworkInterfaceInfo {0, "eth0",
+        ethernet::Address::fromString("3e:15:c2:8b:65:00"),
+        {address_v4::from_string("0.0.0.0")},
+        {address_v6::from_string("::")},
+        address_v4(),
+        IFF_UP});
+    fakeInterfaces->push_back(
+      NetworkInterfaceInfo {1, "eth0",
+        ethernet::Address::fromString("3e:15:c2:8b:65:00"),
+        {address_v4::from_string("192.168.2.1"), address_v4::from_string("192.168.2.2")},
+        {},
+        address_v4::from_string("192.168.2.255"),
+        0});
+    fakeInterfaces->push_back(
+      NetworkInterfaceInfo {2, "eth1",
+        ethernet::Address::fromString("3e:15:c2:8b:65:00"),
+        {address_v4::from_string("198.51.100.1")},
+        {address_v6::from_string("2001:db8::2"), address_v6::from_string("2001:db8::3")},
+        address_v4::from_string("198.51.100.255"),
+        IFF_MULTICAST | IFF_BROADCAST | IFF_UP});
+
+    setDebugNetworkInterfaces(fakeInterfaces);
+  }
+
+  ~FakeNetworkInterfaceFixture()
+  {
+    setDebugNetworkInterfaces(nullptr);
+  }
+};
+
+BOOST_FIXTURE_TEST_CASE(Bug2292, FakeNetworkInterfaceFixture)
+{
+  using namespace boost::asio::ip;
+
+  UdpFactory factory;
+  factory.prohibitEndpoint(udp::Endpoint(address_v4::from_string("192.168.2.1"), 1024));
+  BOOST_REQUIRE_EQUAL(factory.m_prohibitedEndpoints.size(), 1);
+  BOOST_CHECK((factory.m_prohibitedEndpoints ==
+               std::set<udp::Endpoint> {
+                 udp::Endpoint(address_v4::from_string("192.168.2.1"), 1024),
+               }));
+
+  factory.m_prohibitedEndpoints.clear();
+  factory.prohibitEndpoint(udp::Endpoint(address_v6::from_string("2001:db8::1"), 2048));
+  BOOST_REQUIRE_EQUAL(factory.m_prohibitedEndpoints.size(), 1);
+  BOOST_CHECK((factory.m_prohibitedEndpoints ==
+               std::set<udp::Endpoint> {
+                 udp::Endpoint(address_v6::from_string("2001:db8::1"), 2048),
+               }));
+
+  factory.m_prohibitedEndpoints.clear();
+  factory.prohibitEndpoint(udp::Endpoint(address_v4(), 1024));
+  BOOST_REQUIRE_EQUAL(factory.m_prohibitedEndpoints.size(), 6);
+  BOOST_CHECK((factory.m_prohibitedEndpoints ==
+               std::set<udp::Endpoint> {
+                 udp::Endpoint(address_v4::from_string("192.168.2.1"), 1024),
+                 udp::Endpoint(address_v4::from_string("192.168.2.2"), 1024),
+                 udp::Endpoint(address_v4::from_string("198.51.100.1"), 1024),
+                 udp::Endpoint(address_v4::from_string("198.51.100.255"), 1024),
+                 udp::Endpoint(address_v4::from_string("255.255.255.255"), 1024),
+                 udp::Endpoint(address_v4::from_string("0.0.0.0"), 1024)
+               }));
+
+  factory.m_prohibitedEndpoints.clear();
+  factory.prohibitEndpoint(udp::Endpoint(address_v6(), 2048));
+  BOOST_REQUIRE_EQUAL(factory.m_prohibitedEndpoints.size(), 3);
+  BOOST_CHECK((factory.m_prohibitedEndpoints ==
+               std::set<udp::Endpoint> {
+                 udp::Endpoint(address_v6::from_string("2001:db8::2"), 2048),
+                 udp::Endpoint(address_v6::from_string("2001:db8::3"), 2048),
+                 udp::Endpoint(address_v6::from_string("::"), 2048),
+               }));
+}
+
 BOOST_AUTO_TEST_SUITE_END()
 
 } // namespace tests