tools: nfd-autoreg

Change-Id: Id4fefea85cc240c476cb7e33022f05d5f839ae27
Refs: #1371
diff --git a/daemon/mgmt/notification-stream.hpp b/daemon/mgmt/notification-stream.hpp
index 2741035..780ea94 100644
--- a/daemon/mgmt/notification-stream.hpp
+++ b/daemon/mgmt/notification-stream.hpp
@@ -42,6 +42,7 @@
   dataName.appendSegment(m_sequenceNo);
   shared_ptr<Data> data(make_shared<Data>(dataName));
   data->setContent(notification.wireEncode());
+  data->setFreshnessPeriod(time::seconds(1));
 
   m_face->sign(*data);
   m_face->put(*data);
diff --git a/tools/network.hpp b/tools/network.hpp
new file mode 100644
index 0000000..d2d3cfe
--- /dev/null
+++ b/tools/network.hpp
@@ -0,0 +1,143 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (C) 2014 Named Data Networking Project
+ * See COPYING for copyright and distribution information.
+ */
+
+#ifndef NFD_TOOLS_NETWORK_HPP
+#define NFD_TOOLS_NETWORK_HPP
+
+#include <boost/asio.hpp>
+
+class Network
+{
+public:
+  Network()
+  {
+  }
+
+  Network(const boost::asio::ip::address& minAddress,
+          const boost::asio::ip::address& maxAddress)
+    : m_minAddress(minAddress)
+    , m_maxAddress(maxAddress)
+  {
+  }
+
+  void
+  print(std::ostream& os) const
+  {
+    os << m_minAddress << " <-> " << m_maxAddress;
+  }
+
+  bool
+  doesContain(const boost::asio::ip::address& address) const
+  {
+    return (m_minAddress <= address && address <= m_maxAddress);
+  }
+
+  static const Network&
+  getMaxRangeV4()
+  {
+    using boost::asio::ip::address_v4;
+    static Network range = Network(address_v4(0), address_v4(0xFFFFFFFF));
+    return range;
+  }
+
+  static const Network&
+  getMaxRangeV6()
+  {
+    using boost::asio::ip::address_v6;
+    static address_v6::bytes_type maxV6 = {{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+                                            0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}};
+    static Network range = Network(address_v6(), address_v6(maxV6));
+    return range;
+  }
+
+private:
+  boost::asio::ip::address m_minAddress;
+  boost::asio::ip::address m_maxAddress;
+
+  friend std::istream&
+  operator>>(std::istream& is, Network& network);
+
+  friend std::ostream&
+  operator<<(std::ostream& os, const Network& network);
+};
+
+inline std::ostream&
+operator<<(std::ostream& os, const Network& network)
+{
+  network.print(os);
+  return os;
+}
+
+inline std::istream&
+operator>>(std::istream& is, Network& network)
+{
+  using namespace boost::asio;
+
+  std::string networkStr;
+  is >> networkStr;
+
+  size_t position = networkStr.find('/');
+  if (position == std::string::npos)
+    {
+      network.m_minAddress = ip::address::from_string(networkStr);
+      network.m_maxAddress = ip::address::from_string(networkStr);
+    }
+  else
+    {
+      ip::address address = ip::address::from_string(networkStr.substr(0, position));
+      size_t mask = boost::lexical_cast<size_t>(networkStr.substr(position+1));
+
+      if (address.is_v4())
+        {
+          ip::address_v4::bytes_type maskBytes = {};
+          for (size_t i = 0; i < mask; i++)
+            {
+              size_t byteId = i / 8;
+              size_t bitIndex = 7 - i % 8;
+              maskBytes[byteId] |= (1 << bitIndex);
+            }
+
+          ip::address_v4::bytes_type addressBytes = address.to_v4().to_bytes();
+          ip::address_v4::bytes_type min;
+          ip::address_v4::bytes_type max;
+
+          for (size_t i = 0; i < addressBytes.size(); i++)
+            {
+              min[i] = addressBytes[i] & maskBytes[i];
+              max[i] = addressBytes[i] | ~(maskBytes[i]);
+            }
+
+          network.m_minAddress = ip::address_v4(min);
+          network.m_maxAddress = ip::address_v4(max);
+        }
+      else
+        {
+          ip::address_v6::bytes_type maskBytes = {};
+          for (size_t i = 0; i < mask; i++)
+            {
+              size_t byteId = i / 8;
+              size_t bitIndex = 7 - i % 8;
+              maskBytes[byteId] |= (1 << bitIndex);
+            }
+
+          ip::address_v6::bytes_type addressBytes = address.to_v6().to_bytes();
+          ip::address_v6::bytes_type min;
+          ip::address_v6::bytes_type max;
+
+          for (size_t i = 0; i < addressBytes.size(); i++)
+            {
+              min[i] = addressBytes[i] & maskBytes[i];
+              max[i] = addressBytes[i] | ~(maskBytes[i]);
+            }
+
+          network.m_minAddress = ip::address_v6(min);
+          network.m_maxAddress = ip::address_v6(max);
+        }
+    }
+  return is;
+}
+
+#endif // NFD_TOOLS_NETWORK_HPP
diff --git a/tools/nfd-autoreg.cpp b/tools/nfd-autoreg.cpp
new file mode 100644
index 0000000..ec64a55
--- /dev/null
+++ b/tools/nfd-autoreg.cpp
@@ -0,0 +1,291 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (C) 2014 Named Data Networking Project
+ * See COPYING for copyright and distribution information.
+ */
+
+#include <ndn-cpp-dev/face.hpp>
+#include <ndn-cpp-dev/name.hpp>
+
+#include <ndn-cpp-dev/management/nfd-face-event-notification.hpp>
+#include <ndn-cpp-dev/management/nfd-controller.hpp>
+
+#include <boost/program_options/options_description.hpp>
+#include <boost/program_options/variables_map.hpp>
+#include <boost/program_options/parsers.hpp>
+
+#include "daemon/core/face-uri.hpp"
+#include "network.hpp"
+
+namespace po = boost::program_options;
+
+namespace nfd {
+
+using namespace ndn::nfd;
+using ndn::Face;
+
+class AutoregServer
+{
+public:
+  AutoregServer()
+    : m_controller(m_face)
+    // , m_lastNotification(<undefined>)
+    , m_cost(255)
+  {
+  }
+
+  void
+  onNfdCommandSuccess(const FaceEventNotification& notification)
+  {
+    std::cerr << "SUCCEED: " << notification << std::endl;
+  }
+
+  void
+  onNfdCommandFailure(const FaceEventNotification& notification, const std::string& reason)
+  {
+    std::cerr << "FAILED: " << notification << std::endl;
+  }
+
+  bool
+  isFiltered(const FaceUri& uri)
+  {
+    const std::string& scheme = uri.getScheme();
+    if (!(scheme == "udp4" || scheme == "tcp4" ||
+          scheme == "udp6" || scheme == "tcp6"))
+      return true;
+
+    boost::asio::ip::address address = boost::asio::ip::address::from_string(uri.getHost());
+
+    for (std::vector<Network>::const_iterator network = m_blackList.begin();
+         network != m_blackList.end();
+         ++network)
+      {
+        if (network->doesContain(address))
+          return true;
+      }
+
+    for (std::vector<Network>::const_iterator network = m_whiteList.begin();
+         network != m_whiteList.end();
+         ++network)
+      {
+        if (network->doesContain(address))
+          return false;
+      }
+
+    return true;
+  }
+
+  void
+  processCreateFace(const FaceEventNotification& notification)
+  {
+    FaceUri uri(notification.getUri());
+
+    if (isFiltered(uri))
+      {
+        std::cerr << "Not processing (filtered): " << notification << std::endl;
+        return;
+      }
+
+    for (std::vector<ndn::Name>::const_iterator prefix = m_autoregPrefixes.begin();
+         prefix != m_autoregPrefixes.end();
+         ++prefix)
+      {
+        std::cout << "Try auto-register: " << *prefix << std::endl;
+        m_controller.fibAddNextHop(*prefix, notification.getFaceId(), m_cost,
+                                   bind(&AutoregServer::onNfdCommandSuccess, this, notification),
+                                   bind(&AutoregServer::onNfdCommandFailure, this, notification, _1));
+      }
+  }
+
+  void
+  onNotification(const Data& data)
+  {
+    m_lastNotification = data.getName().get(-1).toSegment();
+
+    // process
+    FaceEventNotification notification(data.getContent().blockFromValue());
+
+    if (notification.getEventKind() == FACE_EVENT_CREATED &&
+        !notification.isLocal() &&
+        notification.isOnDemand())
+      {
+        processCreateFace(notification);
+      }
+    else
+      {
+        std::cout << "IGNORED: " << notification << std::endl;
+      }
+
+    Name nextNotification("/localhost/nfd/faces/events");
+    nextNotification
+      .appendSegment(m_lastNotification + 1);
+
+    // no need to set freshness or child selectors
+    m_face.expressInterest(nextNotification,
+                           bind(&AutoregServer::onNotification, this, _2),
+                           bind(&AutoregServer::onTimeout, this, _1));
+  }
+
+  void
+  onTimeout(const Interest& timedOutInterest)
+  {
+    // re-express the timed out interest, but reset Nonce, since it has to change
+
+    // To be robust against missing notification, use ChildSelector and MustBeFresh
+    Interest interest("/localhost/nfd/faces/events");
+    interest
+      .setMustBeFresh(true)
+      .setChildSelector(1)
+      .setInterestLifetime(time::seconds(60))
+      ;
+
+    m_face.expressInterest(interest,
+                           bind(&AutoregServer::onNotification, this, _2),
+                           bind(&AutoregServer::onTimeout, this, _1));
+  }
+
+  void
+  signalHandler()
+  {
+    m_face.shutdown();
+  }
+
+
+
+  void
+  usage(const po::options_description& optionDesciption,
+        const char* programName)
+  {
+    std::cerr << "General Usage\n  "
+              << programName << " --prefix=</autoreg/prefix> [--prefix=/another/prefix] ..."
+              << std::endl << std::endl;
+    std::cerr << optionDesciption << std::endl;
+  }
+
+  void
+  startProcessing()
+  {
+    std::cout << "AUTOREG prefixes: " << std::endl;
+    for (std::vector<ndn::Name>::const_iterator prefix = m_autoregPrefixes.begin();
+         prefix != m_autoregPrefixes.end();
+         ++prefix)
+      {
+        std::cout << "  " << *prefix << std::endl;
+      }
+
+    if (!m_blackList.empty())
+      {
+        std::cout << "Blacklisted networks: " << std::endl;
+        for (std::vector<Network>::const_iterator network = m_blackList.begin();
+             network != m_blackList.end();
+             ++network)
+          {
+            std::cout << "  " << *network << std::endl;
+          }
+      }
+
+    std::cout << "Whitelisted networks: " << std::endl;
+    for (std::vector<Network>::const_iterator network = m_whiteList.begin();
+         network != m_whiteList.end();
+         ++network)
+      {
+        std::cout << "  " << *network << std::endl;
+      }
+
+    Interest interest("/localhost/nfd/faces/events");
+    interest
+      .setMustBeFresh(true)
+      .setChildSelector(1)
+      .setInterestLifetime(time::seconds(60))
+      ;
+
+    m_face.expressInterest(interest,
+                           bind(&AutoregServer::onNotification, this, _2),
+                           bind(&AutoregServer::onTimeout, this, _1));
+
+    boost::asio::signal_set signalSet(*m_face.ioService(), SIGINT, SIGTERM);
+    signalSet.async_wait(boost::bind(&AutoregServer::signalHandler, this));
+
+    m_face.processEvents();
+  }
+
+  int
+  main(int argc, char* argv[])
+  {
+    po::options_description optionDesciption;
+    optionDesciption.add_options()
+      ("help,h", "produce help message")
+      ("prefix,i", po::value<std::vector<ndn::Name> >(&m_autoregPrefixes)->composing(),
+       "prefix that should be automatically registered when new remote face is established")
+      ("cost,c", po::value<uint64_t>(&m_cost)->default_value(255),
+       "FIB cost which should be assigned to autoreg nexthops")
+      ("whitelist,w", po::value<std::vector<Network> >(&m_whiteList)->composing(),
+       "Whitelisted network, e.g., 192.168.2.0/24 or ::1/128")
+      ("blacklist,b", po::value<std::vector<Network> >(&m_blackList)->composing(),
+       "Blacklisted network, e.g., 192.168.2.32/30 or ::1/128")
+      ;
+
+    po::variables_map options;
+    try
+      {
+        po::store(po::command_line_parser(argc, argv).options(optionDesciption).run(), options);
+        po::notify(options);
+      }
+    catch (std::exception& e)
+      {
+        std::cerr << "ERROR: " << e.what() << std::endl << std::endl;
+        usage(optionDesciption, argv[0]);
+        return 1;
+      }
+
+    if (options.count("help"))
+      {
+        usage(optionDesciption, argv[0]);
+        return 0;
+      }
+
+    if (m_autoregPrefixes.empty())
+      {
+        std::cerr << "ERROR: at least one --prefix must be specified" << std::endl << std::endl;
+        usage(optionDesciption, argv[0]);
+        return 2;
+      }
+
+    if (m_whiteList.empty())
+      {
+        // Allow everything
+        m_whiteList.push_back(Network::getMaxRangeV4());
+        m_whiteList.push_back(Network::getMaxRangeV6());
+      }
+
+    try
+      {
+        startProcessing();
+      }
+    catch (std::exception& e)
+      {
+        std::cerr << "ERROR: " << e.what() << std::endl;
+        return 2;
+      }
+
+    return 0;
+  }
+
+private:
+  Face m_face;
+  Controller m_controller;
+  uint64_t m_lastNotification;
+  std::vector<ndn::Name> m_autoregPrefixes;
+  uint64_t m_cost;
+  std::vector<Network> m_whiteList;
+  std::vector<Network> m_blackList;
+};
+
+} // namespace nfd
+
+int
+main(int argc, char* argv[])
+{
+  nfd::AutoregServer server;
+  return server.main(argc, argv);
+}
diff --git a/wscript b/wscript
index c728fa8..77b8e80 100644
--- a/wscript
+++ b/wscript
@@ -45,7 +45,7 @@
                        linkflags="-L%s/lib" % conf.options.ndn_cpp_dir,
                        mandatory=True)
 
-    boost_libs = 'system chrono'
+    boost_libs = 'system chrono program_options'
     if conf.options.with_tests:
         conf.env['WITH_TESTS'] = 1
         conf.define('WITH_TESTS', 1);
@@ -103,7 +103,8 @@
         bld(features=['cxx', 'cxxprogram'],
             target = 'bin/%s' % (str(app.change_ext(''))),
             source = ['tools/%s' % (str(app))],
-            use = 'BOOST NDN_CPP RT RESOLV',
+            includes = [".", "daemon"],
+            use = 'nfd-objects RESOLV',
             )
 
     # Unit tests