VideoTools
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Pages
gresolver.cpp
1 //
2 // Copyright (C) 2017 Graeme Walker
3 //
4 // This program is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
8 //
9 // This program is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 // GNU General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with this program. If not, see <http://www.gnu.org/licenses/>.
16 // ===
17 //
18 // gresolver.cpp
19 //
20 
21 #include "gdef.h"
22 #include "gresolver.h"
23 #include "geventloop.h"
24 #include "gfutureevent.h"
25 #include "gtest.h"
26 #include "gstr.h"
27 #include "gsleep.h"
28 #include "gdebug.h"
29 #include "gassert.h"
30 #include "glog.h"
31 #include <utility>
32 #include <vector>
33 #include <cstring>
34 #include <cstdio>
35 
36 #ifndef AI_ADDRCONFIG
37 #define AI_ADDRCONFIG 0
38 #endif
39 
40 // ==
41 
42 namespace GNet
43 {
44  class ResolverFuture ;
45 }
46 
47 namespace
48 {
49  const char * ipvx( int family )
50  {
51  return family == AF_UNSPEC ? "ip" : ( family == AF_INET ? "ipv4" : "ipv6" ) ;
52  }
53 }
54 
55 /// \class GNet::ResolverFuture
56 /// A 'future' object for asynchronous name resolution, in practice holding
57 /// just enough state for a single call to getaddrinfo().
58 ///
59 /// The run() method can be called from a worker thread and the results
60 /// collected by the main thread with get() once the run() has finished.
61 ///
62 /// Signalling completion from worker thread to main thread is out of scope
63 /// for this class; see GNet::FutureEvent.
64 ///
65 /// Eg:
66 ////
67 //// ResolverFuture f( "example.com" , "smtp" , AF_INET , false ) ;
68 //// std::thread t( &ResolverFuture::run , f ) ;
69 //// ...
70 //// t.join() ;
71 //// Address a = f.get().first ;
72 ///
74 {
75 public:
76  typedef std::pair<Address,std::string> Pair ;
77  typedef std::vector<Address> List ;
78 
79  ResolverFuture( const std::string & host , const std::string & service ,
80  int family , bool dgram , bool for_async_hint = false ) ;
81  // Constructor for resolving the given host and service names.
82 
83  ~ResolverFuture() ;
84  // Destructor.
85 
86  void run() ;
87  // Does the name resolution.
88 
89  Pair get() ;
90  // Returns the resolved address/name pair.
91 
92  void get( List & ) ;
93  // Returns the list of resolved addresses.
94 
95  bool error() const ;
96  // Returns true if name resolution failed or no suitable
97  // address was returned. Use after get().
98 
99  std::string reason() const ;
100  // Returns the reason for the error().
101  // Precondition: error()
102 
103 private:
104  ResolverFuture( const ResolverFuture & ) ;
105  void operator=( const ResolverFuture & ) ;
106  std::string failure() const ;
107  bool fetch( List & ) const ;
108  bool fetch( Pair & ) const ;
109  bool failed() const ;
110  std::string none() const ;
111  std::string ipvx_() const ;
112 
113 private:
114  bool m_numeric_service ;
115  int m_socktype ;
116  std::string m_host ;
117  const char * m_host_p ;
118  std::string m_service ;
119  const char * m_service_p ;
120  int m_family ;
121  struct addrinfo m_ai_hint ;
122  bool m_test_mode ;
123  int m_rc ;
124  struct addrinfo * m_ai ;
125  std::string m_reason ;
126 } ;
127 
128 GNet::ResolverFuture::ResolverFuture( const std::string & host , const std::string & service , int family ,
129  bool dgram , bool for_async_hint ) :
130  m_numeric_service(false) ,
131  m_socktype(dgram?SOCK_DGRAM:SOCK_STREAM) ,
132  m_host(host) ,
133  m_host_p(m_host.c_str()) ,
134  m_service(service) ,
135  m_service_p(m_service.c_str()) ,
136  m_family(family) ,
137  m_test_mode(for_async_hint&&G::Test::enabled("getaddrinfo-slow")) ,
138  m_rc(0) ,
139  m_ai(nullptr)
140 {
141  m_numeric_service = !service.empty() && G::Str::isNumeric(service) ;
142  std::memset( &m_ai_hint , 0 , sizeof(m_ai_hint) ) ;
143  m_ai_hint.ai_flags = AI_CANONNAME |
144  ( family == AF_UNSPEC ? AI_ADDRCONFIG : 0 ) |
145  ( m_numeric_service ? AI_NUMERICSERV : 0 ) ;
146  m_ai_hint.ai_family = family ;
147  m_ai_hint.ai_socktype = m_socktype ;
148 }
149 
150 GNet::ResolverFuture::~ResolverFuture()
151 {
152  if( m_ai )
153  ::freeaddrinfo( m_ai ) ; // documented as "thread-safe"
154 }
155 
156 void GNet::ResolverFuture::run()
157 {
158  // worker thread - as simple as possible
159  if( m_test_mode ) sleep( 10 ) ;
160  m_rc = ::getaddrinfo( m_host_p , m_service_p , &m_ai_hint , &m_ai ) ;
161 }
162 
163 std::string GNet::ResolverFuture::failure() const
164 {
165  std::stringstream ss ;
166  if( m_numeric_service )
167  ss << "no such " << ipvx_() << "host: \"" << m_host << "\"" ;
168  else
169  ss << "no such " << ipvx_() << "host or service: \"" << m_host << ":" << m_service << "\"" ;
170  //ss << " (" << G::Str::lower(gai_strerror(m_rc)) << ")" ; // not portable
171  return ss.str() ;
172 }
173 
174 std::string GNet::ResolverFuture::ipvx_() const
175 {
176  return m_family == AF_UNSPEC ? std::string() : (ipvx(m_family)+std::string(1U,' ')) ;
177 }
178 
179 bool GNet::ResolverFuture::failed() const
180 {
181  return m_rc != 0 || m_ai == nullptr || m_ai->ai_addr == nullptr || m_ai->ai_addrlen == 0 ;
182 }
183 
184 std::string GNet::ResolverFuture::none() const
185 {
186  return std::string("no usable addresses returned for \"") + m_host + "\"" ;
187 }
188 
189 bool GNet::ResolverFuture::fetch( Pair & pair ) const
190 {
191  // fetch the first valid address/name pair
192  for( const struct addrinfo * p = m_ai ; p ; p = p->ai_next )
193  {
194  if( Address::validData( p->ai_addr , p->ai_addrlen ) )
195  {
196  Address address( p->ai_addr , p->ai_addrlen ) ;
197  std::string name( p->ai_canonname ? p->ai_canonname : "" ) ;
198  pair = std::make_pair( address , name ) ;
199  return true ;
200  }
201  }
202  return false ;
203 }
204 
205 bool GNet::ResolverFuture::fetch( List & list ) const
206 {
207  // fetch all valid addresses
208  for( const struct addrinfo * p = m_ai ; p ; p = p->ai_next )
209  {
210  if( Address::validData( p->ai_addr , p->ai_addrlen ) )
211  list.push_back( Address( p->ai_addr , p->ai_addrlen ) ) ;
212  }
213  return !list.empty() ;
214 }
215 
216 void GNet::ResolverFuture::get( List & list )
217 {
218  if( failed() )
219  m_reason = failure() ;
220  else if( !fetch(list) )
221  m_reason = none() ;
222 }
223 
224 GNet::ResolverFuture::Pair GNet::ResolverFuture::get()
225 {
226  Pair result( Address::defaultAddress() , std::string() ) ;
227  if( failed() )
228  m_reason = failure() ;
229  else if( !fetch(result) )
230  m_reason = none() ;
231  return result ;
232 }
233 
234 bool GNet::ResolverFuture::error() const
235 {
236  return !m_reason.empty() ;
237 }
238 
239 std::string GNet::ResolverFuture::reason() const
240 {
241  return m_reason ;
242 }
243 
244 // ==
245 
246 /// \class GNet::ResolverImp
247 /// A private "pimple" implementation class used by GNet::Resolver to do
248 /// asynchronous name resolution. The implementation contains a worker thread using
249 /// the future/promise pattern.
250 ///
252 {
253 public:
255  // Constructor.
256 
257  virtual ~ResolverImp() ;
258  // Destructor. The destructor will block if the worker thread is still busy.
259 
260  void disarm( bool do_delete_this ) ;
261  // Disables the Resolver::done() callback and optionally enables
262  // 'delete this' instead.
263 
264  static void start( ResolverImp * , FutureEvent::handle_type ) ;
265  // Static worker-thread function to do name resolution. Calls
266  // ResolverFuture::run() to do the work and then FutureEvent::send()
267  // to signal the main thread. The event plumbing then results in a call
268  // to Resolver::done() on the main thread.
269 
270  static size_t count() ;
271  // Returns the number of objects.
272 
273 private:
274  virtual void onFutureEvent( unsigned int ) override ; // FutureEventHandler
275  virtual void onException( std::exception & ) override ; // EventExceptionHandler
276 
277 private:
278  typedef ResolverFuture::Pair Pair ;
279  Resolver * m_resolver ;
280  bool m_do_delete_this ;
281  EventExceptionHandler & m_event_exception_handler ;
282  unique_ptr<FutureEvent> m_future_event ;
283  Location m_location ;
284  ResolverFuture m_future ;
285  G::threading::thread_type m_thread ;
286  static size_t m_instance_count ;
287 } ;
288 
289 size_t GNet::ResolverImp::m_instance_count = 0U ;
290 
291 GNet::ResolverImp::ResolverImp( Resolver & resolver , EventExceptionHandler & event_exception_handler , const Location & location ) :
292  m_resolver(&resolver) ,
293  m_do_delete_this(false) ,
294  m_event_exception_handler(event_exception_handler) ,
295  m_future_event(new FutureEvent(*this)) ,
296  m_location(location) ,
297  m_future(location.host(),location.service(),location.family(),location.dgram(),true) ,
298  m_thread(ResolverImp::start,this,m_future_event->handle())
299 {
300  m_instance_count++ ;
301 }
302 
303 GNet::ResolverImp::~ResolverImp()
304 {
305  if( m_thread.joinable() )
306  {
307  G_WARNING( "ResolverImp::dtor: waiting for getaddrinfo thread to complete" ) ;
308  m_thread.join() ; // blocks if getaddrinfo() still running
309  }
310  m_instance_count-- ;
311 }
312 
313 size_t GNet::ResolverImp::count()
314 {
315  return m_instance_count ;
316 }
317 
318 void GNet::ResolverImp::start( ResolverImp * This , FutureEvent::handle_type handle )
319 {
320  // thread function, spawned from ctor and join()ed from dtor
321  try
322  {
323  This->m_future.run() ;
324  FutureEvent::send( handle , 0/*payload*/ ) ;
325  }
326  catch(...) // worker thread outer function
327  {
328  FutureEvent::send( handle , 1 ) ; // nothrow
329  }
330 }
331 
332 void GNet::ResolverImp::onFutureEvent( unsigned int e )
333 {
334  G_DEBUG( "GNet::ResolverImp::onFutureEvent: future event: e=" << e << " ptr=" << m_resolver << " dt=" << m_do_delete_this ) ;
335 
336  m_thread.join() ; // worker thread is finishing, so no delay here
337  if( e != 0 )
338  throw Resolver::Error( "exception in worker thread" ) ; // should never happen
339 
340  Pair result = m_future.get() ;
341  if( !m_future.error() )
342  m_location.update( result.first , result.second ) ;
343  G_DEBUG( "GNet::ResolverImp::onFutureEvent: [" << m_future.reason() << "][" << m_location.displayString() << "]" ) ;
344 
345  if( m_resolver != nullptr )
346  m_resolver->done( m_future.reason() , m_location ) ;
347 
348  if( m_do_delete_this )
349  delete this ;
350 }
351 
352 void GNet::ResolverImp::disarm( bool do_delete_this )
353 {
354  m_resolver = nullptr ;
355  m_do_delete_this = do_delete_this ;
356 }
357 
358 void GNet::ResolverImp::onException( std::exception & e )
359 {
360  m_event_exception_handler.onException( e ) ;
361 }
362 
363 // ==
364 
366  m_callback(callback) ,
367  m_busy(false)
368 {
369  // lazy imp construction
370 }
371 
373 {
374  const size_t sanity_limit = 50U ;
375  if( m_busy && ResolverImp::count() < sanity_limit )
376  {
377  // release the imp to an independent lifetime until its getaddrinfo() completes
378  G_ASSERT( m_imp.get() != nullptr ) ;
379  G_DEBUG( "GNet::Resolver::dtor: releasing still-busy thread: " << ResolverImp::count() ) ;
380  m_imp->disarm( true ) ;
381  m_imp.release() ;
382  }
383 }
384 
385 std::string GNet::Resolver::resolve( Location & location )
386 {
387  // synchronous resolve
388  typedef ResolverFuture::Pair Pair ;
389  G_DEBUG( "GNet::Resolver::resolve: resolve request [" << location.displayString() << "] (" << location.family() << ")" ) ;
390  ResolverFuture future( location.host() , location.service() , location.family() , location.dgram() ) ;
391  future.run() ; // blocks until complete
392  Pair result = future.get() ;
393  if( future.error() )
394  {
395  G_DEBUG( "GNet::Resolver::resolve: resolve error [" << future.reason() << "]" ) ;
396  return future.reason() ;
397  }
398  else
399  {
400  G_DEBUG( "GNet::Resolver::resolve: resolve result [" << result.first.displayString() << "][" << result.second << "]" ) ;
401  location.update( result.first , result.second ) ;
402  return std::string() ;
403  }
404 }
405 
406 GNet::Resolver::AddressList GNet::Resolver::resolve( const std::string & host , const std::string & service , int family , bool dgram )
407 {
408  // synchronous resolve
409  G_DEBUG( "GNet::Resolver::resolve: resolve-request [" << host << "/" << service << "/" << ipvx(family) << "]" ) ;
410  ResolverFuture future( host , service , family , dgram ) ;
411  future.run() ;
412  AddressList list ;
413  future.get( list ) ;
414  G_DEBUG( "GNet::Resolver::resolve: resolve result: list of " << list.size() ) ;
415  return list ;
416 }
417 
418 void GNet::Resolver::start( const Location & location )
419 {
420  // asynchronous resolve
421  if( !EventLoop::instance().running() ) throw Error("no event loop") ;
422  if( busy() ) throw BusyError() ;
423  G_DEBUG( "GNet::Resolver::start: resolve start [" << location.displayString() << "]" ) ;
424  m_imp.reset( new ResolverImp(*this,m_callback,location) ) ;
425  m_busy = true ;
426 }
427 
428 void GNet::Resolver::done( const std::string & error , const Location & location )
429 {
430  // callback from the event loop after worker thread is done
431  G_DEBUG( "GNet::Resolver::done: resolve done [" << error << "] [" << location.displayString() << "]" ) ;
432  m_busy = false ;
433  m_callback.onResolved( error , location ) ;
434 }
435 
437 {
438  return m_busy ;
439 }
440 
442 {
443  static bool threading_works = G::threading::works() ;
444  if( threading_works )
445  {
446  return EventLoop::instance().running() ;
447  }
448  else
449  {
450  //G_WARNING_ONCE( "GNet::Resolver::async: multi-threading not built-in: using synchronous domain name lookup");
451  G_DEBUG( "GNet::Resolver::async: multi-threading not built-in: using synchronous domain name lookup");
452  return false ;
453  }
454 }
455 
456 // ==
457 
458 GNet::Resolver::Callback::~Callback()
459 {
460 }
461 
~Resolver()
Destructor.
Definition: gresolver.cpp:372
void start(const Location &)
Starts asynchronous name-to-address resolution.
Definition: gresolver.cpp:418
A callback interface for GNet::FutureEvent.
Definition: gfutureevent.h:95
An abstract interface for handling exceptions thrown out of event-loop callbacks (socket events and t...
Definition: geventhandler.h:44
static bool validData(const sockaddr *, socklen_t len)
Returns true if the sockaddr data is valid.
An object that hooks into the event loop and calls back to the client code with a small unsigned inte...
Definition: gfutureevent.h:62
A class for synchronous or asynchronous network name to address resolution.
Definition: gresolver.h:43
static bool isNumeric(const std::string &s, bool allow_minus_sign=false)
Returns true if every character is a decimal digit.
Definition: gstr.cpp:228
std::string displayString() const
Returns a string representation for logging and debug.
Definition: glocation.cpp:145
An interface used for GNet::Resolver callbacks.
Definition: gresolver.h:49
A class that holds a host/service name pair and the preferred address family (if any), and also the results of a name-to-address lookup, ie.
Definition: glocation.h:51
bool dgram() const
Returns true if the name resolution should be specifically for datagram sockets.
Definition: glocation.cpp:107
static Address defaultAddress()
Returns a default address, being the IPv4 wildcard address with a zero port number.
virtual bool running() const =0
Returns true if called from within run().
Resolver(Callback &)
Constructor taking a callback interface reference.
Definition: gresolver.cpp:365
void update(const Address &address, const std::string &canonical_name)
Updates the address and canonical name, typically after doing a name lookup on host() and service()...
Definition: glocation.cpp:129
std::string service() const
Returns the remote service name, as passed in to the constructor.
Definition: glocation.cpp:97
static bool async()
Returns true if the resolver supports asynchronous operation.
Definition: gresolver.cpp:441
bool busy() const
Returns true if there is a pending resolve request.
Definition: gresolver.cpp:436
static std::string resolve(Location &)
Does syncronous name resolution.
Definition: gresolver.cpp:385
int family() const
Returns the preferred name resolution address family, or AF_UNSPEC.
Definition: glocation.cpp:102
Address a = f.get().first ;.
Definition: gresolver.cpp:73
A private "pimple" implementation class used by GNet::Resolver to do asynchronous name resolution...
Definition: gresolver.cpp:251
std::string host() const
Returns the remote host name, as passed in to the constructor.
Definition: glocation.cpp:92
static bool send(handle_type handle, unsigned int payload) g__noexcept
Pokes the event payload into the main event loop so that the callback is called once the stack is unw...
static EventLoop & instance()
Returns a reference to an instance of the class, if any.
Definition: geventloop.cpp:43