1 // Boost pinger (c) 2011 by Guilherme Maciel Ferreira / Intra2net AG
2 // Based upon work copyright (c) 2003-2010 Christopher M. Kohlhoff (ping.cpp)
4 // Distributed under the Boost Software License, Version 1.0.
5 // (See accompanying file LICENSE_1_0.txt or copy at
6 // http://www.boost.org/LICENSE_1_0.txt)
7 #include "icmp/icmppinger.h"
13 #include <boost/bind.hpp>
14 #include <boost/date_time/posix_time/posix_time.hpp>
15 #include <boost/date_time/posix_time/posix_time_types.hpp>
16 #include <boost/uuid/uuid.hpp>
17 #include <boost/uuid/uuid_generators.hpp>
18 #include <boost/foreach.hpp>
20 #include <logfunc.hpp>
22 #include "boost_assert_handler.h"
23 #include "icmp/icmppacketfactory.h"
24 #include "host/networkinterface.hpp"
27 using boost::asio::const_buffers_1;
28 using boost::asio::io_service;
29 using boost::asio::ip::address;
30 using boost::asio::ip::icmp;
31 using boost::function;
32 using boost::posix_time::microsec_clock;
33 using boost::posix_time::seconds;
34 using boost::shared_ptr;
35 using I2n::Logger::GlobalLogger;
37 using boost::asio::ip::icmp;
39 //-----------------------------------------------------------------------------
41 //-----------------------------------------------------------------------------
44 * @brief factory function for IcmpPingers, ensures that set_myself is set
46 * @returns a shared pointer to a Pinger
48 PingerItem IcmpPinger::create(
49 const IoServiceItem io_serv,
50 const icmp::socket::protocol_type &protocol,
51 const string &source_network_interface,
52 const int echo_reply_timeout_in_sec )
55 IcmpPacketDistributorItem distributor = IcmpPacketDistributor::get_distributor(
56 icmp::v4(), source_network_interface, io_serv);
59 IcmpPinger *ptr = new IcmpPinger(io_serv, protocol, echo_reply_timeout_in_sec, distributor);
60 IcmpPingerItem shared_ptr_(ptr);
62 // keep weak pointer to self
63 //shared_ptr_->set_myself( weak_ptr ); //Error: Pinger::set_myself is protected
64 ptr->set_myself( shared_ptr_ );
66 // register in distributor
67 distributor->register_pinger(shared_ptr_);
69 // done, return shared ptr
74 * @brief Parameterized constructor.
76 * @param io_serv The one @c io_service object that controls async processing
77 * @param protocol The network layer protocol to use.
78 * @param source_network_interface The network interface name from where to
80 * @param echo_reply_timeout_in_sec The amount of time to wait for a reply.
82 IcmpPinger::IcmpPinger(
83 const IoServiceItem io_serv,
84 const icmp::socket::protocol_type &protocol,
85 const int echo_reply_timeout_in_sec,
86 const IcmpPacketDistributorItem distributor
88 PacketDistributor( distributor ),
89 DestinationEndpoint(),
91 IcmpPacketReceiveTimer( *io_serv ),
94 TimeSent( microsec_clock::universal_time() ),
95 ReplyReceived( false ),
96 EchoReplyTimeoutInSec( echo_reply_timeout_in_sec ),
97 PingerStatus( PingStatus_NotSent ),
100 // Create "unique" identifier
101 boost::uuids::random_generator random_gen;
102 boost::uuids::uuid random_tag = random_gen();
104 BOOST_ASSERT( sizeof(Identifier) <= random_tag.size() );
105 memcpy( &Identifier, random_tag.data, sizeof(Identifier) );
111 IcmpPinger::~IcmpPinger()
116 * @brief Ping a destination address from an available local source.
118 * @param destination_ip The address of the host to ping.
119 * @param destination_port The port at the destination host to ping.
120 * @param done_handler Done handler will be called on successful ping or timeout.
124 void IcmpPinger::ping(
125 const address &destination_ip,
126 const uint16_t /*destination_port*/, // the ICMP protocol does not use ports
127 function<void(bool)> ping_done_callback
130 PingDoneCallback = ping_done_callback;
133 set_ping_status( PingStatus_NotSent );
135 set_destination_endpoint( destination_ip );
140 void IcmpPinger::stop_pinging()
143 << DestinationEndpoint.address().to_string()
144 << ": stop_pinging" << endl;
147 << DestinationEndpoint.address().to_string()
148 << ": cancel timer" << endl;
149 IcmpPacketReceiveTimer.cancel();
152 << DestinationEndpoint.address().to_string()
153 << ": unregister" << endl;
155 IcmpPingerItem icmp_item = boost::static_pointer_cast<IcmpPinger>( get_myself().lock() );
158 PacketDistributor->unregister_pinger( icmp_item );
161 GlobalLogger.warning()
162 << DestinationEndpoint.address().to_string()
163 << ": weak pointer to pinger broken is empty. Huh?" << endl;
168 void IcmpPinger::set_destination_endpoint( const address &destination_ip )
171 DestinationEndpoint = icmp::endpoint( destination_ip, port );
174 bool IcmpPinger::start_send()
178 IcmpPacketItem icmp_packet_echo_request = IcmpPacketFactory::create_icmp_packet_echo_request(
179 Protocol, Identifier, SequenceNumber );
181 BOOST_ASSERT( PingerStatus == PingStatus_NotSent );
182 return send_echo_request( icmp_packet_echo_request );
185 bool IcmpPinger::send_echo_request( const IcmpPacketItem icmp_packet )
187 boost::asio::streambuf request_buffer;
188 ostream os( &request_buffer );
189 if ( !icmp_packet->write( os ) )
192 << DestinationEndpoint.address().to_string()
193 << ": fail writing ping data." << endl;
196 TimeSent = microsec_clock::universal_time();
198 string dest_address_string = DestinationEndpoint.address().to_string();
199 BOOST_ASSERT( !dest_address_string.empty() );
202 size_t bytes_sent = 0;
206 << DestinationEndpoint.address().to_string()
207 << ": sending ping" << endl;
208 const_buffers_1 data = request_buffer.data();
210 // Block until send the data
211 bytes_sent = PacketDistributor->get_socket()->send_to( data, DestinationEndpoint );
212 if ( bytes_sent != buffer_size( data ) )
215 << DestinationEndpoint.address().to_string()
216 << ": fail sending ping data." << endl;
219 catch ( const exception &ex )
222 << DestinationEndpoint.address().to_string()
223 << ": fail sending ping data. " << ex.what() << endl;
226 ReplyReceived = false;
227 schedule_timeout_echo_reply();
229 return (bytes_sent > 0);
232 void IcmpPinger::schedule_timeout_echo_reply()
234 // Wait up to N seconds for a reply.
235 (void) IcmpPacketReceiveTimer.expires_at(
236 TimeSent + seconds( EchoReplyTimeoutInSec )
238 IcmpPacketReceiveTimer.async_wait(
239 boost::bind( &IcmpPinger::handle_timeout, this, boost::asio::placeholders::error )
244 * @brief Gets called when the ping is finished: Either on timeout or on ping reply
248 void IcmpPinger::handle_timeout(const boost::system::error_code& error)
252 if ( error == boost::asio::error::operation_aborted )
255 GlobalLogger.notice() << "Timer waiting for ICMP echo reply was cancelled!" << endl;
256 // otherwise probably called by IcmpPacketReceiveTimer.cancel in handle_receive_icmp_packet!
259 GlobalLogger.notice() << "Error " << error << " waiting for ICMP echo reply!" << endl;
261 // Still continue with rest of function, so PingStatus is updated and Callback executed
262 // when timer was cancelled
265 // Check ReplyReceived as the timer handler
266 // is also called by Timer.cancel();
267 if ( !ReplyReceived )
270 << DestinationEndpoint.address().to_string()
271 << ": Request timed out" << endl;
273 set_ping_status( PingStatus_FailureTimeout );
276 // Call ping-done handler
277 bool ping_success = (PingerStatus == PingStatus_SuccessReply);
278 PingDoneCallback( ping_success );
283 * @brief Receive ICMP packets
284 * @param bytes_transferred Number of bytes transferred.
285 * @return true if packet matches a request from this pinger, false otherwise
287 bool IcmpPinger::handle_receive_icmp_packet(const IcmpPacketItem icmp_packet,
288 const size_t bytes_transferred )
290 bool does_match = false;
294 // continue, might be an old packet
295 // or return false right away, do not want packet anyway...
298 else if ( DestinationEndpoint.address() == address() )
299 { // we have no IP set yet
303 // We can receive all ICMP packets received by the host, so we need to
304 // filter out only the echo replies that match our identifier,
305 // expected sequence number, and destination host address (receive just
306 // the ICMP packets from the host we had ping).
310 if ( icmp_packet->match_echo_reply(
311 Identifier, SequenceNumber,
312 DestinationEndpoint.address() ) )
315 << DestinationEndpoint.address().to_string()
316 << ": Received reply" << endl;
318 ReplyReceived = true;
321 icmp_packet->print( bytes_transferred, TimeSent );
323 set_ping_status( PingStatus_SuccessReply );
325 IcmpPacketReceiveTimer.cancel(); //lint !e534
327 else if ( icmp_packet->match_destination_unreachable(
328 Identifier, SequenceNumber,
329 DestinationEndpoint.address() ) )
332 << DestinationEndpoint.address().to_string()
333 << ": Received destination unreachable" << endl;
335 ReplyReceived = true;
338 icmp_packet->print( bytes_transferred, TimeSent );
340 set_ping_status( PingStatus_FailureDestinationUnreachable );
342 IcmpPacketReceiveTimer.cancel(); //lint !e534
344 else if ( icmp_packet->match_time_exceeded(
345 Identifier, SequenceNumber,
346 DestinationEndpoint.address() ) )
349 << DestinationEndpoint.address().to_string()
350 << ": Received time exceeded" << endl;
352 ReplyReceived = true;
355 icmp_packet->print( bytes_transferred, TimeSent );
357 set_ping_status( PingStatus_FailureDestinationUnreachable );
359 IcmpPacketReceiveTimer.cancel(); //lint !e534
364 << DestinationEndpoint.address().to_string()
365 << ": Received packet that does not match or has wrong seq.nr"
369 catch ( std::exception &exc)
371 GlobalLogger.warning()
372 << DestinationEndpoint.address().to_string()
373 << ": Caught exception in packet interpretation: " << exc.what()
375 if ( IcmpPacketFactory::PacketDumpMode == DUMP_ALWAYS ||
376 IcmpPacketFactory::PacketDumpMode == DUMP_IF_ERROR )
377 IcmpPacketFactory::dump_packet(*icmp_packet);
378 does_match = true; // avoid the same procedure in all other pingers
382 GlobalLogger.warning()
383 << DestinationEndpoint.address().to_string()
384 << ": Caught unspecified exception in packet interpretation!"
386 if ( IcmpPacketFactory::PacketDumpMode == DUMP_ALWAYS ||
387 IcmpPacketFactory::PacketDumpMode == DUMP_IF_ERROR )
388 IcmpPacketFactory::dump_packet(*icmp_packet);
389 does_match = true; // avoid the same procedure in all other pingers
395 void IcmpPinger::set_ping_status( PingStatus ping_status )
397 PingerStatus = ping_status;
400 //------------------------------------------------------------------------
401 // IcmpPacketDistributor
402 //------------------------------------------------------------------------
404 static const std::size_t SOCKET_BUFFER_SIZE = 65536; // 64kB
406 typedef std::set<IcmpPingerItem>::iterator PingerListIterator;
409 bool IcmpPacketDistributor::InstanceIdentifierComparator::operator() (
410 const IcmpPacketDistributor::DistributorInstanceIdentifier &a,
411 const IcmpPacketDistributor::DistributorInstanceIdentifier &b )
414 if ( a.first == boost::asio::ip::icmp::v4() )
416 if ( b.first == boost::asio::ip::icmp::v4() )
417 return a.second < b.second; // v4 == v4
419 BOOST_ASSERT( b.first == boost::asio::ip::icmp::v6() );
420 return true; // a(v4) < b(b6)
424 BOOST_ASSERT( a.first == boost::asio::ip::icmp::v6() );
426 if ( b.first == boost::asio::ip::icmp::v4() )
427 return false; // a(v6) > b(v4)
429 BOOST_ASSERT( b.first == boost::asio::ip::icmp::v6() );
430 return a.second < b.second; // v6 == v6
434 //-----------------------------------------------------------------------------
435 // Definition of IcmpPacketDistributor
436 //-----------------------------------------------------------------------------
438 IcmpPacketDistributor::map_type IcmpPacketDistributor::Instances; // initialize
441 IcmpPacketDistributorItem IcmpPacketDistributor::get_distributor(
442 const icmp::socket::protocol_type &protocol,
443 const std::string &network_interface,
444 const IoServiceItem io_serv )
446 IcmpPacketDistributor::DistributorInstanceIdentifier identifier(
447 protocol, network_interface);
449 // check if there is an instance for this protocol and interface
450 if ( Instances.count(identifier) == 0 )
451 { // need to create an instance for this protocol and network interface
452 GlobalLogger.info() << "Creating IcmpPacketDistributor for interface "
453 << network_interface << std::endl;
454 IcmpPacketDistributorItem new_instance( new IcmpPacketDistributor(
455 protocol, network_interface, io_serv ) );
456 Instances[identifier] = new_instance;
459 BOOST_ASSERT( Instances.count(identifier) == 1 );
461 // return the one instance for this protocol and interface
462 return Instances[identifier];
466 IcmpPacketDistributorItem IcmpPacketDistributor::get_distributor(
467 const icmp::socket::protocol_type &protocol,
468 const std::string &network_interface )
470 IcmpPacketDistributor::DistributorInstanceIdentifier identifier(
471 protocol, network_interface);
473 BOOST_ASSERT( Instances.count(identifier) == 1 );
475 // return the one instance for this protocol and interface
476 return Instances[identifier];
480 IcmpPacketDistributor::IcmpPacketDistributor(
481 const icmp::socket::protocol_type &protocol,
482 const std::string &network_interface,
483 const IoServiceItem io_serv ):
484 Protocol( protocol ),
485 Socket( new icmp::socket(*io_serv, protocol) ),
489 // set TTL for testing
490 //const boost::asio::ip::unicast::hops option( 3 );
491 //Socket->set_option(option);
493 NetworkInterface<icmp::socket, boost::asio::ip::icmp>
494 NetInterface( network_interface, *Socket );
496 if ( !NetInterface.bind() )
499 << "Trouble creating IcmpPacketDistributor for interface "
500 << network_interface// << " and protocol " << protocol
501 << ": could not bind the socket with the local interface. "
502 << ::strerror( errno ) << std::endl;
505 register_receive_handler();
509 void IcmpPacketDistributor::register_receive_handler()
511 // wait for reply, prepare buffer to receive up to SOCKET_BUFFER_SIZE bytes
512 Socket->async_receive(
513 ReplyBuffer.prepare( SOCKET_BUFFER_SIZE ),
514 boost::bind( &IcmpPacketDistributor::handle_receive, this,
515 boost::asio::placeholders::error,
516 boost::asio::placeholders::bytes_transferred )
520 void IcmpPacketDistributor::handle_receive(
521 const boost::system::error_code &error,
522 const size_t &bytes_transferred )
526 GlobalLogger.warning()
527 << ": Received error " << error
528 << " in ICMP packet distributor; end handler and schedule another.";
529 register_receive_handler();
533 // The actual number of bytes received is committed to the buffer so that we
534 // can extract it using a std::istream object.
535 ReplyBuffer.commit( bytes_transferred );
537 GlobalLogger.info() << "received packet in distributor" << std::endl;
539 std::istream is( &ReplyBuffer );
542 GlobalLogger.error() << "Can't handle ReplyBuffer" << std::endl;
546 // Decode the reply packet.
547 IcmpPacketItem icmp_packet = IcmpPacketFactory::create_icmp_packet(
551 GlobalLogger.warning() << "Ignoring broken ICMP packet"
556 GlobalLogger.debug() << "Succesfully parsed ICMP packet"
559 // check which pinger wants this packet
560 bool packet_matches = false;
561 BOOST_FOREACH( const IcmpPingerItem &pinger, PingerList )
563 packet_matches = pinger->handle_receive_icmp_packet(
564 icmp_packet, bytes_transferred);
569 GlobalLogger.info() << "Packet did not match any pinger"
573 // re-register receive handler
574 register_receive_handler();
577 bool IcmpPacketDistributor::register_pinger( const IcmpPingerItem &new_pinger )
579 std::pair<PingerListIterator, bool> result = PingerList.insert(new_pinger);
580 bool was_new = result.second;
582 GlobalLogger.info() << "Register new pinger with IcmpPacketDistributor"
585 GlobalLogger.warning()
586 << "Pinger to register was already known in IcmpPacketDistributor"
592 bool IcmpPacketDistributor::unregister_pinger( const IcmpPingerItem &old_pinger )
594 int n_erased = PingerList.erase(old_pinger);
595 bool was_erased = n_erased > 0;
597 GlobalLogger.info() << "Removed pinger from IcmpPacketDistributor"
600 GlobalLogger.warning()
601 << "Could not find pinger to remove from IcmpPacketDistributor"
607 * @brief for all instances: close sockets, unregister all pingers
609 void IcmpPacketDistributor::clean_up_all()
611 BOOST_FOREACH( IcmpPacketDistributor::map_type::value_type &instance,
614 instance.second->clean_up();
620 void IcmpPacketDistributor::clean_up()
622 if (PingerList.size() == 0)
623 GlobalLogger.info() << "All IcmpPingers have de-registered"
626 GlobalLogger.warning() << "There were still " << PingerList.size()
627 << " pingers registered in IcmpPacketDistributor!" << std::endl;
630 boost::system::error_code error;
631 //Socket->shutdown(icmp::socket::shutdown_both, error); //both=send&receive
633 // GlobalLogger.warning() << "Received error " << error
634 // << " when shutting down ICMP socket";
635 // always gave an error system:9 (probably EBADF: Bad file descriptor)
637 Socket->close(error);
639 GlobalLogger.warning() << "Received error " << error
640 << " when closing ICMP socket";
643 IcmpPacketDistributor::~IcmpPacketDistributor()
645 GlobalLogger.info() << "Destroying IcmpPacketDistributor" << std::endl;
648 SocketItem IcmpPacketDistributor::get_socket() const