From: Guilherme Maciel Ferreira Date: Fri, 22 Jul 2011 03:47:22 +0000 (-0300) Subject: Working TCP pinger X-Git-Tag: v1.1^2~46 X-Git-Url: http://developer.intra2net.com/git/?a=commitdiff_plain;h=f8a8d9dc9cf935863fb490164c55cd656e1e8c2c;p=pingcheck Working TCP pinger - Needs to fix the source IP address used in the pseudo IP header --- diff --git a/src/tcp/tcppinger.cpp b/src/tcp/tcppinger.cpp index 3da92bb..b681660 100644 --- a/src/tcp/tcppinger.cpp +++ b/src/tcp/tcppinger.cpp @@ -19,13 +19,32 @@ on this file might be covered by the GNU General Public License. */ #include "tcp/tcppinger.h" +#include +#include + #include +#include +#include +#include +#include +#include + +#include +#include "ip/ipv4header.h" #include "tcp/tcpheader.h" using namespace std; +using boost::asio::const_buffers_1; using boost::asio::io_service; +using boost::asio::ip::address; +using boost::asio::ip::tcp_raw_protocol; +using boost::date_time::time_resolution_traits_adapted64_impl; using boost::function; +using boost::posix_time::microsec_clock; +using boost::posix_time::ptime; +using boost::posix_time::seconds; +using I2n::Logger::GlobalLogger; //----------------------------------------------------------------------------- // TcpPinger @@ -35,18 +54,243 @@ TcpPinger::TcpPinger( io_service &io_serv, const string &source_network_interface, const int echo_reply_timeout_in_sec -) +) : + IoService( io_serv ), + DestinationEndpoint(), + Socket( IoService, tcp_raw_protocol::v4() ), + TcpSegmentReceiveTimer( 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) ); } TcpPinger::~TcpPinger() { } +/** + * @brief 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 TcpPinger::ping( const string &destination_ip, - function /*ping_done_callback*/ + function ping_done_callback ) { BOOST_ASSERT( !destination_ip.empty() ); + + PingDoneCallback = ping_done_callback; + + // Prepare ping + set_ping_status( PingStatus_NotSent ); + + set_destination_endpoint( destination_ip ); + + start_send(); + start_receive(); +} + +uint32_t TcpPinger::get_source_address() const +{ + return 0xC0A80166; // TODO 192.168.1.102 +} + +uint32_t TcpPinger::get_destination_address() const +{ + return ((unsigned int) DestinationEndpoint.data()->sa_data[2] & 0xFF) << 24 | + ((unsigned int) DestinationEndpoint.data()->sa_data[3] & 0xFF) << 16 | + ((unsigned int) DestinationEndpoint.data()->sa_data[4] & 0xFF) << 8 | + ((unsigned int) DestinationEndpoint.data()->sa_data[5] & 0xFF); +} + +void TcpPinger::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 = tcp_raw_protocol::endpoint( destination_address, port ); +} + +void TcpPinger::start_send() +{ + ++SequenceNumber; + + // Create an TCP header for an ACK request. + TcpHeader tcp_header; + uint16_t port = ( random() % 16383 ) + 49152; // same as random() % 65536; + tcp_header.source_port( port ); // assign an random ephemeral port number + tcp_header.destination_port( 80 ); + tcp_header.sequence_number( ++SequenceNumber ); + tcp_header.header_length( 5 ); // header size in units of 32 bits (5 words of 32 bits = 5 * 4 bytes = 20 bytes) + tcp_header.acknowledgment( true ); + tcp_header.window_size( 32768 ); // window size + + // TODO isolate this part, so it can change without break the rest of the code + uint32_t src_addr = get_source_address(); + uint32_t dest_addr = get_destination_address(); + + uint16_t cksum = tcp_header.calculate_tcp_checksum( src_addr, dest_addr, NULL, 0 ); + tcp_header.checksum( cksum ); + + // Encode the request packet. + boost::asio::streambuf request_buffer; + ostream os( &request_buffer ); + os << tcp_header; + + // Send the request. + 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; + } + + // Wait up to N seconds for a reply. + ReceivedReply = false; + (void) TcpSegmentReceiveTimer.expires_at( + TimeSent + seconds( EchoReplyTimeoutInSec ) + ); + TcpSegmentReceiveTimer.async_wait( + boost::bind( &TcpPinger::handle_timeout, this ) + ); +} + +void TcpPinger::handle_timeout() +{ + ReceivedReply = false; + TcpSegmentReceiveTimer.expires_at( + TimeSent + seconds( EchoReplyTimeoutInSec ) + ); + TcpSegmentReceiveTimer.async_wait( + boost::bind( &TcpPinger::start_send, this ) + ); +} + +void TcpPinger::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( &TcpPinger::handle_receive_tcp_segment, this, _2 ) + ); +} + +void TcpPinger::handle_receive_tcp_segment( const size_t &bytes_transferred ) +{ + std::cerr << "handle_receive( " << bytes_transferred << ")" << std::endl; // TODO + + // 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 ); + + istream is( &ReplyBuffer ); + if ( !is ) + { + GlobalLogger.error() << "Error: can't handle ReplyBuffer" << endl; + return; + } + + Ipv4Header ipv4_hdr; + TcpHeader tcp_hdr; + is >> ipv4_hdr >> tcp_hdr; + + // filter out only the TCP reset (RST) replies that match the our expected + // sequence number. + if ( is && + tcp_hdr.reset() && + tcp_hdr.sequence_number() == SequenceNumber && + ipv4_hdr.get_source_address() == DestinationEndpoint.address() ) + { + ReceivedReply = true; + + // TODO print_rst_xxx + // Print out some information about the reply packet. + // RST from 74.125.234.17: seq=4279499795 ttl=56 time=1707165.000s + ptime now = microsec_clock::universal_time(); + GlobalLogger.notice() << " RST from " << ipv4_hdr.get_source_address() + << ": tcp_seq=" << tcp_hdr.sequence_number() + << ", ttl=" << ipv4_hdr.get_time_to_live() + << " time=" << (now - TimeSent).total_milliseconds() << " ms" << endl; + + TcpSegmentReceiveTimer.cancel(); + } + + start_receive(); +} + + +void TcpPinger::set_ping_status( TcpPinger::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 TcpPinger::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/tcp/tcppinger.h b/src/tcp/tcppinger.h index bb64169..4795f7f 100644 --- a/src/tcp/tcppinger.h +++ b/src/tcp/tcppinger.h @@ -23,6 +23,7 @@ on this file might be covered by the GNU General Public License. #include #include +#include #include #include "host/pinger.h" @@ -31,6 +32,10 @@ on this file might be covered by the GNU General Public License. // TcpPinger //----------------------------------------------------------------------------- +/** + * @brief This class performs a TCP ping to host using Boost Asio. + * Scope: one object per host. + */ class TcpPinger : public Pinger { public: @@ -46,6 +51,59 @@ public: boost::function ping_done_callback ); +private: + enum PingStatus + { + PingStatus_NotSent, + PingStatus_SuccessReply, + PingStatus_FailureTimeout, + PingStatus_FailureDestinationUnreachable + }; + +private: + uint32_t get_source_address() const; + uint32_t get_destination_address() const; + void set_destination_endpoint( const std::string &destination_ip ); + + void start_send(); + void handle_timeout(); + + void start_receive(); + void handle_receive_tcp_segment( const std::size_t &bytes_transferred ); + + void set_ping_status( TcpPinger::PingStatus ping_status ); + + 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::tcp_raw_protocol::endpoint DestinationEndpoint; + /// the socket object + boost::asio::ip::tcp_raw_protocol::socket Socket; + /// the timer of TCP segment receive, triggers the timeout to avoid infinite + /// wait + boost::asio::deadline_timer TcpSegmentReceiveTimer; + /// 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 + TcpPinger::PingStatus PingerStatus; + /// Callback to notify when the ping is done (got reply/timeout) + boost::function< void(bool) > PingDoneCallback; }; #endif /* TCPPINGER_H */