VideoTools
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Pages
gsimpleclient.cpp
Go to the documentation of this file.
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 // gsimpleclient.cpp
19 //
20 
21 #include "gdef.h"
22 #include "gnet.h"
23 #include "gaddress.h"
24 #include "gsocket.h"
25 #include "gdatetime.h"
26 #include "gexception.h"
27 #include "gresolver.h"
28 #include "groot.h"
29 #include "gmonitor.h"
30 #include "gsimpleclient.h"
31 #include "gassert.h"
32 #include "gtest.h"
33 #include "gdebug.h"
34 #include "glog.h"
35 #include <cstdlib>
36 
37 namespace
38 {
39  const char * c_cannot_connect_to = "cannot connect to " ;
40 }
41 
42 // ==
43 
45  bool bind_local_address , const Address & local_address ,
46  bool sync_dns , unsigned int secure_connection_timeout ) :
47  m_resolver(*this) ,
48  m_remote_location(remote) ,
49  m_bind_local_address(bind_local_address) ,
50  m_local_address(local_address) ,
51  m_state(Idle) ,
52  m_sync_dns(sync_dns) ,
53  m_secure_connection_timeout(secure_connection_timeout) ,
54  m_on_connect_timer(*this,&GNet::SimpleClient::onConnectTimer,*static_cast<GNet::EventHandler*>(this))
55 {
56  G_DEBUG( "SimpleClient::ctor" ) ;
57  if( Monitor::instance() ) Monitor::instance()->addClient( *this ) ;
58 }
59 
61 {
63  close() ;
64 }
65 
66 std::string GNet::SimpleClient::logId() const
67 {
68  std::string s = m_remote_location.displayString() ;
69  if( m_socket.get() != nullptr )
70  s.append( std::string() + "@" + m_socket->asString() ) ; // cf. ServerPeer::logId()
71  return s ;
72 }
73 
75 {
76  return m_remote_location ;
77 }
78 
80 {
81  if( m_remote_location.host() == update.host() && m_remote_location.service() == update.service() && update.resolved() )
82  {
83  G_DEBUG( "GNet::SimpleClient::updateLocation: reusing dns lookup for " << update.displayString() ) ;
84  m_remote_location = update ;
85  }
86 }
87 
89 {
90  if( m_socket.get() == nullptr )
91  throw NotConnected() ;
92  return *m_socket.get() ;
93 }
94 
96 {
97  if( m_socket.get() == nullptr )
98  throw NotConnected() ;
99  return *m_socket.get() ;
100 }
101 
103 {
104  try
105  {
106  G_DEBUG( "GNet::SimpleClient::connect: [" << m_remote_location.displayString() << "]" ) ;
107  if( m_state != Idle )
108  throw ConnectError( "wrong state" ) ;
109 
110  m_remote_location.resolveTrivially() ; // if host:service is already address:port
111  if( m_remote_location.resolved() )
112  {
113  setState( Connecting ) ;
114  startConnecting() ;
115  }
116  else if( m_sync_dns || !Resolver::async() )
117  {
118  std::string error = Resolver::resolve( m_remote_location ) ;
119  if( !error.empty() )
120  throw DnsError( error ) ;
121 
122  setState( Connecting ) ;
123  startConnecting() ;
124  }
125  else
126  {
127  setState( Resolving ) ;
128  m_resolver.start( m_remote_location ) ;
129  }
130  }
131  catch( ... )
132  {
133  setState( Idle ) ;
134  throw ;
135  }
136 }
137 
138 void GNet::SimpleClient::onResolved( std::string error , Location location )
139 {
140  try
141  {
142  if( !error.empty() )
143  throw DnsError( error ) ;
144 
145  G_DEBUG( "GNet::SimpleClient::onResolved: " << location.displayString() ) ;
146  m_remote_location.update( location.address() , location.name() ) ;
147  setState( Connecting ) ;
148  startConnecting() ;
149  }
150  catch(...)
151  {
152  close() ;
153  setState( Idle ) ;
154  throw ;
155  }
156 }
157 
158 void GNet::SimpleClient::startConnecting()
159 {
160  try
161  {
162  G_DEBUG( "GNet::SimpleClient::startConnecting: local: " << m_local_address.displayString() ) ;
163  G_DEBUG( "GNet::SimpleClient::startConnecting: remote: " << m_remote_location.displayString() ) ;
164  if( G::Test::enabled("slow-client-connect") )
165  setState( Testing ) ;
166 
167  // create and open a socket
168  //
169  m_socket.reset( new StreamSocket(m_remote_location.address().domain()) ) ;
170  socket().addWriteHandler( *this ) ;
171 
172  // create a socket protocol object
173  //
174  m_sp.reset( new SocketProtocol(*this,*this,*m_socket.get(),m_secure_connection_timeout) ) ;
175 
176  // bind a local address to the socket (throws on failure)
177  //
178  if( m_bind_local_address )
179  bindLocalAddress( m_local_address ) ;
180 
181  // start connecting
182  //
183  bool immediate = false ;
184  if( !socket().connect( m_remote_location.address() , &immediate ) )
185  throw ConnectError( c_cannot_connect_to + m_remote_location.address().displayString() ) ;
186 
187  // deal with immediate connection (typically if connecting locally)
188  //
189  if( immediate )
190  {
191  socket().dropWriteHandler() ;
192  m_on_connect_timer.startTimer( 0U ) ; // -> onConnectTimer()
193  }
194  }
195  catch( ... )
196  {
197  close() ;
198  throw ;
199  }
200 }
201 
202 void GNet::SimpleClient::onConnectTimer()
203 {
204  G_DEBUG( "GNet::SimpleClient::onConnectTimer: immediate connection" ) ;
205  onWriteable() ;
206 }
207 
209 {
210  G_DEBUG( "GNet::SimpleClient::writeEvent" ) ;
211  onWriteable() ;
212 }
213 
214 void GNet::SimpleClient::onWriteable()
215 {
216  if( m_state == Connected )
217  {
218  if( m_sp->writeEvent() )
219  onSendComplete() ;
220  }
221  else if( m_state == Testing )
222  {
223  socket().dropWriteHandler() ;
224  setState( Connecting ) ;
225  m_on_connect_timer.startTimer( 2U , 100000U ) ; // -> onConnectTimer()
226  }
227  else if( m_state == Connecting && socket().hasPeer() )
228  {
229  socket().dropWriteHandler() ;
230  socket().addReadHandler( *this ) ;
231  socket().addExceptionHandler( *this ) ;
232 
233  if( m_remote_location.socks() )
234  {
235  setState( Socksing ) ;
236  sendSocksRequest() ;
237  }
238  else
239  {
240  setState( Connected ) ;
241  onConnectImp() ;
242  onConnect() ;
243  }
244  }
245  else if( m_state == Connecting )
246  {
247  socket().dropWriteHandler() ;
248  throw ConnectError( c_cannot_connect_to + m_remote_location.address().displayString() ) ;
249  }
250 }
251 
253 {
254  G_ASSERT( m_sp.get() != nullptr ) ;
255  if( m_state == Socksing )
256  {
257  bool complete = readSocksResponse() ;
258  if( complete )
259  {
260  setState( Connected ) ;
261  onConnectImp() ;
262  onConnect() ;
263  }
264  }
265  else
266  {
267  if( m_sp.get() != nullptr )
268  m_sp->readEvent() ;
269  }
270 }
271 
272 bool GNet::SimpleClient::connectError( const std::string & error )
273 {
274  return error.find( c_cannot_connect_to ) == 0U ;
275 }
276 
277 void GNet::SimpleClient::close()
278 {
279  m_sp.reset() ;
280  m_socket.reset() ;
281 }
282 
284 {
285  return m_state == Connected ;
286 }
287 
288 void GNet::SimpleClient::bindLocalAddress( const Address & local_address )
289 {
290  {
291  G::Root claim_root ;
292  socket().bind( local_address ) ;
293  }
294 
295  if( local_address.isLoopback() && !m_remote_location.address().isLoopback() )
296  G_WARNING_ONCE( "GNet::SimpleClient::bindLocalAddress: binding the loopback address for "
297  "outgoing connections may result in connection failures" ) ;
298 }
299 
300 void GNet::SimpleClient::setState( State new_state )
301 {
302  m_state = new_state ;
303 }
304 
305 std::pair<bool,GNet::Address> GNet::SimpleClient::localAddress() const
306 {
307  return
308  m_socket.get() != nullptr ?
309  socket().getLocalAddress() :
310  std::make_pair(false,GNet::Address::defaultAddress()) ;
311 }
312 
313 std::pair<bool,GNet::Address> GNet::SimpleClient::peerAddress() const
314 {
315  return
316  m_socket.get() != nullptr ?
317  socket().getPeerAddress() :
318  std::make_pair(false,GNet::Address::defaultAddress()) ;
319 }
320 
322 {
323  return m_sp->peerCertificate() ;
324 }
325 
327 {
328  if( m_sp.get() == nullptr )
329  throw NotConnected( "for ssl-connect" ) ;
330  m_sp->sslConnect() ;
331 }
332 
334 {
335 }
336 
337 bool GNet::SimpleClient::send( const std::string & data , std::string::size_type offset )
338 {
339  bool rc = m_sp->send( data , offset ) ;
340  onSendImp() ; // allow derived classes to implement a response timeout
341  return rc ;
342 }
343 
345 {
346 }
347 
348 void GNet::SimpleClient::sendSocksRequest()
349 {
350  unsigned int far_port = m_remote_location.socksFarPort() ;
351  if( !Address::validPort(far_port) ) throw SocksError("invalid port") ;
352  g_port_t far_port_n = htons( static_cast<g_port_t>(far_port) ) ;
353  g_port_t far_port_lo = far_port_n & 0xffU ;
354  g_port_t far_port_hi = (far_port_n>>8U) & g_port_t(0xffU) ;
355 
356  std::string userid ; // TODO - socks userid
357  std::string data ;
358  data.append( 1U , 4 ) ; // version 4
359  data.append( 1U , 1 ) ; // connect request
360  data.append( 1U , static_cast<char>(far_port_lo) ) ;
361  data.append( 1U , static_cast<char>(far_port_hi) ) ;
362  data.append( 1U , 0 ) ;
363  data.append( 1U , 0 ) ;
364  data.append( 1U , 0 ) ;
365  data.append( 1U , 1 ) ;
366  data.append( userid ) ;
367  data.append( 1U , 0 ) ; // NUL
368  data.append( m_remote_location.socksFarHost() ) ;
369  data.append( 1U , 0 ) ; // NUL
370  GNet::Socket::ssize_type n = socket().write( data.data() , data.size() ) ;
371  if( static_cast<std::string::size_type>(n) != data.size() ) // TODO - socks flow control
372  throw SocksError( "request not sent" ) ;
373 }
374 
375 bool GNet::SimpleClient::readSocksResponse()
376 {
377  char buffer[8] ;
378  GNet::Socket::ssize_type rc = socket().read( buffer , sizeof(buffer) ) ;
379  if( rc == 0 || ( rc == -1 && !socket().eWouldBlock() ) ) throw SocksError( "read error" ) ;
380  else if( rc == -1 ) return false ; // go again
381  if( rc != 8 ) throw SocksError( "incomplete response" ) ; // TODO - socks response reassembly
382  if( buffer[0] != 0 ) throw SocksError( "invalid response" ) ;
383  if( buffer[1] != 'Z' ) throw SocksError( "request rejected" ) ;
384  G_LOG( "GNet::SimpleClient::readSocksResponse: " << logId() << ": socks connection completed" ) ;
385  return true ;
386 }
387 
389 {
390  if( G::Test::enabled("dns-asynchronous") ) return false ;
391  if( G::Test::enabled("dns-synchronous") ) return true ;
392  return false ; // default to async, but G::thread might run synchronously as a build-time option
393 }
394 
395 /// \file gsimpleclient.cpp
virtual void writeEvent() override
Override from GNet::EventHandler.
virtual void readEvent() override
Override from GNet::EventHandler.
A class for making an outgoing connection to a remote server, with support for socket-level protocols...
Definition: gsimpleclient.h:60
bool send(const std::string &data, std::string::size_type offset=0)
Returns true if all sent, or false if flow control was asserted.
void sslConnect()
Starts TLS/SSL client-side negotiation.
The GNet::Address class encapsulates a TCP/UDP transport address.
Definition: gaddress.h:55
bool isLoopback() const
Returns true if this is a loopback address.
virtual std::string peerCertificate() const override
Returns the peer's TLS certificate.
A class which acquires the process's special privileges on construction and releases them on destruct...
Definition: groot.h:49
void addClient(const Connection &simple_client)
Adds a client connection.
Definition: gmonitor.cpp:124
bool connected() const
Returns true if connected to the peer.
virtual void onConnectImp()
An alternative to onConnect() for private implementation classes.
std::string displayString() const
Returns a string representation for logging and debug.
Definition: glocation.cpp:145
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
void connect()
Initates a connection to the remote server.
A derivation of GNet::Socket for a stream socket.
Definition: gsocket.h:245
static Address defaultAddress()
Returns a default address, being the IPv4 wildcard address with a zero port number.
std::string name() const
Returns the remote canonical name.
Definition: glocation.cpp:140
static bool enabled()
Returns true if test features are enabled.
Definition: gtest.cpp:49
static Monitor * instance()
Returns the singleton pointer. Returns null if none.
Definition: gmonitor.cpp:114
Location remoteLocation() const
Returns a Location structure containing the result of host() and service() name lookup if available...
virtual std::pair< bool, Address > localAddress() const override
Override from Connection.
Address address() const
Returns the remote address.
Definition: glocation.cpp:124
SimpleClient(const Location &remote_info, bool bind_local_address=false, const Address &local_address=Address::defaultAddress(), bool sync_dns=synchronousDnsDefault(), unsigned int secure_connection_timeout=0U)
Constructor.
bool resolved() const
Returns true after update() has been called.
Definition: glocation.cpp:119
A base class for classes that handle asynchronous events from the event loop.
Definition: geventhandler.h:78
static bool validPort(unsigned int n)
Returns true if the port number is within the valid range.
std::string logId() const
Returns a identification string for logging purposes.
std::string service() const
Returns the remote service name, as passed in to the constructor.
Definition: glocation.cpp:97
void removeClient(const Connection &simple_client)
Removes a client connection.
Definition: gmonitor.cpp:130
static bool synchronousDnsDefault()
Returns true if DNS queries should normally be synchronous on this platform.
virtual void onSendImp()
Called from within send().
static bool async()
Returns true if the resolver supports asynchronous operation.
Definition: gresolver.cpp:441
virtual ~SimpleClient()
Destructor.
virtual std::pair< bool, Address > peerAddress() const override
Override from Connection.
static bool connectError(const std::string &reason)
Returns true if the reason string implies the SimpleClient::connect() failed.
static std::string resolve(Location &)
Does syncronous name resolution.
Definition: gresolver.cpp:385
void updateLocation(const Location &)
Updates the constructor's Location object with the given one as long as both objects have the same ho...
StreamSocket & socket()
Returns a reference to the socket. Throws if not connected.
std::string host() const
Returns the remote host name, as passed in to the constructor.
Definition: glocation.cpp:92