在 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() 並進一步使用 位元運算符,二進位是二進位系統表示,可以透過以下方式取得: base_convert() 函數.

歷史信息無類別尋址之前是類別隔離。 在那些遙遠的歲月裡,沒有人想到會有這麼多的子網,它們分佈在大塊的左右: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服務器一分錢?

來源: www.habr.com

為具有 DDoS 保護、VPS VDS 服務器的站點購買可靠的主機 🔥 購買具備 DDoS 防護的可靠網站寄存服務,包括 VPS 和 VDS 伺服器 | ProHoster