tools: Add NDN-FCH stage (service to get "closest" NDN hub) to ndn-autoconfig
Change-Id: Ia2b0bf93d3d25b45713eeba58924e64871715bdb
Refs: #3800
diff --git a/docs/manpages/ndn-autoconfig.rst b/docs/manpages/ndn-autoconfig.rst
index f4fbc20..74db30d 100644
--- a/docs/manpages/ndn-autoconfig.rst
+++ b/docs/manpages/ndn-autoconfig.rst
@@ -32,6 +32,10 @@
Use the specified configuration file. If `enabled = true` is not specified in the
configuration file, no actions will be performed.
+``--ndn-fch-url=[URL]``
+ Use the specified URL to find the closest hub (NDN-FCH protocol). If not specified,
+ ``http://ndn-fch.named-data.net`` will be used. Only ``http://`` URLs are supported.
+
``-V`` or ``--version``
Print version information.
@@ -47,7 +51,7 @@
Overview
^^^^^^^^
-This procedure contains three methods to discover a NDN router:
+This procedure contains four methods to discover a NDN router:
1. Look for a local NDN router by multicast.
This is useful in a home or small office network.
@@ -55,7 +59,9 @@
2. Look for a local NDN router by DNS query with default suffix.
This allows network administrator to configure a NDN router in a large enterprise network.
-3. Connect to the home NDN router according to user certificate.
+3. Find closest hub by sending an HTTP request to NDN-FCH server.
+
+4. Connect to the home NDN router according to user certificate.
This ensures connectivity from anywhere.
After connecting to an NDN router, two prefixes will be automatically registered:
@@ -96,7 +102,25 @@
The DNS server should answer with an SRV record that contains the hostname and UDP port
number of the NDN router.
-Stage 3: find home router
+Stage 3: HTTP Request to NDN-FCH server
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+This stage uses a simple HTTP-based API. For more information about NDN-FCH server, refer
+to the `NDN-FCH README file <https://github.com/named-data/ndn-fch>`__.
+
+Request
++++++++
+
+HTTP/1.0 request for the NDN-FCH server URI (``http://ndn-fch.named-data.net`` by default)
+
+Response
+++++++++
+
+The HTTP response is expected to be a hostname or an IP address of the closest hub,
+inferred using IP-geo approximation service.
+
+
+Stage 4: find home router
^^^^^^^^^^^^^^^^^^^^^^^^^
This stage assumes that user has configured default certificate using
@@ -140,6 +164,14 @@
Stage 3
^^^^^^^
+Send HTTP request to NDN-FCH server.
+
+If request succeeds, attempt to connect to the discovered HUB and terminate
+auto-discovery.
+
+Stage 4
+^^^^^^^
+
* Load default user identity, and convert it to DNS format; if either fails, the
auto-discovery fails.
diff --git a/tools/ndn-autoconfig/base.cpp b/tools/ndn-autoconfig/base.cpp
index c95eb21..35c6f56 100644
--- a/tools/ndn-autoconfig/base.cpp
+++ b/tools/ndn-autoconfig/base.cpp
@@ -41,6 +41,7 @@
Base::connectToHub(const std::string& uri)
{
FaceUri faceUri(uri);
+ std::cerr << "About to connect to: " << uri << std::endl;
faceUri.canonize(bind(&Base::onCanonizeSuccess, this, _1),
bind(&Base::onCanonizeFailure, this, _1),
@@ -52,8 +53,6 @@
void
Base::onCanonizeSuccess(const FaceUri& canonicalUri)
{
- std::cerr << "About to connect to: " << canonicalUri.toString() << std::endl;
-
m_controller.start<ndn::nfd::FaceCreateCommand>(
ControlParameters().setUri(canonicalUri.toString()),
bind(&Base::onHubConnectSuccess, this, _1),
diff --git a/tools/ndn-autoconfig/main.cpp b/tools/ndn-autoconfig/main.cpp
index 61ae22a..fc3a6fc 100644
--- a/tools/ndn-autoconfig/main.cpp
+++ b/tools/ndn-autoconfig/main.cpp
@@ -27,6 +27,7 @@
#include "multicast-discovery.hpp"
#include "guess-from-search-domains.hpp"
+#include "ndn-fch-discovery.hpp"
#include "guess-from-identity-name.hpp"
#include <ndn-cxx/util/network-monitor.hpp>
@@ -60,7 +61,7 @@
};
explicit
- NdnAutoconfig(bool isDaemonMode)
+ NdnAutoconfig(const std::string& ndnFchUrl, bool isDaemonMode)
: m_face(m_io)
, m_scheduler(m_io)
, m_startStagesEvent(m_scheduler)
@@ -77,8 +78,14 @@
m_stage3.start();
})
, m_stage3(m_face, m_keyChain,
+ ndnFchUrl,
[&] (const std::string& errorMessage) {
std::cerr << "Stage 3 failed: " << errorMessage << std::endl;
+ m_stage4.start();
+ })
+ , m_stage4(m_face, m_keyChain,
+ [&] (const std::string& errorMessage) {
+ std::cerr << "Stage 4 failed: " << errorMessage << std::endl;
if (!m_isDaemonMode)
BOOST_THROW_EXCEPTION(Error("No more stages, automatic discovery failed"));
else
@@ -155,7 +162,8 @@
MulticastDiscovery m_stage1;
GuessFromSearchDomains m_stage2;
- GuessFromIdentityName m_stage3;
+ NdnFchDiscovery m_stage3;
+ GuessFromIdentityName m_stage4;
};
static int
@@ -163,6 +171,7 @@
{
bool isDaemonMode = false;
std::string configFile;
+ std::string ndnFchUrl;
po::options_description optionDescription("Options");
optionDescription.add_options()
@@ -172,6 +181,8 @@
"auto-discovery procedure. In addition, the auto-discovery procedure "
"is unconditionally re-run every hour.\n"
"NOTE: if connection to NFD fails, the daemon will be terminated.")
+ ("ndn-fch-url", po::value<std::string>(&ndnFchUrl)->default_value("http://ndn-fch.named-data.net"),
+ "URL for NDN-FCH (Find Closest Hub) service")
("config,c", po::value<std::string>(&configFile), "configuration file. If `enabled = true` "
"is not specified, no actions will be performed.")
("version,V", "show version and exit")
@@ -223,7 +234,7 @@
}
try {
- NdnAutoconfig autoConfigInstance(isDaemonMode);
+ NdnAutoconfig autoConfigInstance(ndnFchUrl, isDaemonMode);
autoConfigInstance.run();
}
catch (const std::exception& error) {
diff --git a/tools/ndn-autoconfig/ndn-fch-discovery.cpp b/tools/ndn-autoconfig/ndn-fch-discovery.cpp
new file mode 100644
index 0000000..56b7fd1
--- /dev/null
+++ b/tools/ndn-autoconfig/ndn-fch-discovery.cpp
@@ -0,0 +1,216 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2016, Regents of the University of California,
+ * Arizona Board of Regents,
+ * Colorado State University,
+ * University Pierre & Marie Curie, Sorbonne University,
+ * Washington University in St. Louis,
+ * Beijing Institute of Technology,
+ * The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD 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.
+ *
+ * NFD 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
+ * NFD, e.g., in COPYING.md file. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "ndn-fch-discovery.hpp"
+#include <boost/regex.hpp>
+#include <boost/algorithm/string.hpp>
+
+namespace ndn {
+namespace tools {
+namespace autoconfig {
+
+/**
+ * A partial and specialized copy of ndn::FaceUri implementation
+ *
+ * Consider removing in favor of a library-provided URL parsing, if project
+ * includes such a library.
+ */
+class Url
+{
+public:
+ Url(const std::string& url)
+ : m_isValid(false)
+ {
+ static const boost::regex protocolExp("(\\w+\\d?(\\+\\w+)?)://([^/]*)(\\/[^?]*)?");
+ boost::smatch protocolMatch;
+ if (!boost::regex_match(url, protocolMatch, protocolExp)) {
+ return;
+ }
+ m_scheme = protocolMatch[1];
+ const std::string& authority = protocolMatch[3];
+ m_path = protocolMatch[4];
+
+ // pattern for IPv6 address enclosed in [ ], with optional port number
+ static const boost::regex v6Exp("^\\[([a-fA-F0-9:]+)\\](?:\\:(\\d+))?$");
+ // pattern for IPv4-mapped IPv6 address, with optional port number
+ static const boost::regex v4MappedV6Exp("^\\[::ffff:(\\d+(?:\\.\\d+){3})\\](?:\\:(\\d+))?$");
+ // pattern for IPv4/hostname/fd/ifname, with optional port number
+ static const boost::regex v4HostExp("^([^:]+)(?:\\:(\\d+))?$");
+
+ if (authority.empty()) {
+ // UNIX, internal
+ }
+ else {
+ boost::smatch match;
+ bool isV6 = boost::regex_match(authority, match, v6Exp);
+ if (isV6 ||
+ boost::regex_match(authority, match, v4MappedV6Exp) ||
+ boost::regex_match(authority, match, v4HostExp)) {
+ m_host = match[1];
+ m_port = match[2];
+ }
+ else {
+ return;
+ }
+ }
+ if (m_port.empty()) {
+ m_port = "80";
+ }
+ if (m_path.empty()) {
+ m_path = "/";
+ }
+ m_isValid = true;
+ }
+
+ bool
+ isValid() const
+ {
+ return m_isValid;
+ }
+
+ const std::string&
+ getScheme() const
+ {
+ return m_scheme;
+ }
+
+ const std::string&
+ getHost() const
+ {
+ return m_host;
+ }
+
+ const std::string&
+ getPort() const
+ {
+ return m_port;
+ }
+
+ const std::string&
+ getPath() const
+ {
+ return m_path;
+ }
+
+private:
+ bool m_isValid;
+ std::string m_scheme;
+ std::string m_host;
+ std::string m_port;
+ std::string m_path;
+};
+
+class HttpException : public std::runtime_error
+{
+public:
+ explicit
+ HttpException(const std::string& what)
+ : std::runtime_error(what)
+ {
+ }
+};
+
+NdnFchDiscovery::NdnFchDiscovery(Face& face, KeyChain& keyChain,
+ const std::string& url,
+ const NextStageCallback& nextStageOnFailure)
+ : Base(face, keyChain, nextStageOnFailure)
+ , m_url(url)
+{
+}
+
+void
+NdnFchDiscovery::start()
+{
+ try {
+ using namespace boost::asio::ip;
+ tcp::iostream requestStream;
+
+ requestStream.expires_from_now(boost::posix_time::milliseconds(3000));
+
+ Url url(m_url);
+ if (!url.isValid()) {
+ BOOST_THROW_EXCEPTION(HttpException("Invalid NDN-FCH URL: " + m_url));
+ }
+
+ if (!boost::iequals(url.getScheme(), "http")) {
+ BOOST_THROW_EXCEPTION(HttpException("Only http:// NDN-FCH URLs are supported"));
+ }
+
+ requestStream.connect(url.getHost(), url.getPort());
+
+ if (!requestStream) {
+ BOOST_THROW_EXCEPTION(HttpException("HTTP connection error to " + m_url));
+ }
+
+ requestStream << "GET " << url.getPath() << " HTTP/1.0\r\n";
+ requestStream << "Host: " << url.getHost() << ":" << url.getPort() << "\r\n";
+ requestStream << "Accept: */*\r\n";
+ requestStream << "Cache-Control: no-cache\r\n";
+ requestStream << "Connection: close\r\n\r\n";
+ requestStream.flush();
+
+ std::string statusLine;
+ std::getline(requestStream, statusLine);
+ if (!requestStream) {
+ BOOST_THROW_EXCEPTION(HttpException("HTTP communication error"));
+ }
+
+ std::stringstream responseStream(statusLine);
+ std::string httpVersion;
+ responseStream >> httpVersion;
+ unsigned int statusCode;
+ responseStream >> statusCode;
+ std::string statusMessage;
+
+ std::getline(responseStream, statusMessage);
+ if (!static_cast<bool>(requestStream) || httpVersion.substr(0, 5) != "HTTP/") {
+ throw HttpException("HTTP communication error");
+ }
+ if (statusCode != 200) {
+ boost::trim(statusMessage);
+ throw HttpException("HTTP request failed: " +
+ std::to_string(statusCode) + " " + statusMessage);
+ }
+ std::string header;
+ while (std::getline(requestStream, header) && header != "\r")
+ ;
+
+ std::string hubHost;
+ requestStream >> hubHost;
+
+ if (hubHost.empty()) {
+ throw HttpException("NDN-FCH did not return hub host");
+ }
+
+ this->connectToHub("udp://" + hubHost);
+ }
+ catch (const std::runtime_error& e) {
+ m_nextStageOnFailure(std::string("Failed to find NDN router using NDN-FCH service (") + e.what() + ")");
+ }
+}
+
+} // namespace autoconfig
+} // namespace tools
+} // namespace ndn
diff --git a/tools/ndn-autoconfig/ndn-fch-discovery.hpp b/tools/ndn-autoconfig/ndn-fch-discovery.hpp
new file mode 100644
index 0000000..2e587c6
--- /dev/null
+++ b/tools/ndn-autoconfig/ndn-fch-discovery.hpp
@@ -0,0 +1,62 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/**
+ * Copyright (c) 2014-2016, Regents of the University of California,
+ * Arizona Board of Regents,
+ * Colorado State University,
+ * University Pierre & Marie Curie, Sorbonne University,
+ * Washington University in St. Louis,
+ * Beijing Institute of Technology,
+ * The University of Memphis.
+ *
+ * This file is part of NFD (Named Data Networking Forwarding Daemon).
+ * See AUTHORS.md for complete list of NFD authors and contributors.
+ *
+ * NFD 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.
+ *
+ * NFD 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
+ * NFD, e.g., in COPYING.md file. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef NFD_TOOLS_NDN_AUTOCONFIG_NDN_FCH_DISCOVERY_HPP
+#define NFD_TOOLS_NDN_AUTOCONFIG_NDN_FCH_DISCOVERY_HPP
+
+#include "base-dns.hpp"
+
+namespace ndn {
+namespace tools {
+namespace autoconfig {
+
+/**
+ * @brief Discovery NDN hub using NDN-FCH protocol
+ *
+ * @see https://github.com/cawka/ndn-fch/blob/master/README.md
+ */
+class NdnFchDiscovery : public Base
+{
+public:
+ /**
+ * @brief Create stage to discover NDN hub using NDN-FCH protocol
+ * @sa Base::Base
+ */
+ NdnFchDiscovery(Face& face, KeyChain& keyChain,
+ const std::string& url,
+ const NextStageCallback& nextStageOnFailure);
+
+ void
+ start() override;
+
+private:
+ std::string m_url;
+};
+
+} // namespace autoconfig
+} // namespace tools
+} // namespace ndn
+
+#endif // NFD_TOOLS_NDN_AUTOCONFIG_NDN_FCH_DISCOVERY_HPP