Getting first address of specified IP and prefixlen in PHP

Dumping this here so I can refer to this when needed and in case anyone is looking for this.

function inet_prefixlen_start(string $inet, int $version, int $prefixlen): ?string
{
    switch ($version) {
        case 4:
            if (filter_var($inet, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) !== false) {
                $pack = 'c*';
                $size = 8;
            }
            break;
        case 6:
            if (filter_var($inet, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) !== false) {
                $pack = 'n*';
                $size = 16;
            }
            break;
    }

    if (!isset($pack)) {
        return null;
    }

    $ipArray = unpack($pack, inet_pton($inet));
    $groupMask = (1 << $size) - 1;
    $fullSize = $size * count($ipArray);
    $groupCount = count($ipArray);
    for ($i = 0; $i < $groupCount; $i++) {
        $mask = $groupMask;
        $indexStart = $i * $size;
        for ($j = 0; $j < $size; $j++) {
            $fullIndex = $indexStart + $j;
            if ($fullIndex < $prefixlen) {
                continue;
            }
            // shifting goes from the end, one less of size:
            // (size 8)
            // i = 0 -> shift = 7 (1000 0000)
            // i = 7 -> shift = 0 (0000 0001)
            $index = $size - $j - 1;
            $mask ^= 1 << $index;
        }
        // ipArray is 1-indexed because that's how unpack works
        $group = $i + 1;
        $ipArray[$group] = $ipArray[$group] & $mask;
    }

    return inet_ntop(pack($pack, ...$ipArray));
}

/**
some sample data
['1.2.3.4', 4, 24, '1.2.3.0'],
['1.2.3.5', 4, 31, '1.2.3.4'],
['::10.0.0.3', 4, 32, null],
['1:2:3:4::', 4, 24, null],
['invalid', 4, 32, null],
['1:2:3:4::1', 6, 64, '1:2:3:4::'],
['1:2:3::1', 6, 64, '1:2:3::'],
['1:2:3:4:5:6:7:8', 6, 64, '1:2:3:4::'],
['1::4:5:6:7:8', 6, 64, '1:0:0:4::'],
['1:2:3:4::10.0.0.1', 6, 64, '1:2:3:4::'],
['1:2:3:ffff::1', 6, 56, '1:2:3:ff00::'],
['::3', 6, 127, '::2'],
['1.2.3.4', 6, 128, null],
['invalid', 6, 128, null],
*/

Or just use one of those IP handling libraries.

Leave a Reply

Your email address will not be published. Required fields are marked *