Viewed   103 times

After searching around somewhat thoroughly, I noticed a slight lack of functions in PHP for handling IPv6. For my own personal satisfaction I created a few functions to help the transition.

The IPv6ToLong() function is a temporary solution to that brought up here: How to store IPv6-compatible address in a relational database. It will split the IP in to two integers and return them in an array.

/**
 * Convert an IPv4 address to IPv6
 *
 * @param string IP Address in dot notation (192.168.1.100)
 * @return string IPv6 formatted address or false if invalid input
 */
function IPv4To6($Ip) {
    static $Mask = '::ffff:'; // This tells IPv6 it has an IPv4 address
    $IPv6 = (strpos($Ip, '::') === 0);
    $IPv4 = (strpos($Ip, '.') > 0);

    if (!$IPv4 && !$IPv6) return false;
    if ($IPv6 && $IPv4) $Ip = substr($Ip, strrpos($Ip, ':')+1); // Strip IPv4 Compatibility notation
    elseif (!$IPv4) return $Ip; // Seems to be IPv6 already?
    $Ip = array_pad(explode('.', $Ip), 4, 0);
    if (count($Ip) > 4) return false;
    for ($i = 0; $i < 4; $i++) if ($Ip[$i] > 255) return false;

    $Part7 = base_convert(($Ip[0] * 256) + $Ip[1], 10, 16);
    $Part8 = base_convert(($Ip[2] * 256) + $Ip[3], 10, 16);
    return $Mask.$Part7.':'.$Part8;
}

/**
 * Replace '::' with appropriate number of ':0'
 */
function ExpandIPv6Notation($Ip) {
    if (strpos($Ip, '::') !== false)
        $Ip = str_replace('::', str_repeat(':0', 8 - substr_count($Ip, ':')).':', $Ip);
    if (strpos($Ip, ':') === 0) $Ip = '0'.$Ip;
    return $Ip;
}

/**
 * Convert IPv6 address to an integer
 *
 * Optionally split in to two parts.
 *
 * @see https://.com/questions/420680/
 */
function IPv6ToLong($Ip, $DatabaseParts= 2) {
    $Ip = ExpandIPv6Notation($Ip);
    $Parts = explode(':', $Ip);
    $Ip = array('', '');
    for ($i = 0; $i < 4; $i++) $Ip[0] .= str_pad(base_convert($Parts[$i], 16, 2), 16, 0, STR_PAD_LEFT);
    for ($i = 4; $i < 8; $i++) $Ip[1] .= str_pad(base_convert($Parts[$i], 16, 2), 16, 0, STR_PAD_LEFT);

    if ($DatabaseParts == 2)
            return array(base_convert($Ip[0], 2, 10), base_convert($Ip[1], 2, 10));
    else    return base_convert($Ip[0], 2, 10) + base_convert($Ip[1], 2, 10);
}

For these functions I typically implement them by calling this function first:

/**
 * Attempt to find the client's IP Address
 *
 * @param bool Should the IP be converted using ip2long?
 * @return string|long The IP Address
 */
function GetRealRemoteIp($ForDatabase= false, $DatabaseParts= 2) {
    $Ip = '0.0.0.0';
    if (isset($_SERVER['HTTP_CLIENT_IP']) && $_SERVER['HTTP_CLIENT_IP'] != '')
        $Ip = $_SERVER['HTTP_CLIENT_IP'];
    elseif (isset($_SERVER['HTTP_X_FORWARDED_FOR']) && $_SERVER['HTTP_X_FORWARDED_FOR'] != '')
        $Ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
    elseif (isset($_SERVER['REMOTE_ADDR']) && $_SERVER['REMOTE_ADDR'] != '')
        $Ip = $_SERVER['REMOTE_ADDR'];
    if (($CommaPos = strpos($Ip, ',')) > 0)
        $Ip = substr($Ip, 0, ($CommaPos - 1));

    $Ip = IPv4To6($Ip);
    return ($ForDatabase ? IPv6ToLong($Ip, $DatabaseParts) : $Ip);
}

Someone please tell me if I'm reinventing the wheel here or I've done something wrong.

This implementation converts IPv4 to IPv6. Any IPv6 address it doesn't touch.

 Answers

4

How about inet_ntop()? Then instead of chopping things into integers, you just use a varbinary(16) to store it.

Monday, August 8, 2022
2

Since you cannot convert IPv6 addresses to integer, you should operate bits, like this:

$ip='21DA:00D3:0000:2F3B:02AC:00FF:FE28:9C5A';
$cidrnet='21DA:00D3:0000:2F3B::/64';

// converts inet_pton output to string with bits
function inet_to_bits($inet) 
{
   $splitted = str_split($inet);
   $binaryip = '';
   foreach ($splitted as $char) {
             $binaryip .= str_pad(decbin(ord($char)), 8, '0', STR_PAD_LEFT);
   }
   return $binaryip;
}    

$ip = inet_pton($ip);
$binaryip=inet_to_bits($ip);

list($net,$maskbits)=explode('/',$cidrnet);
$net=inet_pton($net);
$binarynet=inet_to_bits($net);

$ip_net_bits=substr($binaryip,0,$maskbits);
$net_bits   =substr($binarynet,0,$maskbits);

if($ip_net_bits!==$net_bits) echo 'Not in subnet';
else echo 'In subnet';

Also, if you use some database to store IPs, it may already have all the functions to compare them. For example, Postgres has an inet type and can determine, whether IP is contained within subnet like this:

SELECT 
   '21DA:00D3:0000:2F3B:02AC:00FF:FE28:9C5A'::inet << 
   '21DA:00D3:0000:2F3B::/64'::inet;

9.11. Network Address Functions and Operators in PostgreSQL

Sunday, September 18, 2022
 
1

You can't.

Only the IP address the request came from is available.

There's no reliable way to identify other IP addresses (my laptop currently has 12 IP addresses) that route to the same computer.

Friday, November 11, 2022
 
2

I would use PHPExcel for writing the files (in fact, I do use it)

http://phpexcel.codeplex.com/

And you could try

http://sourceforge.net/projects/phpexcelreader/

for reading but I have no experience with reading Excel files in PHP, only writing them.

Thursday, August 4, 2022
 
attila
 
4

If you want to assign a reference using a ternary statement, then you need this clumsy workaround:

 list($foo) = $a ? array(&$a) : array('whatever');

However, as said in the other answers, this seldomly saves memory.

Monday, November 21, 2022
Only authorized users can answer the search term. Please sign in first, or register a free account.
Not the answer you're looking for? Browse other questions tagged :