如何使用C或C 查找给定的IPv6地址是否属于CIDR范围

How to find whether a given IPv6 address falls in the CIDR range using C or C++?

本文关键字:是否 地址 属于 CIDR 范围 IPv6 何使用 查找      更新时间:2023-10-16

如果我有IPv6地址2001:4860:4860:0000:0000:0000:0000:0000:012D:8888,想找到它是否属于给定的CIDR范围2001:4860:4860:4860:4860:4860:4860:4860:4860:4860:4860:4860:4860:4860:4860:0000:0000:0000:0000:8888/32。我该如何在C或C ?

中执行此操作

我的尝试与IPv4的尝试类似。(ip& netmask(==(范围& netmask(

unsigned int ipv6 = (ip[0]<<112) + (ip[1]<<96) + (ip[2]<<80) + (ip[3]<<64)+ (ip[4]<<48) + (ip[5]<<32) + (ip[6]<<16) + ip[7];
unsigned int range = (cidr_ip[0]<<112) + (cidr_ip[1]<<96) + (cidr_ip[2]<<80) + (cidr_ip[3]<<64)+ (cidr_ip[4]<<48) + (cidr_ip[5]<<32) + (cidr_ip[6]<<16) + cidr_ip[7];
unsigned int mask = (~0u) << (128-netmask);
if((ipv6 & mask) == (range & mask)){
    printf("matchedn");
}
else
{
    printf("no matchn");
}

这对我预期不起作用。以上IPv6属于该范围。但是该程序说"不匹配"。

BOOST ASIO的IP地址和范围类为您完成大部分工作。不幸的是,他们目前没有提供从字符串中解析IP地址范围的方法。

以下大多数代码是IP地址范围的解析器,该代码可与IP4和IP6一起使用:

#include <iostream>
#include <boost/asio.hpp>
using namespace boost::asio::ip;
template < typename Addr >
bool parseAddress( const std::string& str, Addr& addr )
{
  boost::system::error_code ec;
  addr = Addr::from_string( str, ec );
  return !ec;
}
 address_v4_range getRange( address_v4 address, size_t size )
{
  address_v4 end = address_v4( ( address.to_ulong() + ( 1 << ( 32 - size ) ) ) & 0xFFFFFFFF );
  return address_v4_range( address, end );
}
address_v6_range getRange( address_v6 address, size_t size )
{
  auto bytes = address.to_bytes();
  size_t offset = size >> 3;
  uint8_t toAdd = 1 << ( 8 - ( size & 0x7 ) );
  while ( toAdd )
  {
    int value = bytes[ offset ] + toAdd;
    bytes[ offset ] = value & 0xFF;
    toAdd = value >> 8;
    if ( offset == 0 )
    {
      break;
    }
    offset--;
  }
  address_v6 end = address_v6( bytes );
  return address_v6_range( address, end );
} 
template < typename Addr >
bool parseRange( const std::string& str, basic_address_range< Addr >& range )
{
  size_t pos = str.find( '/' );
  if ( pos == std::string::npos )
  {
    return false;
  }
  // should only be one slash
  if ( str.find( '/', pos + 1 ) != std::string::npos )
  {
    return false;
  }
  boost::system::error_code ec;
  Addr address;
  if ( !parseAddress( str.substr( 0, pos ), address ) )
  {
    return false;
  }
  std::string sizeStr = str.substr( pos + 1 );
  size_t index;
  int size = std::stoi( sizeStr, &index );
  if ( index != sizeStr.size() )
  {
    return false;
  }
  if ( size > std::tuple_size< typename Addr::bytes_type >::value * 8 || size < 0 )
  {
    return false;
  }
  range = getRange( address, size );
  return !ec;
}
int main()
{
    address_v6 address;
    if ( !parseAddress( "2001:4860:4860:0000:0000:0000:012D:8888", address ) )
    {
        std::cout << "invalid addressn";
        return 1;
    }
    address_v6_range range;
    if ( !parseRange( "2001:4860:4860:0000:0000:0000:0000:8888/32", range ) )
    {
        std::cout << "invalid rangen";
        return 1;
    }
    bool inRange = range.find( address ) != range.end();
    std::cout << "in range: " << inRange << "n";
    return 0;
}

问题是unsigned int通常只有32位。有时是更多,有时是少的,但是今天没有128位的主流编译器。IPv6地址需要128位。

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
//Little struct containing a unsigned int array with the size of 4
//4 * 32bit = 128bit (== IPv6)
typedef struct IPv6_address{
    uint32_t ip_parts[4];
} IPv6;
IPv6 and(IPv6 first, IPv6 second);
short match(IPv6 first, IPv6 second);
IPv6 mask_from_prefix(int prefix);
int main(){
    int netmask = 32;
    unsigned int ip[8] = {0x2001, 0x4860, 0x4860, 0, 0, 0, 0x012D, 0x8888};
    unsigned int cidr_ip[8] = {0x2001, 0x4860, 0x4860, 0, 0, 0, 0, 0x8888};
    IPv6 ipv6 = {(ip[0]<<16) + (ip[1]), (ip[2]<<16) + (ip[3]), (ip[4]<<16) + (ip[5]), (ip[6]<<16) + ip[7]};
    IPv6 range = {(cidr_ip[0]<<16) + (cidr_ip[1]), (cidr_ip[2]<<16) + (cidr_ip[3]), (cidr_ip[4]<<16) + (cidr_ip[5]), (cidr_ip[6]<<16) + cidr_ip[7]};
    IPv6 mask = mask_from_prefix(netmask);
    //if((ipv6 & mask) == (range & mask)){
    if(match(and(ipv6, mask), and(range, mask))){
        printf("matchedn");
    }
    else{
        printf("no matchn");
    }
    return EXIT_SUCCESS;
}
//Bitwise-AND operation between two IPv6 addresses (128 bit integer as struct)
IPv6 and(IPv6 first, IPv6 second){
    IPv6 toReturn = {};
    for(int i = 0; i < 4; i++){
        toReturn.ip_parts[i] = first.ip_parts[i] & second.ip_parts[i];
    }
    return toReturn;
}
//Returns 1, if the given IPv6 addresses match
//Otherwise 0
short match(IPv6 first, IPv6 second){
    short matchCount = 0;
    for(int i = 0; i < 4; i++){
        if(first.ip_parts[i] == second.ip_parts[i]){
            matchCount++;
        }
    }
    return matchCount == 4 ? 1 : 0; //If all four parts match return 1, otherwise 0
}
//Returns an IPv6 address representing the net mask of the given prefix
IPv6 mask_from_prefix(int prefix){
    IPv6 mask = {};
    for(int i = 0; i < 4; i++){
        int onesForThisPart = prefix - i*32;
        //Check if 0 (or less), because shifting by 32 would result in an "erroneous" behavior, where nothing would happen at all
        if(onesForThisPart <= 0){
            mask.ip_parts[i] = 0;
        }
        else{
            mask.ip_parts[i] = (~0u) << (32 - onesForThisPart);
        }
    }
    return mask;
}

这不是最美丽的解决方案,但它起作用。希望我能提供帮助!

,除非您的系统上有128位未签名的INT(您不符合(,否则您在那里的范围内。之后,您的数学不再起作用。

改用不错的IP库!

将IPv6地址视为字节数组而不是整数类型,因为它们是128位,并且在网络字节顺序中。

#include <algorithm> // std::equal
#include <cassert>   // assert
struct ipv6addr {
    unsigned char addr[16];
    bool is_subnet(const ipv6addr& prefix, unsigned int len) const {
        assert(len <= sizeof(addr) * 8);
        int cmp_bytes = len / 8;
        if (!std::equal(addr, addr + cmp_bytes, prefix.addr)) return false;
        int cmp_bits = len % 8;
        if (cmp_bits) {
            int bitmask = 0xff << (8 - cmp_bits);
            if ((addr[cmp_bytes] & bitmask) != (prefix.addr[cmp_bytes] & bitmask))
                return false;
        }
        return true;
    }
};
int main() {
    ipv6addr addr {0x20, 0x01, 0x48, 0x60, 0, 0, 0, 0, 0, 0, 0x01, 0x2D, 0x88, 0x88};
    ipv6addr prefix {0x20, 0x01, 0x48, 0x60, 0, 0, 0, 0, 0, 0, 0, 0, 0x88, 0x88};
    unsigned int len = 32;
    assert(addr.is_subnet(prefix, len));
    return 0;
}

如果您真的想将它们存储为整数,则可以使用struct { uint64_t hi; uint64_t lo; }之类的东西,但是您需要在访问/修改这些字段时考虑Endianness。


编辑:开始考虑这一点,我意识到使用memcmp进行子网比较不正确。我纠正了上面的代码。另外,我已经用std::equal替换了memcmp,但是任何一个都可以。我并没有进行广泛的测试,因此,如果您决定使用它,则绝对应该进行更多测试。