Warning: file_get_contents(/data/phpspider/zhask/data//catemap/1/php/246.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Php 测试cidr表示法中的一个网络是否与另一个网络重叠_Php_Ipv4_Cidr - Fatal编程技术网

Php 测试cidr表示法中的一个网络是否与另一个网络重叠

Php 测试cidr表示法中的一个网络是否与另一个网络重叠,php,ipv4,cidr,Php,Ipv4,Cidr,我正在寻找一种php算法,它可以有效地测试一个cidr标记的网络是否与另一个重叠 我基本上有以下情况: cidr地址数组: $cidrNetworks = array( '192.168.10.0/24', '10.10.0.30/20', etc. ); 我有一个方法可以将网络添加到数组中,但是当添加的网络与数组中的网络allready重叠时,该方法应该引发异常 因此,如果添加了192.168.10.0/25,则应引发异常 有没有人有/知道/能想到一种有效测试这一点的

我正在寻找一种php算法,它可以有效地测试一个cidr标记的网络是否与另一个重叠

我基本上有以下情况:

cidr地址数组:

$cidrNetworks = array(
    '192.168.10.0/24',
    '10.10.0.30/20',
    etc.
);
我有一个方法可以将网络添加到数组中,但是当添加的网络与数组中的网络allready重叠时,该方法应该引发异常

因此,如果添加了192.168.10.0/25,则应引发异常


有没有人有/知道/能想到一种有效测试这一点的方法

凭直觉,我建议你做如下事情:

  • 让新条目为
    X
  • X
    转换为单个整数形式,让该整数为
    Y
  • 让任何条目的掩码长度
    A
    be
    mask(A)
  • 比较
    mask(条目)=mask(Y)
  • 屏蔽
    Mask(entry)>Mask(Y)
    中的现有条目,并与
    Y
  • 为每个现有条目关闭
    Y
    ,其中
    Mask(entry)
    ,以便
    Mask(Y)=Mask(entry)
    并进行比较
  • 只要你没有遇到碰撞,一切都很好

    当然,这不会检查建议的子网是否有效


    我在这里的正确性主张是,我想不出一个反例,但很可能有一个反例,所以我提供这个作为进一步思考的基础-希望这有帮助。

    正如PHP chat中简要讨论的,下面是我将如何实现它,以比较任意两个地址

  • 将IP地址转换为二进制形式
  • 从CIDR格式中提取掩码
  • 取两个面具中最小的一个(最不具体)= (包含更多地址)
  • 在两个二进制表示上使用掩码
  • 比较两者
  • 如果存在匹配项,则一个包含在另一个中

    下面是一些示例代码,它不是很漂亮,您需要对其进行调整以适应您的阵列

    function bin_pad($num)
    {
        return str_pad(decbin($num), 8, '0', STR_PAD_LEFT);
    }
    
    $ip1 = '192.168.0.0/23';
    $ip2 = '192.168.1.0/24';
    
    $regex = '~(\d+)\.(\d+)\.(\d+)\.(\d+)/(\d+)~';
    
    preg_match($regex, $ip1, $ip1);
    preg_match($regex, $ip2, $ip2);
    
    $mask = min($ip1[5], $ip2[5]);
    
    $ip1 = substr(
        bin_pad($ip1[1]) . bin_pad($ip1[2]) .
        bin_pad($ip1[3]) . bin_pad($ip1[4]),
        0, $mask
    );
    
    $ip2 = substr(
        bin_pad($ip2[1]) . bin_pad($ip2[2]) .
        bin_pad($ip2[3]) . bin_pad($ip2[4]),
        0, $mask
    );
    
    var_dump($ip1, $ip2, $ip1 === $ip2);
    
    我很难使它兼容32位,这就是为什么我最终选择将IP地址的每个八位字节分别转换为二进制,然后使用
    substr

    我一开始使用的是
    pack('C4',$ip[1]..$ip[4])
    ,但当涉及到使用完整的32位掩码时,我在将其转换为二进制时遇到了问题(因为PHP整数是有符号的)。但考虑到了未来的实现

    
    
    <?php
        function checkOverlap ($net1, $net2) {
    
            $mask1 = explode("/", $net1)[1];
            $net1 = explode("/", $net1)[0];
            $netArr1 = explode(".",$net1);
    
            $mask2 = explode("/", $net2)[1];
            $net2 = explode("/", $net2)[0];
            $netArr2 = explode(".",$net2);
    
            $newnet1 = $newnet2 = "";
    
            foreach($netArr1 as $num) {
                $binnum = decbin($num);
                $length = strlen($binnum);
                for ($i = 0; $i < 8-$length; $i++) {
                    $binnum = '0'.$binnum;
                }
                $newnet1 .= $binnum;
            }
    
            foreach($netArr2 as $num) {
                $binnum = decbin($num);
                $length = strlen($binnum);
                for ($i = 0; $i < 8-$length; $i++) {
                    $binnum = '0'.$binnum;
                }
                $newnet2 .= $binnum;
            }
    
            $length = min($mask1, $mask2);
    
            $newnet1 = substr($newnet1,0,$length);
            $newnet2 = substr($newnet2,0,$length);
    
            $overlap = 0;
            if ($newnet1 == $newnet2) $overlap = 1;
    
            return $overlap;
        }
    
        function networksOverlap ($networks, $newnet) {
    
            $overlap = false;
    
            foreach ($networks as $network) {
                $overlap = checkOverlap($network, $newnet);
                if ($overlap) return 1;
            }
    
            return $overlap;        
    
        }
    
        $cidrNetworks = array(
            '192.168.10.0/24',
            '10.10.0.30/20'
        );
    
        $newnet = "192.168.10.0/25";
    
        $overlap = networksOverlap($cidrNetworks, $newnet);
    ?>
    

    不确定这是否100%正确,但请尝试一下,看看是否有效。

    这是之前在chat中讨论的类的更新版本。它可以做你需要的,以及许多其他有用的事情

    <?php
    
        class IPv4Subnet implements ArrayAccess, Iterator {
    
            /*
             * Address format constants
             */
            const ADDRESS_BINARY = 0x01;
            const ADDRESS_INT = 0x02;
            const ADDRESS_DOTDEC = 0x04;
            const ADDRESS_SUBNET = 0x08;
    
            /*
             * Constants to control whether getHosts() returns the network/broadcast addresses
             */
            const HOSTS_WITH_NETWORK = 0x10;
            const HOSTS_WITH_BROADCAST = 0x20;
            const HOSTS_ALL = 0x30;
    
            /*
             * Properties to store base address and subnet mask as binary strings
             */
            protected $address;
            protected $mask;
    
            /*
             * Counter to track the current iteration offset
             */
            private $iteratorOffset = 0;
    
            /*
             * Array to hold values retrieved via ArrayAccess
             */
            private $arrayAccessObjects = array();
    
            /*
             * Helper methods
             */
            private function longToBinary ($long) {
                return pack('N', $long);
            }
            private function longToDottedDecimal ($long) {
                return ($long >> 24 & 0xFF).'.'.($long >> 16 & 0xFF).'.'.($long >> 8 & 0xFF).'.'.($long & 0xFF);
            }
            private function longToByteArray ($long) {
                return array(
                    $long >> 24 & 0xFF,
                    $long >> 16 & 0xFF,
                    $long >> 8 & 0xFF,
                    $long & 0xFF
                );
            }
            private function longToSubnet ($long) {
                if (!isset($this->arrayAccessObjects[$long])) {
                    $this->arrayAccessObjects[$long] = new self($long);
                }
                return $this->arrayAccessObjects[$long];
            }
            private function binaryToLong ($binary) {
                return current(unpack('N', $binary));
            }
            private function binaryToDottedDecimal ($binary) {
                return implode('.', unpack('C*', $binary));
            }
            private function binaryToX ($binary, $mode) {
                if ($mode & self::ADDRESS_BINARY) {
                    $result = $binary;
                } else if ($mode & self::ADDRESS_INT) {
                    $result = $this->binaryToLong($binary);
                } else if ($mode & self::ADDRESS_DOTDEC) {
                    $result = $this->binaryToDottedDecimal($binary);
                } else {
                    $result = $this->longToSubnet($this->binaryToLong($binary));
                }
                return $result;
            }
            private function byteArrayToLong($bytes) {
                return ($bytes[0] << 24) | ($bytes[1] << 16) | ($bytes[2] << 8) | $bytes[3];
            }
            private function byteArrayToBinary($bytes) {
                return pack('C*', $bytes[0], $bytes[1], $bytes[2], $bytes[3]);
            }
    
            private function normaliseComparisonSubject (&$subject) {
                if (!is_object($subject)) {
                    $subject = new self($subject);
                }
                if (!($subject instanceof self)) {
                    throw new InvalidArgumentException('Subject must be an instance of IPv4Subnet');
                }
            }
    
            private function validateOctetArray (&$octets) {
                foreach ($octets as &$octet) {
                    $octet = (int) $octet;
                    if ($octet < 0 || $octet > 255) {
                        return FALSE;
                    }
                }
                return TRUE;
            }
    
            /*
             * Constructor
             */
            public function __construct ($address = NULL, $mask = NULL) {
                if ($address === NULL || (is_string($address) && trim($address) === '')) {
                    $address = array(0, 0, 0, 0);
                } else if (is_int($address)) {
                    $address = $this->longToByteArray($address);
                } else if (is_string($address)) {
                    $parts = preg_split('#\s*/\s*#', trim($address), -1, PREG_SPLIT_NO_EMPTY);
                    if (count($parts) > 2) {
                        throw new InvalidArgumentException('No usable IP address supplied: Syntax error');
                    } else if ($parts[0] === '') {
                        throw new InvalidArgumentException('No usable IP address supplied: IP address empty');
                    }
                    if (!empty($parts[1]) && !isset($mask)) {
                        $mask = $parts[1];
                    }
                    $address = preg_split('#\s*\.\s*#', $parts[0], -1, PREG_SPLIT_NO_EMPTY);
                } else if (is_array($address)) {
                    $address = array_values($address);
                } else {
                    throw new InvalidArgumentException('No usable IP address supplied: Value must be a string or an integer');
                }
    
                $suppliedAddressOctets = count($address);
                $address += array(0, 0, 0, 0);
                if ($suppliedAddressOctets > 4) {
                    throw new InvalidArgumentException('No usable IP address supplied: IP address has more than 4 octets');
                } else if (!$this->validateOctetArray($address)) {
                    throw new InvalidArgumentException('No usable IP address supplied: At least one octet value outside acceptable range 0 - 255');
                }
    
                if ($mask === NULL) {
                    $mask = array_pad(array(), $suppliedAddressOctets, 255) + array(0, 0, 0, 0);
                } else if (is_int($mask)) {
                    $mask = $this->longToByteArray($mask);
                } else if (is_string($mask)) {
                    $mask = preg_split('#\s*\.\s*#', trim($mask), -1, PREG_SPLIT_NO_EMPTY);
    
                    switch (count($mask)) {
                        case 1: // CIDR
                            $cidr = (int) $mask[0];
                            if ($cidr === 0) {
                                // Shifting 32 bits on a 32 bit system doesn't work, so treat this as a special case
                                $mask = array(0, 0, 0, 0);
                            } else if ($cidr <= 32) {
                                // This looks odd, but it's the nicest way I have found to get the 32 least significant bits set in a
                                // way that works on both 32 and 64 bit platforms
                                $base = ~((~0 << 16) << 16);
                                $mask = $this->longToByteArray($base << (32 - $cidr));
                            } else {
                                throw new InvalidArgumentException('Supplied mask invalid: CIDR outside acceptable range 0 - 32');
                            }
                            break;
                        case 4: break; // Dotted decimal
                        default: throw new InvalidArgumentException('Supplied mask invalid: Must be either a full dotted-decimal or a CIDR');
                    }
                } else if (is_array($mask)) {
                    $mask = array_values($mask);
                } else {
                    throw new InvalidArgumentException('Supplied mask invalid: Type invalid');
                }
    
                if (!$this->validateOctetArray($mask)) {
                    throw new InvalidArgumentException('Supplied mask invalid: At least one octet value outside acceptable range 0 - 255');
                }
                // Check bits are contiguous from left
                // TODO: Improve this mechanism
                $asciiBits = sprintf('%032b', $this->byteArrayToLong($mask));
                if (strpos(rtrim($asciiBits, '0'), '0') !== FALSE) {
                    throw new InvalidArgumentException('Supplied mask invalid: Set bits are not contiguous from the most significant bit');
                }
    
                $this->mask = $this->byteArrayToBinary($mask);
                $this->address = $this->byteArrayToBinary($address) & $this->mask;
            }
    
            /*
             * ArrayAccess interface methods (read only)
             */
            public function offsetExists ($offset) {
                if ($offset === 'network' || $offset === 'broadcast') {
                    return TRUE;
                }
    
                $offset = filter_var($offset, FILTER_VALIDATE_INT);
                if ($offset === FALSE || $offset < 0) {
                    return FALSE;
                }
    
                return $offset < $this->getHostsCount();
            }
            public function offsetGet ($offset) {
                if (!$this->offsetExists($offset)) {
                    return NULL;
                }
    
                if ($offset === 'network') {
                    $address = $this->getNetworkAddress(self::ADDRESS_INT);
                } else if ($offset === 'broadcast') {
                    $address = $this->getBroadcastAddress(self::ADDRESS_INT);
                } else {
                    // How much the address needs to be adjusted by to account for network address
                    $adjustment = (int) ($this->getHostsCount() > 2);
                    $address = $this->binaryToLong($this->address) + $offset + $adjustment;
                }
    
                return $this->longToSubnet($address);
            }
            public function offsetSet ($offset, $value) {}
            public function offsetUnset ($offset) {}
    
            /*
             * Iterator interface methods
             */
            public function current () {
                return $this->offsetGet($this->iteratorOffset);
            }
            public function key () {
                return $this->iteratorOffset;
            }
            public function next () {
                $this->iteratorOffset++;
            }
            public function rewind () {
                $this->iteratorOffset = 0;
            }
            public function valid () {
                return $this->iteratorOffset < $this->getHostsCount();
            }
    
            /*
             * Data access methods
             */
            public function getHosts ($mode = self::ADDRESS_SUBNET) {
                // Parse flags and initialise vars
                $bin = (bool) ($mode & self::ADDRESS_BINARY);
                $int = (bool) ($mode & self::ADDRESS_INT);
                $dd = (bool) ($mode & self::ADDRESS_DOTDEC);
                $base = $this->binaryToLong($this->address);
                $mask = $this->binaryToLong($this->mask);
                $hasNwBc = !($mask & 0x03);
                $result = array();
    
                // Get network address if requested
                if (($mode & self::HOSTS_WITH_NETWORK) && $hasNwBc) {
                    $result[] = $base;
                }
    
                // Get hosts
                for ($current = $hasNwBc ? $base + 1 : $base; ($current & $mask) === $base; $current++) {
                    $result[] = $current;
                }
    
                // Remove broadcast address if present and not requested
                if ($hasNwBc && !($mode & self::HOSTS_WITH_BROADCAST)) {
                    array_pop($result);
                }
    
                // Convert to the correct type
                if ($bin) {
                    $result = array_map(array($this, 'longToBinary'), $result);
                } else if ($dd) {
                    $result = array_map(array($this, 'longToDottedDecimal'), $result);
                } else if (!$int) {
                    $result = array_map(array($this, 'longToSubnet'), $result);
                }
    
                return $result;
            }
            public function getHostsCount () {
                $count = $this->getBroadcastAddress(self::ADDRESS_INT) - $this->getNetworkAddress(self::ADDRESS_INT);
                return $count > 2 ? $count - 1 : $count + 1; // Adjust return value to exclude network/broadcast addresses
            }
            public function getNetworkAddress ($mode = self::ADDRESS_SUBNET) {
                return $this->binaryToX($this->address, $mode);
            }
            public function getBroadcastAddress ($mode = self::ADDRESS_SUBNET) {
                return $this->binaryToX($this->address | ~$this->mask, $mode);
            }
            public function getMask ($mode = self::ADDRESS_DOTDEC) {
                return $this->binaryToX($this->mask, $mode);
            }
    
            /*
             * Stringify methods
             */
            public function __toString () {
                if ($this->getHostsCount() === 1) {
                    $result = $this->toDottedDecimal();
                } else {
                    $result = $this->toCIDR();
                }
                return $result;
            }
            public function toDottedDecimal () {
                $result = $this->getNetworkAddress(self::ADDRESS_DOTDEC);
                if ($this->mask !== "\xFF\xFF\xFF\xFF") {
                    $result .= '/'.$this->getMask(self::ADDRESS_DOTDEC);
                }
                return $result;
            }
            public function toCIDR () {
                $address = $this->getNetworkAddress(self::ADDRESS_DOTDEC);
                $cidr = strlen(trim(sprintf('%b', $this->getMask(self::ADDRESS_INT)), '0')); // TODO: Improve this mechanism
                return $address.'/'.$cidr;
            }
    
            /*
             * Comparison methods
             */
            public function contains ($subject) {
                $this->normaliseComparisonSubject($subject);
    
                $subjectAddress = $subject->getNetworkAddress(self::ADDRESS_BINARY);
                $subjectMask = $subject->getMask(self::ADDRESS_BINARY);
    
                return $this->mask !== $subjectMask && ($this->mask | ($this->mask ^ $subjectMask)) !== $this->mask && ($subjectAddress & $this->mask) === $this->address;
            }
    
            public function within ($subject) {
                $this->normaliseComparisonSubject($subject);
    
                $subjectAddress = $subject->getNetworkAddress(self::ADDRESS_BINARY);
                $subjectMask = $subject->getMask(self::ADDRESS_BINARY);
    
                return $this->mask !== $subjectMask && ($this->mask | ($this->mask ^ $subjectMask)) === $this->mask && ($this->address & $subjectMask) === $subjectAddress;
            }
            public function equalTo ($subject) {
                $this->normaliseComparisonSubject($subject);
    
                return $this->address === $subject->getNetworkAddress(self::ADDRESS_BINARY) && $this->mask === $subject->getMask(self::ADDRESS_BINARY);
            }
            public function intersect ($subject) {
                $this->normaliseComparisonSubject($subject);
    
                return $this->equalTo($subject) || $this->contains($subject) || $this->within($subject);
            }
    
        }
    
    这些的示例用法:

    // Also accepts dotted decimal mask. The mask may also be passed to the second
    // argument. Any valid combination of dotted decimal, CIDR and integers will be
    // accepted
    $subnet = new IPv4Subnet('192.168.0.0/24');
    
    // These methods will accept a string or another instance
    var_dump($subnet->contains('192.168.0.1')); //TRUE
    var_dump($subnet->contains('192.168.1.1')); //FALSE
    var_dump($subnet->contains('192.168.0.0/16')); //FALSE
    var_dump($subnet->within('192.168.0.0/16')); //TRUE
    // ...hopefully you get the picture. intersect() returns TRUE if any of the
    // other three match.
    
    该类还实现了
    迭代器
    接口,允许您迭代子网中的所有地址。迭代器不包括可以单独检索的网络地址和广播地址

    例如:

    $subnet = new IPv4Subnet('192.168.0.0/28');
    echo "Network: ", $subnet->getNetworkAddress(),
         "; Broadcast: ", $subnet->getBroadcastAddress(),
         "\nHosts:\n";
    foreach ($subnet as $host) {
        echo $host, "\n";
    }
    
    该类还实现了
    ArrayAccess
    ,允许您将其视为一个数组:

    $subnet = new IPv4Subnet('192.168.0.0/28');
    echo $subnet['network'], "\n"; // 192.168.0.0
    echo $subnet[0], "\n"; // 192.168.0.1
    // ...
    echo $subnet[13], "\n"; // 192.168.0.14
    echo $subnet['broadcast'], "\n"; // 192.168.0.15
    
    注意:访问子网主机地址的迭代器/数组方法将返回另一个
    IPv4Subnet
    对象。该类实现了
    \uuuToString()
    ,如果IP地址表示单个地址,它将以虚线小数形式返回,如果它表示多个地址,则返回CIDR。通过调用相关的
    get*()
    方法并传递所需的标志(参见类顶部定义的常量),可以直接以字符串或整数的形式访问数据

    所有操作都是32位和64位安全的。兼容性应符合第5.2节的要求(尽管未进行彻底测试)+


    为了完整性,我设想您的用例将按照以下思路实现:

    public function addSubnet ($newSubnet) {
        $newSubnet = new IPv4Subnet($newSubnet);
        foreach ($this->subnets as &$existingSubnet) {
            if ($existingSubnet->contains($newSubnet)) {
                throw new Exception('Subnet already added');
            } else if ($existingSubnet->within($newSubnet)) {
                $existingSubnet = $newSubnet;
                return;
            }
        }
        $this->subnets[] = $newSubnet;
    }
    

    两件事,您多次执行相同的
    explode
    操作。您可以使用
    list($mask1,$net1)
    只执行一次。查看
    str\u pad
    ,这些填充循环是不必要的。返回最大值的三元比较是不必要的,请使用
    max
    (尽管我确信您实际上想要
    min
    )。除此之外,它本质上做的事情和我的一样。+1用于超越职责的召唤;)喝点咖啡因后,我会尝试理解
    中包含的所有按位魔法。再次感谢Dave。我还需要对它进行测试,但我很确定我需要的魔法就在这里:)如果没有,我就在聊天中给你设置bug:)不过这只是一件小事,你的类不会覆盖ArrayAccess的offsetUnset方法。不过很容易修好。哦,事实上是的。。为什么我的netbeans会让我烦恼:)@DamienOvereem这个类被设计成是不可变的,因为允许地址/掩码被改变会打开一整罐蠕虫。此外,如果允许在子网的中间更改单个项,这将是毫无意义的,因为它将不再是一个完整的子网。如果您的IDE对空方法有问题,请添加
    return对他们的陈述很可能会让他们再次感到高兴。谢谢你的回答。戴夫做得很好,但是你的帮助,一直以来,都是非常感谢的+1.
    
    public function addSubnet ($newSubnet) {
        $newSubnet = new IPv4Subnet($newSubnet);
        foreach ($this->subnets as &$existingSubnet) {
            if ($existingSubnet->contains($newSubnet)) {
                throw new Exception('Subnet already added');
            } else if ($existingSubnet->within($newSubnet)) {
                $existingSubnet = $newSubnet;
                return;
            }
        }
        $this->subnets[] = $newSubnet;
    }