E-MailRelay

- Main Page
- Namespaces
- Classes
- Files

- File List
- File Members

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