[英]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.