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