tools: Add nlsrc command line tool
refs: #1834
Change-Id: Ifbe75a3f49e2689930e61ebfd348d9932b04c975
diff --git a/docs/conf.py b/docs/conf.py
index 83e251b..7314c6d 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -59,7 +59,7 @@
# General information about the project.
project = u'NLSR - Named Data Link State Routing Protocol'
-copyright = u'2014, Named Data Networking Project'
+copyright = u'2014-2015, Named Data Networking Project'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
@@ -239,6 +239,7 @@
man_pages = [
('manpages/nlsr', 'nlsr', u'Named Data Link State Routing Protocol Daemon', None, 1),
('manpages/nlsr.conf', 'nlsr.conf', u'Named Data Link State Routing Protocol Daemon config file', None, 5),
+ ('manpages/nlsrc', 'nlsrc', u'Command-line utility to interact with and collect statistics from NLSR', None, 1),
]
diff --git a/docs/manpages.rst b/docs/manpages.rst
index 40bd0de..e2e1582 100644
--- a/docs/manpages.rst
+++ b/docs/manpages.rst
@@ -6,4 +6,5 @@
.. toctree::
manpages/nlsr
manpages/nlsr.conf
+ manpages/nlsrc
:maxdepth: 1
diff --git a/docs/manpages/nlsrc.rst b/docs/manpages/nlsrc.rst
new file mode 100644
index 0000000..1db6cf3
--- /dev/null
+++ b/docs/manpages/nlsrc.rst
@@ -0,0 +1,55 @@
+nlsrc
+=====
+
+Usage
+-----
+
+::
+
+ nlsrc [-h] [-V] COMMAND [<Command Options>]
+
+
+Description
+-----------
+
+``nlsrc`` is a tool to retrieve link-state database (LSDB) status information from NLSR and
+announce/withdraw Name prefixes advertised by NLSR.
+
+Options
+-------
+
+``-h``
+ Print usage information
+
+``-V``
+ Show NLSRC version information
+
+``COMMAND``
+
+ ``status``
+ Retrieve LSDB status information
+
+ ``advertise``
+ Add a Name prefix to be advertised by NLSR
+
+ ``advertise <name>``
+
+ ``name``
+ The Name prefix to be advertised
+
+ ``withdraw``
+ Remove a Name prefix advertised through NLSR
+
+ ``withdraw <name>``
+
+ ``name``
+ The Name prefix to be withdrawn
+
+Exit Status
+-----------
+
+nlsrc exits with one of the following values:
+::
+
+ 0 nlsrc exited successfully
+ >0 An error occurred
diff --git a/tools/nlsrc.cpp b/tools/nlsrc.cpp
new file mode 100644
index 0000000..9142774
--- /dev/null
+++ b/tools/nlsrc.cpp
@@ -0,0 +1,412 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2015, The University of Memphis,
+ * Regents of the University of California,
+ * Arizona Board of Regents.
+ *
+ * This file is part of NLSR (Named-data Link State Routing).
+ * See AUTHORS.md for complete list of NLSR authors and contributors.
+ *
+ * NLSR is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NLSR is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NLSR, e.g., in COPYING.md file. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+#include "nlsrc.hpp"
+
+#include "version.hpp"
+#include "src/publisher/lsa-publisher.hpp"
+
+#include <ndn-cxx/face.hpp>
+#include <ndn-cxx/data.hpp>
+#include <ndn-cxx/interest.hpp>
+#include <ndn-cxx/encoding/block.hpp>
+#include <ndn-cxx/management/nfd-control-parameters.hpp>
+#include <ndn-cxx/management/nfd-control-response.hpp>
+#include <ndn-cxx/util/segment-fetcher.hpp>
+
+#include <iostream>
+
+namespace nlsrc {
+
+const ndn::Name Nlsrc::LOCALHOST_PREFIX = ndn::Name("/localhost/nlsr");
+const ndn::Name Nlsrc::LSDB_PREFIX = ndn::Name(Nlsrc::LOCALHOST_PREFIX).append("lsdb");
+const ndn::Name Nlsrc::NAME_UPDATE_PREFIX = ndn::Name(Nlsrc::LOCALHOST_PREFIX).append("prefix-update");
+
+const uint32_t Nlsrc::ERROR_CODE_TIMEOUT = 10060;
+const uint32_t Nlsrc::RESPONSE_CODE_SUCCESS = 200;
+
+Nlsrc::Nlsrc(ndn::Face& face)
+ : m_face(face)
+{
+}
+
+void
+Nlsrc::printUsage()
+{
+ std::cout << "Usage:\n" << programName << " [-h] [-V] COMMAND [<Command Options>]\n"
+ " -h print usage and exit\n"
+ " -V print version and exit\n"
+ "\n"
+ " COMMAND can be one of the following:\n"
+ " status\n"
+ " display NLSR status\n"
+ " advertise name\n"
+ " advertise a name prefix through NLSR\n"
+ " withdraw name\n"
+ " remove a name prefix advertised through NLSR"
+ << std::endl;
+}
+
+void
+Nlsrc::getStatus()
+{
+ m_fetchSteps.push_back(std::bind(&Nlsrc::fetchAdjacencyLsas, this));
+ m_fetchSteps.push_back(std::bind(&Nlsrc::fetchCoordinateLsas, this));
+ m_fetchSteps.push_back(std::bind(&Nlsrc::fetchNameLsas, this));
+ m_fetchSteps.push_back(std::bind(&Nlsrc::printLsdb, this));
+
+ runNextStep();
+}
+
+bool
+Nlsrc::dispatch(const std::string& command)
+{
+ if (command == "advertise") {
+ if (nOptions != 1) {
+ return false;
+ }
+
+ advertiseName();
+ return true;
+ }
+ else if (command == "withdraw") {
+ if (nOptions != 1) {
+ return false;
+ }
+
+ withdrawName();
+ return true;
+ }
+ else if (command == "status") {
+ if (nOptions != 0) {
+ return false;
+ }
+
+ getStatus();
+ return true;
+ }
+
+ return false;
+}
+
+void
+Nlsrc::runNextStep()
+{
+ if (m_fetchSteps.empty()) {
+ return;
+ }
+
+ std::function<void()> nextStep = m_fetchSteps.front();
+ m_fetchSteps.pop_front();
+
+ nextStep();
+}
+
+void
+Nlsrc::advertiseName()
+{
+ ndn::Name name = commandLineArguments[0];
+ ndn::Name::Component verb("advertise");
+ std::string info = "(Advertise: " + name.toUri() + ")";
+
+ sendNamePrefixUpdate(name, verb, info);
+}
+
+void
+Nlsrc::withdrawName()
+{
+ ndn::Name name = commandLineArguments[0];
+ ndn::Name::Component verb("withdraw");
+ std::string info = "(Withdraw: " + name.toUri() + ")";
+
+ sendNamePrefixUpdate(name, verb, info);
+}
+
+void
+Nlsrc::sendNamePrefixUpdate(const ndn::Name& name,
+ const ndn::Name::Component& verb,
+ const std::string& info)
+{
+ ndn::nfd::ControlParameters parameters;
+ parameters.setName(name);
+
+ ndn::Name commandName = NAME_UPDATE_PREFIX;
+ commandName.append(verb);
+
+ ndn::Interest interest(commandName.append(parameters.wireEncode()));
+ interest.setMustBeFresh(true);
+ m_keyChain.sign(interest);
+
+ m_face.expressInterest(interest,
+ std::bind(&Nlsrc::onControlResponse, this, info, _2),
+ std::bind(&Nlsrc::onTimeout, this, ERROR_CODE_TIMEOUT, "Timeout"));
+}
+
+void
+Nlsrc::onControlResponse(const std::string& info, const ndn::Data& data)
+{
+ ndn::nfd::ControlResponse response;
+
+ try {
+ response.wireDecode(data.getContent().blockFromValue());
+ }
+ catch (const std::exception& e) {
+ std::cerr << "ERROR: Control response decoding error" << std::endl;
+ return;
+ }
+
+ uint32_t code = response.getCode();
+
+ if (code != RESPONSE_CODE_SUCCESS) {
+ std::cerr << "Name prefix update error (code: " << code << ")" << std::endl;
+ return;
+ }
+
+ std::cout << "Applied Name prefix update successfully: " << info << std::endl;
+}
+
+void
+Nlsrc::fetchAdjacencyLsas()
+{
+ fetchFromLsdb<nlsr::tlv::AdjacencyLsa>(nlsr::AdjacencyLsaPublisher::DATASET_COMPONENT,
+ std::bind(&Nlsrc::recordAdjacencyLsa, this, _1));
+}
+
+void
+Nlsrc::fetchCoordinateLsas()
+{
+ fetchFromLsdb<nlsr::tlv::CoordinateLsa>(nlsr::CoordinateLsaPublisher::DATASET_COMPONENT,
+ std::bind(&Nlsrc::recordCoordinateLsa, this, _1));
+}
+
+void
+Nlsrc::fetchNameLsas()
+{
+ fetchFromLsdb<nlsr::tlv::NameLsa>(nlsr::NameLsaPublisher::DATASET_COMPONENT,
+ std::bind(&Nlsrc::recordNameLsa, this, _1));
+}
+
+template <class T>
+void
+Nlsrc::fetchFromLsdb(const ndn::Name::Component& datasetType,
+ const std::function<void(const T&)>& recordLsa)
+{
+ ndn::Name command = LSDB_PREFIX;
+ command.append(datasetType);
+
+ ndn::Interest interest(command);
+
+ ndn::util::SegmentFetcher::fetch(m_face,
+ interest,
+ ndn::util::DontVerifySegment(),
+ std::bind(&Nlsrc::onFetchSuccess<T>,
+ this, _1, recordLsa),
+ std::bind(&Nlsrc::onTimeout, this, _1, _2));
+}
+
+template <class T>
+void
+Nlsrc::onFetchSuccess(const ndn::ConstBufferPtr& data,
+ const std::function<void(const T&)>& recordLsa)
+{
+ ndn::Block block;
+ size_t offset = 0;
+
+ while (offset < data->size()) {
+ bool isOk = false;
+ std::tie(isOk, block) = ndn::Block::fromBuffer(data, offset);
+
+ if (!isOk) {
+ std::cerr << "ERROR: cannot decode LSA TLV" << std::endl;
+ break;
+ }
+
+ offset += block.size();
+
+ T lsa(block);
+ recordLsa(lsa);
+ }
+
+ runNextStep();
+}
+
+void
+Nlsrc::onTimeout(uint32_t errorCode, const std::string& error)
+{
+ std::cerr << "Request timed out (code: " << errorCode
+ << ", error: " << error << ")" << std::endl;
+}
+
+std::string
+Nlsrc::getLsaInfoString(const nlsr::tlv::LsaInfo& info)
+{
+ std::ostringstream os;
+ os << " info=" << info;
+
+ return os.str();
+}
+
+void
+Nlsrc::recordAdjacencyLsa(const nlsr::tlv::AdjacencyLsa& lsa)
+{
+ Router& router = getRouter(lsa.getLsaInfo());
+
+ std::ostringstream os;
+ os << " AdjacencyLsa:" << std::endl;
+
+ os << getLsaInfoString(lsa.getLsaInfo()) << std::endl;
+
+ for (const auto& adjacency : lsa.getAdjacencies()) {
+ os << " adjacency=" << adjacency << std::endl;
+ }
+
+ router.adjacencyLsaString = os.str();
+}
+
+void
+Nlsrc::recordCoordinateLsa(const nlsr::tlv::CoordinateLsa& lsa)
+{
+ Router& router = getRouter(lsa.getLsaInfo());
+
+ std::ostringstream os;
+ os << " Coordinate LSA:" << std::endl;
+
+ os << getLsaInfoString(lsa.getLsaInfo()) << std::endl;
+
+ os << " angle=" << lsa.getHyperbolicAngle() << std::endl;
+ os << " radius=" << lsa.getHyperbolicRadius() << std::endl;
+
+ router.coordinateLsaString = os.str();
+}
+
+void
+Nlsrc::recordNameLsa(const nlsr::tlv::NameLsa& lsa)
+{
+ Router& router = getRouter(lsa.getLsaInfo());
+
+ std::ostringstream os;
+ os << " Name LSA:" << std::endl;
+
+ os << getLsaInfoString(lsa.getLsaInfo()) << std::endl;
+
+ for (const auto& name : lsa.getNames()) {
+ os << " name=" << name << std::endl;
+ }
+
+ router.nameLsaString = os.str();
+}
+
+void
+Nlsrc::printLsdb()
+{
+ std::cout << "NLSR Status" << std::endl;
+ std::cout << "LSDB:" << std::endl;
+
+ for (const auto& item : m_routers) {
+ std::cout << " OriginRouter: " << item.first << std::endl;
+ std::cout << std::endl;
+
+ const Router& router = item.second;
+
+ if (!router.adjacencyLsaString.empty()) {
+ std::cout << router.adjacencyLsaString << std::endl;
+ }
+
+ if (!router.coordinateLsaString.empty()) {
+ std::cout << router.coordinateLsaString << std::endl;
+ }
+
+ if (!router.nameLsaString.empty()) {
+ std::cout << router.nameLsaString << std::endl;
+ }
+ }
+}
+
+Nlsrc::Router&
+Nlsrc::getRouter(const nlsr::tlv::LsaInfo& info)
+{
+ const ndn::Name& originRouterName = info.getOriginRouter();
+
+ const auto& pair =
+ m_routers.insert(std::make_pair(originRouterName, std::move(Router())));
+
+ return pair.first->second;
+}
+
+} // namespace nlsrc
+
+////////////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////////
+
+int
+main(int argc, char** argv)
+{
+ ndn::Face face;
+ nlsrc::Nlsrc nlsrc(face);
+
+ nlsrc.programName = argv[0];
+
+ if (argc < 2) {
+ nlsrc.printUsage();
+ return 0;
+ }
+
+ int opt;
+ while ((opt = ::getopt(argc, argv, "hV")) != -1) {
+ switch (opt) {
+ case 'h':
+ nlsrc.printUsage();
+ return 0;
+ case 'V':
+ std::cout << NLSR_VERSION_BUILD_STRING << std::endl;
+ return 0;
+ default:
+ nlsrc.printUsage();
+ return 1;
+ }
+ }
+
+ if (argc == ::optind) {
+ nlsrc.printUsage();
+ return 1;
+ }
+
+ try {
+ ::optind = 2; // Set ::optind to the command's index
+
+ nlsrc.commandLineArguments = argv + ::optind;
+ nlsrc.nOptions = argc - ::optind;
+
+ // argv[1] points to the command, so pass it to the dispatch
+ bool isOk = nlsrc.dispatch(argv[1]);
+ if (!isOk) {
+ nlsrc.printUsage();
+ return 1;
+ }
+
+ face.processEvents();
+ }
+ catch (const std::exception& e) {
+ std::cerr << "ERROR: " << e.what() << std::endl;
+ return 2;
+ }
+ return 0;
+}
\ No newline at end of file
diff --git a/tools/nlsrc.hpp b/tools/nlsrc.hpp
new file mode 100644
index 0000000..b62cca0
--- /dev/null
+++ b/tools/nlsrc.hpp
@@ -0,0 +1,162 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2015, The University of Memphis,
+ * Regents of the University of California,
+ * Arizona Board of Regents.
+ *
+ * This file is part of NLSR (Named-data Link State Routing).
+ * See AUTHORS.md for complete list of NLSR authors and contributors.
+ *
+ * NLSR is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU General Public License as published by the Free Software Foundation,
+ * either version 3 of the License, or (at your option) any later version.
+ *
+ * NLSR is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * NLSR, e.g., in COPYING.md file. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+#include "tlv/adjacency-lsa.hpp"
+#include "tlv/coordinate-lsa.hpp"
+#include "tlv/name-lsa.hpp"
+
+#include <boost/noncopyable.hpp>
+#include <ndn-cxx/face.hpp>
+#include <ndn-cxx/security/key-chain.hpp>
+
+#include <deque>
+#include <map>
+#include <stdexcept>
+
+#ifndef NLSR_TOOLS_NLSRC_HPP
+#define NLSR_TOOLS_NLSRC_HPP
+
+namespace nlsrc {
+
+class Nlsrc : boost::noncopyable
+{
+public:
+ explicit
+ Nlsrc(ndn::Face& face);
+
+ void
+ printUsage();
+
+ void
+ getStatus();
+
+ bool
+ dispatch(const std::string& cmd);
+
+private:
+ void
+ runNextStep();
+
+ /**
+ * \brief Adds a name prefix to be advertised in NLSR's Name LSA
+ *
+ * cmd format:
+ * name
+ *
+ */
+ void
+ advertiseName();
+
+ /**
+ * \brief Removes a name prefix from NLSR's Name LSA
+ *
+ * cmd format:
+ * name
+ *
+ */
+ void
+ withdrawName();
+
+ void
+ sendNamePrefixUpdate(const ndn::Name& name,
+ const ndn::Name::Component& verb,
+ const std::string& info);
+
+ void
+ onControlResponse(const std::string& info, const ndn::Data& data);
+
+private:
+ void
+ fetchAdjacencyLsas();
+
+ void
+ fetchCoordinateLsas();
+
+ void
+ fetchNameLsas();
+
+ template <class T>
+ void
+ fetchFromLsdb(const ndn::Name::Component& datasetType,
+ const std::function<void(const T&)>& recordLsa);
+
+ template <class T>
+ void
+ onFetchSuccess(const ndn::ConstBufferPtr& data,
+ const std::function<void(const T&)>& recordLsa);
+
+ void
+ onTimeout(uint32_t errorCode, const std::string& error);
+
+private:
+ std::string
+ getLsaInfoString(const nlsr::tlv::LsaInfo& info);
+
+ void
+ recordAdjacencyLsa(const nlsr::tlv::AdjacencyLsa& lsa);
+
+ void
+ recordCoordinateLsa(const nlsr::tlv::CoordinateLsa& lsa);
+
+ void
+ recordNameLsa(const nlsr::tlv::NameLsa& lsa);
+
+ void
+ printLsdb();
+
+public:
+ const char* programName;
+
+ // command parameters without leading 'cmd' component
+ const char* const* commandLineArguments;
+ int nOptions;
+
+private:
+ struct Router
+ {
+ std::string adjacencyLsaString;
+ std::string coordinateLsaString;
+ std::string nameLsaString;
+ };
+
+ Router&
+ getRouter(const nlsr::tlv::LsaInfo& info);
+
+ typedef std::map<const ndn::Name, Router> RouterMap;
+ RouterMap m_routers;
+
+private:
+ ndn::KeyChain m_keyChain;
+ ndn::Face& m_face;
+
+ std::deque<std::function<void()>> m_fetchSteps;
+
+ static const ndn::Name LOCALHOST_PREFIX;
+ static const ndn::Name LSDB_PREFIX;
+ static const ndn::Name NAME_UPDATE_PREFIX;
+
+ static const uint32_t ERROR_CODE_TIMEOUT;
+ static const uint32_t RESPONSE_CODE_SUCCESS;
+};
+
+} // namespace nlsrc
+
+#endif // NLSR_TOOLS_NLSRC_HPP
diff --git a/wscript b/wscript
index 946bb5a..0dbf533 100644
--- a/wscript
+++ b/wscript
@@ -128,10 +128,25 @@
use='nlsr-objects',
)
+ nlsrc = bld(
+ target='bin/nlsrc',
+ features='cxx cxxprogram',
+ source='tools/nlsrc.cpp',
+ use='nlsr-objects BOOST',
+ )
+
if bld.env['WITH_TESTS']:
bld.recurse('tests')
bld.recurse('tests-integrated')
+ if bld.env['SPHINX_BUILD']:
+ bld(features="sphinx",
+ builder="man",
+ outdir="docs/manpages",
+ config="docs/conf.py",
+ source=bld.path.ant_glob('docs/manpages/**/*.rst'),
+ install_path="${MANDIR}/",
+ VERSION=VERSION)
def docs(bld):
from waflib import Options