Whitelisting IPv6 subnets with php
If you would like to restrict access to a page on your website using an IP whitelist, there’s a few ways you can do this (e.g. in your http server config). In this post, I’m going to show you how to achieve this in php, but only for ipv6 addresses.
Whitelists for IPv4 have already been developed numerous times (see
here). This can be done easily by converting the ipv4
address to a 32bit integer (using php’s
ip2long() function), bitshifting the integer and comparing the network bits. The
same technique can’t be used for ipv6 addresses however, because these addresses consist
of 128 bits and hence, are too large for a native php integer type (subsequently, there is no php function to convert an ipv6 address to an integer).
Whitelisting IPv6 subnets
First, we define our subnets in an array. I’ll be using CIDR notation (xx.xx.xx.xx/xx) for my subnets, but at the end of this post I discuss ways to support other formats.
I like to leave a trailing comma in the array definition so you don’t forget the comma when adding new elements to the array.
Next, we create a function that fetches the client’s IP address and iterates over the whitelist subnets to check if the client IP belongs to any of them.
Before creating the
ip_in_subnet() function, we define a function which just takes
the binary representation of an IP address and converts it into a 128 character-long string
of bits (1’s and 0’s). You’ll see why this function is useful in the next step.
The next function is the main part of the whitelister. It uses php’s
inet_pton() function to convert the IP address of the client and an IP from
the subnet into their binary representations. It then converts them to a
string of bits (using the function we just defined above) and compares the
network part of the addresses to see if they belong to the same subnet.
Finally, you can test if your whitelist is working with the following code. It’s useful to set the correct HTTP headers if the client is not part of the whitelist so that they know why they have been blocked.
Some notes to consider
Forwarded IP headers / Getting the client IP correctly
You may have noticed that I obtained the client IP using
$_SERVER['REMOTE_ADDR']. If you’re running a simple web server this will most likely be fine. However, in my case for example, the application server was located behind a load balancer. Thus,
$_SERVER['REMOTE_ADDR'] actually reflects the IP of the load balancer and not the client. A much safer way of getting the client’s IP would first check to see if the
HTTP_X_FORWARDED_FOR header was set. The following function does exactly that:
Subnets in other formats
CIDR notation is most commonly used to specify subnets, but 2 other common formats are:
- Start-End range:
I’ll leave this as a challenge for you to figure out :)
You’ll probably want to add IPv4 validation to this and write your own class that can be used to check all types of subnets. Jalle19 has already written quite a nice class which you can find here and, either use as-is or draw some inspiration for your own class.
You can download the source for this IPv6 whitelist here.