face-uri.cpp
Go to the documentation of this file.
1 /* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
2 /*
3  * Copyright (c) 2013-2019 Regents of the University of California,
4  * Arizona Board of Regents,
5  * Colorado State University,
6  * University Pierre & Marie Curie, Sorbonne University,
7  * Washington University in St. Louis,
8  * Beijing Institute of Technology,
9  * The University of Memphis.
10  *
11  * This file is part of ndn-cxx library (NDN C++ library with eXperimental eXtensions).
12  *
13  * ndn-cxx library is free software: you can redistribute it and/or modify it under the
14  * terms of the GNU Lesser General Public License as published by the Free Software
15  * Foundation, either version 3 of the License, or (at your option) any later version.
16  *
17  * ndn-cxx library is distributed in the hope that it will be useful, but WITHOUT ANY
18  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
19  * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
20  *
21  * You should have received copies of the GNU General Public License and GNU Lesser
22  * General Public License along with ndn-cxx, e.g., in COPYING.md file. If not, see
23  * <http://www.gnu.org/licenses/>.
24  *
25  * See AUTHORS.md for complete list of ndn-cxx authors and contributors.
26  */
27 
28 #include "ndn-cxx/net/face-uri.hpp"
29 #include "ndn-cxx/net/dns.hpp"
31 
32 #include <boost/algorithm/string.hpp>
33 #include <boost/lexical_cast.hpp>
34 #include <boost/mpl/vector.hpp>
35 #include <boost/mpl/for_each.hpp>
36 
37 #include <regex>
38 #include <set>
39 #include <sstream>
40 
41 namespace ndn {
42 
43 BOOST_CONCEPT_ASSERT((boost::EqualityComparable<FaceUri>));
44 
46  : m_isV6(false)
47 {
48 }
49 
50 FaceUri::FaceUri(const std::string& uri)
51 {
52  if (!parse(uri)) {
53  NDN_THROW(Error("Malformed URI: " + uri));
54  }
55 }
56 
57 FaceUri::FaceUri(const char* uri)
58  : FaceUri(std::string(uri))
59 {
60 }
61 
62 bool
63 FaceUri::parse(const std::string& uri)
64 {
65  m_scheme.clear();
66  m_host.clear();
67  m_port.clear();
68  m_path.clear();
69  m_isV6 = false;
70 
71  static const std::regex protocolExp("(\\w+\\d?(\\+\\w+)?)://([^/]*)(\\/[^?]*)?");
72  std::smatch protocolMatch;
73  if (!std::regex_match(uri, protocolMatch, protocolExp)) {
74  return false;
75  }
76  m_scheme = protocolMatch[1];
77  std::string authority = protocolMatch[3];
78  m_path = protocolMatch[4];
79 
80  // pattern for IPv6 link local address enclosed in [ ], with optional port number
81  static const std::regex v6LinkLocalExp("^\\[([a-fA-F0-9:]+)%([^\\s/:]+)\\](?:\\:(\\d+))?$");
82  // pattern for IPv6 address enclosed in [ ], with optional port number
83  static const std::regex v6Exp("^\\[([a-fA-F0-9:]+)\\](?:\\:(\\d+))?$");
84  // pattern for Ethernet address in standard hex-digits-and-colons notation
85  static const std::regex etherExp("^\\[((?:[a-fA-F0-9]{1,2}\\:){5}(?:[a-fA-F0-9]{1,2}))\\]$");
86  // pattern for IPv4-mapped IPv6 address, with optional port number
87  static const std::regex v4MappedV6Exp("^\\[::ffff:(\\d+(?:\\.\\d+){3})\\](?:\\:(\\d+))?$");
88  // pattern for IPv4/hostname/fd/ifname, with optional port number
89  static const std::regex v4HostExp("^([^:]+)(?:\\:(\\d+))?$");
90 
91  if (authority.empty()) {
92  // UNIX, internal
93  }
94  else {
95  std::smatch match;
96  if (std::regex_match(authority, match, v6LinkLocalExp)) {
97  m_isV6 = true;
98  m_host = match[1].str() + "%" + match[2].str();
99  m_port = match[3];
100  return true;
101  }
102 
103  m_isV6 = std::regex_match(authority, match, v6Exp);
104  if (m_isV6 ||
105  std::regex_match(authority, match, etherExp) ||
106  std::regex_match(authority, match, v4MappedV6Exp) ||
107  std::regex_match(authority, match, v4HostExp)) {
108  m_host = match[1];
109  m_port = match[2];
110  }
111  else {
112  return false;
113  }
114  }
115 
116  return true;
117 }
118 
119 FaceUri::FaceUri(const boost::asio::ip::udp::endpoint& endpoint)
120 {
121  m_isV6 = endpoint.address().is_v6();
122  m_scheme = m_isV6 ? "udp6" : "udp4";
123  m_host = endpoint.address().to_string();
124  m_port = to_string(endpoint.port());
125 }
126 
127 FaceUri::FaceUri(const boost::asio::ip::tcp::endpoint& endpoint)
128 {
129  m_isV6 = endpoint.address().is_v6();
130  m_scheme = m_isV6 ? "tcp6" : "tcp4";
131  m_host = endpoint.address().to_string();
132  m_port = to_string(endpoint.port());
133 }
134 
135 FaceUri::FaceUri(const boost::asio::ip::tcp::endpoint& endpoint, const std::string& scheme)
136 {
137  m_isV6 = endpoint.address().is_v6();
138  m_scheme = scheme;
139  m_host = endpoint.address().to_string();
140  m_port = to_string(endpoint.port());
141 }
142 
143 #ifdef BOOST_ASIO_HAS_LOCAL_SOCKETS
144 FaceUri::FaceUri(const boost::asio::local::stream_protocol::endpoint& endpoint)
145  : m_scheme("unix")
146  , m_path(endpoint.path())
147  , m_isV6(false)
148 {
149 }
150 #endif // BOOST_ASIO_HAS_LOCAL_SOCKETS
151 
152 FaceUri
154 {
155  FaceUri uri;
156  uri.m_scheme = "fd";
157  uri.m_host = to_string(fd);
158  return uri;
159 }
160 
162  : m_scheme("ether")
163  , m_host(address.toString())
164  , m_isV6(true)
165 {
166 }
167 
168 FaceUri
169 FaceUri::fromDev(const std::string& ifname)
170 {
171  FaceUri uri;
172  uri.m_scheme = "dev";
173  uri.m_host = ifname;
174  return uri;
175 }
176 
177 FaceUri
178 FaceUri::fromUdpDev(const boost::asio::ip::udp::endpoint& endpoint, const std::string& ifname)
179 {
180  FaceUri uri;
181  uri.m_scheme = endpoint.address().is_v6() ? "udp6+dev" : "udp4+dev";
182  uri.m_host = ifname;
183  uri.m_port = to_string(endpoint.port());
184  return uri;
185 }
186 
187 std::string
189 {
190  std::ostringstream os;
191  os << *this;
192  return os.str();
193 }
194 
195 std::ostream&
196 operator<<(std::ostream& os, const FaceUri& uri)
197 {
198  os << uri.m_scheme << "://";
199  if (uri.m_isV6) {
200  os << "[" << uri.m_host << "]";
201  }
202  else {
203  os << uri.m_host;
204  }
205  if (!uri.m_port.empty()) {
206  os << ":" << uri.m_port;
207  }
208  os << uri.m_path;
209  return os;
210 }
211 
212 
215 class CanonizeProvider : noncopyable
216 {
217 public:
218  virtual
219  ~CanonizeProvider() = default;
220 
221  virtual std::set<std::string>
222  getSchemes() const = 0;
223 
224  virtual bool
225  isCanonical(const FaceUri& faceUri) const = 0;
226 
227  virtual void
228  canonize(const FaceUri& faceUri,
229  const FaceUri::CanonizeSuccessCallback& onSuccess,
230  const FaceUri::CanonizeFailureCallback& onFailure,
231  boost::asio::io_service& io, time::nanoseconds timeout) const = 0;
232 };
233 
234 template<typename Protocol>
235 class IpHostCanonizeProvider : public CanonizeProvider
236 {
237 public:
238  std::set<std::string>
239  getSchemes() const override
240  {
241  return {m_baseScheme, m_v4Scheme, m_v6Scheme};
242  }
243 
244  bool
245  isCanonical(const FaceUri& faceUri) const override
246  {
247  if (faceUri.getPort().empty()) {
248  return false;
249  }
250  if (!faceUri.getPath().empty()) {
251  return false;
252  }
253 
254  boost::system::error_code ec;
255  auto addr = boost::asio::ip::address::from_string(unescapeHost(faceUri.getHost()), ec);
256  if (ec) {
257  return false;
258  }
259 
260  bool hasCorrectScheme = (faceUri.getScheme() == m_v4Scheme && addr.is_v4()) ||
261  (faceUri.getScheme() == m_v6Scheme && addr.is_v6());
262  if (!hasCorrectScheme) {
263  return false;
264  }
265 
266  auto checkAddressWithUri = [] (const boost::asio::ip::address& addr,
267  const FaceUri& faceUri) -> bool {
268  if (addr.is_v4() || !addr.to_v6().is_link_local()) {
269  return addr.to_string() == faceUri.getHost();
270  }
271 
272  std::vector<std::string> addrFields, faceUriFields;
273  std::string addrString = addr.to_string();
274  std::string faceUriString = faceUri.getHost();
275 
276  boost::algorithm::split(addrFields, addrString, boost::is_any_of("%"));
277  boost::algorithm::split(faceUriFields, faceUriString, boost::is_any_of("%"));
278  if (addrFields.size() != 2 || faceUriFields.size() != 2) {
279  return false;
280  }
281 
282  if (faceUriFields[1].size() > 2 && faceUriFields[1].compare(0, 2, "25") == 0) {
283  // %25... is accepted, but not a canonical form
284  return false;
285  }
286 
287  return addrFields[0] == faceUriFields[0] &&
288  addrFields[1] == faceUriFields[1];
289  };
290 
291  return checkAddressWithUri(addr, faceUri) && checkAddress(addr).first;
292  }
293 
294  void
295  canonize(const FaceUri& faceUri,
296  const FaceUri::CanonizeSuccessCallback& onSuccess,
297  const FaceUri::CanonizeFailureCallback& onFailure,
298  boost::asio::io_service& io, time::nanoseconds timeout) const override
299  {
300  if (this->isCanonical(faceUri)) {
301  onSuccess(faceUri);
302  return;
303  }
304 
305  // make a copy because caller may modify faceUri
306  auto uri = make_shared<FaceUri>(faceUri);
307  boost::system::error_code ec;
308  auto ipAddress = boost::asio::ip::address::from_string(unescapeHost(faceUri.getHost()), ec);
309  if (!ec) {
310  // No need to resolve IP address if host is already an IP
311  if ((faceUri.getScheme() == m_v4Scheme && !ipAddress.is_v4()) ||
312  (faceUri.getScheme() == m_v6Scheme && !ipAddress.is_v6())) {
313  return onFailure("IPv4/v6 mismatch");
314  }
315 
316  onDnsSuccess(uri, onSuccess, onFailure, ipAddress);
317  }
318  else {
319  dns::AddressSelector addressSelector;
320  if (faceUri.getScheme() == m_v4Scheme) {
321  addressSelector = dns::Ipv4Only();
322  }
323  else if (faceUri.getScheme() == m_v6Scheme) {
324  addressSelector = dns::Ipv6Only();
325  }
326  else {
327  BOOST_ASSERT(faceUri.getScheme() == m_baseScheme);
328  addressSelector = dns::AnyAddress();
329  }
330 
331  dns::asyncResolve(unescapeHost(faceUri.getHost()),
332  bind(&IpHostCanonizeProvider<Protocol>::onDnsSuccess, this, uri, onSuccess, onFailure, _1),
333  bind(&IpHostCanonizeProvider<Protocol>::onDnsFailure, this, uri, onFailure, _1),
334  io, addressSelector, timeout);
335  }
336  }
337 
338 protected:
339  explicit
340  IpHostCanonizeProvider(const std::string& baseScheme,
341  uint16_t defaultUnicastPort = 6363,
342  uint16_t defaultMulticastPort = 56363)
343  : m_baseScheme(baseScheme)
344  , m_v4Scheme(baseScheme + '4')
345  , m_v6Scheme(baseScheme + '6')
346  , m_defaultUnicastPort(defaultUnicastPort)
347  , m_defaultMulticastPort(defaultMulticastPort)
348  {
349  }
350 
351 private:
352  void
353  onDnsSuccess(const shared_ptr<FaceUri>& faceUri,
354  const FaceUri::CanonizeSuccessCallback& onSuccess,
355  const FaceUri::CanonizeFailureCallback& onFailure,
356  const dns::IpAddress& ipAddress) const
357  {
358  bool isOk = false;
359  std::string reason;
360  std::tie(isOk, reason) = this->checkAddress(ipAddress);
361  if (!isOk) {
362  return onFailure(reason);
363  }
364 
365  uint16_t port = 0;
366  if (faceUri->getPort().empty()) {
367  port = ipAddress.is_multicast() ? m_defaultMulticastPort : m_defaultUnicastPort;
368  }
369  else {
370  try {
371  port = boost::lexical_cast<uint16_t>(faceUri->getPort());
372  }
373  catch (const boost::bad_lexical_cast&) {
374  return onFailure("invalid port number '" + faceUri->getPort() + "'");
375  }
376  }
377 
378  FaceUri canonicalUri(typename Protocol::endpoint(ipAddress, port));
379  BOOST_ASSERT(canonicalUri.isCanonical());
380  onSuccess(canonicalUri);
381  }
382 
383  void
384  onDnsFailure(const shared_ptr<FaceUri>& faceUri,
385  const FaceUri::CanonizeFailureCallback& onFailure,
386  const std::string& reason) const
387  {
388  onFailure(reason);
389  }
390 
395  virtual std::pair<bool, std::string>
396  checkAddress(const dns::IpAddress& ipAddress) const
397  {
398  return {true, ""};
399  }
400 
401  static std::string
402  unescapeHost(std::string host)
403  {
404  auto escapePos = host.find("%25");
405  if (escapePos != std::string::npos && escapePos < host.size() - 3) {
406  host = unescape(host);
407  }
408  return host;
409  }
410 
411 private:
412  std::string m_baseScheme;
413  std::string m_v4Scheme;
414  std::string m_v6Scheme;
415  uint16_t m_defaultUnicastPort;
416  uint16_t m_defaultMulticastPort;
417 };
418 
419 class UdpCanonizeProvider : public IpHostCanonizeProvider<boost::asio::ip::udp>
420 {
421 public:
422  UdpCanonizeProvider()
423  : IpHostCanonizeProvider("udp")
424  {
425  }
426 };
427 
428 class TcpCanonizeProvider : public IpHostCanonizeProvider<boost::asio::ip::tcp>
429 {
430 public:
431  TcpCanonizeProvider()
432  : IpHostCanonizeProvider("tcp")
433  {
434  }
435 
436 protected:
437  std::pair<bool, std::string>
438  checkAddress(const dns::IpAddress& ipAddress) const override
439  {
440  if (ipAddress.is_multicast()) {
441  return {false, "cannot use multicast address"};
442  }
443  return {true, ""};
444  }
445 };
446 
447 class EtherCanonizeProvider : public CanonizeProvider
448 {
449 public:
450  std::set<std::string>
451  getSchemes() const override
452  {
453  return {"ether"};
454  }
455 
456  bool
457  isCanonical(const FaceUri& faceUri) const override
458  {
459  if (!faceUri.getPort().empty()) {
460  return false;
461  }
462  if (!faceUri.getPath().empty()) {
463  return false;
464  }
465 
466  auto addr = ethernet::Address::fromString(faceUri.getHost());
467  return addr.toString() == faceUri.getHost();
468  }
469 
470  void
471  canonize(const FaceUri& faceUri,
472  const FaceUri::CanonizeSuccessCallback& onSuccess,
473  const FaceUri::CanonizeFailureCallback& onFailure,
474  boost::asio::io_service& io, time::nanoseconds timeout) const override
475  {
476  auto addr = ethernet::Address::fromString(faceUri.getHost());
477  if (addr.isNull()) {
478  return onFailure("invalid ethernet address '" + faceUri.getHost() + "'");
479  }
480 
481  FaceUri canonicalUri(addr);
482  BOOST_ASSERT(canonicalUri.isCanonical());
483  onSuccess(canonicalUri);
484  }
485 };
486 
487 class DevCanonizeProvider : public CanonizeProvider
488 {
489 public:
490  std::set<std::string>
491  getSchemes() const override
492  {
493  return {"dev"};
494  }
495 
496  bool
497  isCanonical(const FaceUri& faceUri) const override
498  {
499  return !faceUri.getHost().empty() && faceUri.getPort().empty() && faceUri.getPath().empty();
500  }
501 
502  void
503  canonize(const FaceUri& faceUri,
504  const FaceUri::CanonizeSuccessCallback& onSuccess,
505  const FaceUri::CanonizeFailureCallback& onFailure,
506  boost::asio::io_service& io, time::nanoseconds timeout) const override
507  {
508  if (faceUri.getHost().empty()) {
509  onFailure("network interface name is missing");
510  return;
511  }
512  if (!faceUri.getPort().empty()) {
513  onFailure("port number is not allowed");
514  return;
515  }
516  if (!faceUri.getPath().empty() && faceUri.getPath() != "/") { // permit trailing slash only
517  onFailure("path is not allowed");
518  return;
519  }
520 
521  FaceUri canonicalUri = FaceUri::fromDev(faceUri.getHost());
522  BOOST_ASSERT(canonicalUri.isCanonical());
523  onSuccess(canonicalUri);
524  }
525 };
526 
527 class UdpDevCanonizeProvider : public CanonizeProvider
528 {
529 public:
530  std::set<std::string>
531  getSchemes() const override
532  {
533  return {"udp4+dev", "udp6+dev"};
534  }
535 
536  bool
537  isCanonical(const FaceUri& faceUri) const override
538  {
539  if (faceUri.getPort().empty()) {
540  return false;
541  }
542  if (!faceUri.getPath().empty()) {
543  return false;
544  }
545  return true;
546  }
547 
548  void
549  canonize(const FaceUri& faceUri,
550  const FaceUri::CanonizeSuccessCallback& onSuccess,
551  const FaceUri::CanonizeFailureCallback& onFailure,
552  boost::asio::io_service& io, time::nanoseconds timeout) const override
553  {
554  if (this->isCanonical(faceUri)) {
555  onSuccess(faceUri);
556  }
557  else {
558  onFailure("cannot canonize " + faceUri.toString());
559  }
560  }
561 };
562 
563 using CanonizeProviders = boost::mpl::vector<UdpCanonizeProvider*,
564  TcpCanonizeProvider*,
565  EtherCanonizeProvider*,
566  DevCanonizeProvider*,
567  UdpDevCanonizeProvider*>;
568 using CanonizeProviderTable = std::map<std::string, shared_ptr<CanonizeProvider>>;
569 
570 class CanonizeProviderTableInitializer
571 {
572 public:
573  explicit
574  CanonizeProviderTableInitializer(CanonizeProviderTable& providerTable)
575  : m_providerTable(providerTable)
576  {
577  }
578 
579  template<typename CP>
580  void
581  operator()(CP*)
582  {
583  shared_ptr<CanonizeProvider> cp = make_shared<CP>();
584  auto schemes = cp->getSchemes();
585  BOOST_ASSERT(!schemes.empty());
586 
587  for (const auto& scheme : schemes) {
588  BOOST_ASSERT(m_providerTable.count(scheme) == 0);
589  m_providerTable[scheme] = cp;
590  }
591  }
592 
593 private:
594  CanonizeProviderTable& m_providerTable;
595 };
596 
597 static const CanonizeProvider*
598 getCanonizeProvider(const std::string& scheme)
599 {
600  static CanonizeProviderTable providerTable;
601  if (providerTable.empty()) {
602  boost::mpl::for_each<CanonizeProviders>(CanonizeProviderTableInitializer(providerTable));
603  BOOST_ASSERT(!providerTable.empty());
604  }
605 
606  auto it = providerTable.find(scheme);
607  return it == providerTable.end() ? nullptr : it->second.get();
608 }
609 
610 
611 bool
612 FaceUri::canCanonize(const std::string& scheme)
613 {
614  return getCanonizeProvider(scheme) != nullptr;
615 }
616 
617 bool
619 {
620  const CanonizeProvider* cp = getCanonizeProvider(this->getScheme());
621  if (cp == nullptr) {
622  return false;
623  }
624 
625  return cp->isCanonical(*this);
626 }
627 
628 void
630  const CanonizeFailureCallback& onFailure,
631  boost::asio::io_service& io, time::nanoseconds timeout) const
632 {
633  const CanonizeProvider* cp = getCanonizeProvider(this->getScheme());
634  if (cp == nullptr) {
635  if (onFailure) {
636  onFailure("scheme not supported");
637  }
638  return;
639  }
640 
641  cp->canonize(*this,
642  onSuccess ? onSuccess : [] (auto&&) {},
643  onFailure ? onFailure : [] (auto&&) {},
644  io, timeout);
645 }
646 
647 } // namespace ndn
static Address fromString(const std::string &str)
Creates an Address from a string containing an Ethernet address in hexadecimal notation, with colons or hyphens as separators.
Definition: ethernet.cpp:92
Definition: data.cpp:26
function< void(const std::string &reason)> CanonizeFailureCallback
Definition: face-uri.hpp:154
static FaceUri fromFd(int fd)
create fd FaceUri from file descriptor
Definition: face-uri.cpp:153
const std::string & getHost() const
get host (domain)
Definition: face-uri.hpp:116
std::string to_string(const T &val)
Definition: backports.hpp:102
function< void(const FaceUri &)> CanonizeSuccessCallback
Definition: face-uri.hpp:153
static bool canCanonize(const std::string &scheme)
Definition: face-uri.cpp:612
std::string toString() const
write as a string
Definition: face-uri.cpp:188
static const CanonizeProvider * getCanonizeProvider(const std::string &scheme)
Definition: face-uri.cpp:598
const std::string & getPort() const
get port
Definition: face-uri.hpp:123
STL namespace.
std::string toString(char sep=':') const
Converts the address to a human-readable string.
Definition: ethernet.cpp:78
function< bool(const IpAddress &address)> AddressSelector
Definition: dns.hpp:34
static FaceUri fromUdpDev(const boost::asio::ip::udp::endpoint &endpoint, const std::string &ifname)
create udp4 or udp6 NIC-associated FaceUri from endpoint and network device name
Definition: face-uri.cpp:178
#define NDN_THROW(e)
Definition: exception.hpp:61
bool parse(const std::string &uri)
exception-safe parsing
Definition: face-uri.cpp:63
std::string unescape(const std::string &str)
Decode a percent-encoded string.
void canonize(const CanonizeSuccessCallback &onSuccess, const CanonizeFailureCallback &onFailure, boost::asio::io_service &io, time::nanoseconds timeout) const
asynchronously convert this FaceUri to canonical form
Definition: face-uri.cpp:629
represents the underlying protocol and address used by a Face
Definition: face-uri.hpp:44
represents an Ethernet hardware address
Definition: ethernet.hpp:52
const std::string & getScheme() const
get scheme (protocol)
Definition: face-uri.hpp:109
boost::asio::ip::address IpAddress
Definition: dns.hpp:33
bool isCanonical() const
determine whether this FaceUri is in canonical form
Definition: face-uri.cpp:618
std::map< std::string, shared_ptr< CanonizeProvider > > CanonizeProviderTable
Definition: face-uri.cpp:568
const std::string & getPath() const
get path
Definition: face-uri.hpp:130
boost::mpl::vector< UdpCanonizeProvider *, TcpCanonizeProvider *, EtherCanonizeProvider *, DevCanonizeProvider *, UdpDevCanonizeProvider * > CanonizeProviders
Definition: face-uri.cpp:567
void asyncResolve(const std::string &host, const SuccessCallback &onSuccess, const ErrorCallback &onError, boost::asio::io_service &ioService, const AddressSelector &addressSelector, time::nanoseconds timeout)
Asynchronously resolve host.
Definition: dns.cpp:145
static FaceUri fromDev(const std::string &ifname)
create dev FaceUri from network device name
Definition: face-uri.cpp:169
friend std::ostream & operator<<(std::ostream &os, const FaceUri &uri)
Definition: face-uri.cpp:196