繁体   English   中英

如何使用 C 或 C++ 查找给定的 IPv6 地址是否在 CIDR 范围内?

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

如果我有一个 ipv6 地址 2001:4860:4860:0000:0000:0000:012D:8888 并且想找出它是否落在给定的 CIDR 范围 2001:4860:4860:0000:0000:0000:088032 . 我如何在 C 或 C++ 中做到这一点?

我尝试过与我们为 ipv4 所做的类似的尝试。 (ip & netmask) == (range & 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("matched\n");
}
else
{
    printf("no match\n");
}

这对我没有预期的效果。 上面的ipv6属于这个范围。 但程序说“不匹配”。

问题是 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("matched\n");
    }
    else{
        printf("no match\n");
    }

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

这不是最漂亮的解决方案,但它有效。 希望我能帮上忙!

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 address\n";
        return 1;
    }
    address_v6_range range;
    if ( !parseRange( "2001:4860:4860:0000:0000:0000:0000:8888/32", range ) )
    {
        std::cout << "invalid range\n";
        return 1;
    }
    bool inRange = range.find( address ) != range.end();
    std::cout << "in range: " << inRange << "\n";
    return 0;
}

除非您的系统上有 128 位 unsigned 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; }类的东西struct { uint64_t hi; uint64_t lo; } struct { uint64_t hi; uint64_t lo; } struct { uint64_t hi; uint64_t lo; } ,但你需要考虑字节序,同时访问/修改这些字段。


编辑:仔细想想,我意识到使用 memcmp 进行子网比较是不正确的。 我更正了上面的代码。 我也用std::equal替换了memcmp ,但任何一个都可以。 不过,我没有对其进行广泛的测试,因此如果您决定使用它,您一定要对其进行更多的测试。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM