For nylig fik vi status som LIR og en /29 blok IPv6. Og der opstod behovet for at holde styr på de tildelte subnet. Da vores billing er skrevet i PHP, måtte jeg lidt ind i emnet og indse, at dette sprog ikke er det mest venlige, når det kommer til arbejde med IPv6. Under artiklen er vores løsning på de opståede opgaver relateret til adresser og intervaller. Måske ikke den mest elegante, men den løser de stillede opgaver.

Lidt teori
Fraskrivelse. Hvis du kender til, hvad IPv6 er, og hvordan det fungerer, kan denne del være kedelig for dig. Den kan også være det modsatte.
Folk, der ser IPv6-annotationen for første gang, kan blive forvirrede. Efter elegante 64.233.177.101 støder vi pludselig på 2607:f8b0:4002:c08::8b og kan blive overvældede. Både Det ene og det andet er blot menneskeligt forståelige repræsentationer af henholdsvis 32 og 128 bit. Ethvert IP-pakke indeholder en header med en strengt standardiseret bestemmelse for hver bit. Uden at gå dybere ind i headerstrukturen, skal vi herfra forstå ét: til operationer med IP-adresser og intervaller er det mest praktisk at bruge binær matematik og bitwise operationer. De er også lettest at opbevare i databasen som BINARY(4) for IPv4 og BINARY(16) for IPv6.
En anden vigtig aspekt, der bør nævnes, er netværksmasker og CIDR-notation. CIDR er en forkortelse for Classless Inter-Domain Routing (). Dette koncept kom i stedet for klassisk adressering ved at bestemme, hvilken del af IP-adressen der er netværksprefixet, og hvilken del der er adressen for netværksinterfacet inden for dette netværk. I praksis vil de første n bit, der svarer til prefixet, have værdien 1, mens de øvrige vil have værdien 0.
I menneskeligt forståelig form skrives dette som ip.add.re.ss/cidr. For eksempel, 64.233.177.0/24 som angiver, at de første 24 bit hører til prefixet. De sidste 8 bit, som også er det sidste tal i den menneskeligt forståelige notation, henviser til adressen inden for subnettet. Et par flere øvelser. 64.233.177.101/32 og 2607:f8b0:4002:c08::8b/128 — en specifik adresse. 2607:f8b0:4002:c08::/64 — de første 64 bit (de første 4 grupper) er prefixet, de øvrige 64 bit er den lokale del. Forresten, hvis nogen undrer sig over "::" i notationen, så erstatter det dobbelte kolon et vilkårligt antal sektioner, der indeholder 0. Det kan kun optræde én gang i annoteringen. Med andre ord, 2607:f8b0:4002:c08::8b = 2607:f8b0:4002:c08:0:0:0:8b.
Hvad kan vi lære af alt dette? For det første kan vi få den første og sidste adresse i subnettet ved at bruge binær AND og OR, når vi kender masken i binært format. For det andet kan vi beregne det næste subnet af en given størrelse (dvs. med CIDR) n ved at tilføje 1 til n-te position i den binære repræsentation. Med binært mener jeg resultatet af anvendelsen af funktionerne pack() og inet_pton() og videre anvendelse af , ved binært mener jeg repræsentation i det binære system, som man for eksempel kan få via base_convert().
Historisk baggrundKlassefri adressering blev foregået af klassesegregation. I de gamle dage forudså ingen, at der ville være så mange subnetter og udleverede dem til højre og venstre i store blokke: klasse A – de første 8 bits (dvs. det første tal), med den ledende bit 0; klasse B – de første 16 (de første to tal), ledende bits 10; klasse C – de første 24 bits, ledende bits 110. Disse ledende bits definerede de områder, hvori adresserne af en given klasse blev tildelt: 0.0.0.0 — 127.255.255.255 for klasse A, 128.0.0.0 — 191.255.255.255 – klasse B, 192.0.0.0 – 223.255.255.255 – klasse C. Som internettet bredte sig over planeten, indså regulatorerne, at de havde fejlet, og i begyndelsen af 90'erne udviklede de en klassefri koncept, der gjorde det muligt ikke at være bundet til de ledende bits. Lidt flere detaljer kan findes, for eksempel i .
Lad os gå videre til praksis
I praksis vil vi implementere de tre mest sandsynlige opgaver, som jeg har forestillet mig:
- at få den første og sidste adresse i intervallet;
- at få det næste interval af en given størrelse (CIDR);
- at kontrollere, om en adresse tilhører intervallet.
Implementeringen vil være for IPv6, men hvis det er nødvendigt, kan logikken nemt tilpasses. Nogle idéer fik jeg , men implementerede dem lidt anderledes. Derudover er der i eksemplerne ikke nogen kontrol for indtastningsfejl. Så lad os komme i gang.
Som jeg tidligere nævnte, kan den første og sidste adresse i intervallet bestemmes ved hjælp af bitoperationer, når vi kender startpunktet for intervallet og den binære subnetmaske. Derfor skal vi først omdanne CIDR til en binær maske. For dette vil vi samle dens hex-repræsentation og pakke den binært.
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);
}Kald pack(‘H*’, $mask) pakker hex-repræsentationen på samme måde som inet_pton(). Med den eneste forskel, at ved kaldet pack() alle 0 skal være på deres pladser, og der må ikke være kolon i posten i modsætning til menneskeforståelig post.
Næste skridt er at beregne begyndelsen og slutningen af intervallet. Og her vil der opstå nuancer. Bitvise operationer er begrænset af processorens ordlength. Derfor, på min 32-bit CubieTruck, som jeg nogle gange bruger til forskellige testeksperimenter, kan jeg ikke behandle alle 128 bits adresser på én gang. Men intet forhindrer os i at opdele det i grupper af 32 bits (for en sikkerheds skyld, hvem ved på hvilke processorer vi skal køre).
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);
}
}Til fremtidig brug vil vi sikre muligheden for at overføre IP og få resultatet både i binær og menneskelæsbar form. Parameteren $which angiver, om vi vil have begyndelsen eller slutningen af intervallet (værdierne 'start' eller 'end' henholdsvis).
Næste opgave (som også er den mest praktiske for vores firma) er at beregne det næste interval. Til denne opgave faldt der ikke noget bedre mig ind end at opdele adressen i en binær streng og tilføje 1 på den relevante position, hvorefter vi samler det hele igen. For at undgå artefakter besluttede jeg at opdele adressen per byte under opdelingen og samlingen.
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);
}
}Ved udgangen får vi præfikset for det næste intervall i den størrelse, der er angivet i $cidr.Denne funktion tildeler adres blokke til vores kunder.
Endelig er der en kontrol for, om adressen tilhører intervallet. For eksempel har vi tildelt en blok /48 til at fordele blokke på /64 til kunderne, og vi skal sikre os, at vi ved tildelingen ikke overskrider den tildelte blok (i praksis vil dette ikke ske snart, men risikoen er der). Her er det enkelt. Vi får begyndelsen og slutningen af intervallet i binær form og kontrollerer, om adressen ligger inden for.
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);
}Jeg håber, det var nyttigt. Hvilke andre funktioner til arbejde med adresser synes du kunne være nyttige? Enhver tilføjelse, kommentar og kodegennemgang er meget velkommen i kommentarerne.
Hvis du allerede er vores kunde eller overvejer at blive det, tilbyder vi dig i anledning af denne artikel en blok /64 helt gratis til alle VPS- eller dedikerede servertjenester i Equinix Tier IV datacenteret i Holland ved at anmode salgsafdelingen og inkludere linket til denne artikel i din supportticket. Tilbuddet gælder indtil marts 2020.
Lidt reklame 🙂
Tak fordi du bliver hos os. Kan du lide vores artikler? Ønsker du at se flere interessante materialer? Støt os ved at bestille eller anbefale os til dine bekendte, , en unik analog til entry-level servere, som vi har designet til dig: (tilgængelige muligheder med RAID1 og RAID10, op til 24 kerner og op til 40 GB DDR4).
Dell R730xd til halv pris i datacenteret Equinix Tier IV i Amsterdam? Kun hos os i Holland! Dell R420 - 2x E5-2430 2.2GHz 6C 128GB DDR3 2x960GB SSD 1Gbps 100TB - fra 99 $! Læs om hvordan
Kilde: habr.com
