merged PingRotate into PingScheduler; fixed save/load of cache to/from file; started...
[pingcheck] / src / host / pingrotate.cpp
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
21 #include "host/pingrotate.h"
22
23 #include <logfunc.hpp>
24
25 #include <boost/bind.hpp>
26 #include <boost/foreach.hpp>
27
28 #include "boost_assert_handler.h"
29 #include "dns/dnsmaster.h"
30 #include "host/pingerfactory.h"
31
32 using namespace std;
33 using boost::function;
34 using boost::shared_ptr;
35
36 using I2n::Logger::GlobalLogger;
37
38 //-----------------------------------------------------------------------------
39 // PingRotate
40 //-----------------------------------------------------------------------------
41
42 /**
43  * @brief Parameterized constructor.
44  *
45  * @param io_serv The one @c io_service object that controls async processing
46  * @param network_interface The name of the network interface from where to
47  * dispatch the pings.
48  * @param destination_address The remote address to ping.
49  * @param destination_port The remote port to ping.
50  * @param protocol_list A list of protocols to be used to ping the
51  * host. The protocols will be used in the order they are in the list.
52  */
53 PingRotate::PingRotate(
54         const IoServiceItem io_serv,
55         const string &network_interface,
56         const string &destination_address,
57         const uint16_t destination_port,
58         const int resolved_ip_ttl_threshold,
59         const int ping_reply_timeout,
60         const PingProtocolList &protocol_list
61 ) :
62     IoService( io_serv ),
63     NetworkInterfaceName( network_interface ),
64     Resolver(),
65     DestinationAddress( destination_address ),
66     DestinationPort( destination_port ),
67     ResolvedIpTtlThreshold( resolved_ip_ttl_threshold ),
68     PingReplyTimeout( ping_reply_timeout ),
69     ProtocolRotate( protocol_list.size() ),
70     Ping(),
71     PingDoneCallback(),
72     DnsResolutionFinished( true ),
73     WantToPing( false )
74 {
75     BOOST_ASSERT( !network_interface.empty() );
76     BOOST_ASSERT( !destination_address.empty() );
77     BOOST_ASSERT( 0 < destination_port );
78     BOOST_ASSERT( 0 < protocol_list.size() );
79
80     // fill circular buffer with protocols
81     BOOST_FOREACH( const PingProtocol &prot, protocol_list )
82         ProtocolRotate.push_back(prot);
83
84     init_ping_protocol();
85 }
86
87 /**
88  * @brief Destructor.
89  */
90 PingRotate::~PingRotate()
91 {
92 }
93
94 /**
95  * @brief Ping a destination address from an available local source.
96  *
97  * @param done_handler Done handler will be called on successful ping or timeout.
98  *
99  * @return void.
100  */
101 void PingRotate::ping( function<void(bool)> ping_done_callback )
102 {
103     BOOST_ASSERT( ( 0 < DestinationPort ) && ( DestinationPort < numeric_limits<uint16_t>::max() ) );
104
105     set_ping_done_callback( ping_done_callback );
106
107     update_ping_protocol();
108
109     WantToPing = true;
110     try_to_ping();
111 }
112
113 void PingRotate::try_to_ping()
114 {
115     if ( !WantToPing )
116     {
117         GlobalLogger.info() << "PingRotate: not pinging yet (not requested to)";
118         return;
119     }
120     else if ( !DnsResolutionFinished )
121     {
122         GlobalLogger.info() << "PingRotate: not pinging yet (DNS not finished)";
123         return;
124     }
125
126     GlobalLogger.info() << "PingRotate: start ping";
127     WantToPing = false;
128     string destination_ip = Resolver->get_next_ip().get_ip().to_string();
129     // TODO: pinger will probably re-create IP from this
130     // --> change pingers to accept ip::address
131
132     Ping->ping(
133             destination_ip,
134             DestinationPort,
135             boost::bind(&PingRotate::ping_done_handler, this, _1)
136     );
137 }
138
139 void PingRotate::stop_pinging()
140 {
141     Ping->stop_pinging();
142 }
143
144 void PingRotate::start_resolving_ping_address()                                                         //lint !e1762
145 {
146     DnsResolutionFinished = false;
147     Resolver->async_resolve( boost::bind(&PingRotate::dns_resolve_callback,
148                                        this, _1, _2) );
149 }
150
151 int PingRotate::get_resolved_ip_count() const
152 {
153     return Resolver->get_resolved_ip_count();
154 }
155
156 bool PingRotate::have_up_to_date_ip() const
157 {
158     return Resolver->have_up_to_date_ip();
159 }
160
161 void PingRotate::set_ping_done_callback( function<void(bool)> ping_done_callback )
162 {
163     PingDoneCallback = ping_done_callback;
164 }
165
166 void PingRotate::ping_done_handler( bool ping_success ) const
167 {
168     PingDoneCallback( ping_success );
169 }
170
171 void PingRotate::init_ping_protocol()
172 {
173     get_next_ping_protocol();
174 }
175
176 void PingRotate::update_ping_protocol()
177 {
178     if ( can_change_ping_protocol() )
179     {
180         get_next_ping_protocol();
181     }
182 }
183
184 void PingRotate::get_next_ping_protocol()
185 {
186     PingProtocol ping_protocol = ProtocolRotate.front();
187     ProtocolRotate.pop_front();
188     ProtocolRotate.push_back(ping_protocol);
189
190     Ping = PingerFactory::createPinger( ping_protocol, IoService, NetworkInterfaceName, PingReplyTimeout );
191
192     update_dns_resolver( ping_protocol );
193 }
194
195 bool PingRotate::can_change_ping_protocol() const
196 {
197     // TODO can_change_ping_protocol() and get_next_ping_protocol() may be implemented in a Algorithm
198     // class that can be exchanged in this class to provide an algorithm neutral class
199     return true;
200 }
201
202 void PingRotate::update_dns_resolver( PingProtocol current_protocol )
203 {
204     // DNS master caches created resolvers and resolved IPs, so this will
205     // probably just return an existing resolver with already resolved IPs for
206     // requested protocol ( ICMP/TCP is ignored, only IPv4/v6 is important)
207     Resolver = DnsMaster::get_instance()->get_resolver_for(DestinationAddress,
208                                                            current_protocol);
209     // start resolving if no ips available
210     if ( !Resolver->have_up_to_date_ip() )
211         start_resolving_ping_address();
212 }
213
214 void PingRotate::dns_resolve_callback(const bool was_success,
215                                       const int recursion_count)
216 {
217     GlobalLogger.info() << "PingRotate: dns resolution finished "
218                         << "with success = " << was_success << " "
219                         << "and recursion_count = " << recursion_count;
220     throw std::runtime_error("Only debugging DNS -- exit by error");
221     if (DnsResolutionFinished)
222     {   // there were probably several calls to async_resolve before it could
223         // finish --> ignore this callback
224         GlobalLogger.info() << "PingRotate: dns resolve callback called "
225                             << "but dns is marked as finished already - ignore";
226         return;
227     }
228     else
229     {
230         DnsResolutionFinished = true;
231         try_to_ping();
232     }
233 }