PHP library to handle IPv4, IPv6 and IP ranges
MIT License
IPLib is a modern, PSR-compliant, test-driven IP addresses and subnets manipulation library. It implements primitives to handle IPv4 and IPv6 addresses, as well as IP ranges (subnets), in CIDR format (like ::1/128
or 127.0.0.1/32
) and in pattern format (like ::*:*
or 127.0.*.*
).
IPLib has very basic requirements as:
Download the latest version, unzip it and add these lines in our PHP files:
require_once 'path/to/iplib/ip-lib.php';
Simply run
composer require mlocati/ip-lib
or add these lines to your composer.json
file:
"require": {
"mlocati/ip-lib": "^1"
}
To parse an IPv4 address:
$address = \IPLib\Address\IPv4::parseString('127.0.0.1');
To parse an IPv6 address:
$address = \IPLib\Address\IPv6::parseString('::1');
To parse an address in any format (IPv4 or IPv6):
$address = \IPLib\Factory::parseAddressString('::1');
$address = \IPLib\Factory::parseAddressString('127.0.0.1');
$address = \IPLib\Factory::parseAddressString('::1');
// This will print ::
echo (string) $address->getPreviousAddress();
// This will print ::2
echo (string) $address->getNextAddress();
For addresses:
$address = \IPLib\Factory::parseAddressString('::1');
// This will print ::1
echo (string) $address->getAddressAtOffset(0);
// This will print ::2
echo (string) $address->getAddressAtOffset(1);
// This will print ::3
echo (string) $address->getAddressAtOffset(2);
// This will print ::3e9
echo (string) $address->getAddressAtOffset(1000);
// This will print ::
echo (string) $address->getAddressAtOffset(-1);
// This will print NULL
echo var_dump($address->getAddressAtOffset(-2));
For ranges:
$range = \IPLib\Factory::parseRangeString('::ff00/120');
// This will print ::ff00
echo (string) $range->getAddressAtOffset(0);
// This will print ::ff10
echo (string) $range->getAddressAtOffset(16);
// This will print ::ff64
echo (string) $range->getAddressAtOffset(100);
// This will print NULL because the address ::1:0 is out of the range
var_dump($range->getAddressAtOffset(256));
// This will print ::ffff
echo (string) $range->getAddressAtOffset(-1);
// This will print ::fff0
echo (string) $range->getAddressAtOffset(-16);
// This will print ::ff00
echo (string) $range->getAddressAtOffset(-256);
// This will print NULL because the address ::feff is out of the range
var_dump($range->getAddressAtOffset(-257));
To parse a subnet (CIDR) range:
$range = \IPLib\Range\Subnet::parseString('127.0.0.1/24');
$range = \IPLib\Range\Subnet::parseString('::1/128');
To parse a pattern (asterisk notation) range:
$range = \IPLib\Range\Pattern::parseString('127.0.0.*');
$range = \IPLib\Range\Pattern::parseString('::*');
To parse an address as a range:
$range = \IPLib\Range\Single::parseString('127.0.0.1');
$range = \IPLib\Range\Single::parseString('::1');
To parse a range in any format:
$range = \IPLib\Factory::parseRangeString('127.0.0.*');
$range = \IPLib\Factory::parseRangeString('::1/128');
$range = \IPLib\Factory::parseRangeString('::');
You can calculate the smallest range that comprises two addresses:
$range = \IPLib\Factory::getRangeFromBoundaries('192.168.0.1', '192.168.255.255');
// This will print 192.168.0.0/16
echo (string) $range;
You can also calculate a list of ranges that exactly describes all the addresses between two addresses:
$ranges = \IPLib\Factory::getRangesFromBoundaries('192.168.0.0', '192.168.0.5');
// This will print 192.168.0.0/30 192.168.0.4/31
echo implode(' ', $ranges);
$range = \IPLib\Factory::parseRangeString('127.0.0.*');
// This will print 127.0.0.0
echo (string) $range->getStartAddress();
// This will print 127.0.0.255
echo (string) $range->getEndAddress();
Both IP addresses and ranges have a toString
method that you can use to retrieve a textual representation:
// This will print 127.0.0.1
echo \IPLib\Factory::parseAddressString('127.0.0.1')->toString();
// This will print 127.0.0.1
echo \IPLib\Factory::parseAddressString('127.000.000.001')->toString();
// This will print ::1
echo \IPLib\Factory::parseAddressString('::1')->toString();
// This will print ::1
echo \IPLib\Factory::parseAddressString('0:0::1')->toString();
// This will print ::1/64
echo \IPLib\Factory::parseRangeString('0:0::1/64')->toString();
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:
// This will print 0000:0000:0000:0000:0000:0000:0000:0000
echo \IPLib\Factory::parseAddressString('::')->toString(true);
// This will print 0000:0000:0000:0000:0000:0000:0000:0001
echo \IPLib\Factory::parseAddressString('::1')->toString(true);
// This will print 0fff:0000:0000:0000:0000:0000:0000:0000
echo \IPLib\Factory::parseAddressString('fff::')->toString(true);
// This will print 0000:0000:0000:0000:0000:0000:0000:0000
echo \IPLib\Factory::parseAddressString('::0:0')->toString(true);
// This will print 0001:0002:0003:0004:0005:0006:0007:0008
echo \IPLib\Factory::parseAddressString('1:2:3:4:5:6:7:8')->toString(true);
// This will print 0000:0000:0000:0000:0000:0000:0000:0001/64
echo \IPLib\Factory::parseRangeString('0:0::1/64')->toString();
The address and range objects implements the __toString()
method, which call the toString()
method.
So, if you want the string (short) representation of an object, you can do any of the following:
$address = \IPLib\Address\IPv6::parseString('::1');
// All these will print ::1
echo $address->toString();
echo $address->toString(false);
echo (string) $address;
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::parseAddressString('1:2:3:4:5:6:7:8');
$range = \IPLib\Factory::parseRangeString('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
.
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::parseRangeString('0:0::1/64');
$range2 = \IPLib\Factory::parseRangeString('0:0::1/65');
$contained = $range1->containsRange($range2);
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::parseAddressString('::');
$type = $address->getRangeType();
$typeName = \IPLib\Range\Type::getName($type);
The most notable values of the range type 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
)If you want to know the type of an address range, you can use the getRangeType
method:
$range = \IPLib\Factory::parseRangeString('2000:0::1/64');
// $type will contain the value of \IPLib\Range\Type::T_PUBLIC
$type = $range->getRangeType();
// This will print Public address
echo \IPLib\Range\Type::getName($type);
Please note that if a range spans across multiple range types, you'll get NULL as the range type:
$range = \IPLib\Factory::parseRangeString('::/127');
// $type will contain null
$type = $range->getRangeType();
// This will print Unknown type
echo \IPLib\Range\Type::getName($type);
This library supports converting IPv4 to/from IPv6 addresses using the 6to4 notation or the IPv4-mapped notation:
$ipv4 = \IPLib\Factory::parseAddressString('1.2.3.4');
// 6to4 notation
$ipv6 = $ipv4->toIPv6();
// This will print 2002:102:304::
echo (string) $ipv6;
// This will print 1.2.3.4
echo $ipv6->toIPv4();
// IPv4-mapped notation
$ipv6_6to4 = $ipv4->toIPv6IPv4Mapped();
// This will print ::ffff:1.2.3.4
echo (string) $ipv6_6to4;
// This will print 1.2.3.4
echo $ipv6_6to4->toIPv4();
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::parseRangeString('::/64')->asPattern()->toString();
// This will print 1:2::/96
echo \IPLib\Factory::parseRangeString('1:2::*:*')->asSubnet()->toString();
// This will print 192.168.0.0/24
echo \IPLib\Factory::parseRangeString('192.168.0.*')->asSubnet()->toString();
// This will print 10.*.*.*
echo \IPLib\Factory::parseRangeString('10.0.0.0/8')->asPattern()->toString();
Please remark that all the range types implement the asPattern()
and asSubnet()
methods.
You can use the getSubnetMask()
to get the subnet mask for IPv4 ranges:
// This will print 255.255.255.0
echo \IPLib\Factory::parseRangeString('192.168.0.*')->getSubnetMask()->toString();
// This will print 255.255.255.252
echo \IPLib\Factory::parseRangeString('192.168.0.12/30')->getSubnetMask()->toString();
You can use the getSize()
to get the count of addresses this IP range contains:
// This will print 256
echo \IPLib\Factory::parseRangeString('192.168.0.*')->getSize();
// This will print 4
echo \IPLib\Factory::parseRangeString('192.168.0.12/30')->getSize();
// This will print 1
echo \IPLib\Factory::parseRangeString('192.168.0.1')->getSize();
To perform reverse DNS queries, you need to use a special format of the IP addresses.
You can use the getReverseDNSLookupName()
method of the IP address instances to retrieve it easily:
$ipv4 = \IPLib\Factory::parseAddressString('1.2.3.255');
$ipv6 = \IPLib\Factory::parseAddressString('1234:abcd::cafe:babe');
// This will print 255.3.2.1.in-addr.arpa
echo $ipv4->getReverseDNSLookupName();
// This will print e.b.a.b.e.f.a.c.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.d.c.b.a.4.3.2.1.ip6.arpa
echo $ipv6->getReverseDNSLookupName();
To parse addresses in reverse DNS lookup format you can use the IPLib\ParseStringFlag::ADDRESS_MAYBE_RDNS
flag when parsing a string:
$ipv4 = \IPLib\Factory::parseAddressString('255.3.2.1.in-addr.arpa', \IPLib\ParseStringFlag::ADDRESS_MAYBE_RDNS);
$ipv6 = \IPLib\Factory::parseAddressString('e.b.a.b.e.f.a.c.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.d.c.b.a.4.3.2.1.ip6.arpa', \IPLib\ParseStringFlag::ADDRESS_MAYBE_RDNS);
// This will print 1.2.3.255
echo $ipv4->toString();
// This will print 1234:abcd::cafe:babe
echo $ipv6->toString();
You can also use getReverseDNSLookupName()
for IP ranges.
In this case, the result is an array of strings:
$range = \IPLib\Factory::parseRangeString('10.155.16.0/22');
/*
* This will print:
* array (
* 0 => '16.155.10.in-addr.arpa',
* 1 => '17.155.10.in-addr.arpa',
* 2 => '18.155.10.in-addr.arpa',
* 3 => '19.155.10.in-addr.arpa',
* )
*/
var_export($range->getReverseDNSLookupName());
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();
If you want to accept addresses that may include ports, you can specify the IPLib\ParseStringFlag::MAY_INCLUDE_PORT
flag:
use IPLib\Factory;
use IPLib\ParseStringFlag;
require_once __DIR__ . '/../ip-lib.php';
// These will print NULL
var_export(Factory::parseAddressString('127.0.0.1:80'));
var_export(Factory::parseAddressString('[::]:80'));
// This will print 127.0.0.1
echo (string) Factory::parseAddressString('127.0.0.1:80', ParseStringFlag::MAY_INCLUDE_PORT);
// This will print ::
echo (string) Factory::parseAddressString('[::]:80', ParseStringFlag::MAY_INCLUDE_PORT);
If you want to accept IPv6 addresses that may include a zone ID, you can specify the IPLib\ParseStringFlag::MAY_INCLUDE_ZONEID
flag:
use IPLib\Factory;
use IPLib\ParseStringFlag;
// This will print NULL
var_export(Factory::parseAddressString('::%11'));
// This will print ::
echo (string) Factory::parseAddressString('::%11', ParseStringFlag::MAY_INCLUDE_ZONEID);
IPv4 addresses are usually expressed in decimal notation, for example as 192.168.0.1
.
By the way, the GNU (used in many Linux distros), BSD (used in Mac) and Windows implementations of inet_aton
and inet_addr
accept IPv4 addresses with numbers in octal and/or hexadecimal format.
Please remark that this does not apply to the inet_pton
and ip2long
functions, as well as to the Musl implementation (used in Alpine Linux) of inet_aton
and inet_addr
.
So, for example, these addresses are all equivalent to 192.168.0.1
:
0xC0.0xA8.0x0.0x01
(only hexadecimal)0300.0250.00.01
(only octal)192.0250.0.0x01
(decimal, octal and hexadecimal numbers)(try it: if you browse to http://0177.0.0.0x1
, your browser will try to browse http://127.0.0.1
).
If you want to accept this non-decimal syntax, you may use the IPLib\ParseStringFlag::IPV4_MAYBE_NON_DECIMAL
flag:
use IPLib\Factory;
use IPLib\ParseStringFlag;
// This will print NULL
var_export(Factory::parseAddressString('0177.0.0.0x1'));
// This will print 127.0.0.1
var_export((string) Factory::parseAddressString('0177.0.0.0x1', ParseStringFlag::IPV4_MAYBE_NON_DECIMAL));
// This will print NULL
var_export(Factory::parseRangeString('0177.0.0.0x1/32'));
// This will print 127.0.0.1/32
var_export((string) Factory::parseRangeString('0177.0.0.0x1/32', ParseStringFlag::IPV4_MAYBE_NON_DECIMAL));
Please be aware that the IPV4_MAYBE_NON_DECIMAL
flag may also affect parsing decimal numbers:
use IPLib\Factory;
use IPLib\ParseStringFlag;
// This will print 127.0.0.10 since the last digit is assumed to be decimal
var_export((string) Factory::parseAddressString('127.0.0.010'));
// This will print 127.0.0.8 since the last digit is assumed to be octal
var_export((string) Factory::parseAddressString('127.0.0.010', ParseStringFlag::IPV4_MAYBE_NON_DECIMAL));
IPv4 addresses are usually expressed with 4 numbers, for example as 192.168.0.1
.
By the way, the GNU (used in many Linux distros), BSD (used in Mac) and Windows implementations of inet_aton
and inet_addr
accept IPv4 addresses with 1 to 4 numbers.
Please remark that this does not apply to the inet_pton
and ip2long
functions, as well as to the Musl implementation (used in Alpine Linux) of inet_aton
and inet_addr
.
If you want to accept this non-decimal syntax, you may use the IPLib\ParseStringFlag::IPV4ADDRESS_MAYBE_NON_QUAD_DOTTED
flag:
use IPLib\Factory;
use IPLib\ParseStringFlag;
// This will print NULL
var_export(Factory::parseAddressString('1.2.500'));
// This will print 0.0.0.0
var_export((string) Factory::parseAddressString('0', ParseStringFlag::IPV4ADDRESS_MAYBE_NON_QUAD_DOTTED));
// This will print 0.0.0.1
var_export((string) Factory::parseAddressString('1', ParseStringFlag::IPV4ADDRESS_MAYBE_NON_QUAD_DOTTED));
// This will print 0.0.1.244
var_export((string) Factory::parseAddressString('0.0.500', ParseStringFlag::IPV4ADDRESS_MAYBE_NON_QUAD_DOTTED));
// This will print 255.255.255.255
var_export((string) Factory::parseAddressString('4294967295', ParseStringFlag::IPV4ADDRESS_MAYBE_NON_QUAD_DOTTED));
Even if there isn't an RFC that describe it, IPv4 subnet notation may also be written in a compact form, omitting extra digits (for example, 127.0.0.0/24
may be written as 127/24
).
If you want to accept such format, you can specify the IPLib\ParseStringFlag::IPV4SUBNET_MAYBE_COMPACT
flag:
use IPLib\Factory;
use IPLib\ParseStringFlag;
// This will print NULL
var_export(Factory::parseRangeString('127/24'));
// This will print 127.0.0.0/24
echo (string) Factory::parseRangeString('127/24', ParseStringFlag::IPV4SUBNET_MAYBE_COMPACT);
Of course, you may use more than one IPLib\ParseStringFlag
flag at once:
use IPLib\Factory;
use IPLib\ParseStringFlag;
// This will print 127.0.0.255
var_export((string) Factory::parseAddressString('127.0.0.0xff:80', ParseStringFlag::MAY_INCLUDE_PORT | ParseStringFlag::IPV4_MAYBE_NON_DECIMAL));
// This will print ::
var_export((string) Factory::parseAddressString('[::%11]:80', ParseStringFlag::MAY_INCLUDE_PORT | ParseStringFlag::MAY_INCLUDE_ZONEID));
The following features can be enabled through environment variables that have been set in your Gitpod preferences.:
* Please note that storing sensitive data in environment variables is not ultimately secure but should be OK for most development situations.
GPG_KEY_ID
(required)
GPG_KEY
(required)
GPG_KEY_ID
GPG_MATCH_GIT_TO_EMAIL
(optional)
~/.gitconfig
to the value providedGPG_AUTO_ULTIMATE_TRUST
(optional)
yes
or YES
then your GPG_KEY
will be automatically ultimately trustedINTELEPHENSE_LICENSEKEY
~/intelephense/licence.txt
and will contain the value providedYou can offer me a monthly coffee or a one-time coffee 😉