/** @file * @brief IPHelper class implementation. This class represents a Helper to get the actual IP. * * * * @copyright Intra2net AG * @license GPLv2 */ #include "ip_addr_helper.hpp" #include #include #include #include #include #include #include "version_info.h" using namespace std; namespace net = boost::asio; /** * Default constructor. */ IPAddrHelper::IPAddrHelper() : Log(new Logger) , WebcheckInterval(0) , LastWebcheck(0) , ProxyPort(0) , UseIPv6(false) { } /** * Constructor. */ 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) : Log(_log) , WebcheckIpUrl(_webcheck_url) , WebcheckIpUrlAlt(_webcheck_url_alt) , WebcheckInterval(_webcheck_interval) , LastWebcheck(_last_webcheck) , Proxy(_proxy) , ProxyPort(_proxy_port) , UseIPv6(_use_ipv6) { Hostname = net::ip::host_name(); Log->print_hostname(Hostname); } /** * Default destructor */ IPAddrHelper::~IPAddrHelper() { } /** * Tests if a given IP is a local IPv6 address * @param ip The IP to test * @return true if given IP is local, false if not. */ bool IPAddrHelper::is_local_ipv6(const string ip) const { // IPv6 any boost::regex expr_any_ipv6("^::$"); // IPV6 loopback boost::regex expr_loopback_ipv6("^::1$"); // IPV6 local unicast address boost::regex expr_local_unicast_ipv6("^fc00:"); // IPV6 link local boost::regex expr_link_local_ipv6("^fe[8,9,a,b]{1}"); // IPV6 site local boost::regex expr_site_local_ipv6("^fe[c,d,e,f]{1}"); // It's time to test against the regex patterns if ( boost::regex_search(ip,expr_any_ipv6) ) { Log->print_regex_match(expr_any_ipv6.str(),ip); return true; } else if ( boost::regex_search(ip,expr_loopback_ipv6) ) { Log->print_regex_match(expr_loopback_ipv6.str(),ip); return true; } else if ( boost::regex_search(ip,expr_local_unicast_ipv6) ) { Log->print_regex_match(expr_local_unicast_ipv6.str(),ip); return true; } else if ( boost::regex_search(ip,expr_link_local_ipv6) ) { Log->print_regex_match(expr_link_local_ipv6.str(),ip); return true; } else if ( boost::regex_search(ip,expr_site_local_ipv6) ) { Log->print_regex_match(expr_site_local_ipv6.str(),ip); return true; } return false; } /** * Tests if a given IP is a local IPv4 address * @param ip The IP to test * @return true if given IP is local, false if not. */ bool IPAddrHelper::is_local_ipv4(const string ip) const { // 127.0.0.1 boost::regex expr_loopback("127\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}"); // 192.168.x.x boost::regex expr_192("192\\.168\\.[0-9]{1,3}\\.[0-9]{1,3}"); // 10.x.x.x boost::regex expr_10("10\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}"); // 169.254.x.x boost::regex expr_169_254("169\\.254\\.[0-9]{1,3}\\.[0-9]{1,3}"); // 172.16.x.x -> 172.31.x.x boost::regex expr_172_1("172\\.1[6-9]{1}\\.[0-9]{1,3}\\.[0-9]{1,3}"); boost::regex expr_172_2("172\\.2[0-9]{1}\\.[0-9]{1,3}\\.[0-9]{1,3}"); boost::regex expr_172_3("172\\.3[0-1]{1}\\.[0-9]{1,3}\\.[0-9]{1,3}"); // It's time to test against the regex patterns if ( boost::regex_search(ip,expr_loopback) ) { Log->print_regex_match(expr_loopback.str(),ip); return true; } else if ( boost::regex_search(ip,expr_192) ) { Log->print_regex_match(expr_192.str(),ip); return true; } else if ( boost::regex_search(ip,expr_10) ) { Log->print_regex_match(expr_10.str(),ip); return true; } else if ( boost::regex_search(ip,expr_169_254) ) { Log->print_regex_match(expr_169_254.str(),ip); return true; } else if ( boost::regex_search(ip,expr_172_1) ) { Log->print_regex_match(expr_172_1.str(),ip); return true; } else if ( boost::regex_search(ip,expr_172_2) ) { Log->print_regex_match(expr_172_2.str(),ip); return true; } else if ( boost::regex_search(ip,expr_172_3) ) { Log->print_regex_match(expr_172_3.str(),ip); return true; } return false; } /** * Get the actual IP of this host through a conventional DNS query or through a IP webcheck URL if configured so. * @param use_webcheck If true: Determine IP via web check * @param changed_to_online Indicates if we just went online * @param wan_override_ip Override automatic WAN IP detection if not empty * @return A string representation of the actual IP in dotted format or an empty string if something went wrong. */ string IPAddrHelper::get_actual_ip( bool use_webcheck, bool changed_to_online, const std::string &wan_override_ip ) { string ip; if ( !WebcheckIpUrl.empty() && use_webcheck ) ip = webcheck_ip(changed_to_online); else if (!wan_override_ip.empty()) ip = wan_override_ip; else ip = get_local_wan_nic_ip(); return ip; } /** * Get the IP address of the local wan interface if there is one. * @return The IP address of the wan interface or an empty string if something went wrong. */ string IPAddrHelper::get_local_wan_nic_ip() const { struct ifaddrs *if_addr_struct, *ifa; unsigned short address_family; int ret_val; char ip_addr_buff[NI_MAXHOST]; list external_ipv4_addresses; list external_ipv6_addresses; // Get linked list of all interface addresses. if ( getifaddrs(&if_addr_struct) == -1 ) { Log->print_error_getting_local_wan_ip("getifaddrs", strerror(errno)); freeifaddrs(if_addr_struct); return ""; } // Iterate through the linked list. for ( ifa = if_addr_struct; ifa != NULL; ifa = ifa->ifa_next) { // Skip interfaces without IP addresses if (!ifa->ifa_addr) continue; // Get the address family of the actual address. address_family = ifa->ifa_addr->sa_family; // If it is an IPv4 then process further. if ( address_family == AF_INET ) { // Translate the address to a protocol independent representation (dottet format). Copy address into ip_addr_buff. ret_val = getnameinfo(ifa->ifa_addr, (socklen_t)sizeof(struct sockaddr_in), ip_addr_buff, NI_MAXHOST, NULL, 0, NI_NUMERICHOST); if ( ret_val != 0 ) { Log->print_error_getting_local_wan_ip("getnameinfo", gai_strerror(ret_val)); freeifaddrs(if_addr_struct); return ""; } // Generate IPv4 string out of char array. string ipv4_addr(ip_addr_buff); Log->print_own_ipv4(ipv4_addr, Hostname); // Test if it is a local address. if ( !is_local_ipv4(ipv4_addr) ) external_ipv4_addresses.push_back(ipv4_addr); else Log->print_ip_is_local(ipv4_addr); } // If it is an IPv6 address and IPv6 is enabled then process further. else if ( (address_family == AF_INET6) && (UseIPv6) ) { // Translate the address to a protocol independent representation (dottet format). Copy address into ip_addr_buff. ret_val = getnameinfo(ifa->ifa_addr, (socklen_t)sizeof(struct sockaddr_in6), ip_addr_buff, NI_MAXHOST, NULL, 0, NI_NUMERICHOST); if ( ret_val != 0 ) { Log->print_error_getting_local_wan_ip("getnameinfo", gai_strerror(ret_val)); freeifaddrs(if_addr_struct); return ""; } // Generate IPv6 string out of char array. string ipv6_addr(ip_addr_buff); Log->print_own_ipv6(ipv6_addr, Hostname); // Test if it is a local address. if ( !is_local_ipv6(ipv6_addr) ) external_ipv6_addresses.push_back(ipv6_addr); else Log->print_ip_is_local(ipv6_addr); } } freeifaddrs(if_addr_struct); // Return the first element in IPv6 list if IPv6 is enabled, otherwise return first element in IPv4 list. if ( (UseIPv6) && (!external_ipv6_addresses.empty()) ) return external_ipv6_addresses.front(); else if ( !external_ipv4_addresses.empty() ) return external_ipv4_addresses.front(); return ""; } /** * Get the actual IP of the given host through a DNS query. * @param _hostname The hostname for the dns lookup, if empty string, then perform a dns lookup to the local hostname. * @return A string representation of the actual IP in dotted format or an empty string if something went wrong. */ string IPAddrHelper::dns_query(const string& _hostname) const { list external_ipv4_addresses; list external_ipv6_addresses; // Init the hostname with the given _hostname or with local Hostname if empty. string hostname = Hostname; if ( !_hostname.empty() ) hostname = _hostname; try { // BOOST asio isn't the simplest way to perform a DNS lookup, but it works just fine. net::io_service io_service; net::ip::tcp::resolver resolver(io_service); // Define the DNS query. net::ip::tcp::resolver::query query(hostname, "0", net::ip::resolver_query_base::address_configured | net::ip::resolver_query_base::all_matching); // Perform the DNS query. net::ip::tcp::resolver::iterator endpoint_iterator = resolver.resolve(query); net::ip::tcp::resolver::iterator end; // Iterate through the returned IP addresses. while(endpoint_iterator != end) { // Get the IP address out of the endpoint iterator. net::ip::address ip; ip = endpoint_iterator->endpoint().address(); // Test if it is a IPv4 address. if ( ip.is_v4() ) { // Get the string representation. string ipv4_addr = ip.to_string(); Log->print_own_ipv4(ipv4_addr, hostname); // If it is not a local address then push it in the external ipv4 address list. //if ( !is_local_ipv4(ipv4_addr) ) external_ipv4_addresses.push_back(ipv4_addr); //else // Log->print_ip_is_local(ipv4_addr); } // Test if it is a IPv6 address and if IPv6 is enabled. else if ( (ip.is_v6()) && (UseIPv6) ) { // Get the string representation. string ipv6_addr = ip.to_string(); Log->print_own_ipv6(ipv6_addr, hostname); // If it is not a local address then push it in the external ipv6 address list. //if ( !is_local_ipv6(ipv6_addr) ) external_ipv6_addresses.push_back(ipv6_addr); //else // Log->print_ip_is_local(ipv6_addr); } endpoint_iterator++; } io_service.reset(); } catch(const exception& e) { Log->print_error_hostname_to_ip(e.what(),hostname); return ""; } // Return the first element in IPv6 list if IPv6 is enabled, otherwise return first element in IPv4 list. if ( (UseIPv6) && (!external_ipv6_addresses.empty()) ) return external_ipv6_addresses.front(); else if ( !external_ipv4_addresses.empty() ) return external_ipv4_addresses.front(); // Could not get a external IP address, so return empty string. return ""; } /** * Get the actual IP of this host through a IP webcheck URL. * @param changed_to_online Indicates if we just went online * @return A string representation of the actual IP in dotted format or an empty string if something went wrong. */ string IPAddrHelper::webcheck_ip(bool changed_to_online) { // Init IPAddress with a empty string. string ip_addr = ""; // Get the current time. time_t current_time = time(NULL); // Test if webcheck is allowed due to webcheck_interval. // Ignored if we just went online. if ( !changed_to_online && (LastWebcheck + ((time_t)WebcheckInterval*60)) >= current_time ) { // Webcheck not allowed, log it and return empty string. Log->print_webcheck_exceed_interval( LastWebcheck, (WebcheckInterval*60), current_time ); return ""; } // Init CURL buffers string curl_writedata_buff; char curl_err_buff[CURL_ERROR_SIZE]; int curl_err_code=1; // Init URL List list url_list; url_list.push_back(WebcheckIpUrl); url_list.push_back(WebcheckIpUrlAlt); string actual_url; // Init CURL CURL * curl_easy_handle = init_curl(curl_writedata_buff,curl_err_buff); if ( curl_easy_handle == NULL ) { return ""; } // Set the LastWebcheck time to current time. LastWebcheck = current_time; // If perform_curl_operation returns a connection problem indicating return code, try the next ip webcheck url if there is one. while ( (curl_err_code == 1) && (url_list.size() != 0) && (url_list.front() != "") ) { // Set URL actual_url = url_list.front(); url_list.pop_front(); if (set_curl_url(curl_easy_handle,actual_url) == CURLE_OK ) { // Perform curl operation, err_code of 1 indicated connection problem, so try next url. curl_err_code = perform_curl_operation(curl_easy_handle, curl_err_buff, actual_url); } } // Cleanup CURL handle curl_easy_cleanup(curl_easy_handle); // If curl_err_code is not 0, the ip couldn't be determined through any configured webcheck url. if ( curl_err_code != 0 ) { // Log it and return the empty string. Log->print_webcheck_no_ip(); return ""; } // Log the received curl data. Log->print_received_curl_data(curl_writedata_buff); // Try to parse a IPv4 address out of the received data. ip_addr = parse_ipv4(curl_writedata_buff); // TODO: Parsing of IPv6 address out of received curl data via webcheck IP. if ( !ip_addr.empty() ) { // Got a IPv4 address out of the received data. Log->print_own_ipv4(ip_addr, Hostname); } else { return ""; } // If IP is within a private range then return "" if ( is_local_ipv4(ip_addr) ) { Log->print_ip_is_local(ip_addr); return ""; } // Return the parsed IPAddress. return ip_addr; } /** * Tries to find a IPv4 Address in dottet format in a given string and returns the IP-Address found. * @param data The string data to search in for a valid IPv4-Address. * @return The IP Address found or an empty string. */ string IPAddrHelper::parse_ipv4(const string& data) const { string ip = ""; // Regex for ipv4 address in dottet format boost::regex expr("([0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3})"); boost::smatch matches; if ( boost::regex_search(data,matches,expr) ) { ip = matches[1]; Log->print_regex_match(expr.str(),ip); } else { Log->print_regex_ip_not_found(data); } return ip; } /** * Performs the curl operation. * @param curl_easy_handle The initialized and configured curl handle. * @param curl_err_buff The pointer to the curl error buffer to get error messages from. * @param actual_url The actual configured URL. * @return 0 if all is fine, 1 if an connection problem to the configured url occurs, -1 on other errors. */ int IPAddrHelper::perform_curl_operation(CURL * curl_easy_handle, const char* curl_err_buff, const string& actual_url) const { CURLcode curl_err_code; if ( (curl_err_code = curl_easy_perform(curl_easy_handle) ) != CURLE_OK ) { // CURL error occurred if ( (curl_err_code == CURLE_COULDNT_CONNECT) || (curl_err_code == CURLE_OPERATION_TIMEOUTED) || (curl_err_code == CURLE_COULDNT_RESOLVE_HOST) ) { // In case of connection problems we should return 1, that the fallback url will be used. Log->print_webcheck_url_connection_problem(curl_err_buff, actual_url); return 1; } else { // other error Log->print_webcheck_error(curl_err_buff, actual_url); return -1; } } // Operation performed without any problems return 0; } /** * Sets a url to the easy curl handle * @param curl_easy_handle The easy curl handle to set the url for. * @param url The url to set. * @return CURLcode Curl Error code, CURLE_OK if everything is right. */ CURLcode IPAddrHelper::set_curl_url(CURL * curl_easy_handle, const string& url) const { CURLcode curl_error = CURLE_OK; if ( curl_easy_handle != NULL ) { curl_error = curl_easy_setopt(curl_easy_handle,CURLOPT_URL,url.c_str()); if ( curl_error != CURLE_OK ) { // Some options could not be set, so destroy the CURL handle. Log->print_curl_error_init("Could not set CURL URL properly.",curl_error); } } return curl_error; } /** * Initialized curl easy handle with a few options. * @param curl_writedata_buff Reference to a string wich will be filled with the curl result * @param curl_err_buff A pointer to an char array which will be filled with error message. * @return A pointer to the easy curl handle. */ CURL * IPAddrHelper::init_curl(string& curl_writedata_buff,char* curl_err_buff) const { ostringstream user_agent_stream; user_agent_stream << "Intra2net AG - Bullet Proof DYNDNS Daemon - " << MAJOR_VERSION << "." << MINOR_VERSION; string user_agent = user_agent_stream.str(); CURL *curl_easy_handle = curl_easy_init(); if ( curl_easy_handle == NULL ) { // something went wrong. Log->print_curl_error_init("Could not initialize CURL object.",CURLE_FAILED_INIT); return NULL; } CURLcode curlError = curl_easy_setopt(curl_easy_handle,CURLOPT_NOPROGRESS,1); if ( curlError == CURLE_OK) curlError = curl_easy_setopt(curl_easy_handle,CURLOPT_CONNECTTIMEOUT,5); if ( curlError == CURLE_OK) curlError = curl_easy_setopt(curl_easy_handle,CURLOPT_TIMEOUT,10); if ( curlError == CURLE_OK) curlError = curl_easy_setopt(curl_easy_handle,CURLOPT_BUFFERSIZE,1024); if ( curlError == CURLE_OK) curlError = curl_easy_setopt(curl_easy_handle,CURLOPT_ERRORBUFFER,curl_err_buff); if ( curlError == CURLE_OK) curlError = curl_easy_setopt(curl_easy_handle,CURLOPT_WRITEFUNCTION,http_receive); if ( curlError == CURLE_OK) curlError = curl_easy_setopt(curl_easy_handle,CURLOPT_WRITEDATA,&curl_writedata_buff); if ( curlError == CURLE_OK) curlError = curl_easy_setopt(curl_easy_handle,CURLOPT_USERAGENT,user_agent.c_str()); if ( !Proxy.empty() ) { if ( curlError == CURLE_OK) curlError = curl_easy_setopt(curl_easy_handle,CURLOPT_PROXY,Proxy.c_str()); if ( curlError == CURLE_OK) curlError = curl_easy_setopt(curl_easy_handle,CURLOPT_PROXYPORT,ProxyPort); } if ( curlError != CURLE_OK ) { // Some options could not be set, so destroy the CURL handle. Log->print_curl_error_init("Could not set CURL options properly.",curlError); curl_easy_cleanup(curl_easy_handle); curl_easy_handle = NULL; } return curl_easy_handle; } /** * Callback Function is called every time CURL is receiving data from HTTPS-Server and will copy all received Data to the given stream pointer * @param inBuffer Pointer to input. * @param size How many mem blocks are received * @param nmemb size of each memblock * @param outBuffer Pointer to output stream. * @return The size received. */ size_t IPAddrHelper::http_receive( void *inBuffer, size_t size, size_t nmemb, string *outBuffer ) { outBuffer->append(static_cast(inBuffer), size*nmemb); return (size*nmemb); } //lint !e818 /** * Get member LastWebcheck * @return LastWebcheck */ time_t IPAddrHelper::get_last_webcheck() const { return LastWebcheck; }