diff --git a/CHANGELOG.md b/CHANGELOG.md
index 700c4888864b818c2e5e75233f0c2a3d7050f8bc..c2d56776b578e0e23804fe104efa7c05910ee771 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,6 +8,12 @@ This is a changelog for Piwik platform developers. All changes for our HTTP API'
 ### Breaking Changes
 * Development related [console commands](http://developer.piwik.org/guides/piwik-on-the-command-line) are only available if the development mode is enabled. To enable the development mode execute `./console development:enable`.
 
+### Deprecations
+* Most methods of `Piwik\IP` have been deprecated in favor of the new [piwik/network](https://github.com/piwik/component-network) component.
+
+### Library updates
+* Code for manipulating IP addresses has been moved to a separate standalone component: [piwik/network](https://github.com/piwik/component-network). Backward compatibility is kept in Piwik core.
+
 ## Piwik 2.8.2
 
 ### Library updates
diff --git a/composer.json b/composer.json
index 24e10fcedcfce82916aacca64aa904b14026eeac..47886d558bc53e56dfb0b4b2f6de244fcafd68d6 100644
--- a/composer.json
+++ b/composer.json
@@ -43,7 +43,8 @@
         "tedivm/jshrink": "~0.5.1",
         "mustangostang/spyc": "~0.5.0",
         "piwik/device-detector": "~2.0",
-        "piwik/decompress": "~0.1.1"
+        "piwik/decompress": "~0.1.1",
+        "piwik/network": "~0.1.0"
     },
     "require-dev": {
         "aws/aws-sdk-php": "2.7.1",
diff --git a/composer.lock b/composer.lock
index 0180bb14819f36c286e9b266d291033ff711d28d..d522b5755dbf0b34e88c470944ff05031a7005df 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
         "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
         "This file is @generated automatically"
     ],
-    "hash": "e8269e35cd792d2a4718e936459423bd",
+    "hash": "7fe1e059c2372246332679bfd15ca4a6",
     "packages": [
         {
             "name": "leafo/lessphp",
@@ -176,6 +176,38 @@
             ],
             "time": "2014-10-06 20:13:03"
         },
+        {
+            "name": "piwik/network",
+            "version": "0.1.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/piwik/component-network.git",
+                "reference": "9037fa29509f86767e02ba58a57d4deb1d01a844"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/piwik/component-network/zipball/9037fa29509f86767e02ba58a57d4deb1d01a844",
+                "reference": "9037fa29509f86767e02ba58a57d4deb1d01a844",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.2"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "~4.0"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Piwik\\Network\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "LGPL-3.0"
+            ],
+            "time": "2014-10-23 03:30:23"
+        },
         {
             "name": "symfony/console",
             "version": "v2.5.5",
diff --git a/core/IP.php b/core/IP.php
index 691bc7442c19762df0a61e3a4793ae570d202139..10c2c62ef8d3a212125bc78a063f888e299e7445 100644
--- a/core/IP.php
+++ b/core/IP.php
@@ -9,6 +9,10 @@
 
 namespace Piwik;
 
+use Piwik\Network\IPUtils;
+use Piwik\Network\IPv4;
+use Piwik\Network\IPv6;
+
 /**
  * Contains IP address helper functions (for both IPv4 and IPv6).
  *
@@ -28,47 +32,18 @@ namespace Piwik;
  */
 class IP
 {
-    const MAPPED_IPv4_START = '::ffff:';
-
     /**
      * Removes the port and the last portion of a CIDR IP address.
      *
      * @param string $ipString The IP address to sanitize.
      * @return string
+     *
+     * @deprecated Use IPUtils::sanitizeIp() instead
+     * @see \Piwik\Network\IPUtils
      */
     public static function sanitizeIp($ipString)
     {
-        $ipString = trim($ipString);
-
-        // CIDR notation, A.B.C.D/E
-        $posSlash = strrpos($ipString, '/');
-        if ($posSlash !== false) {
-            $ipString = substr($ipString, 0, $posSlash);
-        }
-
-        $posColon = strrpos($ipString, ':');
-        $posDot = strrpos($ipString, '.');
-        if ($posColon !== false) {
-            // IPv6 address with port, [A:B:C:D:E:F:G:H]:EEEE
-            $posRBrac = strrpos($ipString, ']');
-            if ($posRBrac !== false && $ipString[0] == '[') {
-                $ipString = substr($ipString, 1, $posRBrac - 1);
-            }
-
-            if ($posDot !== false) {
-                // IPv4 address with port, A.B.C.D:EEEE
-                if ($posColon > $posDot) {
-                    $ipString = substr($ipString, 0, $posColon);
-                }
-                // else: Dotted quad IPv6 address, A:B:C:D:E:F:G.H.I.J
-            } else if (strpos($ipString, ':') === $posColon) {
-                $ipString = substr($ipString, 0, $posColon);
-            }
-            // else: IPv6 address, A:B:C:D:E:F:G:H
-        }
-        // else: IPv4 address, A.B.C.D
-
-        return $ipString;
+        return IPUtils::sanitizeIp($ipString);
     }
 
     /**
@@ -83,43 +58,15 @@ class IP
      *
      * @param string $ipRangeString IP address range
      * @return string|bool  IP address range in CIDR notation OR false
+     *
+     * @deprecated Use IPUtils::sanitizeIpRange() instead
+     * @see \Piwik\Network\IPUtils
      */
     public static function sanitizeIpRange($ipRangeString)
     {
-        $ipRangeString = trim($ipRangeString);
-        if (empty($ipRangeString)) {
-            return false;
-        }
-
-        // IPv4 address with wildcards '*'
-        if (strpos($ipRangeString, '*') !== false) {
-            if (preg_match('~(^|\.)\*\.\d+(\.|$)~D', $ipRangeString)) {
-                return false;
-            }
-
-            $bits = 32 - 8 * substr_count($ipRangeString, '*');
-            $ipRangeString = str_replace('*', '0', $ipRangeString);
-        }
+        $result = IPUtils::sanitizeIpRange($ipRangeString);
 
-        // CIDR
-        if (($pos = strpos($ipRangeString, '/')) !== false) {
-            $bits = substr($ipRangeString, $pos + 1);
-            $ipRangeString = substr($ipRangeString, 0, $pos);
-        }
-
-        // single IP
-        if (($ip = @inet_pton($ipRangeString)) === false)
-            return false;
-
-        $maxbits = strlen($ip) * 8;
-        if (!isset($bits))
-            $bits = $maxbits;
-
-        if ($bits < 0 || $bits > $maxbits) {
-            return false;
-        }
-
-        return "$ipRangeString/$bits";
+        return $result === null ? false : $result;
     }
 
     /**
@@ -127,12 +74,13 @@ class IP
      *
      * @param string $ipString IP address, either IPv4 or IPv6, e.g., `"127.0.0.1"`.
      * @return string Binary-safe string, e.g., `"\x7F\x00\x00\x01"`.
+     *
+     * @deprecated Use IPUtils::stringToBinaryIP() instead
+     * @see \Piwik\Network\IPUtils
      */
     public static function P2N($ipString)
     {
-        // use @inet_pton() because it throws an exception and E_WARNING on invalid input
-        $ip = @inet_pton($ipString);
-        return $ip === false ? "\x00\x00\x00\x00" : $ip;
+        return IPUtils::stringToBinaryIP($ipString);
     }
 
     /**
@@ -142,12 +90,12 @@ class IP
      *
      * @param string $ip IP address in network address format.
      * @return string IP address in presentation format.
+     *
+     * @deprecated Use IPUtils::binaryToStringIP() instead
      */
     public static function N2P($ip)
     {
-        // use @inet_ntop() because it throws an exception and E_WARNING on invalid input
-        $ipStr = @inet_ntop($ip);
-        return $ipStr === false ? '0.0.0.0' : $ipStr;
+        return IPUtils::binaryToStringIP($ip);
     }
 
     /**
@@ -155,10 +103,12 @@ class IP
      *
      * @param string $ip IP address in network address format.
      * @return string IP address in presentation format.
+     *
+     * @deprecated Will be removed
      */
     public static function prettyPrint($ip)
     {
-        return self::N2P($ip);
+        return IPUtils::binaryToStringIP($ip);
     }
 
     /**
@@ -167,27 +117,15 @@ class IP
      *
      * @param string $ip IP address in network address format.
      * @return bool True if IPv4, else false.
+     *
+     * @deprecated Will be removed
+     * @see \Piwik\Network\IP
      */
     public static function isIPv4($ip)
     {
-        // in case mbstring overloads strlen function
-        $strlen = function_exists('mb_orig_strlen') ? 'mb_orig_strlen' : 'strlen';
-
-        // IPv4
-        if ($strlen($ip) == 4) {
-            return true;
-        }
+        $ip = Network\IP::fromBinaryIP($ip);
 
-        // IPv6 - transitional address?
-        if ($strlen($ip) == 16) {
-            if (substr_compare($ip, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff", 0, 12) === 0
-                || substr_compare($ip, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", 0, 12) === 0
-            ) {
-                return true;
-            }
-        }
-
-        return false;
+        return $ip instanceof IPv4;
     }
 
     /**
@@ -200,12 +138,14 @@ class IP
      *
      * @param string $ip IPv4 address in network address format.
      * @return string IP address in presentation format.
+     *
+     * @deprecated This method was kept for backward compatibility and doesn't seem used
      */
     public static function long2ip($ip)
     {
         // IPv4
         if (strlen($ip) == 4) {
-            return self::N2P($ip);
+            return IPUtils::binaryToStringIP($ip);
         }
 
         // IPv6 - transitional address?
@@ -214,7 +154,7 @@ class IP
                 || substr_compare($ip, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", 0, 12) === 0
             ) {
                 // remap 128-bit IPv4-mapped and IPv4-compat addresses
-                return self::N2P(substr($ip, 12));
+                return IPUtils::binaryToStringIP(substr($ip, 12));
             }
         }
 
@@ -227,10 +167,15 @@ class IP
      *
      * @param string $ip
      * @return bool
+     *
+     * @deprecated Will be removed
+     * @see \Piwik\Network\IP
      */
     public static function isIPv6($ip)
     {
-        return filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6);
+        $ip = Network\IP::fromBinaryIP($ip);
+
+        return $ip instanceof IPv6;
     }
 
     /**
@@ -238,10 +183,19 @@ class IP
      *
      * @param string $ip
      * @return bool
+     *
+     * @deprecated Will be removed
+     * @see \Piwik\Network\IP
      */
     public static function isMappedIPv4($ip)
     {
-        return substr($ip, 0, strlen(self::MAPPED_IPv4_START)) === self::MAPPED_IPv4_START;
+        $ip = Network\IP::fromStringIP($ip);
+
+        if (! $ip instanceof IPv6) {
+            return false;
+        }
+
+        return $ip->isMappedIPv4();
     }
 
     /**
@@ -249,10 +203,15 @@ class IP
      *
      * @param string $ip eg, `'::ffff:192.0.2.128'`
      * @return string eg, `'192.0.2.128'`
+     *
+     * @deprecated Use Piwik\Network\IP::toIPv4String() instead
+     * @see \Piwik\Network\IP
      */
     public static function getIPv4FromMappedIPv6($ip)
     {
-        return substr($ip, strlen(self::MAPPED_IPv4_START));
+        $ip = Network\IP::fromStringIP($ip);
+
+        return $ip->toIPv4String();
     }
 
     /**
@@ -260,37 +219,15 @@ class IP
      *
      * @param array $ipRange An IP address range in presentation format.
      * @return array|bool  Array `array($lowIp, $highIp)` in network address format, or false on failure.
+     *
+     * @deprecated Use Piwik\Network\IPUtils::getIPRangeBounds() instead
+     * @see \Piwik\Network\IPUtils
      */
     public static function getIpsForRange($ipRange)
     {
-        if (strpos($ipRange, '/') === false) {
-            $ipRange = self::sanitizeIpRange($ipRange);
-        }
-        $pos = strpos($ipRange, '/');
-
-        $bits = substr($ipRange, $pos + 1);
-        $range = substr($ipRange, 0, $pos);
-        $high = $low = @inet_pton($range);
-        if ($low === false) {
-            return false;
-        }
-
-        $lowLen = strlen($low);
-        $i = $lowLen - 1;
-        $bits = $lowLen * 8 - $bits;
+        $result = IPUtils::getIPRangeBounds($ipRange);
 
-        for ($n = (int)($bits / 8); $n > 0; $n--, $i--) {
-            $low[$i] = chr(0);
-            $high[$i] = chr(255);
-        }
-
-        $n = $bits % 8;
-        if ($n) {
-            $low[$i] = chr(ord($low[$i]) & ~((1 << $n) - 1));
-            $high[$i] = chr(ord($high[$i]) | ((1 << $n) - 1));
-        }
-
-        return array($low, $high);
+        return $result === null ? false : $result;
     }
 
     /**
@@ -301,40 +238,15 @@ class IP
      * @param string $ip IP address in network address format
      * @param array $ipRanges List of IP address ranges
      * @return bool  True if in any of the specified IP address ranges; else false.
+     *
+     * @deprecated Use Piwik\Network\IP::isInRanges() instead
+     * @see \Piwik\Network\IP
      */
     public static function isIpInRange($ip, $ipRanges)
     {
-        $ipLen = strlen($ip);
-        if (empty($ip) || empty($ipRanges) || ($ipLen != 4 && $ipLen != 16)) {
-            return false;
-        }
-
-        foreach ($ipRanges as $range) {
-            if (is_array($range)) {
-                // already split into low/high IP addresses
-                $range[0] = self::P2N($range[0]);
-                $range[1] = self::P2N($range[1]);
-            } else {
-                // expect CIDR format but handle some variations
-                $range = self::getIpsForRange($range);
-            }
-            if ($range === false) {
-                continue;
-            }
-
-            $low = $range[0];
-            $high = $range[1];
-            if (strlen($low) != $ipLen) {
-                continue;
-            }
+        $ip = Network\IP::fromBinaryIP($ip);
 
-            // binary-safe string comparison
-            if ($ip >= $low && $ip <= $high) {
-                return true;
-            }
-        }
-
-        return false;
+        return $ip->isInRanges($ipRanges);
     }
 
     /**
@@ -356,7 +268,7 @@ class IP
         }
 
         $ipString = self::getNonProxyIpFromHeader($default, $clientHeaders);
-        return self::sanitizeIp($ipString);
+        return IPUtils::sanitizeIp($ipString);
     }
 
     /**
@@ -406,7 +318,8 @@ class IP
             $elements = explode(',', $csv);
             for ($i = count($elements); $i--;) {
                 $element = trim(Common::sanitizeInputValue($elements[$i]));
-                if (empty($excludedIps) || (!in_array($element, $excludedIps) && !self::isIpInRange(self::P2N(self::sanitizeIp($element)), $excludedIps))) {
+                $ip = \Piwik\Network\IP::fromStringIP(IPUtils::sanitizeIp($element));
+                if (empty($excludedIps) || (!in_array($element, $excludedIps) && !$ip->isInRanges($excludedIps))) {
                     return $element;
                 }
             }
@@ -415,16 +328,20 @@ class IP
     }
 
     /**
-     * Retirms the hostname for a given IP address.
+     * Returns the hostname for a given IP address.
      *
      * @param string $ipStr Human-readable IP address.
      * @return string The hostname or unmodified $ipStr on failure.
+     *
+     * @deprecated Use Piwik\Network\IP::getHostname() instead
+     * @see \Piwik\Network\IP
      */
     public static function getHostByAddr($ipStr)
     {
-        // PHP's reverse lookup supports ipv4 and ipv6
-        // except on Windows before PHP 5.3
-        $host = strtolower(@gethostbyaddr($ipStr));
-        return $host === '' ? $ipStr : $host;
+        $ip = Network\IP::fromStringIP($ipStr);
+
+        $host = $ip->getHostname();
+
+        return $host === null ? $ipStr : $host;
     }
 }
diff --git a/core/Tracker/Request.php b/core/Tracker/Request.php
index 36b1e3a5b9e59b93cc73a8a095fd127085d1e0bf..b733ae30eae1c6d28b3d17f6dae53d83a367c024 100644
--- a/core/Tracker/Request.php
+++ b/core/Tracker/Request.php
@@ -13,6 +13,7 @@ use Piwik\Common;
 use Piwik\Config;
 use Piwik\Cookie;
 use Piwik\IP;
+use Piwik\Network\IPUtils;
 use Piwik\Piwik;
 use Piwik\Plugins\CustomVariables\CustomVariables;
 use Piwik\Registry;
@@ -563,9 +564,7 @@ class Request
 
     public function getIp()
     {
-        $ipString = $this->getIpString();
-        $ip = IP::P2N($ipString);
-        return $ip;
+        return IPUtils::stringToBinaryIP($this->getIpString());
     }
 
     public function getForcedUserId()
diff --git a/core/Tracker/Visit.php b/core/Tracker/Visit.php
index c8fb12d3897bef83f7c99157854423af4e09dccc..8bdf641ece08d250ce8c76c4405d38109d94a262 100644
--- a/core/Tracker/Visit.php
+++ b/core/Tracker/Visit.php
@@ -11,7 +11,7 @@ namespace Piwik\Tracker;
 
 use Piwik\Common;
 use Piwik\Config;
-use Piwik\IP;
+use Piwik\Network\IPUtils;
 use Piwik\Piwik;
 use Piwik\Plugin\Dimension\VisitDimension;
 use Piwik\Tracker;
@@ -241,7 +241,7 @@ class Visit implements VisitInterface
      */
     protected function handleExistingVisit($visitor, $action, $visitIsConverted)
     {
-        Common::printDebug("Visit is known (IP = " . IP::N2P($this->getVisitorIp()) . ")");
+        Common::printDebug("Visit is known (IP = " . IPUtils::binaryToStringIP($this->getVisitorIp()) . ")");
 
         $valuesToUpdate = $this->getExistingVisitFieldsToUpdate($visitor, $action, $visitIsConverted);
 
@@ -301,7 +301,7 @@ class Visit implements VisitInterface
      */
     protected function handleNewVisit($visitor, $action, $visitIsConverted)
     {
-        Common::printDebug("New Visit (IP = " . IP::N2P($this->getVisitorIp()) . ")");
+        Common::printDebug("New Visit (IP = " . IPUtils::binaryToStringIP($this->getVisitorIp()) . ")");
 
         $this->setNewVisitorInformation($visitor);
 
@@ -467,7 +467,7 @@ class Visit implements VisitInterface
         $debugVisitInfo = $this->visitorInfo;
         $debugVisitInfo['idvisitor'] = bin2hex($debugVisitInfo['idvisitor']);
         $debugVisitInfo['config_id'] = bin2hex($debugVisitInfo['config_id']);
-        $debugVisitInfo['location_ip'] = IP::N2P($debugVisitInfo['location_ip']);
+        $debugVisitInfo['location_ip'] = IPUtils::binaryToStringIP($debugVisitInfo['location_ip']);
         Common::printDebug($debugVisitInfo);
     }
 
diff --git a/core/Tracker/VisitExcluded.php b/core/Tracker/VisitExcluded.php
index 48eb1e3db75e72434ef30299ca9c0303d84b5a02..ca481218cb9149ac96a2ecd5c7fd59e679984e59 100644
--- a/core/Tracker/VisitExcluded.php
+++ b/core/Tracker/VisitExcluded.php
@@ -213,8 +213,9 @@ class VisitExcluded
         $websiteAttributes = Cache::getCacheWebsiteAttributes($this->idSite);
 
         if (!empty($websiteAttributes['excluded_ips'])) {
-            if (IP::isIpInRange($this->ip, $websiteAttributes['excluded_ips'])) {
-                Common::printDebug('Visitor IP ' . IP::N2P($this->ip) . ' is excluded from being tracked');
+            $ip = \Piwik\Network\IP::fromBinaryIP($this->ip);
+            if ($ip->isInRanges($websiteAttributes['excluded_ips'])) {
+                Common::printDebug('Visitor IP ' . $ip->toString() . ' is excluded from being tracked');
                 return true;
             }
         }
diff --git a/core/Url.php b/core/Url.php
index 1d34c17ca3b3c897476a0bb6de537bcf3ad48149..dd9503ca664bbe9fcf1a19604720ce5897f16dcd 100644
--- a/core/Url.php
+++ b/core/Url.php
@@ -10,6 +10,7 @@ namespace Piwik;
 
 use Exception;
 
+use Piwik\Network\IPUtils;
 use Piwik\Session;
 
 /**
@@ -547,7 +548,7 @@ class Url
         $disableHostCheck = Config::getInstance()->General['enable_trusted_host_check'] == 0;
         // compare scheme and host
         $parsedUrl = @parse_url($url);
-        $host = IP::sanitizeIp(@$parsedUrl['host']);
+        $host = IPUtils::sanitizeIp(@$parsedUrl['host']);
         return !empty($host)
         && ($disableHostCheck || in_array($host, $hosts))
         && !empty($parsedUrl['scheme'])
@@ -585,7 +586,7 @@ class Url
      */
     public static function getHostSanitized($host)
     {
-        return IP::sanitizeIp($host);
+        return IPUtils::sanitizeIp($host);
     }
 
     protected static function getHostsFromConfig($domain, $key)
diff --git a/misc/others/geoipUpdateRows.php b/misc/others/geoipUpdateRows.php
index a7564d25ba3a2332efa74e87ed13134064ea49d4..5f36ed5e7d36528a10e2f2f4268e0cd19a29d94c 100755
--- a/misc/others/geoipUpdateRows.php
+++ b/misc/others/geoipUpdateRows.php
@@ -1,10 +1,8 @@
 <?php
 use Piwik\Common;
-use Piwik\Config;
 use Piwik\Db;
-use Piwik\FrontController;
-use Piwik\IP;
 use Piwik\Log;
+use Piwik\Network\IPUtils;
 use Piwik\Piwik;
 use Piwik\Plugins\UserCountry\LocationProvider\GeoIp\Pecl;
 use Piwik\Plugins\UserCountry\LocationProvider;
@@ -183,7 +181,7 @@ for (; $start < $end; $start += $limit) {
             continue;
         }
 
-        $ip = IP::N2P($row['location_ip']);
+        $ip = IPUtils::binaryToStringIP($row['location_ip']);
         $location = $provider->getLocation(array('ip' => $ip));
 
         if (!empty($location[LocationProvider::COUNTRY_CODE_KEY])) {
diff --git a/plugins/Live/Visitor.php b/plugins/Live/Visitor.php
index 965a61ff8fc9e702576c2009761c3da778a43128..27873ec2a1f3d9660675a7b4be6b26556de64a56 100644
--- a/plugins/Live/Visitor.php
+++ b/plugins/Live/Visitor.php
@@ -13,7 +13,7 @@ use Piwik\DataAccess\LogAggregator;
 use Piwik\DataTable\Filter\ColumnDelete;
 use Piwik\Date;
 use Piwik\Db;
-use Piwik\IP;
+use Piwik\Network\IPUtils;
 use Piwik\Piwik;
 use Piwik\Plugins\CustomVariables\CustomVariables;
 use Piwik\Plugins\UserCountry\LocationProvider\GeoIp;
@@ -97,7 +97,7 @@ class Visitor implements VisitorInterface
     function getIp()
     {
         if (isset($this->details['location_ip'])) {
-            return IP::N2P($this->details['location_ip']);
+            return IPUtils::binaryToStringIP($this->details['location_ip']);
         }
         return null;
     }
diff --git a/plugins/PrivacyManager/IPAnonymizer.php b/plugins/PrivacyManager/IPAnonymizer.php
index de588c59fdb8dd94e9b65be668064a97aa8ad30a..a70312916f3fb26827ec661b9487eea6d9f5dd35 100644
--- a/plugins/PrivacyManager/IPAnonymizer.php
+++ b/plugins/PrivacyManager/IPAnonymizer.php
@@ -9,7 +9,7 @@
 namespace Piwik\Plugins\PrivacyManager;
 
 use Piwik\Common;
-use Piwik\IP;
+use Piwik\Network\IP;
 
 /**
  * Anonymize visitor IP addresses to comply with the privacy laws/guidelines in countries, such as Germany.
@@ -19,51 +19,36 @@ class IPAnonymizer
     /**
      * Internal function to mask portions of the visitor IP address
      *
-     * @param string $ip IP address in network address format
+     * @param IP $ip
      * @param int $maskLength Number of octets to reset
-     * @return string
+     * @return IP
      */
-    public static function applyIPMask($ip, $maskLength)
+    public static function applyIPMask(IP $ip, $maskLength)
     {
-        // IPv4 or mapped IPv4 in IPv6
-        if (IP::isIPv4($ip)) {
-            $i = strlen($ip);
-            if ($maskLength > $i) {
-                $maskLength = $i;
-            }
+        $newIpObject = $ip->anonymize($maskLength);
 
-            while ($maskLength-- > 0) {
-                $ip[--$i] = chr(0);
-            }
-        } else {
-            $masks = array(
-                'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff',
-                'ffff:ffff:ffff:ffff::',
-                'ffff:ffff:ffff:0000::',
-                'ffff:ff00:0000:0000::'
-            );
-            $ip = $ip & pack('a16', inet_pton($masks[$maskLength]));
-        }
-        return $ip;
+        return $newIpObject;
     }
 
     /**
      * Hook on Tracker.Visit.setVisitorIp to anomymize visitor IP addresses
+     * @param string $ip IP address in binary format (network format)
      */
     public function setVisitorIpAddress(&$ip)
     {
+        $ipObject = IP::fromBinaryIP($ip);
+
         if (!$this->isActive()) {
-            Common::printDebug("Visitor IP was _not_ anonymized: ". IP::N2P($ip));
+            Common::printDebug("Visitor IP was _not_ anonymized: ". $ipObject->toString());
             return;
         }
 
-        $originalIp = $ip;
-
         $privacyConfig = new Config();
 
-        $ip = self::applyIPMask($ip, $privacyConfig->ipAddressMaskLength);
+        $newIpObject = self::applyIPMask($ipObject, $privacyConfig->ipAddressMaskLength);
+        $ip = $newIpObject->toBinary();
 
-        Common::printDebug("Visitor IP (was: ". IP::N2P($originalIp) .") has been anonymized: ". IP::N2P($ip));
+        Common::printDebug("Visitor IP (was: ". $ipObject->toString() .") has been anonymized: ". $newIpObject->toString());
     }
 
     /**
diff --git a/plugins/PrivacyManager/tests/Unit/AnonymizeIPTest.php b/plugins/PrivacyManager/tests/Unit/AnonymizeIPTest.php
index 0ed4e6e4ca7d9067f7839036709d7ab3a671fd24..d5e50e520923596f11f0cf5403e22efe52e315f6 100644
--- a/plugins/PrivacyManager/tests/Unit/AnonymizeIPTest.php
+++ b/plugins/PrivacyManager/tests/Unit/AnonymizeIPTest.php
@@ -8,7 +8,7 @@
 
 namespace Piwik\Plugins\PrivacyManager\tests;
 
-use Piwik\IP;
+use Piwik\Network\IP;
 use Piwik\Plugins\PrivacyManager\IPAnonymizer;
 
 require_once PIWIK_INCLUDE_PATH . '/plugins/PrivacyManager/IPAnonymizer.php';
@@ -58,23 +58,26 @@ class AnonymizeIPTest extends \PHPUnit_Framework_TestCase
      * @dataProvider getipv4Addresses
      * @group Plugins
      */
-    public function testApplyIPMask($ip, $expected)
+    public function testApplyIPMask($ipString, $expected)
     {
+        $ip = IP::fromStringIP($ipString);
+
         // each IP is tested with 0 to 4 octets masked
         for ($maskLength = 0; $maskLength <= 4; $maskLength++) {
-            $res = IPAnonymizer::applyIPMask(IP::P2N($ip), $maskLength);
-            $this->assertEquals($expected[$maskLength], $res, "Got " . bin2hex($res) . ", Expected " . bin2hex($expected[$maskLength]));
+            $res = IPAnonymizer::applyIPMask($ip, $maskLength);
+            $this->assertEquals($expected[$maskLength], $res->toBinary(), "Got " . $res . ", Expected " . bin2hex($expected[$maskLength]));
         }
 
         // edge case (bounds check)
-        $this->assertEquals("\x00\x00\x00\x00", IPAnonymizer::applyIPMask(IP::P2N($ip), 5));
+        $this->assertEquals("\x00\x00\x00\x00", IPAnonymizer::applyIPMask($ip, 5)->toBinary());
 
         // mask IPv4 mapped addresses
+        $mappedIp = IP::fromStringIP('::ffff:' . $ipString);
         for ($maskLength = 0; $maskLength <= 4; $maskLength++) {
-            $res = IPAnonymizer::applyIPMask(IP::P2N('::ffff:' . $ip), $maskLength);
-            $this->assertEquals($res, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff" . $expected[$maskLength], "Got " . bin2hex($res) . ", Expected " . bin2hex($expected[$maskLength]));
+            $res = IPAnonymizer::applyIPMask($mappedIp, $maskLength);
+            $this->assertEquals("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff" . $expected[$maskLength], $res->toBinary(), "Got " . $res . ", Expected " . bin2hex($expected[$maskLength]));
         }
-        $this->assertEquals("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\x00\x00\x00\x00\x00", IPAnonymizer::applyIPMask(IP::P2N('::ffff:' . $ip), 5));
+        $this->assertEquals("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\x00\x00\x00\x00\x00", IPAnonymizer::applyIPMask($mappedIp, 5)->toBinary());
     }
 
     /**
@@ -85,8 +88,8 @@ class AnonymizeIPTest extends \PHPUnit_Framework_TestCase
     {
         // each IP is tested with 0 to 4 octets masked
         for ($maskLength = 0; $maskLength < 4; $maskLength++) {
-            $res = IPAnonymizer::applyIPMask(IP::P2N($ip), $maskLength);
-            $this->assertEquals($expected[$maskLength], $res, "Got " . bin2hex($res) . ", Expected " . bin2hex($expected[$maskLength]) . ", Mask Level " . $maskLength);
+            $res = IPAnonymizer::applyIPMask(IP::fromStringIP($ip), $maskLength);
+            $this->assertEquals($expected[$maskLength], $res->toBinary(), "Got " . $res . ", Expected " . bin2hex($expected[$maskLength]) . ", Mask Level " . $maskLength);
         }
     }
 }
\ No newline at end of file
diff --git a/plugins/Provider/Columns/Provider.php b/plugins/Provider/Columns/Provider.php
index c20b75ac9c041e2dcd55969301bc26fa692a318c..cba1b8b9f58df6bb51c34c9a6add5273551a4518 100644
--- a/plugins/Provider/Columns/Provider.php
+++ b/plugins/Provider/Columns/Provider.php
@@ -10,6 +10,7 @@ namespace Piwik\Plugins\Provider\Columns;
 
 use Piwik\Common;
 use Piwik\IP;
+use Piwik\Network\IPUtils;
 use Piwik\Piwik;
 use Piwik\Plugin\Dimension\VisitDimension;
 use Piwik\Plugin\Segment;
@@ -54,7 +55,7 @@ class Provider extends VisitDimension
             $ip = $request->getIp();
         }
 
-        $ip = IP::N2P($ip);
+        $ip = IPUtils::binaryToStringIP($ip);
 
         // In case the IP was anonymized, we should not continue since the DNS reverse lookup will fail and this will slow down tracking
         if (substr($ip, -2, 2) == '.0') {
diff --git a/plugins/SitesManager/API.php b/plugins/SitesManager/API.php
index 08beed366f26fcde06dce4642590a92958b6beb4..02f50a1ece707336168eb4f155719b1f34759221 100644
--- a/plugins/SitesManager/API.php
+++ b/plugins/SitesManager/API.php
@@ -15,6 +15,7 @@ use Piwik\Date;
 use Piwik\Db;
 use Piwik\IP;
 use Piwik\MetricsFormatter;
+use Piwik\Network\IPUtils;
 use Piwik\Option;
 use Piwik\Piwik;
 use Piwik\ProxyHttp;
@@ -745,12 +746,12 @@ class API extends \Piwik\Plugin\API
      */
     public function getIpsForRange($ipRange)
     {
-        $range = IP::getIpsForRange($ipRange);
-        if ($range === false) {
+        $range = IPUtils::getIPRangeBounds($ipRange);
+        if ($range === null) {
             return false;
         }
 
-        return array(IP::N2P($range[0]), IP::N2P($range[1]));
+        return array(IPUtils::binaryToStringIP($range[0]), IPUtils::binaryToStringIP($range[1]));
     }
 
     /**
@@ -1322,7 +1323,7 @@ class API extends \Piwik\Plugin\API
      */
     private function isValidIp($ip)
     {
-        return IP::getIpsForRange($ip) !== false;
+        return IPUtils::getIPRangeBounds($ip) !== null;
     }
 
     /**
diff --git a/plugins/UserCountry/Columns/Base.php b/plugins/UserCountry/Columns/Base.php
index c3ee4611834e3b8041e4dd2897dccc72a6b59ffa..3e4678a0e98f0b67bdafacace53864603700f336 100644
--- a/plugins/UserCountry/Columns/Base.php
+++ b/plugins/UserCountry/Columns/Base.php
@@ -9,12 +9,12 @@
 namespace Piwik\Plugins\UserCountry\Columns;
 
 use Piwik\Common;
+use Piwik\Network\IPUtils;
 use Piwik\Plugin\Dimension\VisitDimension;
 use Piwik\Plugins\UserCountry\LocationProvider\GeoIp;
 use Piwik\Plugins\UserCountry\LocationProvider;
 use Piwik\Plugins\PrivacyManager\Config as PrivacyManagerConfig;
 use Piwik\Plugins\UserCountry\LocationProvider\DefaultProvider;
-use Piwik\IP;
 use Piwik\Tracker\Visitor;
 use Piwik\Tracker\Visit;
 use Piwik\Tracker\Request;
@@ -111,7 +111,7 @@ abstract class Base extends VisitDimension
             $ip = $anonymizedIp;
         }
 
-        $ipAddress = IP::N2P($ip);
+        $ipAddress = IPUtils::binaryToStringIP($ip);
 
         return $ipAddress;
     }
diff --git a/plugins/UserCountry/LocationProvider.php b/plugins/UserCountry/LocationProvider.php
index d0ffd93f51da9f93f64feaa07a71bb6ee562dee9..a5a656c42f263bc9da546de3790df51184b2e48e 100755
--- a/plugins/UserCountry/LocationProvider.php
+++ b/plugins/UserCountry/LocationProvider.php
@@ -444,23 +444,17 @@ abstract class LocationProvider
 
     /**
      * Returns an IP address from an array that was passed into getLocation. This
-     * will return an IPv4 address or false if the address is IPv6 (IPv6 is not
+     * will return an IPv4 address or null if the address is IPv6 (IPv6 is not
      * supported yet).
      *
      * @param  array $info Must have 'ip' key.
-     * @return string|bool
+     * @return string|null
      */
     protected function getIpFromInfo($info)
     {
-        $ip = $info['ip'];
-        if (IP::isMappedIPv4($ip)) {
-            return IP::getIPv4FromMappedIPv6($ip);
-        } else if (IP::isIPv6($ip)) // IPv6 is not supported (yet)
-        {
-            return false;
-        } else {
-            return $ip;
-        }
+        $ip = \Piwik\Network\IP::fromStringIP($info['ip']);
+
+        return $ip->toIPv4String();
     }
 }
 
diff --git a/tests/PHPUnit/Integration/Tracker/VisitTest.php b/tests/PHPUnit/Integration/Tracker/VisitTest.php
index 0d1837e2c109f0f2e7936b1a431305ae1014401a..292b98737d03c485257f5641082c8a0389783341 100644
--- a/tests/PHPUnit/Integration/Tracker/VisitTest.php
+++ b/tests/PHPUnit/Integration/Tracker/VisitTest.php
@@ -5,8 +5,9 @@
  * @link http://piwik.org
  * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
  */
+
 use Piwik\Access;
-use Piwik\IP;
+use Piwik\Network\IPUtils;
 use Piwik\Plugins\SitesManager\API;
 use Piwik\Tracker\Request;
 use Piwik\Tracker\VisitExcluded;
@@ -86,7 +87,7 @@ class Core_Tracker_VisitTest extends IntegrationTestCase
 
         // test that IPs within the range, or the given IP, are excluded
         foreach ($tests as $ip => $expected) {
-            $testIpIsExcluded = IP::P2N($ip);
+            $testIpIsExcluded = IPUtils::stringToBinaryIP($ip);
 
             $excluded = new VisitExcluded_public($request, $testIpIsExcluded);
             $this->assertSame($expected, $excluded->public_isVisitorIpExcluded($testIpIsExcluded));
@@ -195,7 +196,7 @@ class Core_Tracker_VisitTest extends IntegrationTestCase
         $request = new Request(array('idsite' => $idsite, 'bots' => 0));
 
         foreach ($isIpBot as $ip => $isBot) {
-            $excluded = new VisitExcluded_public($request, IP::P2N($ip));
+            $excluded = new VisitExcluded_public($request, IPUtils::stringToBinaryIP($ip));
 
             $this->assertSame($isBot, $excluded->public_isNonHumanBot(), $ip);
         }
diff --git a/tests/PHPUnit/Unit/IPTest.php b/tests/PHPUnit/Unit/IPTest.php
index f12d37dfed2ef711fcefc98275591bb3ea6e9f92..39fd083e5f70e2588ee1d4e0a72ed51d1087a489 100644
--- a/tests/PHPUnit/Unit/IPTest.php
+++ b/tests/PHPUnit/Unit/IPTest.php
@@ -1,301 +1,17 @@
 <?php
-use Piwik\Common;
-use Piwik\Config;
-use Piwik\IP;
-use Piwik\SettingsServer;
-
 /**
  * Piwik - free/libre analytics platform
  *
  * @link http://piwik.org
  * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
  */
-class IPTest extends PHPUnit_Framework_TestCase
-{
-    /**
-     * Dataprovider for testSanitizeIp
-     */
-    public function getIPData()
-    {
-        return array( // input, output
-            // single IPv4 address
-            array('127.0.0.1', '127.0.0.1'),
-
-            // single IPv6 address (ambiguous)
-            array('::1', '::1'),
-            array('::ffff:127.0.0.1', '::ffff:127.0.0.1'),
-            array('2001:5c0:1000:b::90f8', '2001:5c0:1000:b::90f8'),
-
-            // single IPv6 address
-            array('[::1]', '::1'),
-            array('[2001:5c0:1000:b::90f8]', '2001:5c0:1000:b::90f8'),
-            array('[::ffff:127.0.0.1]', '::ffff:127.0.0.1'),
-
-            // single IPv4 address (CIDR notation)
-            array('192.168.1.1/32', '192.168.1.1'),
-
-            // single IPv6 address (CIDR notation)
-            array('::1/128', '::1'),
-            array('::ffff:127.0.0.1/128', '::ffff:127.0.0.1'),
-            array('2001:5c0:1000:b::90f8/128', '2001:5c0:1000:b::90f8'),
-
-            // IPv4 address with port
-            array('192.168.1.2:80', '192.168.1.2'),
-
-            // IPv6 address with port
-            array('[::1]:80', '::1'),
-            array('[::ffff:127.0.0.1]:80', '::ffff:127.0.0.1'),
-            array('[2001:5c0:1000:b::90f8]:80', '2001:5c0:1000:b::90f8'),
-
-            // hostnames with port?
-            array('localhost', 'localhost'),
-            array('localhost:80', 'localhost'),
-            array('www.example.com', 'www.example.com'),
-            array('example.com:80', 'example.com'),
-            array('example.com:8080', 'example.com'),
-            array('sub.example.com:8080', 'sub.example.com'),
-        );
-    }
-
-    /**
-     * @dataProvider getIPData
-     * @group Core
-     */
-    public function testSanitizeIp($ip, $expected)
-    {
-        $this->assertEquals($expected, IP::sanitizeIp($ip));
-    }
-
-    /**
-     * Dataprovider for testSanitizeIpRange
-     */
-    public function getIPRangeData()
-    {
-        return array(
-            array('', false),
-            array(' 127.0.0.1 ', '127.0.0.1/32'),
-            array('192.168.1.0', '192.168.1.0/32'),
-            array('192.168.1.1/24', '192.168.1.1/24'),
-            array('192.168.1.2/16', '192.168.1.2/16'),
-            array('192.168.1.3/8', '192.168.1.3/8'),
-            array('192.168.2.*', '192.168.2.0/24'),
-            array('192.169.*.*', '192.169.0.0/16'),
-            array('193.*.*.*', '193.0.0.0/8'),
-            array('*.*.*.*', '0.0.0.0/0'),
-            array('*.*.*.1', false),
-            array('*.*.1.1', false),
-            array('*.1.1.1', false),
-            array('1.*.1.1', false),
-            array('1.1.*.1', false),
-            array('1.*.*.1', false),
-            array('::1', '::1/128'),
-            array('::ffff:127.0.0.1', '::ffff:127.0.0.1/128'),
-            array('2001:5c0:1000:b::90f8', '2001:5c0:1000:b::90f8/128'),
-            array('::1/64', '::1/64'),
-            array('::ffff:127.0.0.1/64', '::ffff:127.0.0.1/64'),
-            array('2001:5c0:1000:b::90f8/64', '2001:5c0:1000:b::90f8/64'),
-        );
-    }
-
-    /**
-     * @dataProvider getIPRangeData
-     * @group Core
-     */
-    public function testSanitizeIpRange($ip, $expected)
-    {
-        $this->assertEquals($expected, IP::sanitizeIpRange($ip));
-    }
-
-    /**
-     * Dataprovider for testP2N
-     */
-    public function getP2NTestData()
-    {
-        return array(
-            // IPv4
-            array('0.0.0.0', "\x00\x00\x00\x00"),
-            array('127.0.0.1', "\x7F\x00\x00\x01"),
-            array('192.168.1.12', "\xc0\xa8\x01\x0c"),
-            array('255.255.255.255', "\xff\xff\xff\xff"),
-
-            // IPv6
-            array('::', "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"),
-            array('::1', "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01"),
-            array('::fffe:7f00:1', "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xfe\x7f\x00\x00\x01"),
-            array('::ffff:127.0.0.1', "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\x7f\x00\x00\x01"),
-            array('2001:5c0:1000:b::90f8', "\x20\x01\x05\xc0\x10\x00\x00\x0b\x00\x00\x00\x00\x00\x00\x90\xf8"),
-        );
-    }
-
-    /**
-     * @dataProvider getP2NTestData
-     * @group Core
-     */
-    public function testP2N($P, $N)
-    {
-        $this->assertEquals($N, IP::P2N($P));
-    }
-
-    /**
-     * Dataprovider for testP2NInvalidInput
-     */
-    public function getP2NInvalidInputData()
-    {
-        return array(
-            // not a series of dotted numbers
-            array(null),
-            array(''),
-            array('alpha'),
-            array('...'),
-
-            // missing an octet
-            array('.0.0.0'),
-            array('0..0.0'),
-            array('0.0..0'),
-            array('0.0.0.'),
-
-            // octets must be 0-255
-            array('-1.0.0.0'),
-            array('1.1.1.256'),
-
-            // leading zeros not supported (i.e., can be ambiguous, e.g., octal)
-            array('07.07.07.07'),
-        );
-    }
-
-    /**
-     * @group Core
-     *
-     * @dataProvider getP2NInvalidInputData
-     */
-    public function testP2NInvalidInput($P)
-    {
-        $this->assertEquals("\x00\x00\x00\x00", IP::P2N($P));
-    }
-
-    /**
-     * @group Core
-     */
-    public function getN2PTestData()
-    {
-        // a valid network address is either 4 or 16 bytes; those lines are intentionally left blank ;)
-        return array(
-            array(null),
-            array(''),
-            array("\x01"),
-            array("\x01\x00"),
-            array("\x01\x00\x00"),
 
-            array("\x01\x00\x00\x00\x00"),
-            array("\x01\x00\x00\x00\x00\x00"),
-            array("\x01\x00\x00\x00\x00\x00\x00"),
-            array("\x01\x00\x00\x00\x00\x00\x00\x00"),
-            array("\x01\x00\x00\x00\x00\x00\x00\x00\x00"),
-            array("\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00"),
-            array("\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"),
-            array("\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"),
-            array("\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"),
-            array("\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"),
-            array("\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"),
-
-            array("\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"),
-        );
-    }
-
-    /**
-     * @dataProvider getP2NTestData
-     * @group Core
-     */
-    public function testN2P($P, $N)
-    {
-        $this->assertEquals($P, IP::N2P($N), "$P vs" . IP::N2P($N));
-    }
-
-    /**
-     * @dataProvider getN2PTestData
-     * @group Core
-     */
-    public function testN2PinvalidInput($N)
-    {
-        $this->assertEquals("0.0.0.0", IP::N2P($N), bin2hex($N));
-    }
-
-    /**
-     * @dataProvider getP2NTestData
-     * @group Core
-     */
-    public function testPrettyPrint($P, $N)
-    {
-        $this->assertEquals($P, IP::prettyPrint($N), "$P vs" . IP::N2P($N));
-    }
-
-    /**
-     * @dataProvider getN2PTestData
-     * @group Core
-     */
-    public function testPrettyPrintInvalidInput($N)
-    {
-        $this->assertEquals("0.0.0.0", IP::prettyPrint($N), bin2hex($N));
-    }
-
-    /**
-     * Dataprovider for IP4 test data
-     */
-    public function getIPv4Data()
-    {
-        // a valid network address is either 4 or 16 bytes; those lines are intentionally left blank ;)
-        return array(
-            // invalid
-            array(null, false),
-            array("", false),
-
-            // IPv4
-            array("\x00\x00\x00\x00", true),
-            array("\x7f\x00\x00\x01", true),
-
-            // IPv4-compatible (this transitional format is deprecated in RFC 4291, section 2.5.5.1)
-            array("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xc0\xa8\x01\x01", true),
-
-            // IPv4-mapped (RFC 4291, 2.5.5.2)
-            array("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xc0\xa8\x01\x02", true),
-
-            // other IPv6 address
-            array("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\x00\xc0\xa8\x01\x03", false),
-            array("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xc0\xa8\x01\x04", false),
-            array("\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xc0\xa8\x01\x05", false),
-
-            /*
-             * We assume all stored IP addresses (pre-Piwik 1.4) were converted from UNSIGNED INT to VARBINARY.
-             * The following is just for informational purposes.
-             */
-
-            // 192.168.1.0
-            array('-1062731520', false),
-            array('3232235776', false),
-
-            // 10.10.10.10
-            array('168430090', false),
-
-            // 0.0.39.15 - this is the ambiguous case (i.e., 4 char string)
-            array('9999', true),
-            array("\x39\x39\x39\x39", true),
-
-            // 0.0.3.231
-            array('999', false),
-            array("\x39\x39\x39", false),
-        );
-
-    }
-
-    /**
-     * @dataProvider getIPv4Data
-     * @group Core
-     */
-    public function testIsIPv4($ip, $bool)
-    {
-        $this->assertEquals($bool, IP::isIPv4($ip), bin2hex($ip));
-    }
+use Piwik\Common;
+use Piwik\Config;
+use Piwik\IP;
 
+class IPTest extends PHPUnit_Framework_TestCase
+{
     /**
      * Dataprovider for long2ip test
      */
@@ -354,204 +70,6 @@ class IPTest extends PHPUnit_Framework_TestCase
         $this->assertEquals($P, Common::long2ip($N), bin2hex($N));
     }
 
-    /**
-     * Dataprovider for ip range test
-     */
-    public function getIPsForRangeTest()
-    {
-        return array(
-
-            // invalid ranges
-            array(null, false),
-            array('', false),
-            array('0', false),
-
-            // single IPv4
-            array('127.0.0.1', array("\x7f\x00\x00\x01", "\x7f\x00\x00\x01")),
-
-            // IPv4 with wildcards
-            array('192.168.1.*', array("\xc0\xa8\x01\x00", "\xc0\xa8\x01\xff")),
-            array('192.168.*.*', array("\xc0\xa8\x00\x00", "\xc0\xa8\xff\xff")),
-            array('192.*.*.*', array("\xc0\x00\x00\x00", "\xc0\xff\xff\xff")),
-            array('*.*.*.*', array("\x00\x00\x00\x00", "\xff\xff\xff\xff")),
-
-            // single IPv4 in expected CIDR notation
-            array('192.168.1.1/24', array("\xc0\xa8\x01\x00", "\xc0\xa8\x01\xff")),
-
-            array('192.168.1.127/32', array("\xc0\xa8\x01\x7f", "\xc0\xa8\x01\x7f")),
-            array('192.168.1.127/31', array("\xc0\xa8\x01\x7e", "\xc0\xa8\x01\x7f")),
-            array('192.168.1.127/30', array("\xc0\xa8\x01\x7c", "\xc0\xa8\x01\x7f")),
-            array('192.168.1.127/29', array("\xc0\xa8\x01\x78", "\xc0\xa8\x01\x7f")),
-            array('192.168.1.127/28', array("\xc0\xa8\x01\x70", "\xc0\xa8\x01\x7f")),
-            array('192.168.1.127/27', array("\xc0\xa8\x01\x60", "\xc0\xa8\x01\x7f")),
-            array('192.168.1.127/26', array("\xc0\xa8\x01\x40", "\xc0\xa8\x01\x7f")),
-            array('192.168.1.127/25', array("\xc0\xa8\x01\x00", "\xc0\xa8\x01\x7f")),
-
-            array('192.168.1.255/32', array("\xc0\xa8\x01\xff", "\xc0\xa8\x01\xff")),
-            array('192.168.1.255/31', array("\xc0\xa8\x01\xfe", "\xc0\xa8\x01\xff")),
-            array('192.168.1.255/30', array("\xc0\xa8\x01\xfc", "\xc0\xa8\x01\xff")),
-            array('192.168.1.255/29', array("\xc0\xa8\x01\xf8", "\xc0\xa8\x01\xff")),
-            array('192.168.1.255/28', array("\xc0\xa8\x01\xf0", "\xc0\xa8\x01\xff")),
-            array('192.168.1.255/27', array("\xc0\xa8\x01\xe0", "\xc0\xa8\x01\xff")),
-            array('192.168.1.255/26', array("\xc0\xa8\x01\xc0", "\xc0\xa8\x01\xff")),
-            array('192.168.1.255/25', array("\xc0\xa8\x01\x80", "\xc0\xa8\x01\xff")),
-
-            array('192.168.255.255/24', array("\xc0\xa8\xff\x00", "\xc0\xa8\xff\xff")),
-            array('192.168.255.255/23', array("\xc0\xa8\xfe\x00", "\xc0\xa8\xff\xff")),
-            array('192.168.255.255/22', array("\xc0\xa8\xfc\x00", "\xc0\xa8\xff\xff")),
-            array('192.168.255.255/21', array("\xc0\xa8\xf8\x00", "\xc0\xa8\xff\xff")),
-            array('192.168.255.255/20', array("\xc0\xa8\xf0\x00", "\xc0\xa8\xff\xff")),
-            array('192.168.255.255/19', array("\xc0\xa8\xe0\x00", "\xc0\xa8\xff\xff")),
-            array('192.168.255.255/18', array("\xc0\xa8\xc0\x00", "\xc0\xa8\xff\xff")),
-            array('192.168.255.255/17', array("\xc0\xa8\x80\x00", "\xc0\xa8\xff\xff")),
-
-            // single IPv6
-            array('::1', array("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01", "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01")),
-
-            // single IPv6 in expected CIDR notation
-            array('::1/128', array("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01", "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01")),
-            array('::1/127', array("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01")),
-            array('::fffe:7f00:1/120', array("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xfe\x7f\x00\x00\x00", "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xfe\x7f\x00\x00\xff")),
-            array('::ffff:127.0.0.1/120', array("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\x7f\x00\x00\x00", "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\x7f\x00\x00\xff")),
-
-            array('2001:ca11:911::b0b:15:dead/128', array("\x20\x01\xca\x11\x09\x11\x00\x00\x00\x00\x0b\x0b\x00\x15\xde\xad", "\x20\x01\xca\x11\x09\x11\x00\x00\x00\x00\x0b\x0b\x00\x15\xde\xad")),
-            array('2001:ca11:911::b0b:15:dead/127', array("\x20\x01\xca\x11\x09\x11\x00\x00\x00\x00\x0b\x0b\x00\x15\xde\xac", "\x20\x01\xca\x11\x09\x11\x00\x00\x00\x00\x0b\x0b\x00\x15\xde\xad")),
-            array('2001:ca11:911::b0b:15:dead/126', array("\x20\x01\xca\x11\x09\x11\x00\x00\x00\x00\x0b\x0b\x00\x15\xde\xac", "\x20\x01\xca\x11\x09\x11\x00\x00\x00\x00\x0b\x0b\x00\x15\xde\xaf")),
-            array('2001:ca11:911::b0b:15:dead/125', array("\x20\x01\xca\x11\x09\x11\x00\x00\x00\x00\x0b\x0b\x00\x15\xde\xa8", "\x20\x01\xca\x11\x09\x11\x00\x00\x00\x00\x0b\x0b\x00\x15\xde\xaf")),
-            array('2001:ca11:911::b0b:15:dead/124', array("\x20\x01\xca\x11\x09\x11\x00\x00\x00\x00\x0b\x0b\x00\x15\xde\xa0", "\x20\x01\xca\x11\x09\x11\x00\x00\x00\x00\x0b\x0b\x00\x15\xde\xaf")),
-            array('2001:ca11:911::b0b:15:dead/123', array("\x20\x01\xca\x11\x09\x11\x00\x00\x00\x00\x0b\x0b\x00\x15\xde\xa0", "\x20\x01\xca\x11\x09\x11\x00\x00\x00\x00\x0b\x0b\x00\x15\xde\xbf")),
-            array('2001:ca11:911::b0b:15:dead/122', array("\x20\x01\xca\x11\x09\x11\x00\x00\x00\x00\x0b\x0b\x00\x15\xde\x80", "\x20\x01\xca\x11\x09\x11\x00\x00\x00\x00\x0b\x0b\x00\x15\xde\xbf")),
-            array('2001:ca11:911::b0b:15:dead/121', array("\x20\x01\xca\x11\x09\x11\x00\x00\x00\x00\x0b\x0b\x00\x15\xde\x80", "\x20\x01\xca\x11\x09\x11\x00\x00\x00\x00\x0b\x0b\x00\x15\xde\xff")),
-            array('2001:ca11:911::b0b:15:dead/120', array("\x20\x01\xca\x11\x09\x11\x00\x00\x00\x00\x0b\x0b\x00\x15\xde\x00", "\x20\x01\xca\x11\x09\x11\x00\x00\x00\x00\x0b\x0b\x00\x15\xde\xff")),
-            array('2001:ca11:911::b0b:15:dead/119', array("\x20\x01\xca\x11\x09\x11\x00\x00\x00\x00\x0b\x0b\x00\x15\xde\x00", "\x20\x01\xca\x11\x09\x11\x00\x00\x00\x00\x0b\x0b\x00\x15\xdf\xff")),
-            array('2001:ca11:911::b0b:15:dead/118', array("\x20\x01\xca\x11\x09\x11\x00\x00\x00\x00\x0b\x0b\x00\x15\xdc\x00", "\x20\x01\xca\x11\x09\x11\x00\x00\x00\x00\x0b\x0b\x00\x15\xdf\xff")),
-            array('2001:ca11:911::b0b:15:dead/117', array("\x20\x01\xca\x11\x09\x11\x00\x00\x00\x00\x0b\x0b\x00\x15\xd8\x00", "\x20\x01\xca\x11\x09\x11\x00\x00\x00\x00\x0b\x0b\x00\x15\xdf\xff")),
-            array('2001:ca11:911::b0b:15:dead/116', array("\x20\x01\xca\x11\x09\x11\x00\x00\x00\x00\x0b\x0b\x00\x15\xd0\x00", "\x20\x01\xca\x11\x09\x11\x00\x00\x00\x00\x0b\x0b\x00\x15\xdf\xff")),
-            array('2001:ca11:911::b0b:15:dead/115', array("\x20\x01\xca\x11\x09\x11\x00\x00\x00\x00\x0b\x0b\x00\x15\xc0\x00", "\x20\x01\xca\x11\x09\x11\x00\x00\x00\x00\x0b\x0b\x00\x15\xdf\xff")),
-            array('2001:ca11:911::b0b:15:dead/114', array("\x20\x01\xca\x11\x09\x11\x00\x00\x00\x00\x0b\x0b\x00\x15\xc0\x00", "\x20\x01\xca\x11\x09\x11\x00\x00\x00\x00\x0b\x0b\x00\x15\xff\xff")),
-            array('2001:ca11:911::b0b:15:dead/113', array("\x20\x01\xca\x11\x09\x11\x00\x00\x00\x00\x0b\x0b\x00\x15\x80\x00", "\x20\x01\xca\x11\x09\x11\x00\x00\x00\x00\x0b\x0b\x00\x15\xff\xff")),
-            array('2001:ca11:911::b0b:15:dead/112', array("\x20\x01\xca\x11\x09\x11\x00\x00\x00\x00\x0b\x0b\x00\x15\x00\x00", "\x20\x01\xca\x11\x09\x11\x00\x00\x00\x00\x0b\x0b\x00\x15\xff\xff")),
-        );
-    }
-
-    /**
-     * @dataProvider getIPsForRangeTest
-     * @group Core
-     */
-    public function testGetIpsForRange($range, $expected)
-    {
-        $this->assertEquals($expected, IP::getIpsForRange($range));
-    }
-
-    /**
-     * Dataprovider for testIsIpInRange
-     */
-    public function getIpsInRangeData()
-    {
-        return array(
-            array('192.168.1.10', array(
-                '192.168.1.9'         => false,
-                '192.168.1.10'        => true,
-                '192.168.1.11'        => false,
-
-                // IPv6 addresses (including IPv4 mapped) have to be compared against IPv6 address ranges
-                '::ffff:192.168.1.10' => false,
-            )),
-
-            array('::ffff:192.168.1.10', array(
-                '::ffff:192.168.1.9'                      => false,
-                '::ffff:192.168.1.10'                     => true,
-                '::ffff:c0a8:010a'                        => true,
-                '0000:0000:0000:0000:0000:ffff:c0a8:010a' => true,
-                '::ffff:192.168.1.11'                     => false,
-
-                // conversely, IPv4 addresses have to be compared against IPv4 address ranges
-                '192.168.1.10'                            => false,
-            )),
-
-            array('192.168.1.10/32', array(
-                '192.168.1.9'  => false,
-                '192.168.1.10' => true,
-                '192.168.1.11' => false,
-            )),
-
-            array('192.168.1.10/31', array(
-                '192.168.1.9'  => false,
-                '192.168.1.10' => true,
-                '192.168.1.11' => true,
-                '192.168.1.12' => false,
-            )),
-
-            array('192.168.1.128/25', array(
-                '192.168.1.127' => false,
-                '192.168.1.128' => true,
-                '192.168.1.255' => true,
-                '192.168.2.0'   => false,
-            )),
-
-            array('192.168.1.10/24', array(
-                '192.168.0.255' => false,
-                '192.168.1.0'   => true,
-                '192.168.1.1'   => true,
-                '192.168.1.2'   => true,
-                '192.168.1.3'   => true,
-                '192.168.1.4'   => true,
-                '192.168.1.7'   => true,
-                '192.168.1.8'   => true,
-                '192.168.1.15'  => true,
-                '192.168.1.16'  => true,
-                '192.168.1.31'  => true,
-                '192.168.1.32'  => true,
-                '192.168.1.63'  => true,
-                '192.168.1.64'  => true,
-                '192.168.1.127' => true,
-                '192.168.1.128' => true,
-                '192.168.1.255' => true,
-                '192.168.2.0'   => false,
-            )),
-
-            array('192.168.1.*', array(
-                '192.168.0.255' => false,
-                '192.168.1.0'   => true,
-                '192.168.1.1'   => true,
-                '192.168.1.2'   => true,
-                '192.168.1.3'   => true,
-                '192.168.1.4'   => true,
-                '192.168.1.7'   => true,
-                '192.168.1.8'   => true,
-                '192.168.1.15'  => true,
-                '192.168.1.16'  => true,
-                '192.168.1.31'  => true,
-                '192.168.1.32'  => true,
-                '192.168.1.63'  => true,
-                '192.168.1.64'  => true,
-                '192.168.1.127' => true,
-                '192.168.1.128' => true,
-                '192.168.1.255' => true,
-                '192.168.2.0'   => false,
-            )),
-        );
-    }
-
-    /**
-     * @group Core
-     *
-     * @dataProvider getIpsInRangeData
-     */
-    public function testIsIpInRange($range, $test)
-    {
-        foreach ($test as $ip => $expected) {
-            // range as a string
-            $this->assertEquals($expected, IP::isIpInRange(IP::P2N($ip), array($range)), "$ip in $range");
-
-            // range as an array(low, high)
-            $aRange = IP::getIpsForRange($range);
-            $aRange[0] = IP::N2P($aRange[0]);
-            $aRange[1] = IP::N2P($aRange[1]);
-            $this->assertEquals($expected, IP::isIpInRange(IP::P2N($ip), array($aRange)), "$ip in $range");
-        }
-    }
-
     /**
      * Dataprovider for ip from header tests
      */
@@ -672,19 +190,4 @@ class IPTest extends PHPUnit_Framework_TestCase
         // with excluded Ips
         $this->assertEquals($expected, IP::getLastIpFromList($csv . ', 10.10.10.10', array('10.10.10.10')));
     }
-
-    /**
-     * @group Core
-     */
-    public function testGetHostByAddr()
-    {
-        $hosts = array('localhost', 'localhost.localdomain', strtolower(@php_uname('n')), '127.0.0.1');
-        $host = IP::getHostByAddr('127.0.0.1');
-        $this->assertTrue(in_array(strtolower($host), $hosts), $host . ' -> localhost');
-
-        if (!SettingsServer::isWindows() || PHP_VERSION >= '5.3') {
-            $hosts = array('ip6-localhost', 'localhost', 'localhost.localdomain', strtolower(@php_uname('n')), '::1');
-            $this->assertTrue(in_array(strtolower(IP::getHostByAddr('::1')), $hosts), '::1 -> ip6-localhost');
-        }
-    }
 }