在 PHP 中使用 IPv6

我们最近收到了 LIR 状态和 /29 IPv6 块。 然后就需要跟踪分配的子网。 由于我们的计费是用 PHP 编写的,因此我们必须深入研究一下这个问题,并意识到这种语言在使用 IPv6 方面并不是最友好的。 下面是我们针对使用地址和范围时出现的问题的解决方案。 也许不是最优雅的,但它可以完成工作。

在 PHP 中使用 IPv6

有些理论

免责声明。 如果您熟悉 IPv6 是什么以及它附带什么,这部分对您来说可能会很无聊。 可能不是。

第一次看到 IPv6 注释的人可能会觉得相当令人畏惧。 优雅之后 64.233.177.101 我们突然面临 2607:f8b0:4002:c08::8b 我们可能会感到困惑。 两者分别只是人类可读的 32 位和 128 位表示。 任何 IP 数据包都包含一个标头,每个位都有严格标准化的用途。 在不深入探讨标头结构的情况下,我们需要了解的一件事是,对于 IP 地址和范围的运算,使用二进制数学和按位运算通常很方便。 将它们存储在数据库中也是最方便的 二进制(4) 对于 IPv4 和 二进制(16) 对于 IPv6。

另一个值得讨论的重要方面是网络掩码和 CIDR 表示法。 CIDR 是无类域间路由的缩写(无类寻址)。 在确定 IP 地址的哪一部分是网络前缀以及哪一部分是该网络内的网络接口地址时,此概念取代了类概念。 实际上,前缀对应的前n位将被设置为1,其余为0。

以人类可以理解的形式,这被写成以下形式 ip.add.re.ss/cidr。 例如, 64.233.177.0/24 表示前24位指的是前缀。 最后 8 位,也称为人类可读符号中的最后一个数字,指的是子网内的地址。 还有几个练习。 64.233.177.101/32 и 2607:f8b0:4002:c08::8b/128 - 一个具体地址。 2607:f8b0:4002:c08::/64 - 前 64 位(前 4 组)是前缀,其余 64 位是本地部分。 顺便说一句,如果有人对条目中的“::”感到困惑,双冒号会替换任意数量的包含 0 的部分。它在注释中只能出现一次。 换句话说, 2607:f8b0:4002:c08::8b = 2607:f8b0:4002:c08:0:0:0:8b.

我们需要从这一切中学到什么? 首先,在知道二进制形式的掩码的情况下,可以使用二进制 AND 和 OR 获得第一个和最后一个子网地址。 其次,下一个子网大小(即使用 CIDR) n 可以通过加 1 来计算 n- 二进制表示中的该位置。 我所说的二进制是指使用函数的结果 盒() и inet_pton() 并进一步使用 按位运算符,二进制是二进制系统表示,可以通过以下方式获得: 基数转换().

历史信息无类寻址之前是类隔离。 在那些遥远的岁月里,谁也没有想到会有这么多的子网,它们分布在大块的左右:A类——前缀是前8位(即第一个数字),前导位为0; B 类 - 前 16 位(前两个数字),前导位 10; C 类 - 前 24 位,前导位 110。这些前导位指定发布特定类地址的范围: 0.0.0.0 - 127.255.255.255 对于A类, 128.0.0.0 - 191.255.255.255 - B 类,192.0.0.0 - 223.255.255.255 - C 类。随着互联网在全球范围内的传播,监管机构意识到他们已经错过了目标,并在 90 年代初开发了一种无类别概念,使不被束缚成为可能到前导位。 可以在以下位置找到更多详细信息: 伟大而无所不知的.

我们继续练习

在实践中,我们将实施在我看来最有可能的三个任务:

  1. 获取范围的第一个和最后一个地址;
  2. 获取下一个给定的大小范围(CIDR);
  3. 检查地址是否属于某个范围。

该实施将针对 IPv6,但如有必要,可以轻松调整逻辑。 我有一些想法 ,但实现方式略有不同。 此外,这些示例不检查输入错误。 那么,我们走吧。

正如我已经提到的,在知道范围的开头和二进制子网掩码的情况下,可以使用按位运算来确定范围的第一个和最后一个地址。 因此,我们要做的第一件事就是将 CIDR 转换为二进制掩码。 为此,我们将收集其十六进制表示形式并将其打包为二进制表示形式。

function cidrToMask ($cidr) {
    $mask = str_repeat('f', ceil($cidr / 4));
    $mask .= dechex(4 * ($cidr % 4));
    $mask = str_pad($mask, 32, '0');
    return pack('H*', $mask);
}

通话 包('H*',$掩码) 以与以下相同的方式打包十六进制表示形式 inet_pton()。 唯一的区别是调用时 盒() 所有 0 必须就位,并且条目中不应有冒号,这与人类可读的条目不同。

下一步是计算范围的开始和结束。 这里出现了细微差别。 按位运算受处理器位容量的限制。 因此,在我有时用于各种测试的 32 位 CubieTruck 上,不可能在一次操作中处理所有 128 位地址。 然而,没有什么可以阻止我们将其分成 32 位组(以防万一,谁知道我们将在什么处理器上运行)。

function getRangeBoundary ($ip, $cidr, $which, $ipIsBin = false, $returnBin = false) {
    $mask = cidrToMask($cidr);
    if (!$ipIsBin) {
        $ip = inet_pton($ip);
    }
    $ipParts   = str_split($ip, 4);
    $maskParts = str_split($mask, 4);
    $rangeParts  = [];
    for ($i = 0; $i < count($ipParts); $i++) {
        if ($which == 'start') {
            /* Побитовый & адреса и маски оставит только биты префикса. */
            $rangeParts[$i] = $ipParts[$i] & $maskParts[$i];
        } else {
            /* Побитовый | с обратной маской (~) оставит биты префикса и установит все биты локальной части в 1. */
            $rangeParts[$i] = $ipParts[$i] | ~$maskParts[$i];
        }
    }
    $rangeBoundary = implode($rangeParts);
    if ($returnBin) {
        return $rangeBoundary;
    } else {
        return inet_ntop($rangeBoundary);
    }
}

为了将来的使用,我们将提供传输 IP 并接收二进制和人类可读形式结果的能力。 范围 $哪个 这里它指定我们是否想要获取范围的开头或结尾(值 '开始' или '结尾' 分别)。

下一个任务(也是我们公司最实际的任务)是计算下一个范围。 对于此任务,没有什么比将地址扩展为二进制字符串并在所需位置添加 1,然后将所有内容折叠回去更好的了。 为了避免在任何地方出现伪影,我决定在分解和组装过程中按字节拆分地址。

function getNextBlock ($ipStart, $cidr, $ipIsBin = false, $returnBin = false) {
    if (!$ipIsBin) {
        $ipStart = inet_pton($ipStart);
    }
    $ipParts = str_split($ipStart, 1);
    $ipBin   = '';
    foreach ($ipParts as $ipPart) {
        $ipBin .= str_pad(base_convert(unpack('H*', $ipPart)[1], 16, 2), 8, '0', STR_PAD_LEFT);
    }
    /* Добавляем 1 в нужном разряде двоичного представления строки "влоб" :) */
    $i = $cidr - 1;
    while ($i >= 0) {
        if ($ipBin[$i] == '0') {
            $ipBin[$i] = '1';
            break;
        } else {
            $ipBin[$i] = '0';
        }
        $i--;
    }
    $ipBinParts = str_split($ipBin, 8);
    foreach ($ipBinParts as $key => $ipBinPart) {
        $ipParts[$key] = pack('H*', str_pad(base_convert($ipBinPart, 2, 16), 2, '0', STR_PAD_LEFT));
    }
    $nextIp = implode($ipParts);
    if ($returnBin) {
        return $nextIp;
    } else {
        return inet_ntop($nextIp);
    }
}

在输出中,我们得到指定的下一个尺寸范围的前缀 $cidr。 通过此功能,我们可以为客户分配地址块。

最后,检查地址是否属于该范围。 例如我们分配了48个区块/XNUMX用于向客户端分发区块 /64,并且我们需要确保分配时不会超出分配的块(实际上这不会很快发生,但仍然有可能)。 这里一切都很简单。 我们以二进制形式获取范围的开头和结尾,并检查地址是否在限制范围内。

function ipInRange ($ip, $rangeStart, $cidr) {
    $start = getRangeBoundary($rangeStart, $cidr, 'start',false, true);
    $end = getRangeBoundary($rangeStart, $cidr, 'end',false, true);
    $ipBin = inet_pton($ip);
    return ($ipBin >= $start && $ipBin <= $end);
}

我希望这可以帮到你。 您认为还有哪些其他处理地址的函数可能有用? 热烈欢迎任何补充、评论和代码审查。

如果您已经是我们的客户或只是想成为我们的客户,在本文发表之际,我们将为您提供一个区块 /64 荷兰 Equinix Tier IV 数据中心的所有 vps 服务或专用服务器完全免费,可通过在票证中提供本文的链接向销售部门提出请求。 该优惠有效期至 2020 年 XNUMX 月。

一些广告🙂

感谢您与我们在一起。 你喜欢我们的文章吗? 想看更多有趣的内容? 通过下订单或推荐给朋友来支持我们, 面向开发人员的云 VPS,4.99 美元起, 我们为您发明的入门级服务器的独特模拟: VPS (KVM) E5-2697 v3(6 核)10​​4GB DDR480 1GB SSD 19Gbps XNUMX 美元或如何共享服务器的全部真相? (适用于 RAID1 和 RAID10,最多 24 个内核和最多 40GB DDR4)。

Dell R730xd 在阿姆斯特丹的 Equinix Tier IV 数据中心便宜 2 倍? 只有这里 2 x Intel TetraDeca-Core Xeon 2x E5-2697v3 2.6GHz 14C 64GB DDR4 4x960GB SSD 1Gbps 100 电视低至 199 美元 在荷兰! Dell R420 - 2x E5-2430 2.2Ghz 6C 128GB DDR3 2x960GB SSD 1Gbps 100TB - 99 美元起! 阅读 如何建设基础设施公司同级使用价值730欧元的Dell R5xd E2650-4 v9000服务器一分钱?

来源: habr.com

为具有 DDoS 保护、VPS VDS 服务器的站点购买可靠的主机 🔥 购买具备 DDoS 防护的可靠网站托管服务,包括 VPS 和 VDS 服务器 | ProHoster