Update pingcheck to work with cmake 3.28
[pingcheck] / src / tcp / tcppinger.cpp
... / ...
CommitLineData
1/*
2The software in this package is distributed under the GNU General
3Public License version 2 (with a special exception described below).
4
5A copy of GNU General Public License (GPL) is included in this distribution,
6in the file COPYING.GPL.
7
8As a special exception, if other files instantiate templates or use macros
9or inline functions from this file, or you compile this file and link it
10with other works to produce a work based on this file, this file
11does not by itself cause the resulting work to be covered
12by the GNU General Public License.
13
14However the source code for this file must still be made available
15in accordance with section (3) of the GNU General Public License.
16
17This exception does not invalidate any other reasons why a work based
18on 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
42using namespace std;
43using boost::asio::const_buffers_1;
44using boost::asio::io_service;
45using boost::asio::ip::address;
46using boost::asio::ip::tcp_raw_protocol;
47using boost::date_time::time_resolution_traits_adapted64_impl;
48using boost::function;
49using boost::posix_time::microsec_clock;
50using boost::posix_time::ptime;
51using boost::posix_time::seconds;
52using boost::shared_ptr;
53using 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 */
68TcpPinger::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
102TcpPinger::~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 */
115void 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
134void TcpPinger::stop_pinging()
135{
136}
137
138address TcpPinger::get_source_address() const
139{
140 return NetInterface.get_address( Protocol );
141}
142
143address TcpPinger::get_destination_address() const
144{
145 return DestinationEndpoint.address();
146}
147
148uint16_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
155uint16_t TcpPinger::get_destination_port() const
156{
157 return DestinationEndpoint.port();
158}
159
160void 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
172void 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
189void 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
226void 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 **/
243void 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
259void 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
271void 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
329void TcpPinger::set_ping_status( PingStatus ping_status )
330{
331 PingerStatus = ping_status;
332}
333
334PingerItem 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}