* @license GPLv2
*/
-#include "ip_addr_helper.h"
+#include "ip_addr_helper.hpp"
#include <boost/asio.hpp>
#include <boost/regex.hpp>
+#include <arpa/inet.h>
+#include <sys/socket.h>
+#include <netdb.h>
+#include <ifaddrs.h>
+#include "version_info.h"
+
using namespace std;
*/
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 bool _use_ipv6, const string& _proxy, const int _proxy_port)
+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)
/**
- * Tests if a given IP is a local address
+ * 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(const string ip) const
+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}");
/**
* 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() const
+string IPAddrHelper::get_actual_ip( bool use_webcheck, bool changed_to_online, const std::string &wan_override_ip )
{
string ip;
- if ( WebcheckIpUrl.empty() )
- {
- ip = dns_query("");
- }
+
+ 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<string> external_ipv4_addresses;
+ list<string> external_ipv6_addresses;
+
+ // Get linked list of all interface addresses.
+ if ( getifaddrs(&if_addr_struct) == -1 )
{
- ip = webcheck_ip();
+ Log->print_error_getting_local_wan_ip("getifaddrs", strerror(errno));
+ freeifaddrs(if_addr_struct);
+ return "";
}
- // If IP is within a private range then return ""
- if ( is_local(ip) )
+ // Iterate through the linked list.
+ for ( ifa = if_addr_struct; ifa != NULL; ifa = ifa->ifa_next)
{
- Log->print_ip_is_local(ip);
- return "";
+ // 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 ip;
+ // 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 "";
}
*/
string IPAddrHelper::dns_query(const string& _hostname) const
{
- string ip_addr_v4;
- string ip_addr_v6;
+ list<string> external_ipv4_addresses;
+ list<string> external_ipv6_addresses;
+ // Init the hostname with the given _hostname or with local Hostname if empty.
string hostname = Hostname;
if ( !_hostname.empty() )
hostname = _hostname;
// 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);
- net::ip::tcp::resolver::query query(hostname, "0");
+
+ // 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() )
- ip_addr_v4 = ip.to_string();
- else if ( ip.is_v6() )
- ip_addr_v6 = ip.to_string();
- Log->print_own_ipv4(ip_addr_v4, hostname);
- if ( UseIPv6 == true )
- Log->print_own_ipv6(ip_addr_v6, hostname);
+ {
+ // 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(exception& e)
+ catch(const exception& e)
{
Log->print_error_hostname_to_ip(e.what(),hostname);
return "";
}
- if ( (UseIPv6 == true) && (ip_addr_v6 != "") )
- return ip_addr_v6;
+ // 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 ip_addr_v4;
+ // 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() const
+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];
// 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();
- set_curl_url(curl_easy_handle,actual_url);
-
- // Perform curl operation
- curl_err_code = perform_curl_operation(curl_easy_handle, curl_err_buff, actual_url);
+ 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
// 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();
- // error handling
- return ip_addr;
+ return "";
}
+ // Log the received curl data.
Log->print_received_curl_data(curl_writedata_buff);
- ip_addr = parse_ip(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 valid IPv4 Address in dottet format in a given string and returns the IP-Address found.
+ * 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_ip(const string& data) const
+string IPAddrHelper::parse_ipv4(const string& data) const
{
string ip = "";
- // regex for valid ip address
+ // 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_found_ip(ip);
+ Log->print_regex_match(expr.str(),ip);
}
else
{
* @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, char* curl_err_buff, const string& actual_url) const
+int IPAddrHelper::perform_curl_operation(CURL * curl_easy_handle, const char* curl_err_buff, const string& actual_url) const
{
- int curl_err_code;
- if ( (curl_err_code = curl_easy_perform(curl_easy_handle) ) != 0 )
+ CURLcode curl_err_code;
+ if ( (curl_err_code = curl_easy_perform(curl_easy_handle) ) != CURLE_OK )
{
- // CURL error occured
+ // 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.
* 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.
*/
-void IPAddrHelper::set_curl_url(CURL * curl_easy_handle, const string& url) const
+CURLcode IPAddrHelper::set_curl_url(CURL * curl_easy_handle, const string& url) const
{
- curl_easy_setopt(curl_easy_handle,CURLOPT_URL,url.c_str());
+ 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;
}
*/
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;
+ }
- curl_easy_setopt(curl_easy_handle,CURLOPT_NOPROGRESS,1);
- curl_easy_setopt(curl_easy_handle,CURLOPT_CONNECTTIMEOUT,5);
- curl_easy_setopt(curl_easy_handle,CURLOPT_TIMEOUT,10);
- curl_easy_setopt(curl_easy_handle,CURLOPT_BUFFERSIZE,1024);
- curl_easy_setopt(curl_easy_handle,CURLOPT_ERRORBUFFER,curl_err_buff);
- curl_easy_setopt(curl_easy_handle,CURLOPT_WRITEFUNCTION,http_receive);
- curl_easy_setopt(curl_easy_handle,CURLOPT_WRITEDATA,&curl_writedata_buff);
+ 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() )
{
- curl_easy_setopt(curl_easy_handle,CURLOPT_PROXY,Proxy.c_str());
- curl_easy_setopt(curl_easy_handle,CURLOPT_PROXYPORT,ProxyPort);
+ 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;
* @param outBuffer Pointer to output stream.
* @return The size received.
*/
-int IPAddrHelper::http_receive( char *inBuffer, size_t size, size_t nmemb, string *outBuffer )
+size_t IPAddrHelper::http_receive( void *inBuffer, size_t size, size_t nmemb, string *outBuffer )
{
- outBuffer->append(inBuffer);
+ outBuffer->append(static_cast<char *>(inBuffer), size*nmemb);
return (size*nmemb);
+} //lint !e818
+
+
+/**
+ * Get member LastWebcheck
+ * @return LastWebcheck
+ */
+time_t IPAddrHelper::get_last_webcheck() const
+{
+ return LastWebcheck;
}