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>
17 #include "version_info.h"
22 namespace net = boost::asio;
25 * Default constructor.
27 IPAddrHelper::IPAddrHelper()
40 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)
42 , WebcheckIpUrl(_webcheck_url)
43 , WebcheckIpUrlAlt(_webcheck_url_alt)
44 , WebcheckInterval(_webcheck_interval)
45 , LastWebcheck(_last_webcheck)
47 , ProxyPort(_proxy_port)
50 Hostname = net::ip::host_name();
51 Log->print_hostname(Hostname);
58 IPAddrHelper::~IPAddrHelper()
64 * Tests if a given IP is a local IPv6 address
65 * @param ip The IP to test
66 * @return true if given IP is local, false if not.
68 bool IPAddrHelper::is_local_ipv6(const string ip) const
71 boost::regex expr_any_ipv6("^::$");
74 boost::regex expr_loopback_ipv6("^::1$");
76 // IPV6 local unicast address
77 boost::regex expr_local_unicast_ipv6("^fc00:");
80 boost::regex expr_link_local_ipv6("^fe[8,9,a,b]{1}");
83 boost::regex expr_site_local_ipv6("^fe[c,d,e,f]{1}");
85 // It's time to test against the regex patterns
86 if ( boost::regex_search(ip,expr_any_ipv6) )
88 Log->print_regex_match(expr_any_ipv6.str(),ip);
91 else if ( boost::regex_search(ip,expr_loopback_ipv6) )
93 Log->print_regex_match(expr_loopback_ipv6.str(),ip);
96 else if ( boost::regex_search(ip,expr_local_unicast_ipv6) )
98 Log->print_regex_match(expr_local_unicast_ipv6.str(),ip);
101 else if ( boost::regex_search(ip,expr_link_local_ipv6) )
103 Log->print_regex_match(expr_link_local_ipv6.str(),ip);
106 else if ( boost::regex_search(ip,expr_site_local_ipv6) )
108 Log->print_regex_match(expr_site_local_ipv6.str(),ip);
117 * Tests if a given IP is a local IPv4 address
118 * @param ip The IP to test
119 * @return true if given IP is local, false if not.
121 bool IPAddrHelper::is_local_ipv4(const string ip) const
124 boost::regex expr_loopback("127\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}");
127 boost::regex expr_192("192\\.168\\.[0-9]{1,3}\\.[0-9]{1,3}");
130 boost::regex expr_10("10\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}");
133 boost::regex expr_169_254("169\\.254\\.[0-9]{1,3}\\.[0-9]{1,3}");
135 // 172.16.x.x -> 172.31.x.x
136 boost::regex expr_172_1("172\\.1[6-9]{1}\\.[0-9]{1,3}\\.[0-9]{1,3}");
137 boost::regex expr_172_2("172\\.2[0-9]{1}\\.[0-9]{1,3}\\.[0-9]{1,3}");
138 boost::regex expr_172_3("172\\.3[0-1]{1}\\.[0-9]{1,3}\\.[0-9]{1,3}");
140 // It's time to test against the regex patterns
141 if ( boost::regex_search(ip,expr_loopback) )
143 Log->print_regex_match(expr_loopback.str(),ip);
146 else if ( boost::regex_search(ip,expr_192) )
148 Log->print_regex_match(expr_192.str(),ip);
151 else if ( boost::regex_search(ip,expr_10) )
153 Log->print_regex_match(expr_10.str(),ip);
156 else if ( boost::regex_search(ip,expr_169_254) )
158 Log->print_regex_match(expr_169_254.str(),ip);
161 else if ( boost::regex_search(ip,expr_172_1) )
163 Log->print_regex_match(expr_172_1.str(),ip);
166 else if ( boost::regex_search(ip,expr_172_2) )
168 Log->print_regex_match(expr_172_2.str(),ip);
171 else if ( boost::regex_search(ip,expr_172_3) )
173 Log->print_regex_match(expr_172_3.str(),ip);
182 * Get the actual IP of this host through a conventional DNS query or through a IP webcheck URL if configured so.
183 * @param use_webcheck If true: Determine IP via web check
184 * @param changed_to_online Indicates if we just went online
185 * @param wan_override_ip Override automatic WAN IP detection if not empty
186 * @return A string representation of the actual IP in dotted format or an empty string if something went wrong.
188 string IPAddrHelper::get_actual_ip( bool use_webcheck, bool changed_to_online, const std::string &wan_override_ip )
192 if ( !WebcheckIpUrl.empty() && use_webcheck )
193 ip = webcheck_ip(changed_to_online);
194 else if (!wan_override_ip.empty())
195 ip = wan_override_ip;
197 ip = get_local_wan_nic_ip();
204 * Get the IP address of the local wan interface if there is one.
205 * @return The IP address of the wan interface or an empty string if something went wrong.
207 string IPAddrHelper::get_local_wan_nic_ip() const
209 struct ifaddrs *if_addr_struct, *ifa;
210 unsigned short address_family;
212 char ip_addr_buff[NI_MAXHOST];
213 list<string> external_ipv4_addresses;
214 list<string> external_ipv6_addresses;
216 // Get linked list of all interface addresses.
217 if ( getifaddrs(&if_addr_struct) == -1 )
219 Log->print_error_getting_local_wan_ip("getifaddrs", strerror(errno));
220 freeifaddrs(if_addr_struct);
224 // Iterate through the linked list.
225 for ( ifa = if_addr_struct; ifa != NULL; ifa = ifa->ifa_next)
227 // Skip interfaces without IP addresses
231 // Get the address family of the actual address.
232 address_family = ifa->ifa_addr->sa_family;
234 // If it is an IPv4 then process further.
235 if ( address_family == AF_INET )
237 // Translate the address to a protocol independent representation (dottet format). Copy address into ip_addr_buff.
238 ret_val = getnameinfo(ifa->ifa_addr, (socklen_t)sizeof(struct sockaddr_in), ip_addr_buff, NI_MAXHOST, NULL, 0, NI_NUMERICHOST);
241 Log->print_error_getting_local_wan_ip("getnameinfo", gai_strerror(ret_val));
242 freeifaddrs(if_addr_struct);
246 // Generate IPv4 string out of char array.
247 string ipv4_addr(ip_addr_buff);
249 Log->print_own_ipv4(ipv4_addr, Hostname);
251 // Test if it is a local address.
252 if ( !is_local_ipv4(ipv4_addr) )
253 external_ipv4_addresses.push_back(ipv4_addr);
255 Log->print_ip_is_local(ipv4_addr);
257 // If it is an IPv6 address and IPv6 is enabled then process further.
258 else if ( (address_family == AF_INET6) && (UseIPv6) )
260 // Translate the address to a protocol independent representation (dottet format). Copy address into ip_addr_buff.
261 ret_val = getnameinfo(ifa->ifa_addr, (socklen_t)sizeof(struct sockaddr_in6), ip_addr_buff, NI_MAXHOST, NULL, 0, NI_NUMERICHOST);
264 Log->print_error_getting_local_wan_ip("getnameinfo", gai_strerror(ret_val));
265 freeifaddrs(if_addr_struct);
269 // Generate IPv6 string out of char array.
270 string ipv6_addr(ip_addr_buff);
272 Log->print_own_ipv6(ipv6_addr, Hostname);
274 // Test if it is a local address.
275 if ( !is_local_ipv6(ipv6_addr) )
276 external_ipv6_addresses.push_back(ipv6_addr);
278 Log->print_ip_is_local(ipv6_addr);
281 freeifaddrs(if_addr_struct);
283 // Return the first element in IPv6 list if IPv6 is enabled, otherwise return first element in IPv4 list.
284 if ( (UseIPv6) && (!external_ipv6_addresses.empty()) )
285 return external_ipv6_addresses.front();
286 else if ( !external_ipv4_addresses.empty() )
287 return external_ipv4_addresses.front();
294 * Get the actual IP of the given host through a DNS query.
295 * @param _hostname The hostname for the dns lookup, if empty string, then perform a dns lookup to the local hostname.
296 * @return A string representation of the actual IP in dotted format or an empty string if something went wrong.
298 string IPAddrHelper::dns_query(const string& _hostname) const
300 list<string> external_ipv4_addresses;
301 list<string> external_ipv6_addresses;
303 // Init the hostname with the given _hostname or with local Hostname if empty.
304 string hostname = Hostname;
305 if ( !_hostname.empty() )
306 hostname = _hostname;
310 // BOOST asio isn't the simplest way to perform a DNS lookup, but it works just fine.
311 net::io_service io_service;
312 net::ip::tcp::resolver resolver(io_service);
314 // Define the DNS query.
315 net::ip::tcp::resolver::query query(hostname, "0", net::ip::resolver_query_base::address_configured | net::ip::resolver_query_base::all_matching);
317 // Perform the DNS query.
318 net::ip::tcp::resolver::iterator endpoint_iterator = resolver.resolve(query);
319 net::ip::tcp::resolver::iterator end;
321 // Iterate through the returned IP addresses.
322 while(endpoint_iterator != end)
324 // Get the IP address out of the endpoint iterator.
326 ip = endpoint_iterator->endpoint().address();
328 // Test if it is a IPv4 address.
331 // Get the string representation.
332 string ipv4_addr = ip.to_string();
334 Log->print_own_ipv4(ipv4_addr, hostname);
336 // If it is not a local address then push it in the external ipv4 address list.
337 //if ( !is_local_ipv4(ipv4_addr) )
338 external_ipv4_addresses.push_back(ipv4_addr);
340 // Log->print_ip_is_local(ipv4_addr);
342 // Test if it is a IPv6 address and if IPv6 is enabled.
343 else if ( (ip.is_v6()) && (UseIPv6) )
345 // Get the string representation.
346 string ipv6_addr = ip.to_string();
348 Log->print_own_ipv6(ipv6_addr, hostname);
350 // If it is not a local address then push it in the external ipv6 address list.
351 //if ( !is_local_ipv6(ipv6_addr) )
352 external_ipv6_addresses.push_back(ipv6_addr);
354 // Log->print_ip_is_local(ipv6_addr);
360 catch(const exception& e)
362 Log->print_error_hostname_to_ip(e.what(),hostname);
366 // Return the first element in IPv6 list if IPv6 is enabled, otherwise return first element in IPv4 list.
367 if ( (UseIPv6) && (!external_ipv6_addresses.empty()) )
368 return external_ipv6_addresses.front();
369 else if ( !external_ipv4_addresses.empty() )
370 return external_ipv4_addresses.front();
372 // Could not get a external IP address, so return empty string.
378 * Get the actual IP of this host through a IP webcheck URL.
379 * @param changed_to_online Indicates if we just went online
380 * @return A string representation of the actual IP in dotted format or an empty string if something went wrong.
382 string IPAddrHelper::webcheck_ip(bool changed_to_online)
384 // Init IPAddress with a empty string.
387 // Get the current time.
388 time_t current_time = time(NULL);
390 // Test if webcheck is allowed due to webcheck_interval.
391 // Ignored if we just went online.
392 if ( !changed_to_online && (LastWebcheck + ((time_t)WebcheckInterval*60)) >= current_time )
394 // Webcheck not allowed, log it and return empty string.
395 Log->print_webcheck_exceed_interval( LastWebcheck, (WebcheckInterval*60), current_time );
400 string curl_writedata_buff;
401 char curl_err_buff[CURL_ERROR_SIZE];
405 list<string> url_list;
406 url_list.push_back(WebcheckIpUrl);
407 url_list.push_back(WebcheckIpUrlAlt);
411 CURL * curl_easy_handle = init_curl(curl_writedata_buff,curl_err_buff);
412 if ( curl_easy_handle == NULL )
417 // Set the LastWebcheck time to current time.
418 LastWebcheck = current_time;
420 // If perform_curl_operation returns a connection problem indicating return code, try the next ip webcheck url if there is one.
421 while ( (curl_err_code == 1) && (url_list.size() != 0) && (url_list.front() != "") )
424 actual_url = url_list.front();
425 url_list.pop_front();
426 if (set_curl_url(curl_easy_handle,actual_url) == CURLE_OK )
428 // Perform curl operation, err_code of 1 indicated connection problem, so try next url.
429 curl_err_code = perform_curl_operation(curl_easy_handle, curl_err_buff, actual_url);
433 // Cleanup CURL handle
434 curl_easy_cleanup(curl_easy_handle);
436 // If curl_err_code is not 0, the ip couldn't be determined through any configured webcheck url.
437 if ( curl_err_code != 0 )
439 // Log it and return the empty string.
440 Log->print_webcheck_no_ip();
444 // Log the received curl data.
445 Log->print_received_curl_data(curl_writedata_buff);
447 // Try to parse a IPv4 address out of the received data.
448 ip_addr = parse_ipv4(curl_writedata_buff);
450 // TODO: Parsing of IPv6 address out of received curl data via webcheck IP.
452 if ( !ip_addr.empty() )
454 // Got a IPv4 address out of the received data.
455 Log->print_own_ipv4(ip_addr, Hostname);
462 // If IP is within a private range then return ""
463 if ( is_local_ipv4(ip_addr) )
465 Log->print_ip_is_local(ip_addr);
469 // Return the parsed IPAddress.
475 * Tries to find a IPv4 Address in dottet format in a given string and returns the IP-Address found.
476 * @param data The string data to search in for a valid IPv4-Address.
477 * @return The IP Address found or an empty string.
479 string IPAddrHelper::parse_ipv4(const string& data) const
483 // Regex for ipv4 address in dottet format
484 boost::regex expr("([0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3})");
486 boost::smatch matches;
488 if ( boost::regex_search(data,matches,expr) )
491 Log->print_regex_match(expr.str(),ip);
495 Log->print_regex_ip_not_found(data);
503 * Performs the curl operation.
504 * @param curl_easy_handle The initialized and configured curl handle.
505 * @param curl_err_buff The pointer to the curl error buffer to get error messages from.
506 * @param actual_url The actual configured URL.
507 * @return 0 if all is fine, 1 if an connection problem to the configured url occurs, -1 on other errors.
509 int IPAddrHelper::perform_curl_operation(CURL * curl_easy_handle, const char* curl_err_buff, const string& actual_url) const
511 CURLcode curl_err_code;
512 if ( (curl_err_code = curl_easy_perform(curl_easy_handle) ) != CURLE_OK )
514 // CURL error occured
515 if ( (curl_err_code == CURLE_COULDNT_CONNECT) || (curl_err_code == CURLE_OPERATION_TIMEOUTED) || (curl_err_code == CURLE_COULDNT_RESOLVE_HOST) )
517 // In case of connection problems we should return 1, that the fallback url will be used.
518 Log->print_webcheck_url_connection_problem(curl_err_buff, actual_url);
524 Log->print_webcheck_error(curl_err_buff, actual_url);
528 // Operation performed without any problems
534 * Sets a url to the easy curl handle
535 * @param curl_easy_handle The easy curl handle to set the url for.
536 * @param url The url to set.
537 * @return CURLcode Curl Error code, CURLE_OK if everything is right.
539 CURLcode IPAddrHelper::set_curl_url(CURL * curl_easy_handle, const string& url) const
541 CURLcode curl_error = CURLE_OK;
543 if ( curl_easy_handle != NULL )
545 curl_error = curl_easy_setopt(curl_easy_handle,CURLOPT_URL,url.c_str());
546 if ( curl_error != CURLE_OK )
548 // Some options could not be set, so destroy the CURL handle.
549 Log->print_curl_error_init("Could not set CURL URL properly.",curl_error);
557 * Initialized curl easy handle with a few options.
558 * @param curl_writedata_buff Reference to a string wich will be filled with the curl result
559 * @param curl_err_buff A pointer to an char array which will be filled with error message.
560 * @return A pointer to the easy curl handle.
562 CURL * IPAddrHelper::init_curl(string& curl_writedata_buff,char* curl_err_buff) const
564 ostringstream user_agent_stream;
565 user_agent_stream << "Intra2net AG - Bullet Proof DYNDNS Daemon - " << MAJOR_VERSION << "." << MINOR_VERSION;
566 string user_agent = user_agent_stream.str();
568 CURL *curl_easy_handle = curl_easy_init();
569 if ( curl_easy_handle == NULL )
571 // something went wrong.
572 Log->print_curl_error_init("Could not initialize CURL object.",CURLE_FAILED_INIT);
576 CURLcode curlError = curl_easy_setopt(curl_easy_handle,CURLOPT_NOPROGRESS,1);
577 if ( curlError == CURLE_OK)
578 curlError = curl_easy_setopt(curl_easy_handle,CURLOPT_CONNECTTIMEOUT,5);
579 if ( curlError == CURLE_OK)
580 curlError = curl_easy_setopt(curl_easy_handle,CURLOPT_TIMEOUT,10);
581 if ( curlError == CURLE_OK)
582 curlError = curl_easy_setopt(curl_easy_handle,CURLOPT_BUFFERSIZE,1024);
583 if ( curlError == CURLE_OK)
584 curlError = curl_easy_setopt(curl_easy_handle,CURLOPT_ERRORBUFFER,curl_err_buff);
585 if ( curlError == CURLE_OK)
586 curlError = curl_easy_setopt(curl_easy_handle,CURLOPT_WRITEFUNCTION,http_receive);
587 if ( curlError == CURLE_OK)
588 curlError = curl_easy_setopt(curl_easy_handle,CURLOPT_WRITEDATA,&curl_writedata_buff);
589 if ( curlError == CURLE_OK)
590 curlError = curl_easy_setopt(curl_easy_handle,CURLOPT_USERAGENT,user_agent.c_str());
592 if ( !Proxy.empty() )
594 if ( curlError == CURLE_OK)
595 curlError = curl_easy_setopt(curl_easy_handle,CURLOPT_PROXY,Proxy.c_str());
596 if ( curlError == CURLE_OK)
597 curlError = curl_easy_setopt(curl_easy_handle,CURLOPT_PROXYPORT,ProxyPort);
600 if ( curlError != CURLE_OK )
602 // Some options could not be set, so destroy the CURL handle.
603 Log->print_curl_error_init("Could not set CURL options properly.",curlError);
604 curl_easy_cleanup(curl_easy_handle);
605 curl_easy_handle = NULL;
608 return curl_easy_handle;
613 * Callback Function is called every time CURL is receiving data from HTTPS-Server and will copy all received Data to the given stream pointer
614 * @param inBuffer Pointer to input.
615 * @param size How many mem blocks are received
616 * @param nmemb size of each memblock
617 * @param outBuffer Pointer to output stream.
618 * @return The size received.
620 size_t IPAddrHelper::http_receive( void *inBuffer, size_t size, size_t nmemb, string *outBuffer )
622 outBuffer->append(static_cast<char *>(inBuffer), size*nmemb);
628 * Get member LastWebcheck
629 * @return LastWebcheck
631 time_t IPAddrHelper::get_last_webcheck() const