From 511ced788a8d5e4c7c7e3f2fed9bcc8c8d87ed8e Mon Sep 17 00:00:00 2001 From: Guilherme Maciel Ferreira Date: Sun, 29 May 2011 12:21:29 -0300 Subject: [PATCH] Moved the icmppinger from host to icmp folder --- src/CMakeLists.txt | 4 +- src/host/icmppinger.cpp | 370 ---------------------------------------------- src/host/icmppinger.h | 104 ------------- src/host/pingscheduler.h | 2 +- src/icmp/icmppinger.cpp | 370 ++++++++++++++++++++++++++++++++++++++++++++++ src/icmp/icmppinger.h | 104 +++++++++++++ 6 files changed, 477 insertions(+), 477 deletions(-) delete mode 100644 src/host/icmppinger.cpp delete mode 100644 src/host/icmppinger.h create mode 100644 src/icmp/icmppinger.cpp create mode 100644 src/icmp/icmppinger.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 8926298..4366f83 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -32,17 +32,17 @@ set(SOURCES dns/hostaddress.cpp dns/timetolive.cpp host/hoststatusanalyzer.cpp - host/icmppinger.cpp host/pinginterval.cpp host/pingscheduler.cpp icmp/icmpdestinationunreachablemessage.cpp icmp/icmpechoreplymessage.cpp icmp/icmpechorequestmessage.cpp icmp/icmpgenericmessage.cpp + icmp/icmpheader.cpp icmp/icmpmessage.cpp icmp/icmpmessagepayload.cpp - icmp/icmpheader.cpp icmp/icmppacket.cpp + icmp/icmppinger.cpp icmp/ipv4header.cpp link/linkstatusanalyzer.cpp link/statusnotifiercommand.cpp diff --git a/src/host/icmppinger.cpp b/src/host/icmppinger.cpp deleted file mode 100644 index 2f22c5a..0000000 --- a/src/host/icmppinger.cpp +++ /dev/null @@ -1,370 +0,0 @@ -// Boost pinger (c) 2011 by Guilherme Maciel Ferreira / Intra2net AG -// Based upon work copyright (c) 2003-2010 Christopher M. Kohlhoff (ping.cpp) -// -// Distributed under the Boost Software License, Version 1.0. -// (See accompanying file LICENSE_1_0.txt or copy at -// http://www.boost.org/LICENSE_1_0.txt) -#include "host/icmppinger.h" - -#include -#include - -#include -#include -#include - -#include -#include - -#include - -#include "icmp/icmpchecksumcalculator.h" -#include "icmp/icmpdata.h" -#include "icmp/icmpheader.h" -#include "icmp/icmppacket.h" -#include "icmp/icmptype.h" -#include "icmp/ipv4header.h" - -using namespace std; -using boost::asio::const_buffers_1; -using boost::asio::io_service; -using boost::asio::ip::address; -using boost::asio::ip::icmp; -using boost::date_time::time_resolution_traits_adapted64_impl; -using boost::posix_time::microsec_clock; -using boost::posix_time::ptime; -using boost::posix_time::seconds; -using I2n::Logger::GlobalLogger; - -//----------------------------------------------------------------------------- -// IcmpPinger -//----------------------------------------------------------------------------- - -IcmpPinger::IcmpPinger( - boost::asio::io_service &io_service, - string source_network_interface, - const int echo_reply_timeout_in_sec -) : - IoService( io_service ), - DestinationEndpoint(), - Socket( IoService, icmp::v4() ), - IcmpPacketReceiveTimer( IoService ), - Identifier( 0 ), - SequenceNumber( 0 ), - TimeSent( microsec_clock::universal_time() ), - ReplyBuffer(), - ReceivedReply( false ), - EchoReplyTimeoutInSec( echo_reply_timeout_in_sec ), - PingerStatus( PingStatus_NotSent ), - PingDoneCallback() -{ - if ( !source_network_interface.empty() && - !select_source_network_interface( source_network_interface ) ) - { - GlobalLogger.error() << "Error: could not bind the socket " - "with the local interface." << ::strerror( errno ) << endl; - } - - // Create "unique" identifier - boost::uuids::random_generator random_gen; - boost::uuids::uuid random_tag = random_gen(); - - BOOST_ASSERT(sizeof(Identifier) <= random_tag.size()); - memcpy(&Identifier, random_tag.data, sizeof(Identifier)); -} - -IcmpPinger::~IcmpPinger() -{ -} - -/** - * Ping a destination address from an available local source. - * - * @param destination_ip The address of the host to ping. - * @param done_handler Done handler will be called on successful ping or timeout - */ -void IcmpPinger::ping( - const string &destination_ip, - boost::function< void(bool) > ping_done_callback -) -{ - BOOST_ASSERT( !destination_ip.empty() ); - - PingDoneCallback = ping_done_callback; - - // Prepare ping - PingerStatus = PingStatus_NotSent; - - set_destination_endpoint( destination_ip ); - - start_send(); - start_receive(); -} - -void IcmpPinger::set_destination_endpoint( const string &destination_ip ) -{ - BOOST_ASSERT( !destination_ip.empty() ); - - uint16_t port = 0; - address destination_address = address::from_string( destination_ip ); - DestinationEndpoint = icmp::endpoint( destination_address, port ); -} - -void IcmpPinger::start_send() -{ - ++SequenceNumber; - - IcmpPacket icmp_echo_request_packet = create_echo_request( SequenceNumber ); - - BOOST_ASSERT( PingerStatus == PingStatus_NotSent ); - send_echo_request( icmp_echo_request_packet ); -} - -IcmpPacket IcmpPinger::create_echo_request( - const uint16_t sequence_number -) const -{ - const IcmpData icmp_data( "ping-message" ); - - IcmpType type = IcmpType_EchoRequest; - uint8_t code = 0; - IcmpChecksumCalculator calculator( icmp_data.begin(), icmp_data.end() ); - uint16_t checksum = calculator.compute( - type, code, Identifier, sequence_number - ); - IcmpHeader icmp_header( - type, code, checksum, Identifier, sequence_number - ); - - return IcmpPacket( icmp_header, icmp_data ); -} - -void IcmpPinger::send_echo_request( const IcmpPacket &icmp_packet ) -{ - boost::asio::streambuf request_buffer; - ostream os( &request_buffer ); - os << icmp_packet; - - TimeSent = microsec_clock::universal_time(); - - string dest_address_string = DestinationEndpoint.address().to_string(); - BOOST_ASSERT( !dest_address_string.empty() ); - - // Send the request - try - { - const_buffers_1 data = request_buffer.data(); - // Block until send the data - size_t bytes_sent = Socket.send_to( data, DestinationEndpoint ); - if ( bytes_sent != buffer_size( data ) ) - { - GlobalLogger.error() << "Error: fail sending ping data." - << endl; - } - } - catch ( const exception &ex ) - { - GlobalLogger.error() << "Error: fail sending ping data. " - << ex.what() << endl; - } - - schedule_timeout_echo_reply(); -} - -void IcmpPinger::schedule_timeout_echo_reply() -{ - // Wait up to N seconds for a reply. - ReceivedReply = false; - (void) IcmpPacketReceiveTimer.expires_at( - TimeSent + seconds( EchoReplyTimeoutInSec ) - ); - IcmpPacketReceiveTimer.async_wait( - boost::bind( &IcmpPinger::handle_ping_done, this ) - ); -} - -void IcmpPinger::start_receive() -{ - // Discard any data already in the buffer. - ReplyBuffer.consume( ReplyBuffer.size() ); - - // Waiting for a reply. We prepare the buffer to receive up to 64KB. - Socket.async_receive( - ReplyBuffer.prepare( 65536 ), - boost::bind( &IcmpPinger::handle_receive_icmp_packet, this, _2 ) - ); -} - -/** - * @brief Gets called when the ping is finished: Either on timeout or on ping reply - * - * @return void - **/ -void IcmpPinger::handle_ping_done() -{ - // Check ReceivedReply as the timer handler - // is also called by Timer.cancel(); - if (ReceivedReply == false) - { - GlobalLogger.info() << "Request timed out" << endl; - - set_ping_status( PingStatus_FailureTimeout ); - } - - // Call ping-done handler - bool ping_success = (PingerStatus == PingStatus_SuccessReply); - PingDoneCallback(ping_success); -} - -/** - * @brief Receive ICMP packets - * Note: Will receive -all- ICMP packets from the kernel. So if the packet doesn't match - * our criteria, we have to schedule another start_receive() requests. The timeout stays the same. - * - * In the future we might redesign the code to handle all ICMP packets - * from a single raw socket. - * - * @param bytes_transferred Number of bytes transferred - * @return void - **/ -void IcmpPinger::handle_receive_icmp_packet( const size_t &bytes_transferred ) -{ - // The actual number of bytes received is committed to the buffer so that we - // can extract it using a std::istream object. - ReplyBuffer.commit( bytes_transferred ); - - try - { - istream is( &ReplyBuffer ); - if ( !is ) - { - GlobalLogger.error() << "can't handle ReplyBuffer" << endl; - return; - } - - // Decode the reply packet. - IcmpPacket icmp_packet; - if (!(is >> icmp_packet)) - { - GlobalLogger.notice() << "ignoring broken ICMP packet" << endl; - return; - } - - // We can receive all ICMP packets received by the host, so we need to - // filter out only the echo replies that match the our identifier, - // expected sequence number, and destination host address (receive just the - // ICMP packets from the host we had ping). - if ( icmp_packet.match( - IcmpType_EchoReply, Identifier, SequenceNumber, - DestinationEndpoint.address()) - ) - { - ReceivedReply = true; - print_echo_reply( icmp_packet, bytes_transferred ); - - set_ping_status( PingStatus_SuccessReply ); - - IcmpPacketReceiveTimer.cancel(); - } - else if ( icmp_packet.match( - IcmpType_DestinationUnreachable, Identifier, SequenceNumber, - DestinationEndpoint.address() - ) ) - { - ReceivedReply = true; - print_destination_unreachable( icmp_packet ); - - set_ping_status( PingStatus_FailureDestinationUnreachable ); - - IcmpPacketReceiveTimer.cancel(); - } else - { - /* - GlobalLogger.notice() << "unknown ICMP reply (src IP: " << icmp_packet.get_ip_header().get_source_address() << ")" - << ". Starting another recieve till timeout." << endl; - */ - start_receive(); - } - } catch(...) - { - GlobalLogger.notice() << "exception during ICMP parse. Starting another recieve till timeout." << endl; - start_receive(); - } -} - -void IcmpPinger::print_echo_reply( - const IcmpPacket &icmp_packet, - const size_t &bytes_transferred -) const -{ - BOOST_ASSERT( icmp_packet.get_icmp_header().get_type() == IcmpType_EchoReply ); - - Ipv4Header ipv4_hdr = icmp_packet.get_ip_header(); - IcmpHeader icmp_hdr = icmp_packet.get_icmp_header(); - - size_t bytes_received = bytes_transferred - ipv4_hdr.get_header_length(); - string remote_address = ipv4_hdr.get_source_address().to_string(); - uint16_t sequence_number = icmp_hdr.get_sequence_number(); - int ttl = ipv4_hdr.get_time_to_live(); - ptime now = microsec_clock::universal_time(); - time_resolution_traits_adapted64_impl::int_type elapsed_time = - (now - TimeSent).total_milliseconds(); - - GlobalLogger.info() << bytes_received << " bytes " - << "from " << remote_address - << ": icmp_seq=" << sequence_number - << " ttl=" << ttl - << " time=" << elapsed_time << " ms" << endl; -} - -void IcmpPinger::print_destination_unreachable( - const IcmpPacket &icmp_packet -) const -{ - BOOST_ASSERT( icmp_packet.get_icmp_header().get_type() == IcmpType_DestinationUnreachable ); - - Ipv4Header ipv4_hdr = icmp_packet.get_ip_header(); - IcmpHeader icmp_hdr = icmp_packet.get_icmp_header(); - - string local_address = ipv4_hdr.get_destination_address().to_string(); - uint16_t sequence_number = icmp_hdr.get_sequence_number(); - - GlobalLogger.info() << "From " << local_address - << " icmp_seq=" << sequence_number - << " Destination Net Unreachable" << endl; -} - -void IcmpPinger::set_ping_status( IcmpPinger::PingStatus ping_status ) -{ - PingerStatus = ping_status; -} - -/** - * Avoid the socket to drop to another network interface if the destination - * is unreachable through the binded interface. Packets are sent only from - * this interface. - * - * @param source_network_interface The network interface to bind the pinger. - * - * @return false if the bind failed. - */ -bool IcmpPinger::select_source_network_interface( - const string &source_network_interface -) -{ - BOOST_ASSERT( !source_network_interface.empty() ); - - int ret = ::setsockopt( - Socket.native(), - SOL_SOCKET, - SO_BINDTODEVICE, - source_network_interface.c_str(), - source_network_interface.size() - ); - if ( ret == -1 ) - { - return false; - } - - return true; -} diff --git a/src/host/icmppinger.h b/src/host/icmppinger.h deleted file mode 100644 index 992917b..0000000 --- a/src/host/icmppinger.h +++ /dev/null @@ -1,104 +0,0 @@ -// Boost pinger (c) 2011 by Guilherme Maciel Ferreira / Intra2net AG -// Based upon work copyright (c) 2003-2010 Christopher M. Kohlhoff (ping.cpp) -// -// Distributed under the Boost Software License, Version 1.0. -// (See accompanying file LICENSE_1_0.txt or copy at -// http://www.boost.org/LICENSE_1_0.txt) -#ifndef ICMPPINGER_H -#define ICMPPINGER_H - -#include -#include - -class IcmpPacket; - -//----------------------------------------------------------------------------- -// IcmpPinger -//----------------------------------------------------------------------------- - -/** - * @brief This class performs ping to host using Boost Asio. - * Scope: one object per host. - */ -class IcmpPinger -{ -public: - IcmpPinger( - boost::asio::io_service &io_service, - std::string source_network_interface, - const int echo_reply_timeout_in_sec - ); - virtual ~IcmpPinger(); - - void ping( - const std::string &destination_ip, - boost::function< void(bool) > ping_done_callback - ); - -private: - enum PingStatus - { - PingStatus_NotSent, - PingStatus_SuccessReply, - PingStatus_FailureTimeout, - PingStatus_FailureDestinationUnreachable - }; - -private: - void set_destination_endpoint( const std::string &destination_ip ); - - void start_send(); - IcmpPacket create_echo_request( const uint16_t sequence_number ) const; - void send_echo_request( const IcmpPacket &icmp_packet ); - void schedule_timeout_echo_reply(); - - void start_receive(); - void handle_receive_icmp_packet( const std::size_t &bytes_transferred ); - void handle_ping_done(); - - void print_echo_reply( - const IcmpPacket &icmp_packet, - const std::size_t &bytes_transferred - ) const; - void print_destination_unreachable( - const IcmpPacket &icmp_packet - ) const; - - void set_ping_status( IcmpPinger::PingStatus ping_status ); - - uint16_t get_identifier() const; - - bool select_source_network_interface( - const std::string &source_network_interface - ); - -private: - /// io service object, which has the loop event - boost::asio::io_service &IoService; - /// the destination host - boost::asio::ip::icmp::endpoint DestinationEndpoint; - /// the socket object - boost::asio::ip::icmp::socket Socket; - /// the timer of ICMP packet receive, triggers the timeout to avoid infinite - /// wait - boost::asio::deadline_timer IcmpPacketReceiveTimer; - /// ICMP packet identifier - uint16_t Identifier; - /// ICMP packet sequence_number - uint16_t SequenceNumber; - /// the time when the last ICMP packet was sent - boost::posix_time::ptime TimeSent; - /// the buffer where the data received will be placed - boost::asio::streambuf ReplyBuffer; - /// Flag to indicate if we got a reply or not - /// number of replies to the ICMP echo request - bool ReceivedReply; - /// the amount of time to wait for the reply - int EchoReplyTimeoutInSec; - /// the status of the pinger - IcmpPinger::PingStatus PingerStatus; - /// Callback to notify when the ping is done (got reply/timeout) - boost::function< void(bool) > PingDoneCallback; -}; - -#endif /* ICMPPINGER_H */ diff --git a/src/host/pingscheduler.h b/src/host/pingscheduler.h index b874f1f..9a7afe7 100644 --- a/src/host/pingscheduler.h +++ b/src/host/pingscheduler.h @@ -30,8 +30,8 @@ on this file might be covered by the GNU General Public License. #include "dns/dnsresolver.h" #include "link/linkstatusanalyzer.h" #include "host/hoststatusanalyzer.h" -#include "host/icmppinger.h" #include "host/pinginterval.h" +#include "icmp/icmppinger.h" //----------------------------------------------------------------------------- // PingScheduler diff --git a/src/icmp/icmppinger.cpp b/src/icmp/icmppinger.cpp new file mode 100644 index 0000000..fa35713 --- /dev/null +++ b/src/icmp/icmppinger.cpp @@ -0,0 +1,370 @@ +// Boost pinger (c) 2011 by Guilherme Maciel Ferreira / Intra2net AG +// Based upon work copyright (c) 2003-2010 Christopher M. Kohlhoff (ping.cpp) +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +#include "icmp/icmppinger.h" + +#include +#include + +#include +#include +#include + +#include +#include + +#include + +#include "icmp/icmpchecksumcalculator.h" +#include "icmp/icmpdata.h" +#include "icmp/icmpheader.h" +#include "icmp/icmppacket.h" +#include "icmp/icmptype.h" +#include "icmp/ipv4header.h" + +using namespace std; +using boost::asio::const_buffers_1; +using boost::asio::io_service; +using boost::asio::ip::address; +using boost::asio::ip::icmp; +using boost::date_time::time_resolution_traits_adapted64_impl; +using boost::posix_time::microsec_clock; +using boost::posix_time::ptime; +using boost::posix_time::seconds; +using I2n::Logger::GlobalLogger; + +//----------------------------------------------------------------------------- +// IcmpPinger +//----------------------------------------------------------------------------- + +IcmpPinger::IcmpPinger( + boost::asio::io_service &io_service, + string source_network_interface, + const int echo_reply_timeout_in_sec +) : + IoService( io_service ), + DestinationEndpoint(), + Socket( IoService, icmp::v4() ), + IcmpPacketReceiveTimer( IoService ), + Identifier( 0 ), + SequenceNumber( 0 ), + TimeSent( microsec_clock::universal_time() ), + ReplyBuffer(), + ReceivedReply( false ), + EchoReplyTimeoutInSec( echo_reply_timeout_in_sec ), + PingerStatus( PingStatus_NotSent ), + PingDoneCallback() +{ + if ( !source_network_interface.empty() && + !select_source_network_interface( source_network_interface ) ) + { + GlobalLogger.error() << "Error: could not bind the socket " + "with the local interface." << ::strerror( errno ) << endl; + } + + // Create "unique" identifier + boost::uuids::random_generator random_gen; + boost::uuids::uuid random_tag = random_gen(); + + BOOST_ASSERT(sizeof(Identifier) <= random_tag.size()); + memcpy(&Identifier, random_tag.data, sizeof(Identifier)); +} + +IcmpPinger::~IcmpPinger() +{ +} + +/** + * Ping a destination address from an available local source. + * + * @param destination_ip The address of the host to ping. + * @param done_handler Done handler will be called on successful ping or timeout + */ +void IcmpPinger::ping( + const string &destination_ip, + boost::function< void(bool) > ping_done_callback +) +{ + BOOST_ASSERT( !destination_ip.empty() ); + + PingDoneCallback = ping_done_callback; + + // Prepare ping + PingerStatus = PingStatus_NotSent; + + set_destination_endpoint( destination_ip ); + + start_send(); + start_receive(); +} + +void IcmpPinger::set_destination_endpoint( const string &destination_ip ) +{ + BOOST_ASSERT( !destination_ip.empty() ); + + uint16_t port = 0; + address destination_address = address::from_string( destination_ip ); + DestinationEndpoint = icmp::endpoint( destination_address, port ); +} + +void IcmpPinger::start_send() +{ + ++SequenceNumber; + + IcmpPacket icmp_echo_request_packet = create_echo_request( SequenceNumber ); + + BOOST_ASSERT( PingerStatus == PingStatus_NotSent ); + send_echo_request( icmp_echo_request_packet ); +} + +IcmpPacket IcmpPinger::create_echo_request( + const uint16_t sequence_number +) const +{ + const IcmpData icmp_data( "ping-message" ); + + IcmpType type = IcmpType_EchoRequest; + uint8_t code = 0; + IcmpChecksumCalculator calculator( icmp_data.begin(), icmp_data.end() ); + uint16_t checksum = calculator.compute( + type, code, Identifier, sequence_number + ); + IcmpHeader icmp_header( + type, code, checksum, Identifier, sequence_number + ); + + return IcmpPacket( icmp_header, icmp_data ); +} + +void IcmpPinger::send_echo_request( const IcmpPacket &icmp_packet ) +{ + boost::asio::streambuf request_buffer; + ostream os( &request_buffer ); + os << icmp_packet; + + TimeSent = microsec_clock::universal_time(); + + string dest_address_string = DestinationEndpoint.address().to_string(); + BOOST_ASSERT( !dest_address_string.empty() ); + + // Send the request + try + { + const_buffers_1 data = request_buffer.data(); + // Block until send the data + size_t bytes_sent = Socket.send_to( data, DestinationEndpoint ); + if ( bytes_sent != buffer_size( data ) ) + { + GlobalLogger.error() << "Error: fail sending ping data." + << endl; + } + } + catch ( const exception &ex ) + { + GlobalLogger.error() << "Error: fail sending ping data. " + << ex.what() << endl; + } + + schedule_timeout_echo_reply(); +} + +void IcmpPinger::schedule_timeout_echo_reply() +{ + // Wait up to N seconds for a reply. + ReceivedReply = false; + (void) IcmpPacketReceiveTimer.expires_at( + TimeSent + seconds( EchoReplyTimeoutInSec ) + ); + IcmpPacketReceiveTimer.async_wait( + boost::bind( &IcmpPinger::handle_ping_done, this ) + ); +} + +void IcmpPinger::start_receive() +{ + // Discard any data already in the buffer. + ReplyBuffer.consume( ReplyBuffer.size() ); + + // Waiting for a reply. We prepare the buffer to receive up to 64KB. + Socket.async_receive( + ReplyBuffer.prepare( 65536 ), + boost::bind( &IcmpPinger::handle_receive_icmp_packet, this, _2 ) + ); +} + +/** + * @brief Gets called when the ping is finished: Either on timeout or on ping reply + * + * @return void + **/ +void IcmpPinger::handle_ping_done() +{ + // Check ReceivedReply as the timer handler + // is also called by Timer.cancel(); + if (ReceivedReply == false) + { + GlobalLogger.info() << "Request timed out" << endl; + + set_ping_status( PingStatus_FailureTimeout ); + } + + // Call ping-done handler + bool ping_success = (PingerStatus == PingStatus_SuccessReply); + PingDoneCallback(ping_success); +} + +/** + * @brief Receive ICMP packets + * Note: Will receive -all- ICMP packets from the kernel. So if the packet doesn't match + * our criteria, we have to schedule another start_receive() requests. The timeout stays the same. + * + * In the future we might redesign the code to handle all ICMP packets + * from a single raw socket. + * + * @param bytes_transferred Number of bytes transferred + * @return void + **/ +void IcmpPinger::handle_receive_icmp_packet( const size_t &bytes_transferred ) +{ + // The actual number of bytes received is committed to the buffer so that we + // can extract it using a std::istream object. + ReplyBuffer.commit( bytes_transferred ); + + try + { + istream is( &ReplyBuffer ); + if ( !is ) + { + GlobalLogger.error() << "can't handle ReplyBuffer" << endl; + return; + } + + // Decode the reply packet. + IcmpPacket icmp_packet; + if (!(is >> icmp_packet)) + { + GlobalLogger.notice() << "ignoring broken ICMP packet" << endl; + return; + } + + // We can receive all ICMP packets received by the host, so we need to + // filter out only the echo replies that match the our identifier, + // expected sequence number, and destination host address (receive just the + // ICMP packets from the host we had ping). + if ( icmp_packet.match( + IcmpType_EchoReply, Identifier, SequenceNumber, + DestinationEndpoint.address()) + ) + { + ReceivedReply = true; + print_echo_reply( icmp_packet, bytes_transferred ); + + set_ping_status( PingStatus_SuccessReply ); + + IcmpPacketReceiveTimer.cancel(); + } + else if ( icmp_packet.match( + IcmpType_DestinationUnreachable, Identifier, SequenceNumber, + DestinationEndpoint.address() + ) ) + { + ReceivedReply = true; + print_destination_unreachable( icmp_packet ); + + set_ping_status( PingStatus_FailureDestinationUnreachable ); + + IcmpPacketReceiveTimer.cancel(); + } else + { + /* + GlobalLogger.notice() << "unknown ICMP reply (src IP: " << icmp_packet.get_ip_header().get_source_address() << ")" + << ". Starting another recieve till timeout." << endl; + */ + start_receive(); + } + } catch(...) + { + GlobalLogger.notice() << "exception during ICMP parse. Starting another recieve till timeout." << endl; + start_receive(); + } +} + +void IcmpPinger::print_echo_reply( + const IcmpPacket &icmp_packet, + const size_t &bytes_transferred +) const +{ + BOOST_ASSERT( icmp_packet.get_icmp_header().get_type() == IcmpType_EchoReply ); + + Ipv4Header ipv4_hdr = icmp_packet.get_ip_header(); + IcmpHeader icmp_hdr = icmp_packet.get_icmp_header(); + + size_t bytes_received = bytes_transferred - ipv4_hdr.get_header_length(); + string remote_address = ipv4_hdr.get_source_address().to_string(); + uint16_t sequence_number = icmp_hdr.get_sequence_number(); + int ttl = ipv4_hdr.get_time_to_live(); + ptime now = microsec_clock::universal_time(); + time_resolution_traits_adapted64_impl::int_type elapsed_time = + (now - TimeSent).total_milliseconds(); + + GlobalLogger.info() << bytes_received << " bytes " + << "from " << remote_address + << ": icmp_seq=" << sequence_number + << " ttl=" << ttl + << " time=" << elapsed_time << " ms" << endl; +} + +void IcmpPinger::print_destination_unreachable( + const IcmpPacket &icmp_packet +) const +{ + BOOST_ASSERT( icmp_packet.get_icmp_header().get_type() == IcmpType_DestinationUnreachable ); + + Ipv4Header ipv4_hdr = icmp_packet.get_ip_header(); + IcmpHeader icmp_hdr = icmp_packet.get_icmp_header(); + + string local_address = ipv4_hdr.get_destination_address().to_string(); + uint16_t sequence_number = icmp_hdr.get_sequence_number(); + + GlobalLogger.info() << "From " << local_address + << " icmp_seq=" << sequence_number + << " Destination Net Unreachable" << endl; +} + +void IcmpPinger::set_ping_status( IcmpPinger::PingStatus ping_status ) +{ + PingerStatus = ping_status; +} + +/** + * Avoid the socket to drop to another network interface if the destination + * is unreachable through the binded interface. Packets are sent only from + * this interface. + * + * @param source_network_interface The network interface to bind the pinger. + * + * @return false if the bind failed. + */ +bool IcmpPinger::select_source_network_interface( + const string &source_network_interface +) +{ + BOOST_ASSERT( !source_network_interface.empty() ); + + int ret = ::setsockopt( + Socket.native(), + SOL_SOCKET, + SO_BINDTODEVICE, + source_network_interface.c_str(), + source_network_interface.size() + ); + if ( ret == -1 ) + { + return false; + } + + return true; +} diff --git a/src/icmp/icmppinger.h b/src/icmp/icmppinger.h new file mode 100644 index 0000000..992917b --- /dev/null +++ b/src/icmp/icmppinger.h @@ -0,0 +1,104 @@ +// Boost pinger (c) 2011 by Guilherme Maciel Ferreira / Intra2net AG +// Based upon work copyright (c) 2003-2010 Christopher M. Kohlhoff (ping.cpp) +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +#ifndef ICMPPINGER_H +#define ICMPPINGER_H + +#include +#include + +class IcmpPacket; + +//----------------------------------------------------------------------------- +// IcmpPinger +//----------------------------------------------------------------------------- + +/** + * @brief This class performs ping to host using Boost Asio. + * Scope: one object per host. + */ +class IcmpPinger +{ +public: + IcmpPinger( + boost::asio::io_service &io_service, + std::string source_network_interface, + const int echo_reply_timeout_in_sec + ); + virtual ~IcmpPinger(); + + void ping( + const std::string &destination_ip, + boost::function< void(bool) > ping_done_callback + ); + +private: + enum PingStatus + { + PingStatus_NotSent, + PingStatus_SuccessReply, + PingStatus_FailureTimeout, + PingStatus_FailureDestinationUnreachable + }; + +private: + void set_destination_endpoint( const std::string &destination_ip ); + + void start_send(); + IcmpPacket create_echo_request( const uint16_t sequence_number ) const; + void send_echo_request( const IcmpPacket &icmp_packet ); + void schedule_timeout_echo_reply(); + + void start_receive(); + void handle_receive_icmp_packet( const std::size_t &bytes_transferred ); + void handle_ping_done(); + + void print_echo_reply( + const IcmpPacket &icmp_packet, + const std::size_t &bytes_transferred + ) const; + void print_destination_unreachable( + const IcmpPacket &icmp_packet + ) const; + + void set_ping_status( IcmpPinger::PingStatus ping_status ); + + uint16_t get_identifier() const; + + bool select_source_network_interface( + const std::string &source_network_interface + ); + +private: + /// io service object, which has the loop event + boost::asio::io_service &IoService; + /// the destination host + boost::asio::ip::icmp::endpoint DestinationEndpoint; + /// the socket object + boost::asio::ip::icmp::socket Socket; + /// the timer of ICMP packet receive, triggers the timeout to avoid infinite + /// wait + boost::asio::deadline_timer IcmpPacketReceiveTimer; + /// ICMP packet identifier + uint16_t Identifier; + /// ICMP packet sequence_number + uint16_t SequenceNumber; + /// the time when the last ICMP packet was sent + boost::posix_time::ptime TimeSent; + /// the buffer where the data received will be placed + boost::asio::streambuf ReplyBuffer; + /// Flag to indicate if we got a reply or not + /// number of replies to the ICMP echo request + bool ReceivedReply; + /// the amount of time to wait for the reply + int EchoReplyTimeoutInSec; + /// the status of the pinger + IcmpPinger::PingStatus PingerStatus; + /// Callback to notify when the ping is done (got reply/timeout) + boost::function< void(bool) > PingDoneCallback; +}; + +#endif /* ICMPPINGER_H */ -- 1.7.1