Added support for multiple interfaces.
authorBjoern Sikora <bjoern.sikora@intra2net.com>
Wed, 14 Oct 2009 15:47:39 +0000 (17:47 +0200)
committerBjoern Sikora <bjoern.sikora@intra2net.com>
Wed, 14 Oct 2009 15:47:39 +0000 (17:47 +0200)
Improved IPv6 Support.

src/ip_addr_helper.cpp
src/ip_addr_helper.h
src/logger.cpp
src/logger.h
src/updater.cpp

index a55e0c3..fffb4a0 100644 (file)
 #include "ip_addr_helper.h"
 #include <boost/asio.hpp>
 #include <boost/regex.hpp>
+#include <arpa/inet.h>
+#include <sys/socket.h>
+#include <netdb.h>
+#include <ifaddrs.h>
+
 
 using namespace std;
 
@@ -55,11 +60,64 @@ IPAddrHelper::~IPAddrHelper()
 
 
 /**
- * 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(const string ip) const
+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}");
@@ -126,23 +184,98 @@ bool IPAddrHelper::is_local(const string ip) const
 string IPAddrHelper::get_actual_ip()
 {
     string ip;
+
     if ( WebcheckIpUrl.empty() )
-    {
-        ip = dns_query("");
-    }
+        ip = get_local_wan_nic_ip();
     else
-    {
         ip = webcheck_ip();
-    }
 
-    // If IP is within a private range then return ""
-    if ( is_local(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()
+{
+    struct ifaddrs *if_addr_struct, *ifa;
+    int address_family, 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 )
     {
-        Log->print_ip_is_local(ip);
+        Log->print_error_getting_local_wan_ip("getifaddrs", strerror(errno));
+        freeifaddrs(if_addr_struct);
         return "";
     }
 
-    return ip;
+    // Iterate through the linked list.
+    for ( ifa = if_addr_struct; ifa != NULL; ifa = ifa->ifa_next)
+    {
+        // 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, 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 == true) )
+        {
+            // Translate the address to a protocol independent representation (dottet format). Copy address into ip_addr_buff.
+            ret_val = getnameinfo(ifa->ifa_addr, 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 == true) && (!external_ipv6_addresses.empty()) )
+        return external_ipv6_addresses.front();
+    else if ( !external_ipv4_addresses.empty() )
+        return external_ipv4_addresses.front();
+
+    return "";
 }
 
 
@@ -153,9 +286,10 @@ string IPAddrHelper::get_actual_ip()
  */
 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;
@@ -165,20 +299,49 @@ string IPAddrHelper::dns_query(const string& _hostname) const
         // 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 == true) )
+            {
+                // 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();
@@ -189,10 +352,14 @@ string IPAddrHelper::dns_query(const string& _hostname) const
         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 == true) && (!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 "";
 }
 
 
@@ -213,7 +380,7 @@ string IPAddrHelper::webcheck_ip()
     {
         // Webcheck not allowed, log it and return empty string.
         Log->print_webcheck_exceed_interval( LastWebcheck, (WebcheckInterval*60), current_time );
-        return ip_addr;
+        return "";
     }
 
     // Init CURL buffers
@@ -250,33 +417,50 @@ string IPAddrHelper::webcheck_ip()
     {
         // Log it and return the empty string.
         Log->print_webcheck_no_ip();
-        return ip_addr;
+        return "";
     }
 
     // Log the received curl data.
     Log->print_received_curl_data(curl_writedata_buff);
 
-    // Try to parse a IPAddress out of the received data.
-    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);
+
+    if ( !ip_addr.empty() )
+    {
+        // Got a IPv4 address out of the received data.
+        Log->print_own_ipv4(ip_addr, Hostname);
+    }
+    else
+    {
+        return "";
+    }
 
     // Set the LastWebcheck time to current time.
     LastWebcheck = current_time;
 
+    // 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;
@@ -284,7 +468,7 @@ string IPAddrHelper::parse_ip(const string& data) const
     if ( boost::regex_search(data,matches,expr) )
     {
         ip = matches[1];
-        Log->print_regex_found_ip(ip);
+        Log->print_regex_match(expr.str(),ip);
     }
     else
     {
index ebd855f..45d970a 100644 (file)
@@ -41,11 +41,12 @@ private:
     bool UseIPv6;
     std::string Hostname;
 
-    bool is_local(const std::string ip) const;
+    bool is_local_ipv4(const std::string ip) const;
+    bool is_local_ipv6(const std::string ip) const;
     std::string webcheck_ip();
     CURL * init_curl(std::string& curl_writedata_buff, char* curl_err_buff) const;
     int perform_curl_operation(CURL * curl_easy_handle, char* curl_err_buff, const std::string& actual_url) const;
-    std::string parse_ip(const std::string& data) const;
+    std::string parse_ipv4(const std::string& data) const;
 
 public:
 
@@ -61,6 +62,8 @@ public:
 
     std::string get_actual_ip();
 
+    std::string get_local_wan_nic_ip();
+
     // libcurl is a C library, so we have to make the callback member function static :-(
     static int http_receive(char *inBuffer, size_t size, size_t nmemb, std::string *outBuffer);
 
index 91d040d..1bc189e 100644 (file)
@@ -1641,3 +1641,35 @@ void Logger::print_no_update_needed(const string& hostname, const string& ip_dns
         log_notice(msg.str(),level);
     }
 }
+
+
+/**
+ * Error while trying to get local wan interface IP address through getifaddrs.
+ * @param error The system call which raised the error.
+ * @param error The error set by getifaddrs.
+ */
+void Logger::print_error_getting_local_wan_ip(const std::string& system_call, const std::string& error) const
+{
+    int level = 0;
+    if ( level <= Loglevel )
+    {
+        ostringstream msg;
+        msg << "Error while trying to get local wan interface IP address through '" << system_call << "': " << error << endl;
+        log_error(msg.str(),level);
+    }
+}
+
+
+/**
+ * Could not get IP address of local wan interface.
+ */
+void Logger::print_no_wan_ip() const
+{
+    int level = 0;
+    if ( level <= Loglevel )
+    {
+        ostringstream msg;
+        msg << "Could not get IP address of local wan interface." << endl;
+        log_error(msg.str(),level);
+    }
+}
index a5e5d73..85f6d99 100644 (file)
@@ -140,6 +140,8 @@ public:
 
     void print_webcheck_no_ip() const;
 
+    void print_no_wan_ip() const;
+
     void print_webcheck_url_connection_problem(const char * curl_err_buff, const std::string& url) const;
 
     void print_webcheck_error(const char * curl_err_buff, const std::string& url) const;
@@ -231,6 +233,8 @@ public:
     void print_update_service_ttl_not_expired(const std::string& hostname, const std::string& ip_dns_recheck, const std::string& ip_last_update, const std::string& ip_host, const int lastupdated, const int dns_cache_ttl, const int current_time) const;
 
     void print_no_update_needed(const std::string& hostname, const std::string& ip_dns_recheck, const std::string& ip_last_update, const std::string& ip_host, const int lastupdated) const;
+
+    void print_error_getting_local_wan_ip(const std::string& system_call, const std::string& error) const;
 };
 
 #endif
index 54306b4..3d18418 100644 (file)
@@ -294,6 +294,10 @@ void Updater::update_services()
             }
         }
     }
+    else
+    {
+        Log->print_no_wan_ip();
+    }
 }