79347f6d6b3b6e09daf15d7efc84a797a67323c2
[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 string &destination_ip,
117         const uint16_t destination_port,
118         function<void(bool)> ping_done_callback
119 )
120 {
121     BOOST_ASSERT( !destination_ip.empty() );
122     BOOST_ASSERT( ( 0 < destination_port ) && ( destination_port < numeric_limits<uint16_t>::max() ) );
123
124     PingDoneCallback = ping_done_callback;
125
126     // Prepare ping
127     set_ping_status( PingStatus_NotSent );
128
129     set_destination_endpoint( destination_ip, destination_port );
130
131     start_send();
132     start_receive();
133 }
134
135 void TcpPinger::stop_pinging()
136 {
137 }
138
139 address TcpPinger::get_source_address() const
140 {
141     return NetInterface.get_address( Protocol );
142 }
143
144 address TcpPinger::get_destination_address() const
145 {
146     return DestinationEndpoint.address();
147 }
148
149 uint16_t TcpPinger::get_source_port() const
150 {
151     uint16_t source_port = static_cast<uint16_t>( ( random() % 16383u ) + 49152u ); // same as random() % 65536;
152
153     return source_port;
154 }
155
156 uint16_t TcpPinger::get_destination_port() const
157 {
158     return DestinationEndpoint.port();
159 }
160
161 void TcpPinger::set_destination_endpoint(
162         const string &destination_ip,
163         const uint16_t destination_port
164 )
165 {
166     BOOST_ASSERT( !destination_ip.empty() );
167     BOOST_ASSERT( ( 0 < destination_port ) && ( destination_port < numeric_limits<uint16_t>::max() ) );
168     BOOST_ASSERT( sizeof(uint16_t) <= sizeof(destination_port) );
169
170     address destination_address = address::from_string( destination_ip );
171     uint16_t port = static_cast<uint16_t>( destination_port );
172     DestinationEndpoint = tcp_raw_protocol::endpoint( destination_address, port );
173 }
174
175 void TcpPinger::start_send()
176 {
177     ++SequenceNumber;
178
179     // Create a TCP header for a SYN request.
180     address source_address = get_source_address();
181     address destination_address = get_destination_address();
182     uint16_t source_port = get_source_port();
183     uint16_t destination_port = get_destination_port();
184     TcpSegmentItem tcp_segment = TcpSegmentFactory::create_tcp_segment_syn_request(
185             Protocol,
186             source_address, destination_address,
187             source_port, destination_port, SequenceNumber );
188
189     send_ack_request( tcp_segment );
190 }
191
192 void TcpPinger::send_ack_request( const TcpSegmentItem tcp_segment )
193 {
194     // Encode the request packet.
195     boost::asio::streambuf request_buffer;
196     ostream os( &request_buffer );
197     if ( !tcp_segment->write( os ) )
198     {
199         GlobalLogger.error() << "Fail writing ping data." << endl;
200     }
201
202     TimeSent = microsec_clock::universal_time();
203
204     address dest_address = DestinationEndpoint.address();
205     string dest_address_string = dest_address.to_string();
206     BOOST_ASSERT( !dest_address_string.empty() );
207
208     // Send the request
209     try
210     {
211         const_buffers_1 data = request_buffer.data();
212         // Block until send the data
213         size_t bytes_sent = Socket.send_to( data, DestinationEndpoint );
214         if ( bytes_sent != buffer_size( data ) )
215         {
216             GlobalLogger.error() << "Fail sending ping data."
217                     << "Amount of bytes sent differs from the buffer." << endl;
218         }
219     }
220     catch ( const exception &ex )
221     {
222         GlobalLogger.error() << "Fail sending ping data. " << ex.what() << endl;
223     }
224
225     // Tell how long to wait for the reply
226     schedule_timeout_rst_reply();
227 }
228
229 void TcpPinger::schedule_timeout_rst_reply()
230 {
231     // Wait up to N seconds for a reply.
232     ReceivedReply = false;
233     (void) TcpSegmentReceiveTimer.expires_at(
234             TimeSent + seconds( RstReplyTimeoutInSec )
235     );
236     TcpSegmentReceiveTimer.async_wait(
237             boost::bind( &TcpPinger::handle_ping_done, this )
238     );
239 }
240
241 /**
242  * @brief Gets called when the ping is finished: Either on timeout or on ping reply
243  *
244  * @return void
245  **/
246 void TcpPinger::handle_ping_done()
247 {
248     // Check ReceivedReply as the timer handler
249     // is also called by Timer.cancel();
250     if ( !ReceivedReply )
251     {
252         GlobalLogger.info() << "Request timed out" << endl;
253
254         set_ping_status( PingStatus_FailureTimeout );
255     }
256
257     // Call ping-done handler
258     bool ping_success = (PingerStatus == PingStatus_SuccessReply);
259     PingDoneCallback( ping_success );
260 }
261
262 void TcpPinger::start_receive()
263 {
264     // Discard any data already in the buffer.
265     ReplyBuffer.consume( ReplyBuffer.size() );
266
267     // Waiting for a reply. We prepare the buffer to receive up to 64KB.
268     Socket.async_receive(
269             ReplyBuffer.prepare( 65536 ),
270             boost::bind( &TcpPinger::handle_receive_tcp_segment, this, _2 )
271     );
272 }
273
274 void TcpPinger::handle_receive_tcp_segment( const size_t &bytes_transferred )
275 {
276     // The actual number of bytes received is committed to the buffer so that we
277     // can extract it using a std::istream object.
278     ReplyBuffer.commit( bytes_transferred );
279
280     try
281     {
282         istream is( &ReplyBuffer );
283         if ( !is )
284         {
285             GlobalLogger.error() << "Can't handle ReplyBuffer" << endl;
286             return;
287         }
288
289         // Decode the reply segment.
290         TcpSegmentItem tcp_segment = TcpSegmentFactory::create_tcp_segment( Protocol, is );
291
292         // The TCP SYN ping will receive a RST if the port is closed.
293         // Filter out the TCP reset (RST) replies. Note that the sequence
294         // number from RST does not match the sent SYN's sequence number.
295         if ( tcp_segment->match_rst_reply( DestinationEndpoint.address() ) )
296         {
297             ReceivedReply = true;
298
299             tcp_segment->print_rst_reply( TimeSent );
300
301             set_ping_status( PingStatus_SuccessReply );
302
303             TcpSegmentReceiveTimer.cancel();                                                                                                                //lint !e534
304         }
305         // The TCP SYN ping will receive a SYN/ACK if the port is open.
306         // Filter out the TCP synchronize (SYN/ACK) replies. Note that the sequence
307         // number from SYN/ACK does not match the sent SYN's sequence number.
308         else if ( tcp_segment->match_syn_ack_reply( DestinationEndpoint.address() ) )
309         {
310             ReceivedReply = true;
311
312             tcp_segment->print_syn_ack_reply( TimeSent );
313
314             set_ping_status( PingStatus_SuccessReply );
315
316             TcpSegmentReceiveTimer.cancel();                                                                                                                //lint !e534
317         }
318         // Unknown TCP reply, start another receive till timeout
319         else
320         {
321             start_receive();
322         }
323     }
324     catch ( ... )
325     {
326         GlobalLogger.notice() << "Exception during ICMP parse. "
327             << "Starting another receive till timeout." << endl;
328         start_receive();
329     }
330 }
331
332 void TcpPinger::set_ping_status( PingStatus ping_status )
333 {
334     PingerStatus = ping_status;
335 }
336
337 PingerItem TcpPinger::create(
338         const IoServiceItem io_serv,
339         const tcp_raw_protocol::socket::protocol_type &protocol,
340         const string &source_network_interface_name,
341         const int rst_reply_timeout_in_sec )
342 {
343     TcpPinger *ptr = new TcpPinger(io_serv, protocol, source_network_interface_name,
344                                            rst_reply_timeout_in_sec);
345     PingerItem shared_ptr(ptr);
346     Pinger::WeakPtr weak_ptr( shared_ptr );
347
348     // keep weak pointer to self
349     ptr->set_myself( weak_ptr );
350     // shared_ptr->set_myself( weak_ptr ); Error: Pinger::set_myself is protected
351
352     // done, return shared ptr
353     return shared_ptr;
354 }