blob: 13dd3abcad8234239079927555d33fa8269aa192 [file] [log] [blame]
/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
/**
* Copyright (c) 2014-2017, 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()
{
std::cerr << "Trying NDN-FCH service..." << std::endl;
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/") {
BOOST_THROW_EXCEPTION(HttpException("HTTP communication error"));
}
if (statusCode != 200) {
boost::trim(statusMessage);
BOOST_THROW_EXCEPTION(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()) {
BOOST_THROW_EXCEPTION(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