blob: d550fd536f5d194faf0f7f22e6d164adf30e7491 [file] [log] [blame]
/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
/**
* Copyright (c) 2013-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 ndn-cxx library (NDN C++ library with eXperimental eXtensions).
*
* ndn-cxx library is free software: you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free Software
* Foundation, either version 3 of the License, or (at your option) any later version.
*
* ndn-cxx library 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 Lesser General Public License for more details.
*
* You should have received copies of the GNU General Public License and GNU Lesser
* General Public License along with ndn-cxx, e.g., in COPYING.md file. If not, see
* <http://www.gnu.org/licenses/>.
*
* See AUTHORS.md for complete list of ndn-cxx authors and contributors.
*/
#include "face-uri.hpp"
#include "dns.hpp"
#include <boost/lexical_cast.hpp>
#include <boost/mpl/vector.hpp>
#include <boost/mpl/for_each.hpp>
#include <boost/regex.hpp>
#include <set>
namespace ndn {
BOOST_CONCEPT_ASSERT((boost::EqualityComparable<FaceUri>));
FaceUri::FaceUri()
: m_isV6(false)
{
}
FaceUri::FaceUri(const std::string& uri)
{
if (!parse(uri)) {
BOOST_THROW_EXCEPTION(Error("Malformed URI: " + uri));
}
}
FaceUri::FaceUri(const char* uri)
: FaceUri(std::string(uri))
{
}
bool
FaceUri::parse(const std::string& uri)
{
m_scheme.clear();
m_host.clear();
m_port.clear();
m_path.clear();
m_isV6 = false;
static const boost::regex protocolExp("(\\w+\\d?(\\+\\w+)?)://([^/]*)(\\/[^?]*)?");
boost::smatch protocolMatch;
if (!boost::regex_match(uri, protocolMatch, protocolExp)) {
return false;
}
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 Ethernet address in standard hex-digits-and-colons notation
static const boost::regex etherExp("^\\[((?:[a-fA-F0-9]{1,2}\\:){5}(?:[a-fA-F0-9]{1,2}))\\]$");
// 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;
m_isV6 = boost::regex_match(authority, match, v6Exp);
if (m_isV6 ||
boost::regex_match(authority, match, etherExp) ||
boost::regex_match(authority, match, v4MappedV6Exp) ||
boost::regex_match(authority, match, v4HostExp)) {
m_host = match[1];
m_port = match[2];
}
else {
return false;
}
}
return true;
}
FaceUri::FaceUri(const boost::asio::ip::udp::endpoint& endpoint)
{
m_isV6 = endpoint.address().is_v6();
m_scheme = m_isV6 ? "udp6" : "udp4";
m_host = endpoint.address().to_string();
m_port = to_string(endpoint.port());
}
FaceUri::FaceUri(const boost::asio::ip::tcp::endpoint& endpoint)
{
m_isV6 = endpoint.address().is_v6();
m_scheme = m_isV6 ? "tcp6" : "tcp4";
m_host = endpoint.address().to_string();
m_port = to_string(endpoint.port());
}
FaceUri::FaceUri(const boost::asio::ip::tcp::endpoint& endpoint, const std::string& scheme)
{
m_isV6 = endpoint.address().is_v6();
m_scheme = scheme;
m_host = endpoint.address().to_string();
m_port = to_string(endpoint.port());
}
#ifdef BOOST_ASIO_HAS_LOCAL_SOCKETS
FaceUri::FaceUri(const boost::asio::local::stream_protocol::endpoint& endpoint)
: m_scheme("unix")
, m_path(endpoint.path())
, m_isV6(false)
{
}
#endif // BOOST_ASIO_HAS_LOCAL_SOCKETS
FaceUri
FaceUri::fromFd(int fd)
{
FaceUri uri;
uri.m_scheme = "fd";
uri.m_host = to_string(fd);
return uri;
}
FaceUri::FaceUri(const ethernet::Address& address)
: m_scheme("ether")
, m_host(address.toString())
, m_isV6(true)
{
}
FaceUri
FaceUri::fromDev(const std::string& ifname)
{
FaceUri uri;
uri.m_scheme = "dev";
uri.m_host = ifname;
return uri;
}
FaceUri
FaceUri::fromUdpDev(const boost::asio::ip::udp::endpoint& endpoint, const std::string& ifname)
{
FaceUri uri;
uri.m_scheme = endpoint.address().is_v6() ? "udp6+dev" : "udp4+dev";
uri.m_host = ifname;
uri.m_port = to_string(endpoint.port());
return uri;
}
bool
FaceUri::operator==(const FaceUri& rhs) const
{
return m_isV6 == rhs.m_isV6 &&
m_scheme == rhs.m_scheme &&
m_host == rhs.m_host &&
m_port == rhs.m_port &&
m_path == rhs.m_path;
}
bool
FaceUri::operator!=(const FaceUri& rhs) const
{
return !(*this == rhs);
}
std::string
FaceUri::toString() const
{
std::ostringstream os;
os << *this;
return os.str();
}
std::ostream&
operator<<(std::ostream& os, const FaceUri& uri)
{
os << uri.m_scheme << "://";
if (uri.m_isV6) {
os << "[" << uri.m_host << "]";
}
else {
os << uri.m_host;
}
if (!uri.m_port.empty()) {
os << ":" << uri.m_port;
}
os << uri.m_path;
return os;
}
/** \brief a CanonizeProvider provides FaceUri canonization functionality for a group of schemes
*/
class CanonizeProvider : noncopyable
{
public:
virtual
~CanonizeProvider() = default;
virtual std::set<std::string>
getSchemes() const = 0;
virtual bool
isCanonical(const FaceUri& faceUri) const = 0;
virtual void
canonize(const FaceUri& faceUri,
const FaceUri::CanonizeSuccessCallback& onSuccess,
const FaceUri::CanonizeFailureCallback& onFailure,
boost::asio::io_service& io, time::nanoseconds timeout) const = 0;
};
template<typename Protocol>
class IpHostCanonizeProvider : public CanonizeProvider
{
public:
std::set<std::string>
getSchemes() const override
{
return {m_baseScheme, m_v4Scheme, m_v6Scheme};
}
bool
isCanonical(const FaceUri& faceUri) const override
{
if (faceUri.getPort().empty()) {
return false;
}
if (!faceUri.getPath().empty()) {
return false;
}
boost::system::error_code ec;
boost::asio::ip::address addr;
if (faceUri.getScheme() == m_v4Scheme) {
addr = boost::asio::ip::address_v4::from_string(faceUri.getHost(), ec);
}
else if (faceUri.getScheme() == m_v6Scheme) {
addr = boost::asio::ip::address_v6::from_string(faceUri.getHost(), ec);
}
else {
return false;
}
return !ec && addr.to_string() == faceUri.getHost() && checkAddress(addr).first;
}
void
canonize(const FaceUri& faceUri,
const FaceUri::CanonizeSuccessCallback& onSuccess,
const FaceUri::CanonizeFailureCallback& onFailure,
boost::asio::io_service& io, time::nanoseconds timeout) const override
{
if (this->isCanonical(faceUri)) {
onSuccess(faceUri);
return;
}
// make a copy because caller may modify faceUri
auto uri = make_shared<FaceUri>(faceUri);
boost::system::error_code ec;
auto ipAddress = boost::asio::ip::address::from_string(faceUri.getHost(), ec);
if (!ec) {
// No need to resolve IP address if host is already an IP
if ((faceUri.getScheme() == m_v4Scheme && !ipAddress.is_v4()) ||
(faceUri.getScheme() == m_v6Scheme && !ipAddress.is_v6())) {
return onFailure("IPv4/v6 mismatch");
}
onDnsSuccess(uri, onSuccess, onFailure, ipAddress);
}
else {
dns::AddressSelector addressSelector;
if (faceUri.getScheme() == m_v4Scheme) {
addressSelector = dns::Ipv4Only();
}
else if (faceUri.getScheme() == m_v6Scheme) {
addressSelector = dns::Ipv6Only();
}
else {
BOOST_ASSERT(faceUri.getScheme() == m_baseScheme);
addressSelector = dns::AnyAddress();
}
dns::asyncResolve(faceUri.getHost(),
bind(&IpHostCanonizeProvider<Protocol>::onDnsSuccess, this, uri, onSuccess, onFailure, _1),
bind(&IpHostCanonizeProvider<Protocol>::onDnsFailure, this, uri, onFailure, _1),
io, addressSelector, timeout);
}
}
protected:
explicit
IpHostCanonizeProvider(const std::string& baseScheme,
uint16_t defaultUnicastPort = 6363,
uint16_t defaultMulticastPort = 56363)
: m_baseScheme(baseScheme)
, m_v4Scheme(baseScheme + '4')
, m_v6Scheme(baseScheme + '6')
, m_defaultUnicastPort(defaultUnicastPort)
, m_defaultMulticastPort(defaultMulticastPort)
{
}
private:
void
onDnsSuccess(const shared_ptr<FaceUri>& faceUri,
const FaceUri::CanonizeSuccessCallback& onSuccess,
const FaceUri::CanonizeFailureCallback& onFailure,
const dns::IpAddress& ipAddress) const
{
bool isOk = false;
std::string reason;
std::tie(isOk, reason) = this->checkAddress(ipAddress);
if (!isOk) {
return onFailure(reason);
}
uint16_t port = 0;
if (faceUri->getPort().empty()) {
port = ipAddress.is_multicast() ? m_defaultMulticastPort : m_defaultUnicastPort;
}
else {
try {
port = boost::lexical_cast<uint16_t>(faceUri->getPort());
}
catch (const boost::bad_lexical_cast&) {
return onFailure("invalid port number '" + faceUri->getPort() + "'");
}
}
FaceUri canonicalUri(typename Protocol::endpoint(ipAddress, port));
BOOST_ASSERT(canonicalUri.isCanonical());
onSuccess(canonicalUri);
}
void
onDnsFailure(const shared_ptr<FaceUri>& faceUri,
const FaceUri::CanonizeFailureCallback& onFailure,
const std::string& reason) const
{
onFailure(reason);
}
/** \brief when overriden in a subclass, check the IP address is allowable
* \return (true,ignored) if the address is allowable;
* (false,reason) if the address is not allowable.
*/
virtual std::pair<bool, std::string>
checkAddress(const dns::IpAddress& ipAddress) const
{
return {true, ""};
}
private:
std::string m_baseScheme;
std::string m_v4Scheme;
std::string m_v6Scheme;
uint16_t m_defaultUnicastPort;
uint16_t m_defaultMulticastPort;
};
class UdpCanonizeProvider : public IpHostCanonizeProvider<boost::asio::ip::udp>
{
public:
UdpCanonizeProvider()
: IpHostCanonizeProvider("udp")
{
}
protected:
// checkAddress is not overriden:
// Although NFD doesn't support IPv6 multicast, it's an implementation limitation.
// FaceMgmt protocol allows IPv6 multicast address in UDP.
};
class TcpCanonizeProvider : public IpHostCanonizeProvider<boost::asio::ip::tcp>
{
public:
TcpCanonizeProvider()
: IpHostCanonizeProvider("tcp")
{
}
protected:
std::pair<bool, std::string>
checkAddress(const dns::IpAddress& ipAddress) const override
{
if (ipAddress.is_multicast()) {
return {false, "cannot use multicast address"};
}
return {true, ""};
}
};
class EtherCanonizeProvider : public CanonizeProvider
{
public:
std::set<std::string>
getSchemes() const override
{
return {"ether"};
}
bool
isCanonical(const FaceUri& faceUri) const override
{
if (!faceUri.getPort().empty()) {
return false;
}
if (!faceUri.getPath().empty()) {
return false;
}
auto addr = ethernet::Address::fromString(faceUri.getHost());
return addr.toString() == faceUri.getHost();
}
void
canonize(const FaceUri& faceUri,
const FaceUri::CanonizeSuccessCallback& onSuccess,
const FaceUri::CanonizeFailureCallback& onFailure,
boost::asio::io_service& io, time::nanoseconds timeout) const override
{
auto addr = ethernet::Address::fromString(faceUri.getHost());
if (addr.isNull()) {
return onFailure("invalid ethernet address '" + faceUri.getHost() + "'");
}
FaceUri canonicalUri(addr);
BOOST_ASSERT(canonicalUri.isCanonical());
onSuccess(canonicalUri);
}
};
class DevCanonizeProvider : public CanonizeProvider
{
public:
std::set<std::string>
getSchemes() const override
{
return {"dev"};
}
bool
isCanonical(const FaceUri& faceUri) const override
{
return !faceUri.getHost().empty() && faceUri.getPort().empty() && faceUri.getPath().empty();
}
void
canonize(const FaceUri& faceUri,
const FaceUri::CanonizeSuccessCallback& onSuccess,
const FaceUri::CanonizeFailureCallback& onFailure,
boost::asio::io_service& io, time::nanoseconds timeout) const override
{
if (faceUri.getHost().empty()) {
onFailure("network interface name is missing");
return;
}
if (!faceUri.getPort().empty()) {
onFailure("port number is not allowed");
return;
}
if (!faceUri.getPath().empty() && faceUri.getPath() != "/") { // permit trailing slash only
onFailure("path is not allowed");
return;
}
FaceUri canonicalUri = FaceUri::fromDev(faceUri.getHost());
BOOST_ASSERT(canonicalUri.isCanonical());
onSuccess(canonicalUri);
}
};
class UdpDevCanonizeProvider : public CanonizeProvider
{
public:
std::set<std::string>
getSchemes() const override
{
return {"udp4+dev", "udp6+dev"};
}
bool
isCanonical(const FaceUri& faceUri) const override
{
if (faceUri.getPort().empty()) {
return false;
}
if (!faceUri.getPath().empty()) {
return false;
}
return true;
}
void
canonize(const FaceUri& faceUri,
const FaceUri::CanonizeSuccessCallback& onSuccess,
const FaceUri::CanonizeFailureCallback& onFailure,
boost::asio::io_service& io, time::nanoseconds timeout) const override
{
if (this->isCanonical(faceUri)) {
onSuccess(faceUri);
}
else {
onFailure("cannot canonize " + faceUri.toString());
}
}
};
using CanonizeProviders = boost::mpl::vector<UdpCanonizeProvider*,
TcpCanonizeProvider*,
EtherCanonizeProvider*,
DevCanonizeProvider*,
UdpDevCanonizeProvider*>;
using CanonizeProviderTable = std::map<std::string, shared_ptr<CanonizeProvider>>;
class CanonizeProviderTableInitializer
{
public:
explicit
CanonizeProviderTableInitializer(CanonizeProviderTable& providerTable)
: m_providerTable(providerTable)
{
}
template<typename CP>
void
operator()(CP*)
{
shared_ptr<CanonizeProvider> cp = make_shared<CP>();
auto schemes = cp->getSchemes();
BOOST_ASSERT(!schemes.empty());
for (const auto& scheme : schemes) {
BOOST_ASSERT(m_providerTable.count(scheme) == 0);
m_providerTable[scheme] = cp;
}
}
private:
CanonizeProviderTable& m_providerTable;
};
static const CanonizeProvider*
getCanonizeProvider(const std::string& scheme)
{
static CanonizeProviderTable providerTable;
if (providerTable.empty()) {
boost::mpl::for_each<CanonizeProviders>(CanonizeProviderTableInitializer(providerTable));
BOOST_ASSERT(!providerTable.empty());
}
auto it = providerTable.find(scheme);
return it == providerTable.end() ? nullptr : it->second.get();
}
bool
FaceUri::canCanonize(const std::string& scheme)
{
return getCanonizeProvider(scheme) != nullptr;
}
bool
FaceUri::isCanonical() const
{
const CanonizeProvider* cp = getCanonizeProvider(this->getScheme());
if (cp == nullptr) {
return false;
}
return cp->isCanonical(*this);
}
void
FaceUri::canonize(const CanonizeSuccessCallback& onSuccess,
const CanonizeFailureCallback& onFailure,
boost::asio::io_service& io, time::nanoseconds timeout) const
{
const CanonizeProvider* cp = getCanonizeProvider(this->getScheme());
if (cp == nullptr) {
if (onFailure) {
onFailure("scheme not supported");
}
return;
}
static CanonizeSuccessCallback successNop = bind([]{});
static CanonizeFailureCallback failureNop = bind([]{});
cp->canonize(*this,
onSuccess ? onSuccess : successNop,
onFailure ? onFailure : failureNop,
io, timeout);
}
} // namespace ndn