Einen IPv6-Bereich aus einem CIDR-Präfix berechnen?

Lesezeit: 9 Minuten

Benutzer-Avatar
Damainman

Ich kann dies mit IPv4 tun, indem ich Code-Snippets aus verschiedenen Online-Quellen verwende. Ich habe mich gefragt, ob es eine Möglichkeit gibt, dies mit IPv6 zu tun.

Im Grunde brauche ich nur ein Formular, in das ich eine IPv6-Adresse und ein Präfix eingeben kann (z. B.: f080:42d2:581a::0/68), und es berechnet die Netzwerkadresse, die erste verwendbare Adresse, die letzte verwendbare Adresse und die Broadcast-Adresse. Dann druckt einfach auf den Bildschirm. Ich möchte es noch nicht in einer Datenbank oder so speichern.

Zunächst einmal: IPv6 hat keine Netzwerk- und Broadcast-Adressen. Sie können alle Adressen in einem Präfix verwenden. Zweitens: In einem LAN ist die Präfixlänge immer (naja, 99,x% der Zeit) ein /64. Das Routing von /68 würde IPv6-Funktionen wie die zustandslose automatische Konfiguration beeinträchtigen.

Nachfolgend finden Sie eine ausführliche Implementierung eines IPv6-Präfixrechners:

<?php

/*
 * This is definitely not the fastest way to do it!
 */

// An example prefix
$prefix = '2001:db8:abc:1400::/54';

// Split in address and prefix length
list($firstaddrstr, $prefixlen) = explode("https://stackoverflow.com/", $prefix);

// Parse the address into a binary string
$firstaddrbin = inet_pton($firstaddrstr);

// Convert the binary string to a string with hexadecimal characters
# unpack() can be replaced with bin2hex()
# unpack() is used for symmetry with pack() below
$firstaddrhex = reset(unpack('H*', $firstaddrbin));

// Overwriting first address string to make sure notation is optimal
$firstaddrstr = inet_ntop($firstaddrbin);

// Calculate the number of 'flexible' bits
$flexbits = 128 - $prefixlen;

// Build the hexadecimal string of the last address
$lastaddrhex = $firstaddrhex;

// We start at the end of the string (which is always 32 characters long)
$pos = 31;
while ($flexbits > 0) {
  // Get the character at this position
  $orig = substr($lastaddrhex, $pos, 1);

  // Convert it to an integer
  $origval = hexdec($orig);

  // OR it with (2^flexbits)-1, with flexbits limited to 4 at a time
  $newval = $origval | (pow(2, min(4, $flexbits)) - 1);

  // Convert it back to a hexadecimal character
  $new = dechex($newval);

  // And put that character back in the string
  $lastaddrhex = substr_replace($lastaddrhex, $new, $pos, 1);

  // We processed one nibble, move to previous position
  $flexbits -= 4;
  $pos -= 1;
}

// Convert the hexadecimal string to a binary string
# Using pack() here
# Newer PHP version can use hex2bin()
$lastaddrbin = pack('H*', $lastaddrhex);

// And create an IPv6 address from the binary string
$lastaddrstr = inet_ntop($lastaddrbin);

// Report to user
echo "Prefix: $prefix\n";
echo "First: $firstaddrstr\n";
echo "Last: $lastaddrstr\n";

?>

Es sollte ausgeben:

Prefix: 2001:db8:abc:1400::/54
First: 2001:db8:abc:1400::
Last: 2001:db8:abc:17ff:ffff:ffff:ffff:ffff

  • Genau das, was ich brauchte!!. Ich versuche seit Tagen, mich damit auseinanderzusetzen, und ich befinde mich derzeit in einem Cisco-Kurs, aber wir haben IPv6 noch nicht erreicht, also versuche ich, das Konzept selbst zu verstehen. Ich habe mir den Code angesehen, wie viel Codeänderung müsste vorgenommen werden, wenn ich auch die 2. IP und die IP kurz vor der letzten haben wollte? Ich habe versucht, es herauszufinden, aber außerhalb meines Bereichs von PHP-Wissen 🙁

    – Damainman

    11. April 2012 um 8:15 Uhr


  • Nun, die zweite Adresse hat als letztes Zeichen eine 1 statt einer 0 und die vorletzte ein e statt eines f.

    – Sander Steffann

    12. April 2012 um 18:32 Uhr

  • Eigentlich funktioniert dein Code genau so, wie ich es brauchte :). Die Änderungen, nach denen ich gesucht habe, waren auf meinen Mangel an Wissen zurückzuführen :). Danke noch einmal!

    – Damainmann

    22. April 2012 um 3:01 Uhr

  • Zur Information, ich habe gesehen, dass Ihre Antwort zum PhpMyAdmin-Code beigetragen hat;) github.com/phpmyadmin/phpmyadmin/blob/…

    – tauf

    24. September 2014 um 12:42 Uhr

  • Für die Aufzeichnung, die IPv6-Adresse enthält eine Netzwerkadresse – die ersten 3 Gruppen (oder 48 Bits) davon.

    – durrrr

    28. Juli 2015 um 12:58 Uhr

Für diejenigen, die auf diese Frage stoßen, können Sie dies effektiver tun, indem Sie die verwenden dtr_pton und dtr_ntop Funktionen u dTRIP Klasse gefunden auf GitHub.

Wir haben auch einen Mangel an Fokus und Tools mit IPv6 in PHP festgestellt und diesen Artikel zusammengestellt, http://www.highonphp.com/5-tips-for-working-with-ipv6-in-phpwas für andere hilfreich sein kann.

Funktionsquelle

Dies konvertiert und IP in eine binäre Darstellung:

/**
 * dtr_pton
 *
 * Converts a printable IP into an unpacked binary string
 *
 * @author Mike Mackintosh - [email protected]
 * @param string $ip
 * @return string $bin
 */
function dtr_pton( $ip ){

    if(filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)){
        return current( unpack( "A4", inet_pton( $ip ) ) );
    }
    elseif(filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)){
        return current( unpack( "A16", inet_pton( $ip ) ) );
    }

    throw new \Exception("Please supply a valid IPv4 or IPv6 address");

    return false;
}

Dies wandelt eine binäre Darstellung in druckbare IP um:

/**
 * dtr_ntop
 *
 * Converts an unpacked binary string into a printable IP
 *
 * @author Mike Mackintosh - [email protected]
 * @param string $str
 * @return string $ip
 */
function dtr_ntop( $str ){
    if( strlen( $str ) == 16 OR strlen( $str ) == 4 ){
        return inet_ntop( pack( "A".strlen( $str ) , $str ) );
    }

    throw new \Exception( "Please provide a 4 or 16 byte string" );

    return false;
}

Beispiele

Verwendung der dtr_pton Funktion können Sie:

$ip = dtr_pton("fe80:1:2:3:a:bad:1dea:dad");
$mask = dtr_pton("ffff:ffff:ffff:ffff:ffff:fff0::");

Holen Sie sich Ihr Netzwerk und senden Sie:

var_dump( dtr_ntop( $ip & $mask ) );
var_dump( dtr_ntop( $ip | ~ $mask ) );

Und Ihre Ausgabe wäre:

string(18) "fe80:1:2:3:a:ba0::"
string(26) "fe80:1:2:3:a:baf:ffff:ffff"

  • IPv6 unterstützt kein Broadcast, daher gibt es kein Konzept einer Broadcast-Adresse.

    – Steve-o

    2. April 2013 um 19:58 Uhr

  • @Steve-o Genau, aber da der Begriff Broadcast schon sehr lange in Netzwerken verwendet wird, ist es für die Menschen einfacher zu verstehen, wenn in IPv6 ein Bereich hervorgehoben wird. Dies ähnelt dem Subnetzsegment, aber viele Netzwerke erstrecken sich weit über dieses Segment hinaus.

    – Mike Mackintosh

    3. April 2013 um 2:29 Uhr

  • Der Link zum Blogbeitrag ist tot. Können Sie es bitte aktualisieren oder entfernen?

    – Michael Cordingley

    10. Mai 2018 um 17:29 Uhr

Dies ist ein Fix für die akzeptierte Antwort, die fälschlicherweise davon ausgeht, dass die “erste Adresse” mit der eingegebenen Zeichenfolge identisch sein sollte. Vielmehr muss es seinen Wert haben modifiziert über eine AND Operator gegen seine Maske.

Um das Problem zu demonstrieren, betrachten Sie diese Beispieleingabe: 2001:db8:abc:1403::/54

Erwartetes Ergebnis:

First: 2001:db8:abc:1400::

Tatsächliche Ergebnis:

First: 2001:db8:abc:1403::

Die relevante Mathematik zur Berechnung der Maske für eine gegebene 4-Bit-Sequenz lautet:

// Calculate the subnet mask. min() prevents the comparison from being negative
$mask = 0xf << (min(4, $flexbits));

// AND the original against its mask
$newval = $origval & $mask;

Vollständiger Code

<?php

/*
 * This is definitely not the fastest way to do it!
 */

// An example prefix
$prefix = '2001:db8:abc:1403::/54';

// Split in address and prefix length
list($addr_given_str, $prefixlen) = explode("https://stackoverflow.com/", $prefix);

// Parse the address into a binary string
$addr_given_bin = inet_pton($addr_given_str);

// Convert the binary string to a string with hexadecimal characters
$addr_given_hex = bin2hex($addr_given_bin);

// Overwriting first address string to make sure notation is optimal
$addr_given_str = inet_ntop($addr_given_bin);

// Calculate the number of 'flexible' bits
$flexbits = 128 - $prefixlen;

// Build the hexadecimal strings of the first and last addresses
$addr_hex_first = $addr_given_hex;
$addr_hex_last = $addr_given_hex;

// We start at the end of the string (which is always 32 characters long)
$pos = 31;
while ($flexbits > 0) {
    // Get the characters at this position
    $orig_first = substr($addr_hex_first, $pos, 1);
    $orig_last = substr($addr_hex_last, $pos, 1);

    // Convert them to an integer
    $origval_first = hexdec($orig_first);
    $origval_last = hexdec($orig_last);

    // First address: calculate the subnet mask. min() prevents the comparison from being negative
    $mask = 0xf << (min(4, $flexbits));

    // AND the original against its mask
    $new_val_first = $origval_first & $mask;

    // Last address: OR it with (2^flexbits)-1, with flexbits limited to 4 at a time
    $new_val_last = $origval_last | (pow(2, min(4, $flexbits)) - 1);

    // Convert them back to hexadecimal characters
    $new_first = dechex($new_val_first);
    $new_last = dechex($new_val_last);

    // And put those character back in their strings
    $addr_hex_first = substr_replace($addr_hex_first, $new_first, $pos, 1);
    $addr_hex_last = substr_replace($addr_hex_last, $new_last, $pos, 1);

    // We processed one nibble, move to previous position
    $flexbits -= 4;
    $pos -= 1;
}

// Convert the hexadecimal strings to a binary string
$addr_bin_first = hex2bin($addr_hex_first);
$addr_bin_last = hex2bin($addr_hex_last);

// And create an IPv6 address from the binary string
$addr_str_first = inet_ntop($addr_bin_first);
$addr_str_last = inet_ntop($addr_bin_last);

// Report to user
echo "Prefix: $prefix\n";
echo "First: $addr_str_first\n";
echo "Last: $addr_str_last\n";

Ausgänge:

Prefix: 2001:db8:abc:1403::/54
First: 2001:db8:abc:1400::
Last: 2001:db8:abc:17ff:ffff:ffff:ffff:ffff

Benutzer-Avatar
CodeWütend

Nun, für die Nachwelt füge ich meine hinzu code hier. Und auch als Dankeschön an euch, die mir geholfen haben, das festzunageln, da ich es für eine brauchte ipv6/ip2country-Skript.

Es ist leicht inspiriert per Code hier gepostet von @mikemacintosh und @Sander Stefanleicht verbessert (Wunschdenken) und gibt ein schönes Objekt zurück, das alle Daten enthält, die Sie benötigen / nicht benötigen:

/**
* This:
* <code>
* Ipv6_Prefix2Range('2001:43f8:10::/48');
* </code>
* returns this:
* <code>
* object(stdClass)#2 (4) {
*   ["Prefix"]=>
*   string(17) "2001:43f8:10::/48"
*   ["FirstHex"]=>
*   string(32) "200143f8001000000000000000000000"
*   ["LastHex"]=>
*   string(32) "200143f80010ffffffffffffffffffff"
*   ["MaskHex"]=>
*   string(32) "ffffffffffff00000000000000000000"
*   // Optional bin equivalents available
* }
* </code>
* 
* Tested against:
* @link https://www.ultratools.com/tools/ipv6CIDRToRange
* 
* @param string $a_Prefix
* @param bool $a_WantBins
* @return object
*/
function Ipv6_Prefix2Range($a_Prefix, $a_WantBins = false){
    // Validate input superficially with a RegExp and split accordingly
    if(!preg_match('~^([0-9a-f:]+)[[:punct:]]([0-9]+)$~i', trim($a_Prefix), $v_Slices)){
        return false;
    }
    // Make sure we have a valid ipv6 address
    if(!filter_var($v_FirstAddress = $v_Slices[1], FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)){
        return false;
    }
    // The /## end of the range
    $v_PrefixLength = intval($v_Slices[2]);
    if($v_PrefixLength > 128){
        return false; // kind'a stupid :)
    }
    $v_SuffixLength = 128 - $v_PrefixLength;

    // Convert the binary string to a hexadecimal string
    $v_FirstAddressBin = inet_pton($v_FirstAddress);
    $v_FirstAddressHex = bin2hex($v_FirstAddressBin);

    // Build the hexadecimal string of the network mask
    // (if the manually formed binary is too large, base_convert() chokes on it... so we split it up)
    $v_NetworkMaskHex = str_repeat('1', $v_PrefixLength) . str_repeat('0', $v_SuffixLength);
    $v_NetworkMaskHex_parts = str_split($v_NetworkMaskHex, 8);
    foreach($v_NetworkMaskHex_parts as &$v_NetworkMaskHex_part){
        $v_NetworkMaskHex_part = base_convert($v_NetworkMaskHex_part, 2, 16);
        $v_NetworkMaskHex_part = str_pad($v_NetworkMaskHex_part, 2, '0', STR_PAD_LEFT);
    }
    $v_NetworkMaskHex = implode(null, $v_NetworkMaskHex_parts);
    unset($v_NetworkMaskHex_part, $v_NetworkMaskHex_parts);
    $v_NetworkMaskBin = inet_pton(implode(':', str_split($v_NetworkMaskHex, 4)));

    // We have the network mask so we also apply it to First Address
    $v_FirstAddressBin &= $v_NetworkMaskBin;
    $v_FirstAddressHex = bin2hex($v_FirstAddressBin);

    // Convert the last address in hexadecimal
    $v_LastAddressBin = $v_FirstAddressBin | ~$v_NetworkMaskBin;
    $v_LastAddressHex =  bin2hex($v_LastAddressBin);

    // Return a neat object with information
    $v_Return = array(
        'Prefix'    => "{$v_FirstAddress}/{$v_PrefixLength}",
        'FirstHex'  => $v_FirstAddressHex,
        'LastHex'   => $v_LastAddressHex,
        'MaskHex'   => $v_NetworkMaskHex,
    );
    // Bins are optional...
    if($a_WantBins){
        $v_Return = array_merge($v_Return, array(
            'FirstBin'  => $v_FirstAddressBin,
            'LastBin'   => $v_LastAddressBin,
            'MaskBin'   => $v_NetworkMaskBin,
        ));
    }
    return (object)$v_Return;
}

Ich mag Funktionen und Klassen und mag keinen nicht wiederverwendbaren Code, in dem wiederverwendbare Funktionen implementiert sind.

PS: Wenn Sie Probleme damit finden, wenden Sie sich bitte an mich. Ich bin weit davon entfernt, ein IPv6-Experte zu sein.

1011960cookie-checkEinen IPv6-Bereich aus einem CIDR-Präfix berechnen?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy