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