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;