2 * @brief IPHelper class implementation. This class represents a Helper to get the actual IP.
6 * @copyright Intra2net AG
10 #include "ip_addr_helper.hpp"
11 #include <boost/asio.hpp>
12 #include <boost/regex.hpp>
13 #include <arpa/inet.h>
14 #include <sys/socket.h>
21 namespace net = boost::asio;
24 * Default constructor.
26 IPAddrHelper::IPAddrHelper()
39 IPAddrHelper::IPAddrHelper(const Logger::Ptr _log, const string& _webcheck_url, const string& _webcheck_url_alt, const int _webcheck_interval, const time_t _last_webcheck ,const bool _use_ipv6, const string& _proxy, const int _proxy_port)
41 , WebcheckIpUrl(_webcheck_url)
42 , WebcheckIpUrlAlt(_webcheck_url_alt)
43 , WebcheckInterval(_webcheck_interval)
44 , LastWebcheck(_last_webcheck)
46 , ProxyPort(_proxy_port)
49 Hostname = net::ip::host_name();
50 Log->print_hostname(Hostname);
57 IPAddrHelper::~IPAddrHelper()
63 * Tests if a given IP is a local IPv6 address
64 * @param ip The IP to test
65 * @return true if given IP is local, false if not.
67 bool IPAddrHelper::is_local_ipv6(const string ip) const
70 boost::regex expr_any_ipv6("^::$");
73 boost::regex expr_loopback_ipv6("^::1$");
75 // IPV6 local unicast address
76 boost::regex expr_local_unicast_ipv6("^fc00:");
79 boost::regex expr_link_local_ipv6("^fe[8,9,a,b]{1}");
82 boost::regex expr_site_local_ipv6("^fe[c,d,e,f]{1}");
84 // It's time to test against the regex patterns
85 if ( boost::regex_search(ip,expr_any_ipv6) )
87 Log->print_regex_match(expr_any_ipv6.str(),ip);
90 else if ( boost::regex_search(ip,expr_loopback_ipv6) )
92 Log->print_regex_match(expr_loopback_ipv6.str(),ip);
95 else if ( boost::regex_search(ip,expr_local_unicast_ipv6) )
97 Log->print_regex_match(expr_local_unicast_ipv6.str(),ip);
100 else if ( boost::regex_search(ip,expr_link_local_ipv6) )
102 Log->print_regex_match(expr_link_local_ipv6.str(),ip);
105 else if ( boost::regex_search(ip,expr_site_local_ipv6) )
107 Log->print_regex_match(expr_site_local_ipv6.str(),ip);
116 * Tests if a given IP is a local IPv4 address
117 * @param ip The IP to test
118 * @return true if given IP is local, false if not.
120 bool IPAddrHelper::is_local_ipv4(const string ip) const
123 boost::regex expr_loopback("127\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}");
126 boost::regex expr_192("192\\.168\\.[0-9]{1,3}\\.[0-9]{1,3}");
129 boost::regex expr_10("10\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}");
132 boost::regex expr_169_254("169\\.254\\.[0-9]{1,3}\\.[0-9]{1,3}");
134 // 172.16.x.x -> 172.31.x.x
135 boost::regex expr_172_1("172\\.1[6-9]{1}\\.[0-9]{1,3}\\.[0-9]{1,3}");
136 boost::regex expr_172_2("172\\.2[0-9]{1}\\.[0-9]{1,3}\\.[0-9]{1,3}");
137 boost::regex expr_172_3("172\\.3[0-1]{1}\\.[0-9]{1,3}\\.[0-9]{1,3}");
139 // It's time to test against the regex patterns
140 if ( boost::regex_search(ip,expr_loopback) )
142 Log->print_regex_match(expr_loopback.str(),ip);
145 else if ( boost::regex_search(ip,expr_192) )
147 Log->print_regex_match(expr_192.str(),ip);
150 else if ( boost::regex_search(ip,expr_10) )
152 Log->print_regex_match(expr_10.str(),ip);
155 else if ( boost::regex_search(ip,expr_169_254) )
157 Log->print_regex_match(expr_169_254.str(),ip);
160 else if ( boost::regex_search(ip,expr_172_1) )
162 Log->print_regex_match(expr_172_1.str(),ip);
165 else if ( boost::regex_search(ip,expr_172_2) )
167 Log->print_regex_match(expr_172_2.str(),ip);
170 else if ( boost::regex_search(ip,expr_172_3) )
172 Log->print_regex_match(expr_172_3.str(),ip);
181 * Get the actual IP of this host through a conventional DNS query or through a IP webcheck URL if configured so.
182 * @return A string representation of the actual IP in dotted format or an empty string if something went wrong.
184 string IPAddrHelper::get_actual_ip( bool use_webcheck )
188 if ( !WebcheckIpUrl.empty() && use_webcheck )
191 ip = get_local_wan_nic_ip();
198 * Get the IP address of the local wan interface if there is one.
199 * @return The IP address of the wan interface or an empty string if something went wrong.
201 string IPAddrHelper::get_local_wan_nic_ip() const
203 struct ifaddrs *if_addr_struct, *ifa;
204 unsigned short address_family;
206 char ip_addr_buff[NI_MAXHOST];
207 list<string> external_ipv4_addresses;
208 list<string> external_ipv6_addresses;
210 // Get linked list of all interface addresses.
211 if ( getifaddrs(&if_addr_struct) == -1 )
213 Log->print_error_getting_local_wan_ip("getifaddrs", strerror(errno));
214 freeifaddrs(if_addr_struct);
218 // Iterate through the linked list.
219 for ( ifa = if_addr_struct; ifa != NULL; ifa = ifa->ifa_next)
221 // Skip interfaces without IP addresses
225 // Get the address family of the actual address.
226 address_family = ifa->ifa_addr->sa_family;
228 // If it is an IPv4 then process further.
229 if ( address_family == AF_INET )
231 // Translate the address to a protocol independent representation (dottet format). Copy address into ip_addr_buff.
232 ret_val = getnameinfo(ifa->ifa_addr, (socklen_t)sizeof(struct sockaddr_in), ip_addr_buff, NI_MAXHOST, NULL, 0, NI_NUMERICHOST);
235 Log->print_error_getting_local_wan_ip("getnameinfo", gai_strerror(ret_val));
236 freeifaddrs(if_addr_struct);
240 // Generate IPv4 string out of char array.
241 string ipv4_addr(ip_addr_buff);
243 Log->print_own_ipv4(ipv4_addr, Hostname);
245 // Test if it is a local address.
246 if ( !is_local_ipv4(ipv4_addr) )
247 external_ipv4_addresses.push_back(ipv4_addr);
249 Log->print_ip_is_local(ipv4_addr);
251 // If it is an IPv6 address and IPv6 is enabled then process further.
252 else if ( (address_family == AF_INET6) && (UseIPv6) )
254 // Translate the address to a protocol independent representation (dottet format). Copy address into ip_addr_buff.
255 ret_val = getnameinfo(ifa->ifa_addr, (socklen_t)sizeof(struct sockaddr_in6), ip_addr_buff, NI_MAXHOST, NULL, 0, NI_NUMERICHOST);
258 Log->print_error_getting_local_wan_ip("getnameinfo", gai_strerror(ret_val));
259 freeifaddrs(if_addr_struct);
263 // Generate IPv6 string out of char array.
264 string ipv6_addr(ip_addr_buff);
266 Log->print_own_ipv6(ipv6_addr, Hostname);
268 // Test if it is a local address.
269 if ( !is_local_ipv6(ipv6_addr) )
270 external_ipv6_addresses.push_back(ipv6_addr);
272 Log->print_ip_is_local(ipv6_addr);
275 freeifaddrs(if_addr_struct);
277 // Return the first element in IPv6 list if IPv6 is enabled, otherwise return first element in IPv4 list.
278 if ( (UseIPv6) && (!external_ipv6_addresses.empty()) )
279 return external_ipv6_addresses.front();
280 else if ( !external_ipv4_addresses.empty() )
281 return external_ipv4_addresses.front();
288 * Get the actual IP of the given host through a DNS query.
289 * @param _hostname The hostname for the dns lookup, if empty string, then perform a dns lookup to the local hostname.
290 * @return A string representation of the actual IP in dotted format or an empty string if something went wrong.
292 string IPAddrHelper::dns_query(const string& _hostname) const
294 list<string> external_ipv4_addresses;
295 list<string> external_ipv6_addresses;
297 // Init the hostname with the given _hostname or with local Hostname if empty.
298 string hostname = Hostname;
299 if ( !_hostname.empty() )
300 hostname = _hostname;
304 // BOOST asio isn't the simplest way to perform a DNS lookup, but it works just fine.
305 net::io_service io_service;
306 net::ip::tcp::resolver resolver(io_service);
308 // Define the DNS query.
309 net::ip::tcp::resolver::query query(hostname, "0", net::ip::resolver_query_base::address_configured | net::ip::resolver_query_base::all_matching);
311 // Perform the DNS query.
312 net::ip::tcp::resolver::iterator endpoint_iterator = resolver.resolve(query);
313 net::ip::tcp::resolver::iterator end;
315 // Iterate through the returned IP addresses.
316 while(endpoint_iterator != end)
318 // Get the IP address out of the endpoint iterator.
320 ip = endpoint_iterator->endpoint().address();
322 // Test if it is a IPv4 address.
325 // Get the string representation.
326 string ipv4_addr = ip.to_string();
328 Log->print_own_ipv4(ipv4_addr, hostname);
330 // If it is not a local address then push it in the external ipv4 address list.
331 //if ( !is_local_ipv4(ipv4_addr) )
332 external_ipv4_addresses.push_back(ipv4_addr);
334 // Log->print_ip_is_local(ipv4_addr);
336 // Test if it is a IPv6 address and if IPv6 is enabled.
337 else if ( (ip.is_v6()) && (UseIPv6) )
339 // Get the string representation.
340 string ipv6_addr = ip.to_string();
342 Log->print_own_ipv6(ipv6_addr, hostname);
344 // If it is not a local address then push it in the external ipv6 address list.
345 //if ( !is_local_ipv6(ipv6_addr) )
346 external_ipv6_addresses.push_back(ipv6_addr);
348 // Log->print_ip_is_local(ipv6_addr);
354 catch(const exception& e)
356 Log->print_error_hostname_to_ip(e.what(),hostname);
360 // Return the first element in IPv6 list if IPv6 is enabled, otherwise return first element in IPv4 list.
361 if ( (UseIPv6) && (!external_ipv6_addresses.empty()) )
362 return external_ipv6_addresses.front();
363 else if ( !external_ipv4_addresses.empty() )
364 return external_ipv4_addresses.front();
366 // Could not get a external IP address, so return empty string.
372 * Get the actual IP of this host through a IP webcheck URL.
373 * @return A string representation of the actual IP in dotted format or an empty string if something went wrong.
375 string IPAddrHelper::webcheck_ip()
377 // Init IPAddress with a empty string.
380 // Get the current time.
381 time_t current_time = time(NULL);
383 // Test if webcheck is allowed due to webcheck_interval.
384 if ( (LastWebcheck + ((time_t)WebcheckInterval*60)) >= current_time )
386 // Webcheck not allowed, log it and return empty string.
387 Log->print_webcheck_exceed_interval( LastWebcheck, (WebcheckInterval*60), current_time );
392 string curl_writedata_buff;
393 char curl_err_buff[CURL_ERROR_SIZE];
397 list<string> url_list;
398 url_list.push_back(WebcheckIpUrl);
399 url_list.push_back(WebcheckIpUrlAlt);
403 CURL * curl_easy_handle = init_curl(curl_writedata_buff,curl_err_buff);
404 if ( curl_easy_handle == NULL )
409 // If perform_curl_operation returns a connection problem indicating return code, try the next ip webcheck url if there is one.
410 while ( (curl_err_code == 1) && (url_list.size() != 0) && (url_list.front() != "") )
413 actual_url = url_list.front();
414 url_list.pop_front();
415 if (set_curl_url(curl_easy_handle,actual_url) == CURLE_OK )
417 // Perform curl operation, err_code of 1 indicated connection problem, so try next url.
418 curl_err_code = perform_curl_operation(curl_easy_handle, curl_err_buff, actual_url);
422 // Cleanup CURL handle
423 curl_easy_cleanup(curl_easy_handle);
425 // If curl_err_code is not 0, the ip couldn't be determined through any configured webcheck url.
426 if ( curl_err_code != 0 )
428 // Log it and return the empty string.
429 Log->print_webcheck_no_ip();
433 // Log the received curl data.
434 Log->print_received_curl_data(curl_writedata_buff);
436 // Try to parse a IPv4 address out of the received data.
437 ip_addr = parse_ipv4(curl_writedata_buff);
439 // TODO: Parsing of IPv6 address out of received curl data via webcheck IP.
441 if ( !ip_addr.empty() )
443 // Got a IPv4 address out of the received data.
444 Log->print_own_ipv4(ip_addr, Hostname);
451 // Set the LastWebcheck time to current time.
452 LastWebcheck = current_time;
454 // If IP is within a private range then return ""
455 if ( is_local_ipv4(ip_addr) )
457 Log->print_ip_is_local(ip_addr);
461 // Return the parsed IPAddress.
467 * Tries to find a IPv4 Address in dottet format in a given string and returns the IP-Address found.
468 * @param data The string data to search in for a valid IPv4-Address.
469 * @return The IP Address found or an empty string.
471 string IPAddrHelper::parse_ipv4(const string& data) const
475 // Regex for ipv4 address in dottet format
476 boost::regex expr("([0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3})");
478 boost::smatch matches;
480 if ( boost::regex_search(data,matches,expr) )
483 Log->print_regex_match(expr.str(),ip);
487 Log->print_regex_ip_not_found(data);
495 * Performs the curl operation.
496 * @param curl_easy_handle The initialized and configured curl handle.
497 * @param curl_err_buff The pointer to the curl error buffer to get error messages from.
498 * @param actual_url The actual configured URL.
499 * @return 0 if all is fine, 1 if an connection problem to the configured url occurs, -1 on other errors.
501 int IPAddrHelper::perform_curl_operation(CURL * curl_easy_handle, const char* curl_err_buff, const string& actual_url) const
503 CURLcode curl_err_code;
504 if ( (curl_err_code = curl_easy_perform(curl_easy_handle) ) != CURLE_OK )
506 // CURL error occured
507 if ( (curl_err_code == CURLE_COULDNT_CONNECT) || (curl_err_code == CURLE_OPERATION_TIMEOUTED) || (curl_err_code == CURLE_COULDNT_RESOLVE_HOST) )
509 // In case of connection problems we should return 1, that the fallback url will be used.
510 Log->print_webcheck_url_connection_problem(curl_err_buff, actual_url);
516 Log->print_webcheck_error(curl_err_buff, actual_url);
520 // Operation performed without any problems
526 * Sets a url to the easy curl handle
527 * @param curl_easy_handle The easy curl handle to set the url for.
528 * @param url The url to set.
529 * @return CURLcode Curl Error code, CURLE_OK if everything is right.
531 CURLcode IPAddrHelper::set_curl_url(CURL * curl_easy_handle, const string& url) const
533 CURLcode curl_error = CURLE_OK;
535 if ( curl_easy_handle != NULL )
537 curl_error = curl_easy_setopt(curl_easy_handle,CURLOPT_URL,url.c_str());
538 if ( curl_error != CURLE_OK )
540 // Some options could not be set, so destroy the CURL handle.
541 Log->print_curl_error_init("Could not set CURL URL properly.",curl_error);
549 * Initialized curl easy handle with a few options.
550 * @param curl_writedata_buff Reference to a string wich will be filled with the curl result
551 * @param curl_err_buff A pointer to an char array which will be filled with error message.
552 * @return A pointer to the easy curl handle.
554 CURL * IPAddrHelper::init_curl(string& curl_writedata_buff,char* curl_err_buff) const
556 string user_agent = "Intra2net AG - Bullet Proof DYNDNS Daemon - 0.1.1";
558 CURL *curl_easy_handle = curl_easy_init();
559 if ( curl_easy_handle == NULL )
561 // something went wrong.
562 Log->print_curl_error_init("Could not initialize CURL object.",CURLE_FAILED_INIT);
566 CURLcode curlError = curl_easy_setopt(curl_easy_handle,CURLOPT_NOPROGRESS,1);
567 if ( curlError == CURLE_OK)
568 curlError = curl_easy_setopt(curl_easy_handle,CURLOPT_CONNECTTIMEOUT,5);
569 if ( curlError == CURLE_OK)
570 curlError = curl_easy_setopt(curl_easy_handle,CURLOPT_TIMEOUT,10);
571 if ( curlError == CURLE_OK)
572 curlError = curl_easy_setopt(curl_easy_handle,CURLOPT_BUFFERSIZE,1024);
573 if ( curlError == CURLE_OK)
574 curlError = curl_easy_setopt(curl_easy_handle,CURLOPT_ERRORBUFFER,curl_err_buff);
575 if ( curlError == CURLE_OK)
576 curlError = curl_easy_setopt(curl_easy_handle,CURLOPT_WRITEFUNCTION,http_receive);
577 if ( curlError == CURLE_OK)
578 curlError = curl_easy_setopt(curl_easy_handle,CURLOPT_WRITEDATA,&curl_writedata_buff);
579 if ( curlError == CURLE_OK)
580 curlError = curl_easy_setopt(curl_easy_handle,CURLOPT_USERAGENT,&user_agent);
582 if ( !Proxy.empty() )
584 if ( curlError == CURLE_OK)
585 curlError = curl_easy_setopt(curl_easy_handle,CURLOPT_PROXY,Proxy.c_str());
586 if ( curlError == CURLE_OK)
587 curlError = curl_easy_setopt(curl_easy_handle,CURLOPT_PROXYPORT,ProxyPort);
590 if ( curlError != CURLE_OK )
592 // Some options could not be set, so destroy the CURL handle.
593 Log->print_curl_error_init("Could not set CURL options properly.",curlError);
594 curl_easy_cleanup(curl_easy_handle);
595 curl_easy_handle = NULL;
598 return curl_easy_handle;
603 * Callback Function is called every time CURL is receiving data from HTTPS-Server and will copy all received Data to the given stream pointer
604 * @param inBuffer Pointer to input.
605 * @param size How many mem blocks are received
606 * @param nmemb size of each memblock
607 * @param outBuffer Pointer to output stream.
608 * @return The size received.
610 size_t IPAddrHelper::http_receive( const char *inBuffer, size_t size, size_t nmemb, string *outBuffer )
612 outBuffer->append(inBuffer);
618 * Get member LastWebcheck
619 * @return LastWebcheck
621 time_t IPAddrHelper::get_last_webcheck() const