mlocati/ip-lib

Handle IPv4, IPv6 addresses and ranges

1.9.0 2019-09-20 08:26 UTC

README

TravisCI Build Status AppVeyor Build Status StyleCI Status Coverage Status Scrutinizer Code Quality Packagist Downloads

IPLib - Handle IPv4, IPv6 and IP ranges

Introduction

This library can handle IPv4, IPv6 addresses, as well as IP ranges, in CIDR formats (like ::1/128 or 127.0.0.1/32) and in pattern format (like ::*:* or 127.0.*.*).

Requirements

The only requirement is PHP 5.3.3. No external dependencies and no special PHP configuration are needed (yes, it will always work even if PHP has not been built with IPv6 support!).

Manual installation

Download the latest version, unzip it and add these lines in our PHP files:

require_once 'path/to/iplib/ip-lib.php';

Installation with Composer

Simply run composer require mlocati/ip-lib, or add these lines to your composer.json file:

"require": {
    "mlocati/ip-lib": "1.*"
}

Sample usage

Parse an address

To parse an IPv4 address:

$address = \IPLib\Address\IPv4::fromString('127.0.0.1');

To parse an IPv6 address:

$address = \IPLib\Address\IPv6::fromString('::1');

To parse an address in any format (IPv4 or IPv6):

$address = \IPLib\Factory::addressFromString('::1');
$address = \IPLib\Factory::addressFromString('127.0.0.1');

Get the next/previous addresses

$address = \IPLib\Factory::addressFromString('::1');
echo (string) $address->getPreviousAddress();
// prints ::
echo (string) $address->getNextAddress();
// prints ::2

Parse an IP address range

To parse a subnet (CIDR) range:

$range = \IPLib\Range\Subnet::fromString('127.0.0.1/24');
$range = \IPLib\Range\Subnet::fromString('::1/128');

To parse a pattern (asterisk notation) range:

$range = \IPLib\Range\Pattern::fromString('127.0.0.*');
$range = \IPLib\Range\Pattern::fromString('::*');

To parse an andress as a range:

$range = \IPLib\Range\Single::fromString('127.0.0.1');
$range = \IPLib\Range\Single::fromString('::1');

To parse a range in any format:

$range = \IPLib\Factory::rangeFromString('127.0.0.*');
$range = \IPLib\Factory::rangeFromString('::1/128');
$range = \IPLib\Factory::rangeFromString('::');

Retrive a range from its boundaries

$range = \IPLib\Factory::rangeFromBoundaries('192.168.0.1', '192.168.255.255');
echo (string) $range;
// prints 192.168.0.0/16

Retrive the boundaries of a range

$range = \IPLib\Factory::rangeFromString('127.0.0.*');
echo (string) $range->getStartAddress();
// prints 127.0.0.0
echo (string) $range->getEndAddress();
// prints 127.0.0.255

Format addresses and ranges

Both IP addresses and ranges have a toString method that you can use to retrieve a textual representation:

echo \IPLib\Factory::addressFromString('127.0.0.1')->toString();
// prints 127.0.0.1
echo \IPLib\Factory::addressFromString('127.000.000.001')->toString();
// prints 127.0.0.1
echo \IPLib\Factory::addressFromString('::1')->toString();
// prints ::1
echo \IPLib\Factory::addressFromString('0:0::1')->toString();
// prints ::1
echo \IPLib\Factory::rangeFromString('0:0::1/64')->toString();
// prints ::1/64

When working with IPv6, you may want the full (expanded) representation of the addresses. In this case, simply use a true parameter for the toString method:

echo \IPLib\Factory::addressFromString('::')->toString(true);
// prints 0000:0000:0000:0000:0000:0000:0000:0000
echo \IPLib\Factory::addressFromString('::1')->toString(true);
// prints 0000:0000:0000:0000:0000:0000:0000:0001
echo \IPLib\Factory::addressFromString('fff::')->toString(true);
// prints 0fff:0000:0000:0000:0000:0000:0000:0000
echo \IPLib\Factory::addressFromString('::0:0')->toString(true);
// prints 0000:0000:0000:0000:0000:0000:0000:0000
echo \IPLib\Factory::addressFromString('1:2:3:4:5:6:7:8')->toString(true);
// prints 0001:0002:0003:0004:0005:0006:0007:0008
echo \IPLib\Factory::rangeFromString('0:0::1/64')->toString();
// prints 0000:0000:0000:0000:0000:0000:0000:0001/64

Check if an address is contained in a range

All the range types offer a contains method, and all the IP address types offer a matches method: you can call them to check if an address is contained in a range:

$address = \IPLib\Factory::addressFromString('1:2:3:4:5:6:7:8');
$range = \IPLib\Factory::rangeFromString('0:0::1/64');

$contained = $address->matches($range);
// that's equivalent to
$contained = $range->contains($address);

Please remark that if the address is IPv4 and the range is IPv6 (or vice-versa), the result will always be false.

Check if a range contains another range

All the range types offer a containsRange method: you can call them to check if an address range fully contains another range:

$range1 = \IPLib\Factory::rangeFromString('0:0::1/64');
$range2 = \IPLib\Factory::rangeFromString('0:0::1/65');
$contained = $range1->containsRange($range2);

Getting the type of an IP address

If you want to know if an address is within a private network, or if it's a public IP, or whatever you want, you can use the getRangeType method:

$address = \IPLib\Factory::addressFromString('::');

$typeID = $address->getRangeType();

$typeName = \IPLib\Range\Type::getName();

The most notable values of the range type ID are:

  • \IPLib\Range\Type::T_UNSPECIFIED if the address is all zeros (0.0.0.0 or ::)
  • \IPLib\Range\Type::T_LOOPBACK if the address is the localhost (usually 127.0.0.1 or ::1)
  • \IPLib\Range\Type::T_PRIVATENETWORK if the address is in the local network (for instance 192.168.0.1 or fc00::1)
  • \IPLib\Range\Type::T_PUBLIC if the address is for public usage (for instance 104.25.25.33 or 2001:503:ba3e::2:30)

Getting the type of an IP address range

If you want to know the type of an address range, you can use the getRangeType method:

$range = \IPLib\Factory::rangeFromString('2000:0::1/64');
$type = $range->getRangeType();
// $type is \IPLib\Range\Type::T_PUBLIC
echo \IPLib\Range\Type::getName($type);
// 'Public address'

Please remark that if a range spans across multiple range types, you'll get NULL as the range type:

$range = \IPLib\Factory::rangeFromString('::/127');
$type = $range->getRangeType();
// $type is null
echo \IPLib\Range\Type::getName($type);
// 'Unknown type'

Converting IP ranges

This library supports IPv4/IPv6 ranges in pattern format (eg. 192.168.*.*) and in CIDR/subnet format (eg. 192.168.0.0/16), and it offers a way to convert between the two formats:

// This will print ::*:*:*:*
echo \IPLib\Factory::rangeFromString('::/64')->asPattern()->toString();

// This will print 1:2::/96
echo \IPLib\Factory::rangeFromString('1:2::*:*')->asSubnet()->toString();

// This will print 192.168.0.0/24
echo \IPLib\Factory::rangeFromString('192.168.0.*')->asSubnet()->toString();

// This will print 10.*.*.*
echo \IPLib\Factory::rangeFromString('10.0.0.0/8')->asPattern()->toString();

Getting the subnet mask for IPv4 ranges

You can use the getSubnetMask() to get the subnet mask for IPv4 ranges:

// This will print 255.255.255.0
echo \IPLib\Factory::rangeFromString('192.168.0.*')->getSubnetMask()->toString();

// This will print 255.255.255.252
echo \IPLib\Factory::rangeFromString('192.168.0.12/30')->getSubnetMask()->toString();

Using a database

This package offers a great feature: you can store address ranges in a database table, and check if an address is contained in one of the saved ranges with a simple query.

To save a range, you need to store the address type (for IPv4 it's 4, for IPv6 it's 6), as well as two values representing the start and the end of the range. These methods are:

$range->getAddressType();
$range->getComparableStartString();
$range->getComparableEndString();

Let's assume that you saved the type in a field called addressType, and the range boundaries in two fields called rangeFrom and rangeTo.

When you want to check if an address is within a stored range, simply use the getComparableString method of the address and check if it's between the fields rangeFrom and rangeTo, and check if the stored addressType is the same as the one of the address instance you want to check.

Here's a sample code:

/*
 * Let's assume that:
 * - $pdo is a PDO instance
 * - $range is a range object
 * - $address is an address object
 */

// Save the $range object
$insertQuery = $pdo->prepare('
    insert into ranges (addressType, rangeFrom, rangeTo)
    values (:addressType, :rangeFrom, :rangeTo)
');
$insertQuery->execute(array(
    ':addressType' => $range->getAddressType(),
    ':rangeFrom' => $range->getComparableStartString(),
    ':rangeTo' => $range->getComparableEndString(),
));

// Retrieve the saved ranges where an address $address falls:
$searchQuery = $pdo->prepare('
    select * from ranges
    where addressType = :addressType
    and :address between rangeFrom and rangeTo
');
$searchQuery->execute(array(
    ':addressType' => $address->getAddressType(),
    ':address' => $address->getComparableString(),
));
$rows = $searchQuery->fetchAll();
$searchQuery->closeCursor();