| 1 | /* |
| 2 | The software in this package is distributed under the GNU General |
| 3 | Public License version 2 (with a special exception described below). |
| 4 | |
| 5 | A copy of GNU General Public License (GPL) is included in this distribution, |
| 6 | in the file COPYING.GPL. |
| 7 | |
| 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. |
| 13 | |
| 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. |
| 16 | |
| 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. |
| 19 | */ |
| 20 | #include "tcp/tcppinger.h" |
| 21 | |
| 22 | #include <errno.h> |
| 23 | #include <net/if.h> |
| 24 | #include <sys/ioctl.h> |
| 25 | #include <sys/socket.h> |
| 26 | |
| 27 | #include <istream> |
| 28 | #include <ostream> |
| 29 | #include <limits> |
| 30 | |
| 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> |
| 36 | |
| 37 | #include <logfunc.hpp> |
| 38 | |
| 39 | #include "boost_assert_handler.h" |
| 40 | #include "tcp/tcpsegmentfactory.h" |
| 41 | |
| 42 | using namespace std; |
| 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; |
| 54 | |
| 55 | //----------------------------------------------------------------------------- |
| 56 | // TcpPinger |
| 57 | //----------------------------------------------------------------------------- |
| 58 | |
| 59 | /** |
| 60 | * @brief Parameterized constructor. |
| 61 | * |
| 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 |
| 65 | * send the segments. |
| 66 | * @param echo_reply_timeout_in_sec The amount of time to wait for a reply. |
| 67 | */ |
| 68 | TcpPinger::TcpPinger( |
| 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 |
| 73 | ) : |
| 74 | DestinationEndpoint(), |
| 75 | Protocol( protocol ), |
| 76 | Socket( *io_serv, Protocol ), |
| 77 | NetInterface( source_network_interface_name, Socket ), |
| 78 | TcpSegmentReceiveTimer( *io_serv ), |
| 79 | Identifier( 0 ), |
| 80 | SequenceNumber( 0 ), |
| 81 | TimeSent( microsec_clock::universal_time() ), |
| 82 | ReplyBuffer(), |
| 83 | ReceivedReply( false ), |
| 84 | RstReplyTimeoutInSec( rst_reply_timeout_in_sec ), |
| 85 | PingerStatus( PingStatus_NotSent ), |
| 86 | PingDoneCallback() |
| 87 | { |
| 88 | if ( !NetInterface.bind() ) |
| 89 | { |
| 90 | GlobalLogger.error() << "Could not bind the socket with the local interface. " |
| 91 | << ::strerror( errno ) << endl; |
| 92 | } |
| 93 | |
| 94 | // Create "unique" identifier |
| 95 | boost::uuids::random_generator random_gen; |
| 96 | boost::uuids::uuid random_tag = random_gen(); |
| 97 | |
| 98 | BOOST_ASSERT( sizeof(Identifier) <= random_tag.size() ); |
| 99 | memcpy( &Identifier, random_tag.data, sizeof(Identifier) ); |
| 100 | } |
| 101 | |
| 102 | TcpPinger::~TcpPinger() |
| 103 | { |
| 104 | } |
| 105 | |
| 106 | /** |
| 107 | * @brief Ping a destination address from an available local source. |
| 108 | * |
| 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. |
| 112 | * |
| 113 | * @return void. |
| 114 | */ |
| 115 | void TcpPinger::ping( |
| 116 | const address &destination_ip, |
| 117 | const uint16_t destination_port, |
| 118 | function<void(PingStatus,long)> ping_done_callback |
| 119 | ) |
| 120 | { |
| 121 | BOOST_ASSERT( ( 0 < destination_port ) && ( destination_port < numeric_limits<uint16_t>::max() ) ); |
| 122 | |
| 123 | PingDoneCallback = ping_done_callback; |
| 124 | |
| 125 | // Prepare ping |
| 126 | set_ping_status( PingStatus_NotSent ); |
| 127 | |
| 128 | set_destination_endpoint( destination_ip, destination_port ); |
| 129 | |
| 130 | start_send(); |
| 131 | start_receive(); |
| 132 | } |
| 133 | |
| 134 | void TcpPinger::stop_pinging() |
| 135 | { |
| 136 | } |
| 137 | |
| 138 | address TcpPinger::get_source_address() const |
| 139 | { |
| 140 | return NetInterface.get_address( Protocol ); |
| 141 | } |
| 142 | |
| 143 | address TcpPinger::get_destination_address() const |
| 144 | { |
| 145 | return DestinationEndpoint.address(); |
| 146 | } |
| 147 | |
| 148 | uint16_t TcpPinger::get_source_port() const |
| 149 | { |
| 150 | uint16_t source_port = static_cast<uint16_t>( ( random() % 16383u ) + 49152u ); // same as random() % 65536; |
| 151 | |
| 152 | return source_port; |
| 153 | } |
| 154 | |
| 155 | uint16_t TcpPinger::get_destination_port() const |
| 156 | { |
| 157 | return DestinationEndpoint.port(); |
| 158 | } |
| 159 | |
| 160 | void TcpPinger::set_destination_endpoint( |
| 161 | const address &destination_ip, |
| 162 | const uint16_t destination_port |
| 163 | ) |
| 164 | { |
| 165 | BOOST_ASSERT( ( 0 < destination_port ) && ( destination_port < numeric_limits<uint16_t>::max() ) ); |
| 166 | BOOST_ASSERT( sizeof(uint16_t) <= sizeof(destination_port) ); |
| 167 | |
| 168 | uint16_t port = static_cast<uint16_t>( destination_port ); |
| 169 | DestinationEndpoint = tcp_raw_protocol::endpoint( destination_ip, port ); |
| 170 | } |
| 171 | |
| 172 | void TcpPinger::start_send() |
| 173 | { |
| 174 | ++SequenceNumber; |
| 175 | |
| 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( |
| 182 | Protocol, |
| 183 | source_address, destination_address, |
| 184 | source_port, destination_port, SequenceNumber ); |
| 185 | |
| 186 | send_ack_request( tcp_segment ); |
| 187 | } |
| 188 | |
| 189 | void TcpPinger::send_ack_request( const TcpSegmentItem tcp_segment ) |
| 190 | { |
| 191 | // Encode the request packet. |
| 192 | boost::asio::streambuf request_buffer; |
| 193 | ostream os( &request_buffer ); |
| 194 | if ( !tcp_segment->write( os ) ) |
| 195 | { |
| 196 | GlobalLogger.error() << "Fail writing ping data." << endl; |
| 197 | } |
| 198 | |
| 199 | TimeSent = microsec_clock::universal_time(); |
| 200 | |
| 201 | address dest_address = DestinationEndpoint.address(); |
| 202 | string dest_address_string = dest_address.to_string(); |
| 203 | BOOST_ASSERT( !dest_address_string.empty() ); |
| 204 | |
| 205 | // Send the request |
| 206 | try |
| 207 | { |
| 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 ) ) |
| 212 | { |
| 213 | GlobalLogger.error() << "Fail sending ping data." |
| 214 | << "Amount of bytes sent differs from the buffer." << endl; |
| 215 | } |
| 216 | } |
| 217 | catch ( const exception &ex ) |
| 218 | { |
| 219 | GlobalLogger.error() << "Fail sending ping data. " << ex.what() << endl; |
| 220 | } |
| 221 | |
| 222 | // Tell how long to wait for the reply |
| 223 | schedule_timeout_rst_reply(); |
| 224 | } |
| 225 | |
| 226 | void TcpPinger::schedule_timeout_rst_reply() |
| 227 | { |
| 228 | // Wait up to N seconds for a reply. |
| 229 | ReceivedReply = false; |
| 230 | (void) TcpSegmentReceiveTimer.expires_at( |
| 231 | TimeSent + seconds( RstReplyTimeoutInSec ) |
| 232 | ); |
| 233 | TcpSegmentReceiveTimer.async_wait( |
| 234 | boost::bind( &TcpPinger::handle_ping_done, this ) |
| 235 | ); |
| 236 | } |
| 237 | |
| 238 | /** |
| 239 | * @brief Gets called when the ping is finished: Either on timeout or on ping reply |
| 240 | * |
| 241 | * @return void |
| 242 | **/ |
| 243 | void TcpPinger::handle_ping_done() |
| 244 | { |
| 245 | // Check ReceivedReply as the timer handler |
| 246 | // is also called by Timer.cancel(); |
| 247 | if ( !ReceivedReply ) |
| 248 | { |
| 249 | GlobalLogger.info() << "Request timed out" << endl; |
| 250 | |
| 251 | set_ping_status( PingStatus_FailureTimeout ); |
| 252 | } |
| 253 | |
| 254 | // Call ping-done handler |
| 255 | PingDoneCallback( PingerStatus, static_cast<long>( |
| 256 | (microsec_clock::universal_time() - TimeSent).total_microseconds()) ); |
| 257 | } |
| 258 | |
| 259 | void TcpPinger::start_receive() |
| 260 | { |
| 261 | // Discard any data already in the buffer. |
| 262 | ReplyBuffer.consume( ReplyBuffer.size() ); |
| 263 | |
| 264 | // Waiting for a reply. We prepare the buffer to receive up to 64KB. |
| 265 | Socket.async_receive( |
| 266 | ReplyBuffer.prepare( 65536 ), |
| 267 | boost::bind( &TcpPinger::handle_receive_tcp_segment, this, _2 ) |
| 268 | ); |
| 269 | } |
| 270 | |
| 271 | void TcpPinger::handle_receive_tcp_segment( const size_t &bytes_transferred ) |
| 272 | { |
| 273 | // The actual number of bytes received is committed to the buffer so that we |
| 274 | // can extract it using a std::istream object. |
| 275 | ReplyBuffer.commit( bytes_transferred ); |
| 276 | |
| 277 | try |
| 278 | { |
| 279 | istream is( &ReplyBuffer ); |
| 280 | if ( !is ) |
| 281 | { |
| 282 | GlobalLogger.error() << "Can't handle ReplyBuffer" << endl; |
| 283 | return; |
| 284 | } |
| 285 | |
| 286 | // Decode the reply segment. |
| 287 | TcpSegmentItem tcp_segment = TcpSegmentFactory::create_tcp_segment( Protocol, is ); |
| 288 | |
| 289 | // The TCP SYN ping will receive a RST if the port is closed. |
| 290 | // Filter out the TCP reset (RST) replies. Note that the sequence |
| 291 | // number from RST does not match the sent SYN's sequence number. |
| 292 | if ( tcp_segment->match_rst_reply( DestinationEndpoint.address() ) ) |
| 293 | { |
| 294 | ReceivedReply = true; |
| 295 | |
| 296 | tcp_segment->print_rst_reply( TimeSent ); |
| 297 | |
| 298 | set_ping_status( PingStatus_SuccessReply ); |
| 299 | |
| 300 | TcpSegmentReceiveTimer.cancel(); //lint !e534 |
| 301 | } |
| 302 | // The TCP SYN ping will receive a SYN/ACK if the port is open. |
| 303 | // Filter out the TCP synchronize (SYN/ACK) replies. Note that the sequence |
| 304 | // number from SYN/ACK does not match the sent SYN's sequence number. |
| 305 | else if ( tcp_segment->match_syn_ack_reply( DestinationEndpoint.address() ) ) |
| 306 | { |
| 307 | ReceivedReply = true; |
| 308 | |
| 309 | tcp_segment->print_syn_ack_reply( TimeSent ); |
| 310 | |
| 311 | set_ping_status( PingStatus_SuccessReply ); |
| 312 | |
| 313 | TcpSegmentReceiveTimer.cancel(); //lint !e534 |
| 314 | } |
| 315 | // Unknown TCP reply, start another receive till timeout |
| 316 | else |
| 317 | { |
| 318 | start_receive(); |
| 319 | } |
| 320 | } |
| 321 | catch ( ... ) |
| 322 | { |
| 323 | GlobalLogger.notice() << "Exception during ICMP parse. " |
| 324 | << "Starting another receive till timeout." << endl; |
| 325 | start_receive(); |
| 326 | } |
| 327 | } |
| 328 | |
| 329 | void TcpPinger::set_ping_status( PingStatus ping_status ) |
| 330 | { |
| 331 | PingerStatus = ping_status; |
| 332 | } |
| 333 | |
| 334 | PingerItem TcpPinger::create( |
| 335 | const IoServiceItem io_serv, |
| 336 | const tcp_raw_protocol::socket::protocol_type &protocol, |
| 337 | const string &source_network_interface_name, |
| 338 | const int rst_reply_timeout_in_sec ) |
| 339 | { |
| 340 | TcpPinger *ptr = new TcpPinger(io_serv, protocol, source_network_interface_name, |
| 341 | rst_reply_timeout_in_sec); |
| 342 | PingerItem shared_ptr(ptr); |
| 343 | Pinger::WeakPtr weak_ptr( shared_ptr ); |
| 344 | |
| 345 | // keep weak pointer to self |
| 346 | ptr->set_myself( weak_ptr ); |
| 347 | // shared_ptr->set_myself( weak_ptr ); Error: Pinger::set_myself is protected |
| 348 | |
| 349 | // done, return shared ptr |
| 350 | return shared_ptr; |
| 351 | } |