Do not invoke any webcheck url to often (once every 10 minutes will be the default).
[bpdyndnsd] / src / ip_addr_helper.cpp
1 /** @file
2  * @brief IPHelper class implementation. This class represents a Helper to get the actual IP.
3  *
4  *
5  *
6  * @copyright Intra2net AG
7  * @license GPLv2
8 */
9
10 #include "ip_addr_helper.h"
11 #include <boost/asio.hpp>
12 #include <boost/regex.hpp>
13
14 using namespace std;
15
16 namespace net = boost::asio;
17
18 /**
19  * Default constructor.
20  */
21 IPAddrHelper::IPAddrHelper()
22     : Log(new Logger)
23     , WebcheckInterval(0)
24     , ProxyPort(0)
25     , UseIPv6(false)
26 {
27 }
28
29
30 /**
31  * Constructor.
32  */
33 IPAddrHelper::IPAddrHelper(const Logger::Ptr _log, const string& _webcheck_url, const string& _webcheck_url_alt, const int _webcheck_interval ,const bool _use_ipv6, const string& _proxy, const int _proxy_port)
34     : Log(_log)
35     , WebcheckIpUrl(_webcheck_url)
36     , WebcheckIpUrlAlt(_webcheck_url_alt)
37     , WebcheckInterval(_webcheck_interval)
38     , LastWebcheck(0)
39     , Proxy(_proxy)
40     , ProxyPort(_proxy_port)
41     , UseIPv6(_use_ipv6)
42 {
43     Hostname = net::ip::host_name();
44     Log->print_hostname(Hostname);
45 }
46
47
48 /**
49  * Default destructor
50  */
51 IPAddrHelper::~IPAddrHelper()
52 {
53 }
54
55
56 /**
57  * Tests if a given IP is a local address
58  * @param ip The IP to test
59  * @return true if given IP is local, false if not.
60  */
61 bool IPAddrHelper::is_local(const string ip) const
62 {
63     // 127.0.0.1
64     boost::regex expr_loopback("127\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}");
65
66     // 192.168.x.x
67     boost::regex expr_192("192\\.168\\.[0-9]{1,3}\\.[0-9]{1,3}");
68
69     // 10.x.x.x
70     boost::regex expr_10("10\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}");
71
72     // 169.254.x.x
73     boost::regex expr_169_254("169\\.254\\.[0-9]{1,3}\\.[0-9]{1,3}");
74
75     // 172.16.x.x -> 172.31.x.x
76     boost::regex expr_172_1("172\\.1[6-9]{1}\\.[0-9]{1,3}\\.[0-9]{1,3}");
77     boost::regex expr_172_2("172\\.2[0-9]{1}\\.[0-9]{1,3}\\.[0-9]{1,3}");
78     boost::regex expr_172_3("172\\.3[0-1]{1}\\.[0-9]{1,3}\\.[0-9]{1,3}");
79
80     // It's time to test against the regex patterns
81     if ( boost::regex_search(ip,expr_loopback) )
82     {
83         Log->print_regex_match(expr_loopback.str(),ip);
84         return true;
85     }
86     else if ( boost::regex_search(ip,expr_192) )
87     {
88         Log->print_regex_match(expr_192.str(),ip);
89         return true;
90     }
91     else if ( boost::regex_search(ip,expr_10) )
92     {
93         Log->print_regex_match(expr_10.str(),ip);
94         return true;
95     }
96     else if ( boost::regex_search(ip,expr_169_254) )
97     {
98         Log->print_regex_match(expr_169_254.str(),ip);
99         return true;
100     }
101     else if ( boost::regex_search(ip,expr_172_1) )
102     {
103         Log->print_regex_match(expr_172_1.str(),ip);
104         return true;
105     }
106     else if ( boost::regex_search(ip,expr_172_2) )
107     {
108         Log->print_regex_match(expr_172_2.str(),ip);
109         return true;
110     }
111     else if ( boost::regex_search(ip,expr_172_3) )
112     {
113         Log->print_regex_match(expr_172_3.str(),ip);
114         return true;
115     }
116
117     return false;
118 }
119
120
121 /**
122  * Get the actual IP of this host through a conventional DNS query or through a IP webcheck URL if configured so.
123  * @return A string representation of the actual IP in dotted format or an empty string if something went wrong.
124  */
125 string IPAddrHelper::get_actual_ip()
126 {
127     string ip;
128     if ( WebcheckIpUrl.empty() )
129     {
130         ip = dns_query("");
131     }
132     else
133     {
134         ip = webcheck_ip();
135     }
136
137     // If IP is within a private range then return ""
138     if ( is_local(ip) )
139     {
140         Log->print_ip_is_local(ip);
141         return "";
142     }
143
144     return ip;
145 }
146
147
148 /**
149  * Get the actual IP of the given host through a DNS query.
150  * @param _hostname The hostname for the dns lookup, if empty string, then perform a dns lookup to the local hostname.
151  * @return A string representation of the actual IP in dotted format or an empty string if something went wrong.
152  */
153 string IPAddrHelper::dns_query(const string& _hostname) const
154 {
155     string ip_addr_v4;
156     string ip_addr_v6;
157
158     string hostname = Hostname;
159     if ( !_hostname.empty() )
160         hostname = _hostname;
161
162     try
163     {
164         // BOOST asio isn't the simplest way to perform a DNS lookup, but it works just fine.
165         net::io_service io_service;
166         net::ip::tcp::resolver resolver(io_service);
167         net::ip::tcp::resolver::query query(hostname, "0");
168         net::ip::tcp::resolver::iterator endpoint_iterator = resolver.resolve(query);
169         net::ip::tcp::resolver::iterator end;
170         while(endpoint_iterator != end)
171         {
172             net::ip::address ip;
173             ip = endpoint_iterator->endpoint().address();
174             if ( ip.is_v4() )
175                 ip_addr_v4 = ip.to_string();
176             else if ( ip.is_v6() )
177                 ip_addr_v6 = ip.to_string();
178             Log->print_own_ipv4(ip_addr_v4, hostname);
179             if ( UseIPv6 == true )
180                 Log->print_own_ipv6(ip_addr_v6, hostname);
181             endpoint_iterator++;
182         }
183         io_service.reset();
184     }
185     catch(exception& e)
186     {
187         Log->print_error_hostname_to_ip(e.what(),hostname);
188         return "";
189     }
190
191     if ( (UseIPv6 == true) && (ip_addr_v6 != "") )
192         return ip_addr_v6;
193
194     return ip_addr_v4;
195 }
196
197
198 /**
199  * Get the actual IP of this host through a IP webcheck URL.
200  * @return A string representation of the actual IP in dotted format or an empty string if something went wrong.
201  */
202 string IPAddrHelper::webcheck_ip()
203 {
204     // Init IPAddress with a empty string.
205     string ip_addr = "";
206
207     // Get the current time.
208     int current_time = time(NULL);
209
210     // Test if webcheck is allowed due to webcheck_interval.
211     if ( (LastWebcheck + (WebcheckInterval*60)) >= current_time )
212     {
213         // Webcheck not allowed, log it and return empty string.
214         Log->print_webcheck_exceed_interval( LastWebcheck, (WebcheckInterval*60), current_time );
215         return ip_addr;
216     }
217
218     // Init CURL buffers
219     string curl_writedata_buff;
220     char curl_err_buff[CURL_ERROR_SIZE];
221     int curl_err_code=1;
222
223     // Init URL List
224     list<string> url_list;
225     url_list.push_back(WebcheckIpUrl);
226     url_list.push_back(WebcheckIpUrlAlt);
227     string actual_url;
228
229     // Init CURL
230     CURL * curl_easy_handle = init_curl(curl_writedata_buff,curl_err_buff);
231
232     // If perform_curl_operation returns a connection problem indicating return code, try the next ip webcheck url if there is one.
233     while ( (curl_err_code == 1) && (url_list.size() != 0) && (url_list.front() != "") )
234     {
235         // Set URL
236         actual_url = url_list.front();
237         url_list.pop_front();
238         set_curl_url(curl_easy_handle,actual_url);
239
240         // Perform curl operation, err_code of 1 indicated connection problem, so try next url.
241         curl_err_code = perform_curl_operation(curl_easy_handle, curl_err_buff, actual_url);
242     }
243
244     // Cleanup CURL handle
245     curl_easy_cleanup(curl_easy_handle);
246
247     // If curl_err_code is not 0, the ip couldn't be determined through any configured webcheck url.
248     if ( curl_err_code != 0 )
249     {
250         // Log it and return the empty string.
251         Log->print_webcheck_no_ip();
252         return ip_addr;
253     }
254
255     // Log the received curl data.
256     Log->print_received_curl_data(curl_writedata_buff);
257
258     // Try to parse a IPAddress out of the received data.
259     ip_addr = parse_ip(curl_writedata_buff);
260
261     // Set the LastWebcheck time to current time.
262     LastWebcheck = current_time;
263
264     // Return the parsed IPAddress.
265     return ip_addr;
266 }
267
268
269 /**
270  * Tries to find a valid IPv4 Address in dottet format in a given string and returns the IP-Address found.
271  * @param data The string data to search in for a valid IPv4-Address.
272  * @return The IP Address found or an empty string.
273  */
274 string IPAddrHelper::parse_ip(const string& data) const
275 {
276     string ip = "";
277
278     // Regex for valid ip address
279     boost::regex expr("([0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3})");
280
281     boost::smatch matches;
282
283     if ( boost::regex_search(data,matches,expr) )
284     {
285         ip = matches[1];
286         Log->print_regex_found_ip(ip);
287     }
288     else
289     {
290         Log->print_regex_ip_not_found(data);
291     }
292
293     return ip;
294 }
295
296
297 /**
298  * Performs the curl operation.
299  * @param curl_easy_handle The initialized and configured curl handle.
300  * @param curl_err_buff The pointer to the curl error buffer to get error messages from.
301  * @param actual_url The actual configured URL.
302  * @return 0 if all is fine, 1 if an connection problem to the configured url occurs, -1 on other errors.
303  */
304 int IPAddrHelper::perform_curl_operation(CURL * curl_easy_handle, char* curl_err_buff, const string& actual_url) const
305 {
306     int curl_err_code;
307     if ( (curl_err_code = curl_easy_perform(curl_easy_handle) ) != 0 )
308     {
309         // CURL error occured
310         if ( (curl_err_code == CURLE_COULDNT_CONNECT) || (curl_err_code == CURLE_OPERATION_TIMEOUTED) || (curl_err_code == CURLE_COULDNT_RESOLVE_HOST) )
311         {
312             // In case of connection problems we should return 1, that the fallback url will be used.
313             Log->print_webcheck_url_connection_problem(curl_err_buff, actual_url);
314             return 1;
315         }
316         else
317         {
318             // other error
319             Log->print_webcheck_error(curl_err_buff, actual_url);
320             return -1;
321         }
322     }
323     // Operation performed without any problems
324     return 0;
325 }
326
327
328 /**
329  * Sets a url to the easy curl handle
330  * @param curl_easy_handle The easy curl handle to set the url for.
331  * @param url The url to set.
332  */
333 void IPAddrHelper::set_curl_url(CURL * curl_easy_handle, const string& url) const
334 {
335     curl_easy_setopt(curl_easy_handle,CURLOPT_URL,url.c_str());
336 }
337
338
339 /**
340  * Initialized curl easy handle with a few options.
341  * @param curl_writedata_buff Reference to a string wich will be filled with the curl result
342  * @param curl_err_buff A pointer to an char array which will be filled with error message.
343  * @return A pointer to the easy curl handle.
344  */
345 CURL * IPAddrHelper::init_curl(string& curl_writedata_buff,char* curl_err_buff) const
346 {
347     CURL *curl_easy_handle = curl_easy_init();
348
349     curl_easy_setopt(curl_easy_handle,CURLOPT_NOPROGRESS,1);
350     curl_easy_setopt(curl_easy_handle,CURLOPT_CONNECTTIMEOUT,5);
351     curl_easy_setopt(curl_easy_handle,CURLOPT_TIMEOUT,10);
352     curl_easy_setopt(curl_easy_handle,CURLOPT_BUFFERSIZE,1024);
353     curl_easy_setopt(curl_easy_handle,CURLOPT_ERRORBUFFER,curl_err_buff);
354     curl_easy_setopt(curl_easy_handle,CURLOPT_WRITEFUNCTION,http_receive);
355     curl_easy_setopt(curl_easy_handle,CURLOPT_WRITEDATA,&curl_writedata_buff);
356
357     if ( !Proxy.empty() )
358     {
359         curl_easy_setopt(curl_easy_handle,CURLOPT_PROXY,Proxy.c_str());
360         curl_easy_setopt(curl_easy_handle,CURLOPT_PROXYPORT,ProxyPort);
361     }
362
363     return curl_easy_handle;
364 }
365
366
367 /**
368  * Callback Function is called every time CURL is receiving data from HTTPS-Server and will copy all received Data to the given stream pointer
369  * @param inBuffer Pointer to input.
370  * @param size How many mem blocks are received
371  * @param nmemb size of each memblock
372  * @param outBuffer Pointer to output stream.
373  * @return The size received.
374  */
375 int IPAddrHelper::http_receive( char *inBuffer, size_t size, size_t nmemb, string *outBuffer )
376 {
377     outBuffer->append(inBuffer);
378     return (size*nmemb);
379 }