2 The software in this package is distributed under the GNU General
3 Public License version 2 (with a special exception described below).
5 A copy of GNU General Public License (GPL) is included in this distribution,
6 in the file COPYING.GPL.
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.
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.
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.
20 #include "tcp/tcppinger.h"
24 #include <sys/ioctl.h>
25 #include <sys/socket.h>
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>
37 #include <logfunc.hpp>
39 #include "boost_assert_handler.h"
40 #include "tcp/tcpsegmentfactory.h"
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;
55 //-----------------------------------------------------------------------------
57 //-----------------------------------------------------------------------------
60 * @brief Parameterized constructor.
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
66 * @param echo_reply_timeout_in_sec The amount of time to wait for a reply.
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
74 DestinationEndpoint(),
76 Socket( *io_serv, Protocol ),
77 NetInterface( source_network_interface_name, Socket ),
78 TcpSegmentReceiveTimer( *io_serv ),
81 TimeSent( microsec_clock::universal_time() ),
83 ReceivedReply( false ),
84 RstReplyTimeoutInSec( rst_reply_timeout_in_sec ),
85 PingerStatus( PingStatus_NotSent ),
88 if ( !NetInterface.bind() )
90 GlobalLogger.error() << "Could not bind the socket with the local interface. "
91 << ::strerror( errno ) << endl;
94 // Create "unique" identifier
95 boost::uuids::random_generator random_gen;
96 boost::uuids::uuid random_tag = random_gen();
98 BOOST_ASSERT( sizeof(Identifier) <= random_tag.size() );
99 memcpy( &Identifier, random_tag.data, sizeof(Identifier) );
102 TcpPinger::~TcpPinger()
107 * @brief Ping a destination address from an available local source.
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.
115 void TcpPinger::ping(
116 const string &destination_ip,
117 const uint16_t destination_port,
118 function<void(bool)> ping_done_callback
121 BOOST_ASSERT( !destination_ip.empty() );
122 BOOST_ASSERT( ( 0 < destination_port ) && ( destination_port < numeric_limits<uint16_t>::max() ) );
124 PingDoneCallback = ping_done_callback;
127 set_ping_status( PingStatus_NotSent );
129 set_destination_endpoint( destination_ip, destination_port );
135 void TcpPinger::stop_pinging()
139 address TcpPinger::get_source_address() const
141 return NetInterface.get_address( Protocol );
144 address TcpPinger::get_destination_address() const
146 return DestinationEndpoint.address();
149 uint16_t TcpPinger::get_source_port() const
151 uint16_t source_port = static_cast<uint16_t>( ( random() % 16383u ) + 49152u ); // same as random() % 65536;
156 uint16_t TcpPinger::get_destination_port() const
158 return DestinationEndpoint.port();
161 void TcpPinger::set_destination_endpoint(
162 const string &destination_ip,
163 const uint16_t destination_port
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) );
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 );
175 void TcpPinger::start_send()
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(
186 source_address, destination_address,
187 source_port, destination_port, SequenceNumber );
189 send_ack_request( tcp_segment );
192 void TcpPinger::send_ack_request( const TcpSegmentItem tcp_segment )
194 // Encode the request packet.
195 boost::asio::streambuf request_buffer;
196 ostream os( &request_buffer );
197 if ( !tcp_segment->write( os ) )
199 GlobalLogger.error() << "Fail writing ping data." << endl;
202 TimeSent = microsec_clock::universal_time();
204 address dest_address = DestinationEndpoint.address();
205 string dest_address_string = dest_address.to_string();
206 BOOST_ASSERT( !dest_address_string.empty() );
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 ) )
216 GlobalLogger.error() << "Fail sending ping data."
217 << "Amount of bytes sent differs from the buffer." << endl;
220 catch ( const exception &ex )
222 GlobalLogger.error() << "Fail sending ping data. " << ex.what() << endl;
225 // Tell how long to wait for the reply
226 schedule_timeout_rst_reply();
229 void TcpPinger::schedule_timeout_rst_reply()
231 // Wait up to N seconds for a reply.
232 ReceivedReply = false;
233 (void) TcpSegmentReceiveTimer.expires_at(
234 TimeSent + seconds( RstReplyTimeoutInSec )
236 TcpSegmentReceiveTimer.async_wait(
237 boost::bind( &TcpPinger::handle_ping_done, this )
242 * @brief Gets called when the ping is finished: Either on timeout or on ping reply
246 void TcpPinger::handle_ping_done()
248 // Check ReceivedReply as the timer handler
249 // is also called by Timer.cancel();
250 if ( !ReceivedReply )
252 GlobalLogger.info() << "Request timed out" << endl;
254 set_ping_status( PingStatus_FailureTimeout );
257 // Call ping-done handler
258 bool ping_success = (PingerStatus == PingStatus_SuccessReply);
259 PingDoneCallback( ping_success );
262 void TcpPinger::start_receive()
264 // Discard any data already in the buffer.
265 ReplyBuffer.consume( ReplyBuffer.size() );
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 )
274 void TcpPinger::handle_receive_tcp_segment( const size_t &bytes_transferred )
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 );
282 istream is( &ReplyBuffer );
285 GlobalLogger.error() << "Can't handle ReplyBuffer" << endl;
289 // Decode the reply segment.
290 TcpSegmentItem tcp_segment = TcpSegmentFactory::create_tcp_segment( Protocol, is );
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() ) )
297 ReceivedReply = true;
299 tcp_segment->print_rst_reply( TimeSent );
301 set_ping_status( PingStatus_SuccessReply );
303 TcpSegmentReceiveTimer.cancel(); //lint !e534
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() ) )
310 ReceivedReply = true;
312 tcp_segment->print_syn_ack_reply( TimeSent );
314 set_ping_status( PingStatus_SuccessReply );
316 TcpSegmentReceiveTimer.cancel(); //lint !e534
318 // Unknown TCP reply, start another receive till timeout
326 GlobalLogger.notice() << "Exception during ICMP parse. "
327 << "Starting another receive till timeout." << endl;
332 void TcpPinger::set_ping_status( PingStatus ping_status )
334 PingerStatus = ping_status;
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 )
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 );
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
352 // done, return shared ptr