Travailler avec IPv6 en PHP

Nous avons récemment reçu le statut LIR et le bloc / 29 IPv6. Et puis il y avait un besoin de garder une trace des sous-réseaux désignés. Et comme notre facturation était écrite en PHP, j'ai dû m'inspirer un peu du problème et me rendre compte que ce langage n'est pas le plus convivial en termes de travail avec IPv6. Under the cut - notre solution aux problèmes de travail avec les adresses et les plages. Peut-être pas le plus élégant, mais il accomplit les tâches.



Un peu de théorie


Clause de non-responsabilité. Si vous êtes familier avec IPv6 et avec quoi il est consommé, cette partie peut être ennuyeuse pour vous. Ce n'est peut-être pas le cas.

Les personnes qui voient d'abord l'annotation IPv6 pourraient bien être découragées. Après l'élégant 64.233.177.101, nous rencontrons soudain 2607: f8b0: 4002: c08 :: 8b et pouvons devenir confus. À la fois cela et un autre - seule représentation lisible par l'homme de 32 et 128 bits respectivement. Tout paquet IP contient un en-tête avec une affectation strictement standardisée de chaque bit. Sans aller encore plus loin dans la structure des en-têtes, nous devons tirer une chose d'ici: pour les opérations avec des adresses IP et des plages, il est généralement pratique d'utiliser les mathématiques binaires et les opérations au niveau du bit. Il est également plus pratique de les stocker dans la base de données en tant que BINARY (4) pour IPv4 et BINARY (16) pour IPv6.

Un autre aspect important qui devrait être abordé est les masques de réseau et la notation CIDR. CIDR est un acronyme pour Classless Inter-Domain Routing. Ce concept a remplacé celui de classe 1 pour déterminer quelle partie de l'adresse IP est le préfixe du réseau et quelle partie est l'adresse de l'interface réseau au sein de ce réseau. En pratique, les n premiers bits correspondant au préfixe seront mis à 1, le reste à 0.

Sous une forme lisible par l'homme, ceci est écrit comme ip.add.re.ss. / cidr . Par exemple, 64.233.177.0/24 signifie que les 24 premiers bits font référence au préfixe. Les 8 derniers bits, ils sont le dernier numéro d'une entrée lisible par l'homme, se réfèrent à l'adresse à l'intérieur du sous-réseau. Encore quelques exercices. 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) - le préfixe, les 64 bits restants - la partie locale. Soit dit en passant, si quelqu'un est gêné par le "::" dans l'entrée, le double signe 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 apprendre de tout cela? Tout d'abord, les première et dernière adresses de sous-réseau peuvent être obtenues en utilisant les ET binaires ET et OU, connaissant le masque sous forme binaire. Deuxièmement, le sous-réseau suivant de taille (c'est-à-dire avec CIDR) n peut être calculé en ajoutant 1 à la nième position en représentation binaire. Par vue binaire, j'entends le résultat de l'utilisation des fonctions pack () et inet_pton () et l'utilisation ultérieure d' opérateurs au niveau du bit , par binaire - une représentation dans le système binaire, qui peut être obtenue, par exemple, en utilisant base_convert () .

Contexte historique
Séparation sans classe de l' adressage précédée sans classe. Dans ces années lointaines, personne ne s'attendait à ce qu'il y ait autant de sous-réseaux; ils étaient répartis à droite et à gauche en grands blocs: classe A - les 8 premiers bits (c'est-à-dire le premier nombre) étaient préfixés, avec le bit de tête 0; classe B - les 16 premiers (deux premiers chiffres), les bits de tête de 10; classe C - les 24 premiers bits, les bits de tête de 110. Ces mêmes bits de tête définissent les plages dans lesquelles l'adresse d'une classe a été émise: 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 se répandait sur la planète, les régulateurs ont réalisé qu'ils avaient raté et, au début des années 90, ont développé un concept sans classe, qui leur a permis de ne pas s'attacher aux bits principaux. Un peu plus de détails peuvent être trouvés, disons, dans le grand et omniscient .


Passons à la pratique


En pratique, nous mettons en œuvre les trois tâches les plus probables, comme il me semble,:

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

L'implémentation sera pour IPv6, mais si nécessaire, la logique peut être facilement adaptée. J'ai eu quelques idées d' ici , mais mises en œuvre un peu différemment. Dans les exemples également, il n'y a pas de vérification des erreurs de saisie. 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 au niveau du bit, connaissant le début de la plage et le masque de sous-réseau binaire. En conséquence, la première chose que nous devons faire est de transformer le CIDR en masque binaire. Pour ce faire, collectez sa représentation hexadécimale et empaquetez-la 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); } 

Le pack d' appels ('H *', $ mask) emballe la représentation hexadécimale de la même manière que inet_pton () . La seule différence est que lorsque vous appelez pack (), tous les 0 doivent être en place et il ne doit pas y avoir de deux-points dans l'entrée, contrairement à l'entrée lisible par l'homme.

L'étape suivante consiste à calculer le début et la fin de la plage. Et ici, il y a des nuances. Les opérations au niveau du bit sont limitées par la capacité du processeur. Par conséquent, sur mon CubieTruck 32 bits, que j'utilise parfois pour tous les tests, tous les 128 bits de l'adresse ne peuvent pas être traités en une seule opération. Cependant, rien ne nous empêche de le diviser en groupes de 32 bits (juste au cas où, qui sait sur quels processeurs nous allons tourner).

 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, nous fournirons la capacité de transmettre IP et d'obtenir le résultat à la fois sous forme binaire et lisible par l'homme. Le paramètre $ which définit ici si nous voulons obtenir le début ou la fin de la plage (les valeurs sont 'start' ou 'end', respectivement).

La tâche suivante (en plus de la plus pratique pour notre entreprise) est de calculer la plage suivante. Pour cette tâche, rien de mieux n'est venu à l'esprit, sauf comment décomposer l'adresse en une chaîne binaire et ajouter 1 à la position souhaitée, puis tout replier. Pour éviter que des artefacts n'apparaissent n'importe où, j'ai décidé de diviser l'adresse par octet pendant la décomposition et l'assemblage.

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

En sortie, nous obtenons 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, vérifiez si l'adresse appartient à la plage. Par exemple, nous avons alloué un bloc / 48 pour la distribution de / 64 blocs aux clients, et nous devons nous assurer que lors du rendez-vous, nous n'allons pas au-delà du bloc alloué (en pratique, cela se produira bientôt, mais il y a encore une chance). Ici, tout est simple. Nous obtenons le début et la fin de la plage sous forme binaire et vérifions si l'adresse est à l'intérieur.

 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 fonctionnalités d'adressage pourriez-vous trouver utiles? Tous les ajouts, commentaires et revues de code sont les bienvenus dans les commentaires.

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

Un peu de publicité :)


Merci de rester avec nous. Aimez-vous nos articles? Vous voulez voir des matériaux plus intéressants? Soutenez-nous en passant une commande ou en recommandant à vos amis des VPS basés sur le cloud pour les développeurs à partir de 4,99 $ , un analogue unique de serveurs d'entrée de gamme que nous avons inventés pour vous: Toute la vérité sur les VPS (KVM) E5-2697 v3 (6 cœurs) 10 Go DDR4 480 Go SSD 1 Gbit / s à partir de 19 $ ou comment diviser le serveur? (les options sont disponibles avec RAID1 et RAID10, jusqu'à 24 cœurs et jusqu'à 40 Go de DDR4).

Dell R730xd 2 fois moins cher au centre de données Equinix Tier IV à Amsterdam? Nous avons seulement 2 x Intel TetraDeca-Core Xeon 2x E5-2697v3 2.6GHz 14C 64GB DDR4 4x960GB SSD 1Gbps 100 TV à partir de 199 $ aux Pays-Bas! Dell R420 - 2x E5-2430 2.2Ghz 6C 128GB DDR3 2x960GB SSD 1Gbps 100TB - à partir de 99 $! Pour en savoir plus sur la création d'un bâtiment d'infrastructure. classe utilisant des serveurs Dell R730xd E5-2650 v4 coûtant 9 000 euros pour un sou?

Source: https://habr.com/ru/post/fr484586/


All Articles