face: Configurable IP subnets for "local" TCP faces

Change-Id: Idaddfe4b2c304b552d2e132235f4c3d3e6c2ebcb
Refs: #4546
diff --git a/daemon/face/tcp-channel.cpp b/daemon/face/tcp-channel.cpp
index 821c6d7..058bbe5 100644
--- a/daemon/face/tcp-channel.cpp
+++ b/daemon/face/tcp-channel.cpp
@@ -24,9 +24,9 @@
  */
 
 #include "tcp-channel.hpp"
+#include "core/global-io.hpp"
 #include "generic-link-service.hpp"
 #include "tcp-transport.hpp"
-#include "core/global-io.hpp"
 
 namespace nfd {
 namespace face {
@@ -35,11 +35,13 @@
 
 namespace ip = boost::asio::ip;
 
-TcpChannel::TcpChannel(const tcp::Endpoint& localEndpoint, bool wantCongestionMarking)
+TcpChannel::TcpChannel(const tcp::Endpoint& localEndpoint, bool wantCongestionMarking,
+                       DetermineFaceScopeFromAddress determineFaceScope)
   : m_localEndpoint(localEndpoint)
   , m_acceptor(getGlobalIoService())
   , m_socket(getGlobalIoService())
   , m_wantCongestionMarking(wantCongestionMarking)
+  , m_determineFaceScope(std::move(determineFaceScope))
 {
   setUri(FaceUri(m_localEndpoint));
   NFD_LOG_CHAN_INFO("Creating channel");
@@ -122,7 +124,9 @@
     }
 
     auto linkService = make_unique<GenericLinkService>(options);
-    auto transport = make_unique<TcpTransport>(std::move(socket), params.persistency);
+    auto faceScope = m_determineFaceScope(socket.local_endpoint().address(),
+                                          socket.remote_endpoint().address());
+    auto transport = make_unique<TcpTransport>(std::move(socket), params.persistency, faceScope);
     face = make_shared<Face>(std::move(linkService), std::move(transport));
 
     m_channelFaces[remoteEndpoint] = face;
diff --git a/daemon/face/tcp-channel.hpp b/daemon/face/tcp-channel.hpp
index 0f4c79a..c840695 100644
--- a/daemon/face/tcp-channel.hpp
+++ b/daemon/face/tcp-channel.hpp
@@ -37,6 +37,9 @@
 
 namespace face {
 
+using DetermineFaceScopeFromAddress = std::function<ndn::nfd::FaceScope(const boost::asio::ip::address& local,
+                                                                        const boost::asio::ip::address& remote)>;
+
 /**
  * \brief Class implementing TCP-based channel to create faces
  *
@@ -53,7 +56,8 @@
    * To enable creation faces upon incoming connections,
    * one needs to explicitly call TcpChannel::listen method.
    */
-  TcpChannel(const tcp::Endpoint& localEndpoint, bool wantCongestionMarking);
+  TcpChannel(const tcp::Endpoint& localEndpoint, bool wantCongestionMarking,
+             DetermineFaceScopeFromAddress determineFaceScope);
 
   bool
   isListening() const override
@@ -126,6 +130,7 @@
   boost::asio::ip::tcp::socket m_socket;
   std::map<tcp::Endpoint, shared_ptr<Face>> m_channelFaces;
   bool m_wantCongestionMarking;
+  DetermineFaceScopeFromAddress m_determineFaceScope;
 };
 
 } // namespace face
diff --git a/daemon/face/tcp-factory.cpp b/daemon/face/tcp-factory.cpp
index 1477e09..1831467 100644
--- a/daemon/face/tcp-factory.cpp
+++ b/daemon/face/tcp-factory.cpp
@@ -72,6 +72,8 @@
   uint16_t port = 6363;
   bool enableV4 = true;
   bool enableV6 = true;
+  IpAddressPredicate local;
+  bool isLocalConfigured = false;
 
   for (const auto& pair : *configSection) {
     const std::string& key = pair.first;
@@ -88,10 +90,28 @@
     else if (key == "enable_v6") {
       enableV6 = ConfigFile::parseYesNo(pair, "face_system.tcp");
     }
+    else if (key == "local") {
+      isLocalConfigured = true;
+      for (const auto& localPair : pair.second) {
+        const std::string& localKey = localPair.first;
+        if (localKey == "whitelist") {
+          local.parseWhitelist(localPair.second);
+        }
+        else if (localKey == "blacklist") {
+          local.parseBlacklist(localPair.second);
+        }
+        else {
+          BOOST_THROW_EXCEPTION(ConfigFile::Error("Unrecognized option face_system.tcp.local." + localKey));
+        }
+      }
+    }
     else {
       BOOST_THROW_EXCEPTION(ConfigFile::Error("Unrecognized option face_system.tcp." + key));
     }
   }
+  if (!isLocalConfigured) {
+    local.assign({{"subnet", "127.0.0.0/8"}, {"subnet", "::1/128"}}, {});
+  }
 
   if (!enableV4 && !enableV6) {
     BOOST_THROW_EXCEPTION(ConfigFile::Error(
@@ -125,6 +145,8 @@
     else if (providedSchemes.count("tcp6") > 0) {
       NFD_LOG_WARN("Cannot close tcp6 channel after its creation");
     }
+
+    m_local = std::move(local);
   }
 }
 
@@ -179,7 +201,8 @@
   if (it != m_channels.end())
     return it->second;
 
-  auto channel = make_shared<TcpChannel>(endpoint, m_wantCongestionMarking);
+  auto channel = make_shared<TcpChannel>(endpoint, m_wantCongestionMarking,
+                                         bind(&TcpFactory::determineFaceScopeFromAddresses, this, _1, _2));
   m_channels[endpoint] = channel;
   return channel;
 }
@@ -190,5 +213,15 @@
   return getChannelsFromMap(m_channels);
 }
 
+ndn::nfd::FaceScope
+TcpFactory::determineFaceScopeFromAddresses(const boost::asio::ip::address& localAddress,
+                                            const boost::asio::ip::address& remoteAddress) const
+{
+  if (m_local(localAddress) && m_local(remoteAddress)) {
+    return ndn::nfd::FACE_SCOPE_LOCAL;
+  }
+  return ndn::nfd::FACE_SCOPE_NON_LOCAL;
+}
+
 } // namespace face
 } // namespace nfd
diff --git a/daemon/face/tcp-factory.hpp b/daemon/face/tcp-factory.hpp
index 6d3dbce..dddcd40 100644
--- a/daemon/face/tcp-factory.hpp
+++ b/daemon/face/tcp-factory.hpp
@@ -72,8 +72,16 @@
   getChannels() const override;
 
 private:
+  ndn::nfd::FaceScope
+  determineFaceScopeFromAddresses(const boost::asio::ip::address& local,
+                                  const boost::asio::ip::address& remote) const;
+
+private:
   bool m_wantCongestionMarking = false;
   std::map<tcp::Endpoint, shared_ptr<TcpChannel>> m_channels;
+
+PUBLIC_WITH_TESTS_ELSE_PRIVATE:
+  IpAddressPredicate m_local;
 };
 
 } // namespace face
diff --git a/daemon/face/tcp-transport.cpp b/daemon/face/tcp-transport.cpp
index c95f693..eef8570 100644
--- a/daemon/face/tcp-transport.cpp
+++ b/daemon/face/tcp-transport.cpp
@@ -39,20 +39,14 @@
 time::milliseconds TcpTransport::s_maxReconnectWait = time::minutes(5);
 float TcpTransport::s_reconnectWaitMultiplier = 2.0f;
 
-TcpTransport::TcpTransport(protocol::socket&& socket, ndn::nfd::FacePersistency persistency)
+TcpTransport::TcpTransport(protocol::socket&& socket, ndn::nfd::FacePersistency persistency, ndn::nfd::FaceScope faceScope)
   : StreamTransport(std::move(socket))
   , m_remoteEndpoint(m_socket.remote_endpoint())
   , m_nextReconnectWait(s_initialReconnectWait)
 {
   this->setLocalUri(FaceUri(m_socket.local_endpoint()));
   this->setRemoteUri(FaceUri(m_socket.remote_endpoint()));
-
-  if (m_socket.local_endpoint().address().is_loopback() &&
-      m_socket.remote_endpoint().address().is_loopback())
-    this->setScope(ndn::nfd::FACE_SCOPE_LOCAL);
-  else
-    this->setScope(ndn::nfd::FACE_SCOPE_NON_LOCAL);
-
+  this->setScope(faceScope);
   this->setPersistency(persistency);
   this->setLinkType(ndn::nfd::LINK_TYPE_POINT_TO_POINT);
   this->setMtu(MTU_UNLIMITED);
diff --git a/daemon/face/tcp-transport.hpp b/daemon/face/tcp-transport.hpp
index d6b0890..9367ffe 100644
--- a/daemon/face/tcp-transport.hpp
+++ b/daemon/face/tcp-transport.hpp
@@ -42,7 +42,7 @@
 class TcpTransport FINAL_UNLESS_WITH_TESTS : public StreamTransport<boost::asio::ip::tcp>
 {
 public:
-  TcpTransport(protocol::socket&& socket, ndn::nfd::FacePersistency persistency);
+  TcpTransport(protocol::socket&& socket, ndn::nfd::FacePersistency persistency, ndn::nfd::FaceScope faceScope);
 
   ssize_t
   getSendQueueLength() final;
diff --git a/nfd.conf.sample.in b/nfd.conf.sample.in
index 334bc5a..c5bf79f 100644
--- a/nfd.conf.sample.in
+++ b/nfd.conf.sample.in
@@ -106,6 +106,19 @@
     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'
+
+    ; A TCP face has local scope if the local and remote IP addresses match the whitelist but not the blacklist
+    local
+    {
+      whitelist
+      {
+        subnet 127.0.0.0/8
+        subnet ::1/128
+      }
+      blacklist
+      {
+      }
+    }
   }
 
   ; The udp section contains settings for UDP faces and channels.
diff --git a/tests/daemon/face/factory-test-common.hpp b/tests/daemon/face/factory-test-common.hpp
index 795bdf3..15b3621 100644
--- a/tests/daemon/face/factory-test-common.hpp
+++ b/tests/daemon/face/factory-test-common.hpp
@@ -64,11 +64,15 @@
            const FaceUri& remoteUri,
            const ndn::optional<FaceUri>& localUri,
            const TestFaceParams& params,
-           const CreateFaceExpectedResult& expected)
+           const CreateFaceExpectedResult& expected,
+           const std::function<void(const Face&)>& extraChecks = nullptr)
 {
   factory.createFace({remoteUri, localUri, params},
-                     [expected] (const shared_ptr<Face>&) {
+                     [expected, extraChecks] (const shared_ptr<Face>& face) {
                        BOOST_CHECK_EQUAL(CreateFaceExpectedResult::SUCCESS, expected.result);
+                       if (extraChecks) {
+                         extraChecks(*face);
+                       }
                      },
                      [expected] (uint32_t actualStatus, const std::string& actualReason) {
                        BOOST_CHECK_EQUAL(CreateFaceExpectedResult::FAILURE, expected.result);
diff --git a/tests/daemon/face/tcp-channel-fixture.hpp b/tests/daemon/face/tcp-channel-fixture.hpp
index 732ad22..fa90735 100644
--- a/tests/daemon/face/tcp-channel-fixture.hpp
+++ b/tests/daemon/face/tcp-channel-fixture.hpp
@@ -27,6 +27,7 @@
 #define NFD_TESTS_DAEMON_FACE_TCP_CHANNEL_FIXTURE_HPP
 
 #include "face/tcp-channel.hpp"
+#include "core/network-predicate.hpp"
 
 #include "channel-fixture.hpp"
 
@@ -37,13 +38,19 @@
 class TcpChannelFixture : public ChannelFixture<TcpChannel, tcp::Endpoint>
 {
 protected:
+  TcpChannelFixture()
+  {
+    local.assign({{"subnet", "127.0.0.0/8"}, {"subnet", "::1/128"}}, {});
+  }
+
   unique_ptr<TcpChannel>
   makeChannel(const boost::asio::ip::address& addr, uint16_t port = 0) final
   {
     if (port == 0)
       port = getNextPort();
 
-    return make_unique<TcpChannel>(tcp::Endpoint(addr, port), false);
+    return make_unique<TcpChannel>(tcp::Endpoint(addr, port), false,
+                                   std::bind(&TcpChannelFixture::determineFaceScope, this, _1, _2));
   }
 
   void
@@ -61,8 +68,21 @@
     });
   }
 
+  ndn::nfd::FaceScope
+  determineFaceScope(const boost::asio::ip::address& localAddress,
+                     const boost::asio::ip::address& remoteAddress)
+  {
+    if (local(localAddress) && local(remoteAddress)) {
+      return ndn::nfd::FACE_SCOPE_LOCAL;
+    }
+    else {
+      return ndn::nfd::FACE_SCOPE_NON_LOCAL;
+    }
+  }
+
 protected:
   std::vector<shared_ptr<Face>> clientFaces;
+  IpAddressPredicate local;
 };
 
 } // namespace tests
diff --git a/tests/daemon/face/tcp-factory.t.cpp b/tests/daemon/face/tcp-factory.t.cpp
index 77b8066..08e4498 100644
--- a/tests/daemon/face/tcp-factory.t.cpp
+++ b/tests/daemon/face/tcp-factory.t.cpp
@@ -45,6 +45,9 @@
                            boost::lexical_cast<uint16_t>(localPort));
     return factory.createChannel(endpoint);
   }
+
+protected:
+  LimitedIo limitedIo;
 };
 
 BOOST_AUTO_TEST_SUITE(Face)
@@ -68,6 +71,11 @@
   auto channels = factory.getChannels();
   BOOST_CHECK(std::all_of(channels.begin(), channels.end(),
                           [] (const shared_ptr<const Channel>& ch) { return ch->isListening(); }));
+
+  BOOST_CHECK_EQUAL(factory.m_local.m_whitelist.size(), 2);
+  BOOST_CHECK_EQUAL(factory.m_local.m_whitelist.count("127.0.0.0/8"), 1);
+  BOOST_CHECK_EQUAL(factory.m_local.m_whitelist.count("::1/128"), 1);
+  BOOST_CHECK_EQUAL(factory.m_local.m_blacklist.size(), 0);
 }
 
 BOOST_AUTO_TEST_CASE(DisableListen)
@@ -132,6 +140,88 @@
   checkChannelListEqual(factory, {"tcp4://0.0.0.0:7001"});
 }
 
+BOOST_AUTO_TEST_CASE(ConfigureLocal)
+{
+  const std::string CONFIG = R"CONFIG(
+    face_system
+    {
+      tcp
+      {
+        local {
+          whitelist {
+            subnet 127.0.0.0/8
+          }
+
+          blacklist {
+            subnet ::1/128
+          }
+        }
+      }
+    }
+  )CONFIG";
+
+  parseConfig(CONFIG, true);
+  parseConfig(CONFIG, false);
+
+  BOOST_CHECK_EQUAL(factory.m_local.m_whitelist.size(), 1);
+  BOOST_CHECK_EQUAL(factory.m_local.m_whitelist.count("127.0.0.0/8"), 1);
+  BOOST_CHECK_EQUAL(factory.m_local.m_blacklist.size(), 1);
+  BOOST_CHECK_EQUAL(factory.m_local.m_blacklist.count("::1/128"), 1);
+
+  createFace(factory,
+             FaceUri("tcp4://127.0.0.1:6363"),
+             {},
+             {ndn::nfd::FACE_PERSISTENCY_PERSISTENT, {}, {}, false, false, false},
+             {CreateFaceExpectedResult::SUCCESS, 0, ""},
+             [] (const nfd::Face& face) {
+               BOOST_CHECK_EQUAL(face.getScope(), ndn::nfd::FACE_SCOPE_LOCAL);
+             });
+
+  limitedIo.run(1, 100_ms);
+}
+
+BOOST_AUTO_TEST_CASE(ConfigureNonLocal)
+{
+  const std::string CONFIG = R"CONFIG(
+    face_system
+    {
+      tcp
+      {
+        local {
+          whitelist {
+            *
+          }
+
+          blacklist {
+            subnet 127.0.0.0/8
+            subnet ::1/128
+          }
+        }
+      }
+    }
+  )CONFIG";
+
+  parseConfig(CONFIG, true);
+  parseConfig(CONFIG, false);
+
+  BOOST_CHECK_EQUAL(factory.m_local.m_whitelist.size(), 1);
+  BOOST_CHECK_EQUAL(factory.m_local.m_whitelist.count("*"), 1);
+  BOOST_CHECK_EQUAL(factory.m_local.m_blacklist.size(), 2);
+  BOOST_CHECK_EQUAL(factory.m_local.m_blacklist.count("127.0.0.0/8"), 1);
+  BOOST_CHECK_EQUAL(factory.m_local.m_blacklist.count("::1/128"), 1);
+
+  createFace(factory,
+             FaceUri("tcp4://127.0.0.1:6363"),
+             {},
+             {ndn::nfd::FACE_PERSISTENCY_PERSISTENT, {}, {}, false, false, false},
+             {CreateFaceExpectedResult::SUCCESS, 0, ""},
+             [] (const nfd::Face& face) {
+               BOOST_CHECK_EQUAL(face.getScope(), ndn::nfd::FACE_SCOPE_NON_LOCAL);
+             });
+
+  limitedIo.run(1, 100_ms);
+}
+
 BOOST_AUTO_TEST_CASE(Omitted)
 {
   const std::string CONFIG = R"CONFIG(
@@ -357,7 +447,6 @@
   }
 
 public:
-  LimitedIo limitedIo;
   shared_ptr<nfd::Face> face;
 };
 
diff --git a/tests/daemon/face/tcp-transport-fixture.hpp b/tests/daemon/face/tcp-transport-fixture.hpp
index face839..0cfae44 100644
--- a/tests/daemon/face/tcp-transport-fixture.hpp
+++ b/tests/daemon/face/tcp-transport-fixture.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,
@@ -88,8 +88,17 @@
     BOOST_REQUIRE_EQUAL(limitedIo.run(2, time::seconds(1)), LimitedIo::EXCEED_OPS);
 
     localEp = sock.local_endpoint();
+
+    ndn::nfd::FaceScope scope;
+    if (sock.local_endpoint().address().is_loopback() && sock.remote_endpoint().address().is_loopback()) {
+      scope = ndn::nfd::FACE_SCOPE_LOCAL;
+    }
+    else {
+      scope = ndn::nfd::FACE_SCOPE_NON_LOCAL;
+    }
+
     face = make_unique<Face>(make_unique<DummyReceiveLinkService>(),
-                             make_unique<TcpTransport>(std::move(sock), persistency));
+                             make_unique<TcpTransport>(std::move(sock), persistency, scope));
     transport = static_cast<TcpTransport*>(face->getTransport());
     receivedPackets = &static_cast<DummyReceiveLinkService*>(face->getLinkService())->receivedPackets;
 
diff --git a/tests/daemon/face/tcp-transport.t.cpp b/tests/daemon/face/tcp-transport.t.cpp
index 96cba88..0b573f3 100644
--- a/tests/daemon/face/tcp-transport.t.cpp
+++ b/tests/daemon/face/tcp-transport.t.cpp
@@ -119,7 +119,7 @@
 {
 public:
   PermanentTcpTransportReconnectObserver(protocol::socket&& socket, LimitedIo& io)
-    : TcpTransport(std::move(socket), ndn::nfd::FACE_PERSISTENCY_PERMANENT)
+    : TcpTransport(std::move(socket), ndn::nfd::FACE_PERSISTENCY_PERMANENT, ndn::nfd::FACE_SCOPE_LOCAL)
     , m_io(io)
   {
   }
diff --git a/tests/other/face-benchmark.cpp b/tests/other/face-benchmark.cpp
index 2557d6f..3499e89 100644
--- a/tests/other/face-benchmark.cpp
+++ b/tests/other/face-benchmark.cpp
@@ -45,7 +45,7 @@
 public:
   FaceBenchmark(const char* configFileName)
     : m_terminationSignalSet{getGlobalIoService()}
-    , m_tcpChannel{tcp::Endpoint{boost::asio::ip::tcp::v4(), 6363}, false}
+    , 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}, time::minutes{10}, false}
   {
     m_terminationSignalSet.add(SIGINT);