*/
#include "tcp/tcppinger.h"
+#include <istream>
+#include <ostream>
+
#include <boost/assert.hpp>
+#include <boost/bind.hpp>
+#include <boost/date_time/posix_time/posix_time.hpp>
+#include <boost/date_time/posix_time/posix_time_types.hpp>
+#include <boost/uuid/uuid.hpp>
+#include <boost/uuid/uuid_generators.hpp>
+
+#include <logfunc.hpp>
+#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
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<void(bool)> /*ping_done_callback*/
+ function<void(bool)> 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;
}
#include <string>
#include <boost/asio.hpp>
+#include <boost/asio/ip/tcp_raw_protocol.hpp>
#include <boost/function.hpp>
#include "host/pinger.h"
// TcpPinger
//-----------------------------------------------------------------------------
+/**
+ * @brief This class performs a TCP ping to host using Boost Asio.
+ * Scope: one object per host.
+ */
class TcpPinger : public Pinger
{
public:
boost::function<void(bool)> 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 */