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
  • Save h2rd/2f47490f12f3257d61fb9154a45ea9e5 to your computer and use it in GitHub Desktop.
Save h2rd/2f47490f12f3257d61fb9154a45ea9e5 to your computer and use it in GitHub Desktop.

Revisions

  1. Matthias Kaschubowski created this gist Aug 23, 2014.
    127 changes: 127 additions & 0 deletions IP-Guide.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,127 @@
    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 `[email protected]`.

    Dropped to Github at 2014/08/23.