195 行
6.2 KiB
PHP
195 行
6.2 KiB
PHP
<?php
|
|
/**
|
|
* Matomo - free/libre analytics platform
|
|
*
|
|
* @link https://matomo.org
|
|
* @license http://www.gnu.org/licenses/lgpl-3.0.html LGPL v3 or later
|
|
*/
|
|
|
|
namespace Matomo\Network;
|
|
|
|
/**
|
|
* IP address utilities (for both IPv4 and IPv6).
|
|
*
|
|
* As a matter of naming convention, we use `$ip` for the binary format (network address format)
|
|
* and `$ipString` for the string/presentation format (i.e., human-readable form).
|
|
*/
|
|
class IPUtils
|
|
{
|
|
/**
|
|
* Removes the port and the last portion of a CIDR IP address.
|
|
*
|
|
* @param string $ipString The IP address to sanitize.
|
|
* @return string
|
|
*/
|
|
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;
|
|
}
|
|
|
|
/**
|
|
* Sanitize human-readable (user-supplied) IP address range.
|
|
*
|
|
* Accepts the following formats for $ipRange:
|
|
* - single IPv4 address, e.g., 127.0.0.1
|
|
* - single IPv6 address, e.g., ::1/128
|
|
* - IPv4 block using CIDR notation, e.g., 192.168.0.0/22 represents the IPv4 addresses from 192.168.0.0 to 192.168.3.255
|
|
* - IPv6 block using CIDR notation, e.g., 2001:DB8::/48 represents the IPv6 addresses from 2001:DB8:0:0:0:0:0:0 to 2001:DB8:0:FFFF:FFFF:FFFF:FFFF:FFFF
|
|
* - wildcards, e.g., 192.168.0.* or 2001:DB8:*:*:*:*:*:*
|
|
*
|
|
* @param string $ipRangeString IP address range
|
|
* @return string|null IP address range in CIDR notation OR null on failure
|
|
*/
|
|
public static function sanitizeIpRange($ipRangeString)
|
|
{
|
|
$ipRangeString = trim($ipRangeString);
|
|
if (empty($ipRangeString)) {
|
|
return null;
|
|
}
|
|
|
|
// IP address with wildcards '*'
|
|
if (strpos($ipRangeString, '*') !== false) {
|
|
// Disallow prefixed wildcards and anything other than wildcards
|
|
// and separators (including IPv6 zero groups) after first wildcard
|
|
if (preg_match('/[^.:]\*|\*.*([^.:*]|::)/', $ipRangeString)) {
|
|
return null;
|
|
}
|
|
|
|
$numWildcards = substr_count($ipRangeString, '*');
|
|
$ipRangeString = str_replace('*', '0', $ipRangeString);
|
|
|
|
// CIDR
|
|
} elseif (($pos = strpos($ipRangeString, '/')) !== false) {
|
|
$bits = substr($ipRangeString, $pos + 1);
|
|
$ipRangeString = substr($ipRangeString, 0, $pos);
|
|
|
|
if (!is_numeric($bits)) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
// single IP
|
|
if (($ip = @inet_pton($ipRangeString)) === false)
|
|
return null;
|
|
|
|
$maxbits = strlen($ip) * 8;
|
|
if (!isset($bits)) {
|
|
$bits = $maxbits;
|
|
|
|
if (isset($numWildcards)) {
|
|
$bits -= ($maxbits === 32 ? 8 : 16) * $numWildcards;
|
|
}
|
|
}
|
|
|
|
if ($bits < 0 || $bits > $maxbits) {
|
|
return null;
|
|
}
|
|
|
|
return "$ipRangeString/$bits";
|
|
}
|
|
|
|
/**
|
|
* Converts an IP address in string/presentation format to binary/network address format.
|
|
*
|
|
* @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"`.
|
|
*/
|
|
public static function stringToBinaryIP($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;
|
|
}
|
|
|
|
/**
|
|
* Convert binary/network address format to string/presentation format.
|
|
*
|
|
* @param string $ip IP address in binary/network address format, e.g. `"\x7F\x00\x00\x01"`.
|
|
* @return string IP address in string format, e.g. `'127.0.0.1'`.
|
|
*/
|
|
public static function binaryToStringIP($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;
|
|
}
|
|
|
|
/**
|
|
* Get low and high IP addresses for a specified IP range.
|
|
*
|
|
* @param string $ipRange An IP address range in string format, e.g. `'192.168.1.1/24'`.
|
|
* @return array|null Array `array($lowIp, $highIp)` in binary format, or null on failure.
|
|
*/
|
|
public static function getIPRangeBounds($ipRange)
|
|
{
|
|
$ipRange = self::sanitizeIpRange($ipRange);
|
|
|
|
if ($ipRange === null ||
|
|
(($pos = strpos($ipRange, '/')) === false) ||
|
|
($pos + 1 === strlen($ipRange))
|
|
) {
|
|
return null;
|
|
}
|
|
|
|
$range = substr($ipRange, 0, $pos);
|
|
$high = $low = @inet_pton($range);
|
|
|
|
if ($low === false) {
|
|
return null;
|
|
}
|
|
|
|
$addrLen = strlen($low);
|
|
$bits = (int) substr($ipRange, $pos + 1);
|
|
|
|
if ($bits < 0 || $bits > $addrLen * 8) {
|
|
return null;
|
|
}
|
|
|
|
$octet = (int) (($bits + 7) / 8);
|
|
|
|
for ($i = $octet; $i < $addrLen; $i++) {
|
|
$low[$i] = chr(0);
|
|
$high[$i] = chr(255);
|
|
}
|
|
|
|
if (($n = $bits % 8)) {
|
|
$mask = (1 << (8 - $n)) - 1;
|
|
$value = ord($low[--$octet]) & ~$mask;
|
|
|
|
$low[$octet] = chr($value);
|
|
$high[$octet] = chr($value | $mask);
|
|
}
|
|
|
|
return array($low, $high);
|
|
}
|
|
}
|