blob: f23ae8c1c82a737c56871f0e0dcaa86d255ee810 [file] [log] [blame]
Junxiao Shi77dcadd2014-10-05 14:40:54 -07001/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
2/**
Junxiao Shie3ef6ee2014-10-05 14:40:54 -07003 * Copyright (c) 2014, 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
215 ~CanonizeProvider()
216 {
217 }
218
219 virtual std::set<std::string>
220 getSchemes() const = 0;
221
222 virtual bool
223 isCanonical(const FaceUri& faceUri) const = 0;
224
225 virtual void
226 canonize(const FaceUri& faceUri,
227 const FaceUri::CanonizeSuccessCallback& onSuccess,
228 const FaceUri::CanonizeFailureCallback& onFailure,
229 boost::asio::io_service& io, const time::nanoseconds& timeout) const = 0;
230};
231
232template<typename Protocol>
233class IpHostCanonizeProvider : public CanonizeProvider
234{
235public:
236 virtual std::set<std::string>
237 getSchemes() const
238 {
239 std::set<std::string> schemes;
240 schemes.insert(m_baseScheme);
241 schemes.insert(m_v4Scheme);
242 schemes.insert(m_v6Scheme);
243 return schemes;
244 }
245
246 virtual bool
247 isCanonical(const FaceUri& faceUri) const
248 {
249 if (faceUri.getPort().empty()) {
250 return false;
251 }
252 if (!faceUri.getPath().empty()) {
253 return false;
254 }
255
256 boost::system::error_code ec;
257 boost::asio::ip::address addr;
258 if (faceUri.getScheme() == m_v4Scheme) {
259 addr = boost::asio::ip::address_v4::from_string(faceUri.getHost(), ec);
260 }
261 else if (faceUri.getScheme() == m_v6Scheme) {
262 addr = boost::asio::ip::address_v6::from_string(faceUri.getHost(), ec);
263 }
264 else {
265 return false;
266 }
267 return !static_cast<bool>(ec) && addr.to_string() == faceUri.getHost() &&
268 this->checkAddress(addr).first;
269 }
270
271 virtual void
272 canonize(const FaceUri& faceUri,
273 const FaceUri::CanonizeSuccessCallback& onSuccess,
274 const FaceUri::CanonizeFailureCallback& onFailure,
275 boost::asio::io_service& io, const time::nanoseconds& timeout) const
276 {
277 if (this->isCanonical(faceUri)) {
278 onSuccess(faceUri);
279 return;
280 }
281
282 dns::AddressSelector addressSelector;
283 if (faceUri.getScheme() == m_v4Scheme) {
284 addressSelector = dns::Ipv4Only();
285 }
286 else if (faceUri.getScheme() == m_v6Scheme) {
287 addressSelector = dns::Ipv6Only();
288 }
289 else {
290 BOOST_ASSERT(faceUri.getScheme() == m_baseScheme);
291 addressSelector = dns::AnyAddress();
292 }
293
294 // make a copy because caller may modify faceUri
295 shared_ptr<FaceUri> uri = make_shared<FaceUri>(faceUri);
296 dns::asyncResolve(faceUri.getHost(),
297 bind(&IpHostCanonizeProvider<Protocol>::onDnsSuccess, this, uri, onSuccess, onFailure, _1),
298 bind(&IpHostCanonizeProvider<Protocol>::onDnsFailure, this, uri, onFailure, _1),
299 io, addressSelector, timeout);
300 }
301
302protected:
303 IpHostCanonizeProvider(const std::string& baseScheme,
304 uint32_t defaultUnicastPort = 6363,
305 uint32_t defaultMulticastPort = 56363)
306 : m_baseScheme(baseScheme)
307 , m_v4Scheme(baseScheme + "4")
308 , m_v6Scheme(baseScheme + "6")
309 , m_defaultUnicastPort(defaultUnicastPort)
310 , m_defaultMulticastPort(defaultMulticastPort)
311 {
312 }
313
314private:
315 // faceUri is a shared_ptr passed by value because this function can take ownership
316 void
317 onDnsSuccess(shared_ptr<FaceUri> faceUri,
318 const FaceUri::CanonizeSuccessCallback& onSuccess,
319 const FaceUri::CanonizeFailureCallback& onFailure,
320 const dns::IpAddress& ipAddress) const
321 {
322 std::pair<bool, std::string> checkAddressRes = this->checkAddress(ipAddress);
323 if (!checkAddressRes.first) {
324 onFailure(checkAddressRes.second);
325 return;
326 }
327
328 uint32_t port = 0;
329 if (faceUri->getPort().empty()) {
330 port = ipAddress.is_multicast() ? m_defaultMulticastPort : m_defaultUnicastPort;
331 }
332 else {
333 try {
334 port = boost::lexical_cast<uint32_t>(faceUri->getPort());
335 }
336 catch (boost::bad_lexical_cast&) {
337 onFailure("invalid port number");
338 return;
339 }
340 }
341
342 FaceUri canonicalUri(typename Protocol::endpoint(ipAddress, port));
343 BOOST_ASSERT(canonicalUri.isCanonical());
344 onSuccess(canonicalUri);
345 }
346
347 // faceUri is a shared_ptr passed by value because this function can take ownership
348 void
349 onDnsFailure(shared_ptr<FaceUri> faceUri, const FaceUri::CanonizeFailureCallback& onFailure,
350 const std::string& reason) const
351 {
352 onFailure(reason);
353 }
354
355 /** \brief when overriden in a subclass, check the IP address is allowable
356 * \return (true,ignored) if the address is allowable;
357 * (false,reason) if the address is not allowable.
358 */
359 virtual std::pair<bool, std::string>
360 checkAddress(const dns::IpAddress& ipAddress) const
361 {
362 return std::make_pair(true, "");
363 }
364
365private:
366 std::string m_baseScheme;
367 std::string m_v4Scheme;
368 std::string m_v6Scheme;
369 uint32_t m_defaultUnicastPort;
370 uint32_t m_defaultMulticastPort;
371};
372
373class UdpCanonizeProvider : public IpHostCanonizeProvider<boost::asio::ip::udp>
374{
375public:
376 UdpCanonizeProvider()
377 : IpHostCanonizeProvider("udp")
378 {
379 }
380
381protected:
382 // checkAddress is not overriden:
383 // Although NFD doesn't support IPv6 multicast, it's an implementation limitation.
384 // FaceMgmt protocol allows IPv6 multicast address in UDP.
385};
386
387class TcpCanonizeProvider : public IpHostCanonizeProvider<boost::asio::ip::tcp>
388{
389public:
390public:
391 TcpCanonizeProvider()
392 : IpHostCanonizeProvider("tcp")
393 {
394 }
395
396protected:
397 virtual std::pair<bool, std::string>
398 checkAddress(const dns::IpAddress& ipAddress) const
399 {
400 if (ipAddress.is_multicast()) {
401 return std::make_pair(false, "cannot use multicast address");
402 }
403 return std::make_pair(true, "");
404 }
405};
406
407class EtherCanonizeProvider : public CanonizeProvider
408{
409public:
410 virtual std::set<std::string>
411 getSchemes() const
412 {
413 std::set<std::string> schemes;
414 schemes.insert("ether");
415 return schemes;
416 }
417
418 virtual bool
419 isCanonical(const FaceUri& faceUri) const
420 {
421 if (!faceUri.getPort().empty()) {
422 return false;
423 }
424 if (!faceUri.getPath().empty()) {
425 return false;
426 }
427
428 ethernet::Address addr = ethernet::Address::fromString(faceUri.getHost());
429 return addr.toString() == faceUri.getHost();
430 }
431
432 virtual void
433 canonize(const FaceUri& faceUri,
434 const FaceUri::CanonizeSuccessCallback& onSuccess,
435 const FaceUri::CanonizeFailureCallback& onFailure,
436 boost::asio::io_service& io, const time::nanoseconds& timeout) const
437 {
438 ethernet::Address addr = ethernet::Address::fromString(faceUri.getHost());
439 if (addr.isNull()) {
440 onFailure("cannot parse address");
441 return;
442 }
443
444 FaceUri canonicalUri(addr);
445 BOOST_ASSERT(canonicalUri.isCanonical());
446 onSuccess(canonicalUri);
447 }
448};
449
450typedef boost::mpl::vector<
451 UdpCanonizeProvider*,
452 TcpCanonizeProvider*,
453 EtherCanonizeProvider*
454 > CanonizeProviders;
455typedef std::map<std::string, shared_ptr<CanonizeProvider> > CanonizeProviderTable;
456
457class CanonizeProviderTableInitializer
458{
459public:
460 explicit
461 CanonizeProviderTableInitializer(CanonizeProviderTable& providerTable)
462 : m_providerTable(providerTable)
463 {
464 }
465
466 template<typename CP> void
467 operator()(CP*)
468 {
469 shared_ptr<CanonizeProvider> cp = make_shared<CP>();
470
471 std::set<std::string> schemes = cp->getSchemes();
472 BOOST_ASSERT(!schemes.empty());
473 for (std::set<std::string>::iterator it = schemes.begin();
474 it != schemes.end(); ++it) {
475 BOOST_ASSERT(m_providerTable.count(*it) == 0);
476 m_providerTable[*it] = cp;
477 }
478 }
479
480private:
481 CanonizeProviderTable& m_providerTable;
482};
483
484static const CanonizeProvider*
485getCanonizeProvider(const std::string& scheme)
486{
487 static CanonizeProviderTable providerTable;
488 if (providerTable.empty()) {
489 boost::mpl::for_each<CanonizeProviders>(CanonizeProviderTableInitializer(providerTable));
490 BOOST_ASSERT(!providerTable.empty());
491 }
492
493 CanonizeProviderTable::const_iterator it = providerTable.find(scheme);
494 if (it == providerTable.end()) {
495 return 0;
496 }
497 return it->second.get();
498}
499
500bool
501FaceUri::canCanonize(const std::string& scheme)
502{
503 return getCanonizeProvider(scheme) != 0;
504}
505
506bool
507FaceUri::isCanonical() const
508{
509 const CanonizeProvider* cp = getCanonizeProvider(this->getScheme());
510 if (cp == 0) {
511 return false;
512 }
513
514 return cp->isCanonical(*this);
515}
516
517static inline void
518nop()
519{
520}
521
522void
523FaceUri::canonize(const CanonizeSuccessCallback& onSuccess,
524 const CanonizeFailureCallback& onFailure,
525 boost::asio::io_service& io, const time::nanoseconds& timeout) const
526{
527 const CanonizeProvider* cp = getCanonizeProvider(this->getScheme());
528 if (cp == 0) {
529 if (static_cast<bool>(onFailure)) {
530 onFailure("scheme not supported");
531 }
532 return;
533 }
534
535 static CanonizeSuccessCallback successNop = bind(&nop);
536 static CanonizeFailureCallback failureNop = bind(&nop);
537
538 cp->canonize(*this,
539 static_cast<bool>(onSuccess) ? onSuccess : successNop,
540 static_cast<bool>(onFailure) ? onFailure : failureNop,
541 io, timeout);
542}
543
Junxiao Shi77dcadd2014-10-05 14:40:54 -0700544} // namespace util
545} // namespace ndn