blob: 4ad9dbac85d81032b70e268f86003245ff031579 [file] [log] [blame]
Junxiao Shi77dcadd2014-10-05 14:40:54 -07001/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
2/**
Davide Pesaventoaeeb3fc2016-08-14 03:40:02 +02003 * Copyright (c) 2015-2016, 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.
Junxiao Shi77dcadd2014-10-05 14:40:54 -070010 *
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 "face-uri.hpp"
Junxiao Shi4083c8d2014-10-12 16:43:16 -070029#include "dns.hpp"
Junxiao Shi77dcadd2014-10-05 14:40:54 -070030
Junxiao Shi4083c8d2014-10-12 16:43:16 -070031#include <set>
Junxiao Shi77dcadd2014-10-05 14:40:54 -070032#include <boost/concept_check.hpp>
33#include <boost/regex.hpp>
Junxiao Shie3ef6ee2014-10-05 14:40:54 -070034#include <boost/lexical_cast.hpp>
Junxiao Shi4083c8d2014-10-12 16:43:16 -070035#include <boost/mpl/vector.hpp>
36#include <boost/mpl/for_each.hpp>
Junxiao Shi77dcadd2014-10-05 14:40:54 -070037
38namespace ndn {
39namespace util {
40
41BOOST_CONCEPT_ASSERT((boost::EqualityComparable<FaceUri>));
42
43FaceUri::FaceUri()
44 : m_isV6(false)
45{
46}
47
48FaceUri::FaceUri(const std::string& uri)
49{
50 if (!parse(uri)) {
Spyridon Mastorakis0d2ed2e2015-07-27 19:09:12 -070051 BOOST_THROW_EXCEPTION(Error("Malformed URI: " + uri));
Junxiao Shi77dcadd2014-10-05 14:40:54 -070052 }
53}
54
55FaceUri::FaceUri(const char* uri)
56{
57 if (!parse(uri)) {
Spyridon Mastorakis0d2ed2e2015-07-27 19:09:12 -070058 BOOST_THROW_EXCEPTION(Error("Malformed URI: " + std::string(uri)));
Junxiao Shi77dcadd2014-10-05 14:40:54 -070059 }
60}
61
62bool
63FaceUri::parse(const std::string& uri)
64{
65 m_scheme.clear();
66 m_host.clear();
67 m_isV6 = false;
68 m_port.clear();
69 m_path.clear();
70
71 static const boost::regex protocolExp("(\\w+\\d?)://([^/]*)(\\/[^?]*)?");
72 boost::smatch protocolMatch;
73 if (!boost::regex_match(uri, protocolMatch, protocolExp)) {
74 return false;
75 }
76 m_scheme = protocolMatch[1];
77 const std::string& authority = protocolMatch[2];
78 m_path = protocolMatch[3];
79
80 // pattern for IPv6 address enclosed in [ ], with optional port number
81 static const boost::regex v6Exp("^\\[([a-fA-F0-9:]+)\\](?:\\:(\\d+))?$");
82 // pattern for Ethernet address in standard hex-digits-and-colons notation
83 static const boost::regex etherExp("^\\[((?:[a-fA-F0-9]{1,2}\\:){5}(?:[a-fA-F0-9]{1,2}))\\]$");
84 // pattern for IPv4-mapped IPv6 address, with optional port number
85 static const boost::regex v4MappedV6Exp("^\\[::ffff:(\\d+(?:\\.\\d+){3})\\](?:\\:(\\d+))?$");
86 // pattern for IPv4/hostname/fd/ifname, with optional port number
87 static const boost::regex v4HostExp("^([^:]+)(?:\\:(\\d+))?$");
88
89 if (authority.empty()) {
90 // UNIX, internal
91 }
92 else {
93 boost::smatch match;
94 m_isV6 = boost::regex_match(authority, match, v6Exp);
95 if (m_isV6 ||
96 boost::regex_match(authority, match, etherExp) ||
97 boost::regex_match(authority, match, v4MappedV6Exp) ||
98 boost::regex_match(authority, match, v4HostExp)) {
99 m_host = match[1];
100 m_port = match[2];
101 }
102 else {
103 return false;
104 }
105 }
106
107 return true;
108}
109
110FaceUri::FaceUri(const boost::asio::ip::udp::endpoint& endpoint)
111{
112 m_isV6 = endpoint.address().is_v6();
113 m_scheme = m_isV6 ? "udp6" : "udp4";
114 m_host = endpoint.address().to_string();
115 m_port = boost::lexical_cast<std::string>(endpoint.port());
116}
117
118FaceUri::FaceUri(const boost::asio::ip::tcp::endpoint& endpoint)
119{
120 m_isV6 = endpoint.address().is_v6();
121 m_scheme = m_isV6 ? "tcp6" : "tcp4";
122 m_host = endpoint.address().to_string();
123 m_port = boost::lexical_cast<std::string>(endpoint.port());
124}
125
126FaceUri::FaceUri(const boost::asio::ip::tcp::endpoint& endpoint, const std::string& scheme)
127 : m_scheme(scheme)
128{
129 m_isV6 = endpoint.address().is_v6();
130 m_host = endpoint.address().to_string();
131 m_port = boost::lexical_cast<std::string>(endpoint.port());
132}
133
134#ifdef BOOST_ASIO_HAS_LOCAL_SOCKETS
135FaceUri::FaceUri(const boost::asio::local::stream_protocol::endpoint& endpoint)
136 : m_isV6(false)
137{
138 m_scheme = "unix";
139 m_path = endpoint.path();
140}
141#endif // BOOST_ASIO_HAS_LOCAL_SOCKETS
142
143FaceUri
144FaceUri::fromFd(int fd)
145{
146 FaceUri uri;
147 uri.m_scheme = "fd";
148 uri.m_host = boost::lexical_cast<std::string>(fd);
149 return uri;
150}
151
152FaceUri::FaceUri(const ethernet::Address& address)
153 : m_isV6(true)
154{
155 m_scheme = "ether";
156 m_host = address.toString();
157}
158
159FaceUri
160FaceUri::fromDev(const std::string& ifname)
161{
162 FaceUri uri;
163 uri.m_scheme = "dev";
164 uri.m_host = ifname;
165 return uri;
166}
167
168bool
169FaceUri::operator==(const FaceUri& rhs) const
170{
171 return (m_scheme == rhs.m_scheme &&
172 m_host == rhs.m_host &&
173 m_isV6 == rhs.m_isV6 &&
174 m_port == rhs.m_port &&
175 m_path == rhs.m_path);
176}
177
178bool
179FaceUri::operator!=(const FaceUri& rhs) const
180{
181 return !(*this == rhs);
182}
183
184std::string
185FaceUri::toString() const
186{
187 std::ostringstream os;
188 os << *this;
189 return os.str();
190}
191
192std::ostream&
193operator<<(std::ostream& os, const FaceUri& uri)
194{
195 os << uri.m_scheme << "://";
196 if (uri.m_isV6) {
197 os << "[" << uri.m_host << "]";
198 }
199 else {
200 os << uri.m_host;
201 }
202 if (!uri.m_port.empty()) {
203 os << ":" << uri.m_port;
204 }
205 os << uri.m_path;
206 return os;
207}
208
Junxiao Shi4083c8d2014-10-12 16:43:16 -0700209/** \brief a CanonizeProvider provides FaceUri canonization functionality for a group of schemes
210 */
211class CanonizeProvider : noncopyable
212{
213public:
214 virtual
Davide Pesaventoaeeb3fc2016-08-14 03:40:02 +0200215 ~CanonizeProvider() = default;
Junxiao Shi4083c8d2014-10-12 16:43:16 -0700216
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_service& io, const time::nanoseconds& timeout) const = 0;
228};
229
230template<typename Protocol>
231class IpHostCanonizeProvider : public CanonizeProvider
232{
233public:
234 virtual std::set<std::string>
Davide Pesaventoaeeb3fc2016-08-14 03:40:02 +0200235 getSchemes() const override
Junxiao Shi4083c8d2014-10-12 16:43:16 -0700236 {
237 std::set<std::string> schemes;
238 schemes.insert(m_baseScheme);
239 schemes.insert(m_v4Scheme);
240 schemes.insert(m_v6Scheme);
241 return schemes;
242 }
243
244 virtual bool
Davide Pesaventoaeeb3fc2016-08-14 03:40:02 +0200245 isCanonical(const FaceUri& faceUri) const override
Junxiao Shi4083c8d2014-10-12 16:43:16 -0700246 {
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 boost::asio::ip::address addr;
256 if (faceUri.getScheme() == m_v4Scheme) {
257 addr = boost::asio::ip::address_v4::from_string(faceUri.getHost(), ec);
258 }
259 else if (faceUri.getScheme() == m_v6Scheme) {
260 addr = boost::asio::ip::address_v6::from_string(faceUri.getHost(), ec);
261 }
262 else {
263 return false;
264 }
265 return !static_cast<bool>(ec) && addr.to_string() == faceUri.getHost() &&
266 this->checkAddress(addr).first;
267 }
268
269 virtual void
270 canonize(const FaceUri& faceUri,
271 const FaceUri::CanonizeSuccessCallback& onSuccess,
272 const FaceUri::CanonizeFailureCallback& onFailure,
Davide Pesaventoaeeb3fc2016-08-14 03:40:02 +0200273 boost::asio::io_service& io, const time::nanoseconds& timeout) const override
Junxiao Shi4083c8d2014-10-12 16:43:16 -0700274 {
275 if (this->isCanonical(faceUri)) {
276 onSuccess(faceUri);
277 return;
278 }
279
280 dns::AddressSelector addressSelector;
281 if (faceUri.getScheme() == m_v4Scheme) {
282 addressSelector = dns::Ipv4Only();
283 }
284 else if (faceUri.getScheme() == m_v6Scheme) {
285 addressSelector = dns::Ipv6Only();
286 }
287 else {
288 BOOST_ASSERT(faceUri.getScheme() == m_baseScheme);
289 addressSelector = dns::AnyAddress();
290 }
291
292 // make a copy because caller may modify faceUri
293 shared_ptr<FaceUri> uri = make_shared<FaceUri>(faceUri);
294 dns::asyncResolve(faceUri.getHost(),
295 bind(&IpHostCanonizeProvider<Protocol>::onDnsSuccess, this, uri, onSuccess, onFailure, _1),
296 bind(&IpHostCanonizeProvider<Protocol>::onDnsFailure, this, uri, onFailure, _1),
297 io, addressSelector, timeout);
298 }
299
300protected:
Davide Pesaventoaeeb3fc2016-08-14 03:40:02 +0200301 explicit
Junxiao Shi4083c8d2014-10-12 16:43:16 -0700302 IpHostCanonizeProvider(const std::string& baseScheme,
303 uint32_t defaultUnicastPort = 6363,
304 uint32_t defaultMulticastPort = 56363)
305 : m_baseScheme(baseScheme)
306 , m_v4Scheme(baseScheme + "4")
307 , m_v6Scheme(baseScheme + "6")
308 , m_defaultUnicastPort(defaultUnicastPort)
309 , m_defaultMulticastPort(defaultMulticastPort)
310 {
311 }
312
313private:
314 // faceUri is a shared_ptr passed by value because this function can take ownership
315 void
316 onDnsSuccess(shared_ptr<FaceUri> faceUri,
317 const FaceUri::CanonizeSuccessCallback& onSuccess,
318 const FaceUri::CanonizeFailureCallback& onFailure,
319 const dns::IpAddress& ipAddress) const
320 {
321 std::pair<bool, std::string> checkAddressRes = this->checkAddress(ipAddress);
322 if (!checkAddressRes.first) {
323 onFailure(checkAddressRes.second);
324 return;
325 }
326
327 uint32_t port = 0;
328 if (faceUri->getPort().empty()) {
329 port = ipAddress.is_multicast() ? m_defaultMulticastPort : m_defaultUnicastPort;
330 }
331 else {
332 try {
333 port = boost::lexical_cast<uint32_t>(faceUri->getPort());
334 }
335 catch (boost::bad_lexical_cast&) {
336 onFailure("invalid port number");
337 return;
338 }
339 }
340
341 FaceUri canonicalUri(typename Protocol::endpoint(ipAddress, port));
342 BOOST_ASSERT(canonicalUri.isCanonical());
343 onSuccess(canonicalUri);
344 }
345
346 // faceUri is a shared_ptr passed by value because this function can take ownership
347 void
348 onDnsFailure(shared_ptr<FaceUri> faceUri, const FaceUri::CanonizeFailureCallback& onFailure,
349 const std::string& reason) const
350 {
351 onFailure(reason);
352 }
353
354 /** \brief when overriden in a subclass, check the IP address is allowable
355 * \return (true,ignored) if the address is allowable;
356 * (false,reason) if the address is not allowable.
357 */
358 virtual std::pair<bool, std::string>
359 checkAddress(const dns::IpAddress& ipAddress) const
360 {
Davide Pesaventoaeeb3fc2016-08-14 03:40:02 +0200361 return {true, ""};
Junxiao Shi4083c8d2014-10-12 16:43:16 -0700362 }
363
364private:
365 std::string m_baseScheme;
366 std::string m_v4Scheme;
367 std::string m_v6Scheme;
368 uint32_t m_defaultUnicastPort;
369 uint32_t m_defaultMulticastPort;
370};
371
372class UdpCanonizeProvider : public IpHostCanonizeProvider<boost::asio::ip::udp>
373{
374public:
375 UdpCanonizeProvider()
376 : IpHostCanonizeProvider("udp")
377 {
378 }
379
380protected:
381 // checkAddress is not overriden:
382 // Although NFD doesn't support IPv6 multicast, it's an implementation limitation.
383 // FaceMgmt protocol allows IPv6 multicast address in UDP.
384};
385
386class TcpCanonizeProvider : public IpHostCanonizeProvider<boost::asio::ip::tcp>
387{
388public:
Junxiao Shi4083c8d2014-10-12 16:43:16 -0700389 TcpCanonizeProvider()
390 : IpHostCanonizeProvider("tcp")
391 {
392 }
393
394protected:
395 virtual std::pair<bool, std::string>
Davide Pesaventoaeeb3fc2016-08-14 03:40:02 +0200396 checkAddress(const dns::IpAddress& ipAddress) const override
Junxiao Shi4083c8d2014-10-12 16:43:16 -0700397 {
398 if (ipAddress.is_multicast()) {
Davide Pesaventoaeeb3fc2016-08-14 03:40:02 +0200399 return {false, "cannot use multicast address"};
Junxiao Shi4083c8d2014-10-12 16:43:16 -0700400 }
Davide Pesaventoaeeb3fc2016-08-14 03:40:02 +0200401 return {true, ""};
Junxiao Shi4083c8d2014-10-12 16:43:16 -0700402 }
403};
404
405class EtherCanonizeProvider : public CanonizeProvider
406{
407public:
408 virtual std::set<std::string>
Davide Pesaventoaeeb3fc2016-08-14 03:40:02 +0200409 getSchemes() const override
Junxiao Shi4083c8d2014-10-12 16:43:16 -0700410 {
411 std::set<std::string> schemes;
412 schemes.insert("ether");
413 return schemes;
414 }
415
416 virtual bool
Davide Pesaventoaeeb3fc2016-08-14 03:40:02 +0200417 isCanonical(const FaceUri& faceUri) const override
Junxiao Shi4083c8d2014-10-12 16:43:16 -0700418 {
419 if (!faceUri.getPort().empty()) {
420 return false;
421 }
422 if (!faceUri.getPath().empty()) {
423 return false;
424 }
425
426 ethernet::Address addr = ethernet::Address::fromString(faceUri.getHost());
427 return addr.toString() == faceUri.getHost();
428 }
429
430 virtual void
431 canonize(const FaceUri& faceUri,
432 const FaceUri::CanonizeSuccessCallback& onSuccess,
433 const FaceUri::CanonizeFailureCallback& onFailure,
Davide Pesaventoaeeb3fc2016-08-14 03:40:02 +0200434 boost::asio::io_service& io, const time::nanoseconds& timeout) const override
Junxiao Shi4083c8d2014-10-12 16:43:16 -0700435 {
436 ethernet::Address addr = ethernet::Address::fromString(faceUri.getHost());
437 if (addr.isNull()) {
438 onFailure("cannot parse address");
439 return;
440 }
441
442 FaceUri canonicalUri(addr);
443 BOOST_ASSERT(canonicalUri.isCanonical());
444 onSuccess(canonicalUri);
445 }
446};
447
448typedef boost::mpl::vector<
449 UdpCanonizeProvider*,
450 TcpCanonizeProvider*,
451 EtherCanonizeProvider*
452 > CanonizeProviders;
453typedef std::map<std::string, shared_ptr<CanonizeProvider> > CanonizeProviderTable;
454
455class CanonizeProviderTableInitializer
456{
457public:
458 explicit
459 CanonizeProviderTableInitializer(CanonizeProviderTable& providerTable)
460 : m_providerTable(providerTable)
461 {
462 }
463
464 template<typename CP> void
465 operator()(CP*)
466 {
467 shared_ptr<CanonizeProvider> cp = make_shared<CP>();
468
469 std::set<std::string> schemes = cp->getSchemes();
470 BOOST_ASSERT(!schemes.empty());
471 for (std::set<std::string>::iterator it = schemes.begin();
472 it != schemes.end(); ++it) {
473 BOOST_ASSERT(m_providerTable.count(*it) == 0);
474 m_providerTable[*it] = cp;
475 }
476 }
477
478private:
479 CanonizeProviderTable& m_providerTable;
480};
481
482static const CanonizeProvider*
483getCanonizeProvider(const std::string& scheme)
484{
485 static CanonizeProviderTable providerTable;
486 if (providerTable.empty()) {
487 boost::mpl::for_each<CanonizeProviders>(CanonizeProviderTableInitializer(providerTable));
488 BOOST_ASSERT(!providerTable.empty());
489 }
490
Davide Pesaventoaeeb3fc2016-08-14 03:40:02 +0200491 auto it = providerTable.find(scheme);
Junxiao Shi4083c8d2014-10-12 16:43:16 -0700492 if (it == providerTable.end()) {
Davide Pesaventoaeeb3fc2016-08-14 03:40:02 +0200493 return nullptr;
Junxiao Shi4083c8d2014-10-12 16:43:16 -0700494 }
495 return it->second.get();
496}
497
498bool
499FaceUri::canCanonize(const std::string& scheme)
500{
501 return getCanonizeProvider(scheme) != 0;
502}
503
504bool
505FaceUri::isCanonical() const
506{
507 const CanonizeProvider* cp = getCanonizeProvider(this->getScheme());
508 if (cp == 0) {
509 return false;
510 }
511
512 return cp->isCanonical(*this);
513}
514
Junxiao Shi4083c8d2014-10-12 16:43:16 -0700515void
516FaceUri::canonize(const CanonizeSuccessCallback& onSuccess,
517 const CanonizeFailureCallback& onFailure,
518 boost::asio::io_service& io, const time::nanoseconds& timeout) const
519{
520 const CanonizeProvider* cp = getCanonizeProvider(this->getScheme());
Davide Pesaventoaeeb3fc2016-08-14 03:40:02 +0200521 if (cp == nullptr) {
522 if (onFailure) {
Junxiao Shi4083c8d2014-10-12 16:43:16 -0700523 onFailure("scheme not supported");
524 }
525 return;
526 }
527
Davide Pesaventoaeeb3fc2016-08-14 03:40:02 +0200528 static CanonizeSuccessCallback successNop = bind([]{});
529 static CanonizeFailureCallback failureNop = bind([]{});
Junxiao Shi4083c8d2014-10-12 16:43:16 -0700530
531 cp->canonize(*this,
Davide Pesaventoaeeb3fc2016-08-14 03:40:02 +0200532 onSuccess ? onSuccess : successNop,
533 onFailure ? onFailure : failureNop,
Junxiao Shi4083c8d2014-10-12 16:43:16 -0700534 io, timeout);
535}
536
Junxiao Shi77dcadd2014-10-05 14:40:54 -0700537} // namespace util
538} // namespace ndn