moved time duration measurement of ping from scheduler to pingers
[pingcheck] / src / tcp / tcppinger.cpp
CommitLineData
cd395966
GMF
1/*
2The software in this package is distributed under the GNU General
3Public License version 2 (with a special exception described below).
4
5A copy of GNU General Public License (GPL) is included in this distribution,
6in the file COPYING.GPL.
7
8As a special exception, if other files instantiate templates or use macros
9or inline functions from this file, or you compile this file and link it
10with other works to produce a work based on this file, this file
11does not by itself cause the resulting work to be covered
12by the GNU General Public License.
13
14However the source code for this file must still be made available
15in accordance with section (3) of the GNU General Public License.
16
17This exception does not invalidate any other reasons why a work based
18on this file might be covered by the GNU General Public License.
19*/
20#include "tcp/tcppinger.h"
21
60d13f4f
GMF
22#include <errno.h>
23#include <net/if.h>
24#include <sys/ioctl.h>
25#include <sys/socket.h>
26
f8a8d9dc
GMF
27#include <istream>
28#include <ostream>
1309d0e4 29#include <limits>
f8a8d9dc 30
f8a8d9dc
GMF
31#include <boost/bind.hpp>
32#include <boost/date_time/posix_time/posix_time.hpp>
33#include <boost/date_time/posix_time/posix_time_types.hpp>
34#include <boost/uuid/uuid.hpp>
35#include <boost/uuid/uuid_generators.hpp>
36
37#include <logfunc.hpp>
cd395966 38
780b0bca 39#include "boost_assert_handler.h"
3596e4f4
GMF
40#include "tcp/tcpsegmentfactory.h"
41
cd395966 42using namespace std;
f8a8d9dc 43using boost::asio::const_buffers_1;
84525f34 44using boost::asio::io_service;
f8a8d9dc
GMF
45using boost::asio::ip::address;
46using boost::asio::ip::tcp_raw_protocol;
47using boost::date_time::time_resolution_traits_adapted64_impl;
cd395966 48using boost::function;
f8a8d9dc
GMF
49using boost::posix_time::microsec_clock;
50using boost::posix_time::ptime;
51using boost::posix_time::seconds;
e58d7507 52using boost::shared_ptr;
f8a8d9dc 53using I2n::Logger::GlobalLogger;
cd395966
GMF
54
55//-----------------------------------------------------------------------------
56// TcpPinger
57//-----------------------------------------------------------------------------
58
a4c872dd
GMF
59/**
60 * @brief Parameterized constructor.
61 *
83ba151a 62 * @param io_serv The one @c io_service object that controls async processing
a4c872dd
GMF
63 * @param protocol The network layer protocol to use.
64 * @param source_network_interface The network interface name from where to
65 * send the segments.
66 * @param echo_reply_timeout_in_sec The amount of time to wait for a reply.
67 */
84525f34 68TcpPinger::TcpPinger(
365036be 69 const IoServiceItem io_serv,
a4c872dd 70 const tcp_raw_protocol::socket::protocol_type &protocol,
60d13f4f 71 const string &source_network_interface_name,
c2a393ee 72 const int rst_reply_timeout_in_sec
f8a8d9dc 73) :
f8a8d9dc 74 DestinationEndpoint(),
3b6a0314 75 Protocol( protocol ),
365036be 76 Socket( *io_serv, Protocol ),
3b668576 77 NetInterface( source_network_interface_name, Socket ),
365036be 78 TcpSegmentReceiveTimer( *io_serv ),
f8a8d9dc
GMF
79 Identifier( 0 ),
80 SequenceNumber( 0 ),
81 TimeSent( microsec_clock::universal_time() ),
82 ReplyBuffer(),
83 ReceivedReply( false ),
c2a393ee 84 RstReplyTimeoutInSec( rst_reply_timeout_in_sec ),
f8a8d9dc
GMF
85 PingerStatus( PingStatus_NotSent ),
86 PingDoneCallback()
cd395966 87{
ac507127 88 if ( !NetInterface.bind() )
f8a8d9dc 89 {
0fd358ca 90 GlobalLogger.error() << "Could not bind the socket with the local interface. "
c754f48e 91 << ::strerror( errno ) << endl;
f8a8d9dc
GMF
92 }
93
94 // Create "unique" identifier
95 boost::uuids::random_generator random_gen;
96 boost::uuids::uuid random_tag = random_gen();
97
98 BOOST_ASSERT( sizeof(Identifier) <= random_tag.size() );
99 memcpy( &Identifier, random_tag.data, sizeof(Identifier) );
cd395966
GMF
100}
101
102TcpPinger::~TcpPinger()
103{
104}
105
f8a8d9dc
GMF
106/**
107 * @brief Ping a destination address from an available local source.
108 *
109 * @param destination_ip The address of the host to ping.
1309d0e4
GMF
110 * @param destination_port The port at the destination host to ping.
111 * @param done_handler Done handler will be called on successful ping or timeout.
112 *
113 * @return void.
f8a8d9dc 114 */
cd395966 115void TcpPinger::ping(
23f51766 116 const address &destination_ip,
080ca508 117 const uint16_t destination_port,
9c0dcf33 118 function<void(PingStatus,long)> ping_done_callback
cd395966
GMF
119)
120{
080ca508 121 BOOST_ASSERT( ( 0 < destination_port ) && ( destination_port < numeric_limits<uint16_t>::max() ) );
f8a8d9dc
GMF
122
123 PingDoneCallback = ping_done_callback;
124
125 // Prepare ping
126 set_ping_status( PingStatus_NotSent );
127
1309d0e4 128 set_destination_endpoint( destination_ip, destination_port );
f8a8d9dc
GMF
129
130 start_send();
131 start_receive();
132}
133
5a9bc2d1
CH
134void TcpPinger::stop_pinging()
135{
136}
137
6fd0993e 138address TcpPinger::get_source_address() const
f8a8d9dc 139{
3b6a0314 140 return NetInterface.get_address( Protocol );
f8a8d9dc
GMF
141}
142
865e078a 143address TcpPinger::get_destination_address() const
f8a8d9dc 144{
865e078a 145 return DestinationEndpoint.address();
f8a8d9dc
GMF
146}
147
6f6db388
GMF
148uint16_t TcpPinger::get_source_port() const
149{
619a2293 150 uint16_t source_port = static_cast<uint16_t>( ( random() % 16383u ) + 49152u ); // same as random() % 65536;
6f6db388
GMF
151
152 return source_port;
153}
154
1309d0e4
GMF
155uint16_t TcpPinger::get_destination_port() const
156{
157 return DestinationEndpoint.port();
158}
159
160void TcpPinger::set_destination_endpoint(
23f51766 161 const address &destination_ip,
080ca508 162 const uint16_t destination_port
1309d0e4 163)
f8a8d9dc 164{
080ca508 165 BOOST_ASSERT( ( 0 < destination_port ) && ( destination_port < numeric_limits<uint16_t>::max() ) );
619a2293 166 BOOST_ASSERT( sizeof(uint16_t) <= sizeof(destination_port) );
f8a8d9dc 167
1309d0e4 168 uint16_t port = static_cast<uint16_t>( destination_port );
23f51766 169 DestinationEndpoint = tcp_raw_protocol::endpoint( destination_ip, port );
f8a8d9dc
GMF
170}
171
172void TcpPinger::start_send()
173{
174 ++SequenceNumber;
175
a7d7778b 176 // Create a TCP header for a SYN request.
865e078a
GMF
177 address source_address = get_source_address();
178 address destination_address = get_destination_address();
3596e4f4
GMF
179 uint16_t source_port = get_source_port();
180 uint16_t destination_port = get_destination_port();
a7d7778b 181 TcpSegmentItem tcp_segment = TcpSegmentFactory::create_tcp_segment_syn_request(
3b6a0314 182 Protocol,
865e078a 183 source_address, destination_address,
3596e4f4 184 source_port, destination_port, SequenceNumber );
f8a8d9dc 185
3596e4f4 186 send_ack_request( tcp_segment );
4efd7f33
GMF
187}
188
3596e4f4 189void TcpPinger::send_ack_request( const TcpSegmentItem tcp_segment )
b594b7d9 190{
f8a8d9dc
GMF
191 // Encode the request packet.
192 boost::asio::streambuf request_buffer;
193 ostream os( &request_buffer );
080ca508
GMF
194 if ( !tcp_segment->write( os ) )
195 {
0fd358ca 196 GlobalLogger.error() << "Fail writing ping data." << endl;
080ca508 197 }
f8a8d9dc 198
f8a8d9dc
GMF
199 TimeSent = microsec_clock::universal_time();
200
ebafed7c
GMF
201 address dest_address = DestinationEndpoint.address();
202 string dest_address_string = dest_address.to_string();
f8a8d9dc
GMF
203 BOOST_ASSERT( !dest_address_string.empty() );
204
205 // Send the request
206 try
207 {
208 const_buffers_1 data = request_buffer.data();
209 // Block until send the data
210 size_t bytes_sent = Socket.send_to( data, DestinationEndpoint );
211 if ( bytes_sent != buffer_size( data ) )
212 {
0fd358ca 213 GlobalLogger.error() << "Fail sending ping data."
3b6a0314 214 << "Amount of bytes sent differs from the buffer." << endl;
f8a8d9dc
GMF
215 }
216 }
217 catch ( const exception &ex )
218 {
0fd358ca 219 GlobalLogger.error() << "Fail sending ping data. " << ex.what() << endl;
f8a8d9dc
GMF
220 }
221
b594b7d9 222 // Tell how long to wait for the reply
81adfb7b 223 schedule_timeout_rst_reply();
f8a8d9dc
GMF
224}
225
81adfb7b
GMF
226void TcpPinger::schedule_timeout_rst_reply()
227{
228 // Wait up to N seconds for a reply.
229 ReceivedReply = false;
230 (void) TcpSegmentReceiveTimer.expires_at(
231 TimeSent + seconds( RstReplyTimeoutInSec )
232 );
233 TcpSegmentReceiveTimer.async_wait(
bd66be8f 234 boost::bind( &TcpPinger::handle_ping_done, this )
81adfb7b
GMF
235 );
236}
237
bd66be8f
GMF
238/**
239 * @brief Gets called when the ping is finished: Either on timeout or on ping reply
240 *
241 * @return void
242 **/
243void TcpPinger::handle_ping_done()
f8a8d9dc 244{
bd66be8f
GMF
245 // Check ReceivedReply as the timer handler
246 // is also called by Timer.cancel();
080ca508 247 if ( !ReceivedReply )
bd66be8f
GMF
248 {
249 GlobalLogger.info() << "Request timed out" << endl;
250
251 set_ping_status( PingStatus_FailureTimeout );
252 }
253
254 // Call ping-done handler
9c0dcf33
CH
255 PingDoneCallback( PingerStatus,
256 (microsec_clock::universal_time() - TimeSent).total_microseconds() );
f8a8d9dc
GMF
257}
258
259void TcpPinger::start_receive()
260{
261 // Discard any data already in the buffer.
262 ReplyBuffer.consume( ReplyBuffer.size() );
263
264 // Waiting for a reply. We prepare the buffer to receive up to 64KB.
265 Socket.async_receive(
266 ReplyBuffer.prepare( 65536 ),
267 boost::bind( &TcpPinger::handle_receive_tcp_segment, this, _2 )
268 );
269}
270
271void TcpPinger::handle_receive_tcp_segment( const size_t &bytes_transferred )
272{
f8a8d9dc
GMF
273 // The actual number of bytes received is committed to the buffer so that we
274 // can extract it using a std::istream object.
275 ReplyBuffer.commit( bytes_transferred );
276
b23911dd 277 try
f8a8d9dc 278 {
b23911dd
GMF
279 istream is( &ReplyBuffer );
280 if ( !is )
281 {
0fd358ca 282 GlobalLogger.error() << "Can't handle ReplyBuffer" << endl;
b23911dd
GMF
283 return;
284 }
f8a8d9dc 285
3596e4f4 286 // Decode the reply segment.
3b6a0314 287 TcpSegmentItem tcp_segment = TcpSegmentFactory::create_tcp_segment( Protocol, is );
f8a8d9dc 288
a7d7778b
GMF
289 // The TCP SYN ping will receive a RST if the port is closed.
290 // Filter out the TCP reset (RST) replies. Note that the sequence
291 // number from RST does not match the sent SYN's sequence number.
3596e4f4 292 if ( tcp_segment->match_rst_reply( DestinationEndpoint.address() ) )
b23911dd
GMF
293 {
294 ReceivedReply = true;
f8a8d9dc 295
3596e4f4 296 tcp_segment->print_rst_reply( TimeSent );
f8a8d9dc 297
b23911dd 298 set_ping_status( PingStatus_SuccessReply );
e0059ebe 299
529e5587 300 TcpSegmentReceiveTimer.cancel(); //lint !e534
b23911dd 301 }
a7d7778b
GMF
302 // The TCP SYN ping will receive a SYN/ACK if the port is open.
303 // Filter out the TCP synchronize (SYN/ACK) replies. Note that the sequence
304 // number from SYN/ACK does not match the sent SYN's sequence number.
305 else if ( tcp_segment->match_syn_ack_reply( DestinationEndpoint.address() ) )
306 {
307 ReceivedReply = true;
308
309 tcp_segment->print_syn_ack_reply( TimeSent );
310
311 set_ping_status( PingStatus_SuccessReply );
312
313 TcpSegmentReceiveTimer.cancel(); //lint !e534
314 }
b23911dd
GMF
315 // Unknown TCP reply, start another receive till timeout
316 else
317 {
318 start_receive();
319 }
320 }
321 catch ( ... )
322 {
0fd358ca 323 GlobalLogger.notice() << "Exception during ICMP parse. "
b23911dd
GMF
324 << "Starting another receive till timeout." << endl;
325 start_receive();
f8a8d9dc 326 }
f8a8d9dc
GMF
327}
328
f5c0f0d0 329void TcpPinger::set_ping_status( PingStatus ping_status )
f8a8d9dc
GMF
330{
331 PingerStatus = ping_status;
332}
4606d2b1
CH
333
334PingerItem TcpPinger::create(
335 const IoServiceItem io_serv,
336 const tcp_raw_protocol::socket::protocol_type &protocol,
337 const string &source_network_interface_name,
338 const int rst_reply_timeout_in_sec )
339{
340 TcpPinger *ptr = new TcpPinger(io_serv, protocol, source_network_interface_name,
341 rst_reply_timeout_in_sec);
342 PingerItem shared_ptr(ptr);
343 Pinger::WeakPtr weak_ptr( shared_ptr );
344
345 // keep weak pointer to self
346 ptr->set_myself( weak_ptr );
347 // shared_ptr->set_myself( weak_ptr ); Error: Pinger::set_myself is protected
348
349 // done, return shared ptr
350 return shared_ptr;
351}