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