blob: 0e5c6b015154f2a6fdb7daf831347126da9903ee [file] [log] [blame]
/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
/*
* Copyright (c) 2014-2023, 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/algorithm/string.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <regex>
#include <sstream>
namespace ndn::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 std::regex protocolExp("(\\w+\\d?(\\+\\w+)?)://([^/]*)(\\/[^?]*)?");
std::smatch protocolMatch;
if (!std::regex_match(url, protocolMatch, protocolExp)) {
return;
}
m_scheme = protocolMatch[1];
std::string authority = protocolMatch[3];
m_path = protocolMatch[4];
// pattern for IPv6 address enclosed in [ ], with optional port number
static const std::regex v6Exp("^\\[([a-fA-F0-9:]+)\\](?:\\:(\\d+))?$");
// pattern for IPv4-mapped IPv6 address, with optional port number
static const std::regex v4MappedV6Exp("^\\[::ffff:(\\d+(?:\\.\\d+){3})\\](?:\\:(\\d+))?$");
// pattern for IPv4/hostname/fd/ifname, with optional port number
static const std::regex v4HostExp("^([^:]+)(?:\\:(\\d+))?$");
if (authority.empty()) {
// UNIX, internal
}
else {
std::smatch match;
bool isV6 = std::regex_match(authority, match, v6Exp);
if (isV6 ||
std::regex_match(authority, match, v4MappedV6Exp) ||
std::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:
using std::runtime_error::runtime_error;
};
NdnFchDiscovery::NdnFchDiscovery(const std::string& url)
: m_url(url)
{
}
void
NdnFchDiscovery::doStart()
{
try {
boost::asio::ip::tcp::iostream requestStream;
requestStream.expires_after(std::chrono::seconds(3));
Url url(m_url);
if (!url.isValid()) {
NDN_THROW(HttpException("Invalid NDN-FCH URL: " + m_url));
}
if (!boost::iequals(url.getScheme(), "http")) {
NDN_THROW(HttpException("Only http:// NDN-FCH URLs are supported"));
}
requestStream.connect(url.getHost(), url.getPort());
if (!requestStream) {
NDN_THROW(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) {
NDN_THROW(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/") {
NDN_THROW(HttpException("HTTP communication error"));
}
if (statusCode != 200) {
boost::trim(statusMessage);
NDN_THROW(HttpException("HTTP request failed: " + to_string(statusCode) + " " + statusMessage));
}
std::string header;
while (std::getline(requestStream, header) && header != "\r")
;
std::string hubHost;
requestStream >> hubHost;
if (hubHost.empty()) {
NDN_THROW(HttpException("NDN-FCH did not return hub host"));
}
this->provideHubFaceUri("udp://" + hubHost);
}
catch (const std::runtime_error& e) {
this->fail(e.what());
}
}
} // namespace ndn::autoconfig