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