Travailler avec IPv6 dans PHP

Récemment, nous avons obtenu le statut LIR et un bloc IPv6 /29. Cela a également créé la nécessité de gérer les sous-réseaux attribués. Étant donné que notre facturation est écrite en PHP, nous avons dû nous intéresser un peu à la question et réaliser que ce langage n'est pas le plus convivial en ce qui concerne IPv6. Ci-dessous, vous trouverez notre solution aux problèmes rencontrés concernant les adresses et les plages. Ce n'est peut-être pas la plus élégante, mais les tâches établies sont réalisées.

Travailler avec IPv6 dans PHP

Un peu de théorie

Avertissement. Si vous êtes familier avec ce qu'est IPv6 et comment cela fonctionne, cette partie pourrait vous sembler ennuyeuse. Cela peut ne pas l'être.

Les personnes qui voient pour la première fois une annotation IPv6 peuvent être assez désarçonnées. Après avoir vu des annotations élégantes, 64.233.177.101 nous nous retrouvons soudain face à 2607:f8b0:4002:c08::8b et nous pouvons être perdus. Les deux ne sont qu'une représentation compréhensible de 32 et 128 bits respectivement. Tout paquet IP contient un en-tête avec une destination strictement standardisée pour chaque bit. Sans entrer plus profondément dans la structure des en-têtes, nous devons tirer une conclusion ici : pour les opérations avec des adresses IP et des plages, il est principalement pratique d'utiliser la mathématique binaire et des opérations sur les bits. Il est également plus facile de les stocker dans la base de données sous forme de BINARY(4) pour IPv4 et BINARY(16) pour IPv6.

Un autre aspect important à aborder est le masque de réseau et la notation CIDR. CIDR est l'acronyme de Classless Inter-Domain Routing (adressage sans classe). Ce concept a remplacé la notation classique pour déterminer quelle partie de l'adresse IP est le préfixe du réseau et laquelle est l'adresse de l'interface réseau à l'intérieur de ce réseau. En pratique, les premiers n bits correspondant au préfixe auront des valeurs de 1, les autres seront des 0.

Cela se note de manière compréhensible sous la forme de ip.add.re.ss/cidr. Par exemple, 64.233.177.0/24 indiquant que les 24 premiers bits appartiennent au préfixe. Les 8 derniers bits, qui correspondent au dernier nombre dans la notation compréhensible, sont l'adresse à l'intérieur du sous-réseau. Un dernier exercice. 64.233.177.101/32 et 2607:f8b0:4002:c08::8b/128 — une adresse spécifique. 2607:f8b0:4002:c08::/64 — les 64 premiers bits (les 4 premiers groupes) sont le préfixe, les autres 64 bits constituent la partie locale. Au fait, pour ceux que le "::" dans la notation dérange, le double deux-points remplace un nombre arbitraire de sections contenant 0. Il ne peut apparaître dans l'annotation qu'une seule fois. En d'autres termes, 2607:f8b0:4002:c08::8b = 2607:f8b0:4002:c08:0:0:0:8b.

Que devons-nous tirer de tout cela ? Tout d'abord, la première et la dernière adresse d'un sous-réseau peuvent être obtenues en utilisant les opérateurs binaires AND et OR, en connaissant le masque sous forme binaire. Deuxièmement, le prochain sous-réseau de taille (c'est-à-dire avec CIDR) n peut être calculé en ajoutant 1 à n-ième position de la représentation binaire. Par représentation binaire, j'entends le résultat de l'utilisation des fonctions pack() et inet_pton() et l'utilisation ultérieure des opérateurs bit à bit, par binaire — la représentation en base binaire, qui peut être obtenue, disons, avec base_convert().

Contexte historiqueAvant le sans-classes, il y avait la segmentation de l'adressage par classes. À l'époque, personne ne s'attendait à ce qu'il y ait autant de sous-réseaux, on les distribuait à droite et à gauche par grands blocs : classe A — le préfixe était les 8 premiers bits (c'est-à-dire le premier nombre), avec un bit dirigeant de 0 ; classe B — les 16 premiers (les deux premiers nombres), les bits dirigeants 10 ; classe C — les 24 premiers bits, les bits dirigeants 110. Ces bits dirigeants déterminaient les plages dans lesquelles une adresse de telle ou telle classe était attribuée : 0.0.0.0 — 127.255.255.255 pour la classe A, 128.0.0.0 — 191.255.255.255 — classe B, 192.0.0.0 — 223.255.255.255 — classe C. Au fur et à mesure que l'Internet s'est répandu sur la planète, les régulateurs ont compris qu'ils s'étaient trompés, et au début des années 90, ils ont développé le concept sans classes, permettant de ne pas s'attacher aux bits dirigeants. On peut trouver un peu plus de détails, disons, dans le grand et omniscient.

Passons à la pratique

Dans la pratique, nous allons résoudre trois tâches les plus probables, selon moi :

  1. obtenir la première et la dernière adresse de la plage ;
  2. obtenir la prochaine plage de taille donnée (CIDR) ;
  3. vérifier si l'adresse appartient à la plage.

La mise en œuvre sera pour IPv6, mais si nécessaire, la logique peut être facilement adaptée. J'ai également tiré certaines idées d'ici, mais je les ai mises en œuvre un peu différemment. De plus, les exemples ne contiennent pas de vérification des erreurs d'entrée. Alors, allons-y.

Comme je l'ai déjà mentionné, la première et la dernière adresse d'une plage peuvent être déterminées à l'aide d'opérations bit à bit, en connaissant le début de la plage et le masque binaire du sous-réseau. Par conséquent, la première chose à faire est de transformer le CIDR en un masque binaire. Pour cela, nous allons recueillir sa représentation hexadécimale et la compacter en binaire.

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);
}

de system-nspawn pack(‘H*’, $mask) compacte la représentation hexadécimale de la même manière que inet_pton(). Avec la seule différence que lors de l'appel pack() Tous les 0 doivent être à leur place, et il ne doit pas y avoir de deux-points dans l'enregistrement, contrairement à un enregistrement compréhensible par l'homme.

La prochaine étape consiste à calculer le début et la fin de la plage. Et c'est ici que des nuances vont apparaître. Les opérations bit à bit sont limitées par la largeur de l'architecture du processeur. Donc, sur mon CubieTruck 32 bits, que j'utilise parfois pour mes tests, il est impossible de traiter tous les 128 bits d'adresse en une seule opération. Cependant, rien ne nous empêche de les diviser en groupes de 32 bits (juste au cas où, qui sait sur quel processeur nous allons fonctionner).

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);
    }
}

Pour une utilisation future, prévoyons la possibilité de transmettre une IP et d'obtenir le résultat à la fois en format binaire et en format lisible par l'homme. Le paramètre $which indique si nous voulons obtenir le début ou la fin de la plage (valeurs 'start' ou 'end' respectivement).

La prochaine tâche (qui est aussi la plus pratique pour notre entreprise) consiste à calculer la plage suivante. Pour cette tâche, rien de mieux ne m'est venu à l'esprit que de décomposer l'adresse en une chaîne binaire et d'ajouter 1 à la position appropriée, puis de reconstituer le tout. Pour éviter d'avoir des artefacts, j'ai décidé de décomposer et de reconstruire l'adresse par octets.

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);
    }
}

Nous allons obtenir en sortie le préfixe de la prochaine plage de taille spécifiée dans $cidr.Avec cette fonction, nous attribuons des blocs d'adresses à nos clients.

Enfin, nous devons vérifier si l'adresse appartient à la plage. Par exemple, nous avons attribué un bloc /48 pour distribuer des blocs /64 à nos clients, et nous devons nous assurer qu'en assignant, nous ne sortons pas des limites du bloc attribué (en pratique, cela se produira dans longtemps, mais il y a un risque). Ici, c'est simple. Nous obtenons le début et la fin de la plage en format binaire et vérifions si l'adresse se trouve dans les limites.

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);
}

J'espère que cela a été utile. Quelles autres fonctions relatives aux adresses pourraient, selon vous, être utiles ? Toute addition, commentaire et revue de code sont les bienvenus dans les commentaires.

Si vous êtes déjà notre client ou envisagez de le devenir, à l'occasion de la publication de cet article, nous vous offrons un bloc /64 gratuitement pour tous les services VPS ou serveur dédié dans le centre de données Equinix Tier IV, aux Pays-Bas, sur demande auprès de notre service commercial, en fournissant le lien vers cet article dans votre ticket. L'offre est valable jusqu'en mars 2020.

Un peu de publicité 🙂

Merci de rester avec nous. Aimez-vous nos articles ? Voulez-vous voir plus de contenu intéressant ? Soutenez-nous en passants une commande ou en nous recommandant à des amis, VPS cloud pour développeurs à partir de 4,99 $, un équivalent unique des serveurs d'entrée de gamme, conçu pour vous : Toute la vérité sur le VPS (KVM) E5-2697 v3 (6 cœurs) 10 Go DDR4 480 Go SSD 1 Gbps à partir de 19 $ ou comment bien diviser un serveur ? (options disponibles avec RAID1 et RAID10, jusqu'à 24 cœurs et jusqu'à 40 Go DDR4).

Dell R730xd deux fois moins cher dans le data center Equinix Tier IV à Amsterdam ? Uniquement chez nous 2 x Intel TetraDeca-Core Xeon 2x E5-2697v3 2.6GHz 14C 64Go DDR4 4x960Go SSD 1Gbps 100 To à partir de 199 $ aux Pays-Bas ! Dell R420 — 2x E5-2430 2.2GHz 6C 128Go DDR3 2x960Go SSD 1Gbps 100To — à partir de 99 $ ! Lisez sur Comment construire une infrastructure de classe entreprise avec des serveurs Dell R730xd E5-2650 v4 coûtant 9000 euros pour des clopinettes ?

Source : habr.com

Acheter un hébergement fiable pour les sites avec protection DDoS, serveurs VPS VDS 🔥 Acheter un hébergement fiable pour les sites avec protection DDoS, serveurs VPS VDS | ProHoster