moved time duration measurement of ping from scheduler to pingers
[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,long)> 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            (microsec_clock::universal_time() - TimeSent).total_microseconds() );
257 }
258
259 void 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
271 void TcpPinger::handle_receive_tcp_segment( const size_t &bytes_transferred )
272 {
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
277     try
278     {
279         istream is( &ReplyBuffer );
280         if ( !is )
281         {
282             GlobalLogger.error() << "Can't handle ReplyBuffer" << endl;
283             return;
284         }
285
286         // Decode the reply segment.
287         TcpSegmentItem tcp_segment = TcpSegmentFactory::create_tcp_segment( Protocol, is );
288
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.
292         if ( tcp_segment->match_rst_reply( DestinationEndpoint.address() ) )
293         {
294             ReceivedReply = true;
295
296             tcp_segment->print_rst_reply( TimeSent );
297
298             set_ping_status( PingStatus_SuccessReply );
299
300             TcpSegmentReceiveTimer.cancel();                                                                                                                //lint !e534
301         }
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         }
315         // Unknown TCP reply, start another receive till timeout
316         else
317         {
318             start_receive();
319         }
320     }
321     catch ( ... )
322     {
323         GlobalLogger.notice() << "Exception during ICMP parse. "
324             << "Starting another receive till timeout." << endl;
325         start_receive();
326     }
327 }
328
329 void TcpPinger::set_ping_status( PingStatus ping_status )
330 {
331     PingerStatus = ping_status;
332 }
333
334 PingerItem 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 }