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