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 address &destination_ip,
117 const uint16_t destination_port,
118 function<void(bool)> ping_done_callback
121 BOOST_ASSERT( ( 0 < destination_port ) && ( destination_port < numeric_limits<uint16_t>::max() ) );
123 PingDoneCallback = ping_done_callback;
126 set_ping_status( PingStatus_NotSent );
128 set_destination_endpoint( destination_ip, destination_port );
134 void TcpPinger::stop_pinging()
138 address TcpPinger::get_source_address() const
140 return NetInterface.get_address( Protocol );
143 address TcpPinger::get_destination_address() const
145 return DestinationEndpoint.address();
148 uint16_t TcpPinger::get_source_port() const
150 uint16_t source_port = static_cast<uint16_t>( ( random() % 16383u ) + 49152u ); // same as random() % 65536;
155 uint16_t TcpPinger::get_destination_port() const
157 return DestinationEndpoint.port();
160 void TcpPinger::set_destination_endpoint(
161 const address &destination_ip,
162 const uint16_t destination_port
165 BOOST_ASSERT( ( 0 < destination_port ) && ( destination_port < numeric_limits<uint16_t>::max() ) );
166 BOOST_ASSERT( sizeof(uint16_t) <= sizeof(destination_port) );
168 uint16_t port = static_cast<uint16_t>( destination_port );
169 DestinationEndpoint = tcp_raw_protocol::endpoint( destination_ip, port );
172 void TcpPinger::start_send()
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(
183 source_address, destination_address,
184 source_port, destination_port, SequenceNumber );
186 send_ack_request( tcp_segment );
189 void TcpPinger::send_ack_request( const TcpSegmentItem tcp_segment )
191 // Encode the request packet.
192 boost::asio::streambuf request_buffer;
193 ostream os( &request_buffer );
194 if ( !tcp_segment->write( os ) )
196 GlobalLogger.error() << "Fail writing ping data." << endl;
199 TimeSent = microsec_clock::universal_time();
201 address dest_address = DestinationEndpoint.address();
202 string dest_address_string = dest_address.to_string();
203 BOOST_ASSERT( !dest_address_string.empty() );
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 ) )
213 GlobalLogger.error() << "Fail sending ping data."
214 << "Amount of bytes sent differs from the buffer." << endl;
217 catch ( const exception &ex )
219 GlobalLogger.error() << "Fail sending ping data. " << ex.what() << endl;
222 // Tell how long to wait for the reply
223 schedule_timeout_rst_reply();
226 void TcpPinger::schedule_timeout_rst_reply()
228 // Wait up to N seconds for a reply.
229 ReceivedReply = false;
230 (void) TcpSegmentReceiveTimer.expires_at(
231 TimeSent + seconds( RstReplyTimeoutInSec )
233 TcpSegmentReceiveTimer.async_wait(
234 boost::bind( &TcpPinger::handle_ping_done, this )
239 * @brief Gets called when the ping is finished: Either on timeout or on ping reply
243 void TcpPinger::handle_ping_done()
245 // Check ReceivedReply as the timer handler
246 // is also called by Timer.cancel();
247 if ( !ReceivedReply )
249 GlobalLogger.info() << "Request timed out" << endl;
251 set_ping_status( PingStatus_FailureTimeout );
254 // Call ping-done handler
255 bool ping_success = (PingerStatus == PingStatus_SuccessReply);
256 PingDoneCallback( ping_success );
259 void TcpPinger::start_receive()
261 // Discard any data already in the buffer.
262 ReplyBuffer.consume( ReplyBuffer.size() );
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 )
271 void TcpPinger::handle_receive_tcp_segment( const size_t &bytes_transferred )
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 );
279 istream is( &ReplyBuffer );
282 GlobalLogger.error() << "Can't handle ReplyBuffer" << endl;
286 // Decode the reply segment.
287 TcpSegmentItem tcp_segment = TcpSegmentFactory::create_tcp_segment( Protocol, is );
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() ) )
294 ReceivedReply = true;
296 tcp_segment->print_rst_reply( TimeSent );
298 set_ping_status( PingStatus_SuccessReply );
300 TcpSegmentReceiveTimer.cancel(); //lint !e534
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() ) )
307 ReceivedReply = true;
309 tcp_segment->print_syn_ack_reply( TimeSent );
311 set_ping_status( PingStatus_SuccessReply );
313 TcpSegmentReceiveTimer.cancel(); //lint !e534
315 // Unknown TCP reply, start another receive till timeout
323 GlobalLogger.notice() << "Exception during ICMP parse. "
324 << "Starting another receive till timeout." << endl;
329 void TcpPinger::set_ping_status( PingStatus ping_status )
331 PingerStatus = ping_status;
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 )
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 );
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
349 // done, return shared ptr