Skip to content

Instantly share code, notes, and snippets.

@h2rd
Forked from Golpha/IP-Guide.md
Created November 22, 2022 08:30
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save h2rd/2f47490f12f3257d61fb9154a45ea9e5 to your computer and use it in GitHub Desktop.
Save h2rd/2f47490f12f3257d61fb9154a45ea9e5 to your computer and use it in GitHub Desktop.
The PHP IP Guide

The PHP IP Guide

This guide shows you common practices to convert IPv4- and IPv6-Addresses into their binary representation.

Who is responsible for this ?

This guide has been written by Matthias Kaschubowski, a autodidactical software developer from germany with about 14 years of practice converting coffee to code. He works in his free time as a php evangelist on a lot of platforms ( last shown as tr0y on php.de, a german PHP related forum and as himself as an adminstrator at the largest PHP-related facebook group ).

Basics

IP Addresses are human readable strings that identify an entity inside of TCP/IP based networks. In the past of the internet, IPv4 Addresses are the way to go to locate a server, connect clients to servers or other clients, to send mails or any other network related task. IPs in general are equal to phone numbers ( with just a different format ), but phone numbers are not limited by a specific range.

Nowadays we reached the limit of the address range of IPv4 addresses (4.294.967.296 unique clients connected at the same time), a new address format has been invented to fullfil the duties of IPv4 in the future: IPv6. Within IPv6 the internet may grow up for a while, at least 340.282.366.920.938.463.463.374.607.431.768.211.456 unique clients may connect to the biggest world wide network to exceed the new IP address range ( yes, thats a number with 39 digits ).

Formats

Full qualified IPv4 addresses are a sequence of unsigned integer, segmented into 4 groups, each segment is seperated with a ., each group may have a value betwen 0 and 255:

127.0.0.1 The local loopback, your beloved localhost.

Full qualified IPv6 addresses are a sequence of hexadecimals, segmented into 8 groups, each segment is seperated with a :, each group may have a value between 0 and ffff, leading zeros may be ommited, sequenced of zeros may be ommited too:

0000:0000:0000:0000:0000:0000:0000:0001 or: ::1 Yes, that is also the local loopback.

PHP and types and the handling of ultra large numbers

PHP comes out of the box with support for an 32 bit signed integer on 32 bit platforms and with support for an 64 bit signed integer on a 64 bit plattform. To leave a statement about what this guide solves: 32 Bit integers already fails on storaging the highest possible IPv4 address. 64 Bit integers are not able to handle IPv6 addresses (which will need at least 128 bit). PHP is able to handle large numbers bitwise with help of bcmath. In detail: with bcmath any number will be represented as an string. We will use binary strings.

The magic of binary strings

Yes, you'll deal with binary strings along this guide. You'll drop them into the database aslong you want to store ip adresses that can be easily queried ( compared with other IP Addresses ) and you'll compare them with other ip addresses inside of PHP.

The way to fail

Do not use ip2long or long2ip. ip2long converts an human readable IPv4 address to its integer representation. On 32-bit plattforms that may end in the PHP_MAX_INT value if the given IP address representation is higher as the PHP_MAX_INT. This will definitely happen aslong the first octed ( segment ) of the ip address is higher than 127.

The way to go

At first we start with the conversion of 127.0.0.1 to the binary representation:

$binaryIP = inet_pton('127.0.0.1');

Keep in mind that what we got is a binary string, var_dump() won't help here to find out what we got, var_dump() is not able to display binary strings and will return an dumped empty string. We simply check if we got a 32 Bit representation while counting the byte-length of the binary string along strlen or mb_strlen:

var_dump(strlen($binaryIP)); 

will result in int(4)

We continue our research with the conversion of ::1 which is the IPv6 representation of 127.0.0.1:

$binaryIPv6 = inet_pton('::1');

We check the result in the same way to make sure that we got an 128 Bit representation ( 16 byte string length ):

var_dump(strlen($binaryIP)); 

will result in int(16)

The next step would be a between-like check to clearify that a given IPv4 address is between 2 IPv4 addresses:

function ip_between($ip, $ipStart, $ipEnd) 
{
    $start = inet_pton($ipStart);
    $end = inet_pton($ipEnd);
    $value = inet_pton($ip);
    
    return $start <= $value && $end >= $value;
}

var_dump(ip_between('127.0.0.10', '127.0.0.1', '127.0.0.255'));

will result in bool(true)

Same function just with IPv6 addresses:

var_dump(ip_between('::DD', '::1', '::FFFF'));

will result in bool(true)

The MySQL way: outsource data transition

Handling IP conversion, no matter which type is not that hard in PHP, but you should prefer to delegate such conversions to the database level as long the conversion was only made for storaging and the database behind your application is on a level of actuality that grants the availability of inet6_aton and inet6_ntoa. MySQL 5.5 or higher will fullfil this requirement.

For a small test you may create the following table:

CREATE TABLE `ip_address_ranges` (
    `start` VARBINARY(16),
    `end` VARBINARY(16)
)

Insert 2 ranges, one for IPv4 and one for IPv6:

INSERT INTO `ip_address_ranges` ( `start`, `end` )
VALUES 
    ( inet6_aton(`::1`), inet6_aton(`::FFFF`) ),
    ( inet6_aton('127.0.0.1'), inet6_aton('127.0.0.255') )

Finally a IPv6 based between query and a IPv4 based between query:

SELECT
    inet6_ntoa(`start`) AS `start-IP`,
    inet6_ntoa(`end`) AS `end-IP`
FROM `ip_address_ranges`
    WHERE
        inet6_aton('::5') BETWEEN `start` AND `end`
        
SELECT
    inet6_ntoa(`start`) AS `start-IP`,
    inet6_ntoa(`end`) AS `end-IP`
FROM `ip_address_ranges`
    WHERE
        inet6_aton('127.0.0.50') BETWEEN `start` AND `end`

Both selects will result 1 row and show you the human readable representation of the range that matches the query.

A small hint: MySQL has also inet_aton- and inet_ntoa-functions, prefer inet6_*-functions due to compability for IPv6 and IPv4. the inet_*-functions are implemented to handle IPv4 addresses only.

Does it help ? - Feel free to comment, no matter if you may donate blood or honor.

Inspirations for new guides or requests may be send to nihylum@gmail.com.

Dropped to Github at 2014/08/23.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment