Commit | Line | Data |
---|---|---|
cd395966 GMF |
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 | ||
60d13f4f GMF |
22 | #include <errno.h> |
23 | #include <net/if.h> | |
24 | #include <sys/ioctl.h> | |
25 | #include <sys/socket.h> | |
26 | ||
f8a8d9dc GMF |
27 | #include <istream> |
28 | #include <ostream> | |
1309d0e4 | 29 | #include <limits> |
f8a8d9dc | 30 | |
f8a8d9dc GMF |
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> | |
cd395966 | 38 | |
780b0bca | 39 | #include "boost_assert_handler.h" |
3596e4f4 GMF |
40 | #include "tcp/tcpsegmentfactory.h" |
41 | ||
cd395966 | 42 | using namespace std; |
f8a8d9dc | 43 | using boost::asio::const_buffers_1; |
84525f34 | 44 | using boost::asio::io_service; |
f8a8d9dc GMF |
45 | using boost::asio::ip::address; |
46 | using boost::asio::ip::tcp_raw_protocol; | |
47 | using boost::date_time::time_resolution_traits_adapted64_impl; | |
cd395966 | 48 | using boost::function; |
f8a8d9dc GMF |
49 | using boost::posix_time::microsec_clock; |
50 | using boost::posix_time::ptime; | |
51 | using boost::posix_time::seconds; | |
e58d7507 | 52 | using boost::shared_ptr; |
f8a8d9dc | 53 | using I2n::Logger::GlobalLogger; |
cd395966 GMF |
54 | |
55 | //----------------------------------------------------------------------------- | |
56 | // TcpPinger | |
57 | //----------------------------------------------------------------------------- | |
58 | ||
a4c872dd GMF |
59 | /** |
60 | * @brief Parameterized constructor. | |
61 | * | |
83ba151a | 62 | * @param io_serv The one @c io_service object that controls async processing |
a4c872dd GMF |
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 | */ | |
84525f34 | 68 | TcpPinger::TcpPinger( |
365036be | 69 | const IoServiceItem io_serv, |
a4c872dd | 70 | const tcp_raw_protocol::socket::protocol_type &protocol, |
60d13f4f | 71 | const string &source_network_interface_name, |
c2a393ee | 72 | const int rst_reply_timeout_in_sec |
f8a8d9dc | 73 | ) : |
f8a8d9dc | 74 | DestinationEndpoint(), |
3b6a0314 | 75 | Protocol( protocol ), |
365036be | 76 | Socket( *io_serv, Protocol ), |
3b668576 | 77 | NetInterface( source_network_interface_name, Socket ), |
365036be | 78 | TcpSegmentReceiveTimer( *io_serv ), |
f8a8d9dc GMF |
79 | Identifier( 0 ), |
80 | SequenceNumber( 0 ), | |
81 | TimeSent( microsec_clock::universal_time() ), | |
82 | ReplyBuffer(), | |
83 | ReceivedReply( false ), | |
c2a393ee | 84 | RstReplyTimeoutInSec( rst_reply_timeout_in_sec ), |
f8a8d9dc GMF |
85 | PingerStatus( PingStatus_NotSent ), |
86 | PingDoneCallback() | |
cd395966 | 87 | { |
ac507127 | 88 | if ( !NetInterface.bind() ) |
f8a8d9dc | 89 | { |
0fd358ca | 90 | GlobalLogger.error() << "Could not bind the socket with the local interface. " |
c754f48e | 91 | << ::strerror( errno ) << endl; |
f8a8d9dc GMF |
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) ); | |
cd395966 GMF |
100 | } |
101 | ||
102 | TcpPinger::~TcpPinger() | |
103 | { | |
104 | } | |
105 | ||
f8a8d9dc GMF |
106 | /** |
107 | * @brief Ping a destination address from an available local source. | |
108 | * | |
109 | * @param destination_ip The address of the host to ping. | |
1309d0e4 GMF |
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. | |
f8a8d9dc | 114 | */ |
cd395966 | 115 | void TcpPinger::ping( |
23f51766 | 116 | const address &destination_ip, |
080ca508 | 117 | const uint16_t destination_port, |
9c0dcf33 | 118 | function<void(PingStatus,long)> ping_done_callback |
cd395966 GMF |
119 | ) |
120 | { | |
080ca508 | 121 | BOOST_ASSERT( ( 0 < destination_port ) && ( destination_port < numeric_limits<uint16_t>::max() ) ); |
f8a8d9dc GMF |
122 | |
123 | PingDoneCallback = ping_done_callback; | |
124 | ||
125 | // Prepare ping | |
126 | set_ping_status( PingStatus_NotSent ); | |
127 | ||
1309d0e4 | 128 | set_destination_endpoint( destination_ip, destination_port ); |
f8a8d9dc GMF |
129 | |
130 | start_send(); | |
131 | start_receive(); | |
132 | } | |
133 | ||
5a9bc2d1 CH |
134 | void TcpPinger::stop_pinging() |
135 | { | |
136 | } | |
137 | ||
6fd0993e | 138 | address TcpPinger::get_source_address() const |
f8a8d9dc | 139 | { |
3b6a0314 | 140 | return NetInterface.get_address( Protocol ); |
f8a8d9dc GMF |
141 | } |
142 | ||
865e078a | 143 | address TcpPinger::get_destination_address() const |
f8a8d9dc | 144 | { |
865e078a | 145 | return DestinationEndpoint.address(); |
f8a8d9dc GMF |
146 | } |
147 | ||
6f6db388 GMF |
148 | uint16_t TcpPinger::get_source_port() const |
149 | { | |
619a2293 | 150 | uint16_t source_port = static_cast<uint16_t>( ( random() % 16383u ) + 49152u ); // same as random() % 65536; |
6f6db388 GMF |
151 | |
152 | return source_port; | |
153 | } | |
154 | ||
1309d0e4 GMF |
155 | uint16_t TcpPinger::get_destination_port() const |
156 | { | |
157 | return DestinationEndpoint.port(); | |
158 | } | |
159 | ||
160 | void TcpPinger::set_destination_endpoint( | |
23f51766 | 161 | const address &destination_ip, |
080ca508 | 162 | const uint16_t destination_port |
1309d0e4 | 163 | ) |
f8a8d9dc | 164 | { |
080ca508 | 165 | BOOST_ASSERT( ( 0 < destination_port ) && ( destination_port < numeric_limits<uint16_t>::max() ) ); |
619a2293 | 166 | BOOST_ASSERT( sizeof(uint16_t) <= sizeof(destination_port) ); |
f8a8d9dc | 167 | |
1309d0e4 | 168 | uint16_t port = static_cast<uint16_t>( destination_port ); |
23f51766 | 169 | DestinationEndpoint = tcp_raw_protocol::endpoint( destination_ip, port ); |
f8a8d9dc GMF |
170 | } |
171 | ||
172 | void TcpPinger::start_send() | |
173 | { | |
174 | ++SequenceNumber; | |
175 | ||
a7d7778b | 176 | // Create a TCP header for a SYN request. |
865e078a GMF |
177 | address source_address = get_source_address(); |
178 | address destination_address = get_destination_address(); | |
3596e4f4 GMF |
179 | uint16_t source_port = get_source_port(); |
180 | uint16_t destination_port = get_destination_port(); | |
a7d7778b | 181 | TcpSegmentItem tcp_segment = TcpSegmentFactory::create_tcp_segment_syn_request( |
3b6a0314 | 182 | Protocol, |
865e078a | 183 | source_address, destination_address, |
3596e4f4 | 184 | source_port, destination_port, SequenceNumber ); |
f8a8d9dc | 185 | |
3596e4f4 | 186 | send_ack_request( tcp_segment ); |
4efd7f33 GMF |
187 | } |
188 | ||
3596e4f4 | 189 | void TcpPinger::send_ack_request( const TcpSegmentItem tcp_segment ) |
b594b7d9 | 190 | { |
f8a8d9dc GMF |
191 | // Encode the request packet. |
192 | boost::asio::streambuf request_buffer; | |
193 | ostream os( &request_buffer ); | |
080ca508 GMF |
194 | if ( !tcp_segment->write( os ) ) |
195 | { | |
0fd358ca | 196 | GlobalLogger.error() << "Fail writing ping data." << endl; |
080ca508 | 197 | } |
f8a8d9dc | 198 | |
f8a8d9dc GMF |
199 | TimeSent = microsec_clock::universal_time(); |
200 | ||
ebafed7c GMF |
201 | address dest_address = DestinationEndpoint.address(); |
202 | string dest_address_string = dest_address.to_string(); | |
f8a8d9dc GMF |
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 | { | |
0fd358ca | 213 | GlobalLogger.error() << "Fail sending ping data." |
3b6a0314 | 214 | << "Amount of bytes sent differs from the buffer." << endl; |
f8a8d9dc GMF |
215 | } |
216 | } | |
217 | catch ( const exception &ex ) | |
218 | { | |
0fd358ca | 219 | GlobalLogger.error() << "Fail sending ping data. " << ex.what() << endl; |
f8a8d9dc GMF |
220 | } |
221 | ||
b594b7d9 | 222 | // Tell how long to wait for the reply |
81adfb7b | 223 | schedule_timeout_rst_reply(); |
f8a8d9dc GMF |
224 | } |
225 | ||
81adfb7b GMF |
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( | |
bd66be8f | 234 | boost::bind( &TcpPinger::handle_ping_done, this ) |
81adfb7b GMF |
235 | ); |
236 | } | |
237 | ||
bd66be8f GMF |
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() | |
f8a8d9dc | 244 | { |
bd66be8f GMF |
245 | // Check ReceivedReply as the timer handler |
246 | // is also called by Timer.cancel(); | |
080ca508 | 247 | if ( !ReceivedReply ) |
bd66be8f GMF |
248 | { |
249 | GlobalLogger.info() << "Request timed out" << endl; | |
250 | ||
251 | set_ping_status( PingStatus_FailureTimeout ); | |
252 | } | |
253 | ||
254 | // Call ping-done handler | |
a7b15639 CH |
255 | PingDoneCallback( PingerStatus, static_cast<long>( |
256 | (microsec_clock::universal_time() - TimeSent).total_microseconds()) ); | |
f8a8d9dc GMF |
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 | { | |
f8a8d9dc GMF |
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 | ||
b23911dd | 277 | try |
f8a8d9dc | 278 | { |
b23911dd GMF |
279 | istream is( &ReplyBuffer ); |
280 | if ( !is ) | |
281 | { | |
0fd358ca | 282 | GlobalLogger.error() << "Can't handle ReplyBuffer" << endl; |
b23911dd GMF |
283 | return; |
284 | } | |
f8a8d9dc | 285 | |
3596e4f4 | 286 | // Decode the reply segment. |
3b6a0314 | 287 | TcpSegmentItem tcp_segment = TcpSegmentFactory::create_tcp_segment( Protocol, is ); |
f8a8d9dc | 288 | |
a7d7778b GMF |
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. | |
3596e4f4 | 292 | if ( tcp_segment->match_rst_reply( DestinationEndpoint.address() ) ) |
b23911dd GMF |
293 | { |
294 | ReceivedReply = true; | |
f8a8d9dc | 295 | |
3596e4f4 | 296 | tcp_segment->print_rst_reply( TimeSent ); |
f8a8d9dc | 297 | |
b23911dd | 298 | set_ping_status( PingStatus_SuccessReply ); |
e0059ebe | 299 | |
529e5587 | 300 | TcpSegmentReceiveTimer.cancel(); //lint !e534 |
b23911dd | 301 | } |
a7d7778b GMF |
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 | } | |
b23911dd GMF |
315 | // Unknown TCP reply, start another receive till timeout |
316 | else | |
317 | { | |
318 | start_receive(); | |
319 | } | |
320 | } | |
321 | catch ( ... ) | |
322 | { | |
0fd358ca | 323 | GlobalLogger.notice() << "Exception during ICMP parse. " |
b23911dd GMF |
324 | << "Starting another receive till timeout." << endl; |
325 | start_receive(); | |
f8a8d9dc | 326 | } |
f8a8d9dc GMF |
327 | } |
328 | ||
f5c0f0d0 | 329 | void TcpPinger::set_ping_status( PingStatus ping_status ) |
f8a8d9dc GMF |
330 | { |
331 | PingerStatus = ping_status; | |
332 | } | |
4606d2b1 CH |
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 | } |