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(PingStatus)> 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 PingDoneCallback( PingerStatus );
258 void TcpPinger::start_receive()
260 // Discard any data already in the buffer.
261 ReplyBuffer.consume( ReplyBuffer.size() );
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 )
270 void TcpPinger::handle_receive_tcp_segment( const size_t &bytes_transferred )
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 );
278 istream is( &ReplyBuffer );
281 GlobalLogger.error() << "Can't handle ReplyBuffer" << endl;
285 // Decode the reply segment.
286 TcpSegmentItem tcp_segment = TcpSegmentFactory::create_tcp_segment( Protocol, is );
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() ) )
293 ReceivedReply = true;
295 tcp_segment->print_rst_reply( TimeSent );
297 set_ping_status( PingStatus_SuccessReply );
299 TcpSegmentReceiveTimer.cancel(); //lint !e534
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() ) )
306 ReceivedReply = true;
308 tcp_segment->print_syn_ack_reply( TimeSent );
310 set_ping_status( PingStatus_SuccessReply );
312 TcpSegmentReceiveTimer.cancel(); //lint !e534
314 // Unknown TCP reply, start another receive till timeout
322 GlobalLogger.notice() << "Exception during ICMP parse. "
323 << "Starting another receive till timeout." << endl;
328 void TcpPinger::set_ping_status( PingStatus ping_status )
330 PingerStatus = ping_status;
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 )
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 );
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
348 // done, return shared ptr