Reputation: 1271
I want to ask about calculation of ipv6 network and host side.
For example, I have the IPv6 address 2001:470:1f15:1bcd:34::41
and prefix 96
.
Do you know a easy way to do bitwise and
between IPv6 address and prefix?
According to IPv4:
192.168.1.2 255.255.255.0 network : 192.168.1.0
So simple.
I want to do the same thing to IPv6 address. But IPv6 address is 16 bytes, so you can't use unsigned int
for that.
Is there any API to do this? Or should I use arrays?
Upvotes: 8
Views: 11811
Reputation: 1271
this is quick and dirty solution, the source code is below if you want, use it. Warning ! the function assumes the IPv6 address is valid.
typedef uint16_t ip6_addr[8];
void ipv6_apply_mask(const char *ip6addr, unsigned int mask, ip6_addr ip6){
ip6_addr in_ip6;
inet_pton(PF_INET6, ip6addr, ip6);
for(int i = 0; i < 8; i++){
in_ip6[i] = ntohs(ip6[i]);
}
int index = (int) (mask / 16);
int residue = mask % 16;
if(residue == 0 && index == 8)
return;
switch(residue){
case 0:in_ip6[index++] = 0; break;
case 1:in_ip6[index++]&=0x8000; break;
case 2:in_ip6[index++]&=0xc000; break;
case 3:in_ip6[index++]&=0xe000; break;
case 4:in_ip6[index++]&=0xf000; break;
case 5:in_ip6[index++]&=0xf800; break;
case 6:in_ip6[index++]&=0xfc00; break;
case 7:in_ip6[index++]&=0xfe00; break;
case 8:in_ip6[index++]&=0xff00; break;
case 9:in_ip6[index++]&=0xff80; break;
case 10:in_ip6[index++]&=0xffc0; break;
case 11:in_ip6[index++]&=0xffe0; break;
case 12:in_ip6[index++]&=0xfff0; break;
case 13:in_ip6[index++]&=0xfff8; break;
case 14:in_ip6[index++]&=0xfffc; break;
case 15:in_ip6[index++]&=0xfffe; break;
}
for (int i = index; i < 8; i++){
in_ip6[i] = 0;
}
for(int i = 0; i < 8; i++){
ip6[i] = htons(in_ip6[i]);
}
return;
}
Upvotes: 0
Reputation: 1538
See the functions in6_addr_mask
and in6_addr_start
from in6calc.c
.
Example usage:
struct in6_addr addr, mask, start;
char addr_buffer[INET6_ADDRSTRLEN] = "2001:470:1f15:1bcd:34::41";
uint8_t bits = 96;
inet_pton(AF_INET6, addr_buffer, &addr);
in6_addr_mask(&mask, bits); // Mask
in6_addr_start(&start, &addr, &mask); // Bitwise &
inet_ntop(AF_INET6, &start, addr_buffer, INET6_ADDRSTRLEN);
printf("Network: %s\n", addr_buffer);
Yields:
Network: 2001:470:1f15:1bcd:34::
Upvotes: 0
Reputation: 6932
I searched for this function but the above seem to complex so I wrote my own;
// Clear all bits above the prefix
void mask_ipv6(struct in6_addr* a, unsigned prefix)
{
assert(prefix <= 128);
unsigned bits_to_clear = 128 - prefix;
unsigned i = 3;
while (bits_to_clear >= 32) {
a->s6_addr32[i] = 0;
bits_to_clear -= 32;
i--;
}
if (bits_to_clear == 0)
return;
uint32_t mask = htonl(~((1 << bits_to_clear) - 1));
a->s6_addr32[i] &= mask;
}
You may of course use a function name more of your liking.
Upvotes: 0
Reputation: 12866
Calculate mask from prefix length:
struct sockaddr_in6 netmask;
for (long i = prefixLength, j = 0; i > 0; i -= 8, ++j)
netmask.sin6_addr.s6_addr[ j ] = i >= 8 ? 0xff
: (ULONG)(( 0xffU << ( 8 - i ) ) & 0xffU );
Apply netmask to address, I derived this from inet_lnaof
.
bool
inet6_lnaof (
struct in6_addr* restrict dst,
const struct in6_addr* restrict src,
const struct in6_addr* restrict netmask
)
{
bool has_lna = FALSE;
assert (NULL != dst);
assert (NULL != src);
assert (NULL != netmask);
for (unsigned i = 0; i < 16; i++) {
dst->s6_addr[i] = src->s6_addr[i] & netmask->s6_addr[i];
has_lna |= (0 != (src->s6_addr[i] & !netmask->s6_addr[i]));
}
return has_lna;
}
Upvotes: 2
Reputation: 71
OK, I did this in C rather than C++, but it should work. Also, it uses bswap_64
which is AFAIK a GNU extension so may not work on everything.
It seems to be very quick on amd64, and faster than the current solution Yasar has come up with:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <arpa/inet.h>
#if defined __GNUC__ && __GNUC__ >= 2
#include <byteswap.h>
#else
#error "Sorry, you need GNU for this"
#endif
struct split
{
uint64_t start;
uint64_t end;
};
void ipv6_prefix (unsigned char *masked, unsigned char *packed, int prefix)
{
struct split parts;
uint64_t mask = 0;
unsigned char *p = masked;
memset(masked, 0, sizeof(struct in6_addr));
memcpy(&parts, packed, sizeof(parts));
if (prefix <= 64)
{
mask = bswap_64(bswap_64(parts.start) & ((uint64_t) (~0) << (64 - prefix)));
memcpy(masked, &mask, sizeof(uint64_t));
return;
}
prefix -= 64;
memcpy(masked, &(parts.start), sizeof(uint64_t));
p += sizeof(uint64_t);
mask = bswap_64(bswap_64(parts.end) & (uint64_t) (~0) << (64 - prefix));
memcpy(p, &mask, sizeof(uint64_t));
}
int main (int argc, char **argv)
{
unsigned char packed[sizeof(struct in6_addr)];
unsigned char masked[sizeof(struct in6_addr)];
char buf[INET6_ADDRSTRLEN], *p;
int prefix = 56;
if (argc < 2)
return 1;
if ((p = strchr(argv[1], '/')))
{
*p++ = '\0';
prefix = atoi(p);
}
inet_pton(AF_INET6, argv[1], packed);
ipv6_prefix(masked, packed, prefix);
inet_ntop(AF_INET6, masked, buf, INET6_ADDRSTRLEN);
printf("prefix = %s/%d\n", buf, prefix);
return 0;
}
Upvotes: 2
Reputation: 9978
You can convert the address to binary in network byte order with inet_pton. Then set/clear the bits one byte at a time.
Upvotes: 1
Reputation: 1145
Threat the IP lik a 16 bytes array, skip the masked/8
bytes in the next byte mask the higher masked%8
bits, set the other ones to 0
int offset=masked/8;
char remmask=0;
int rem = masked%8;
while(rem)
{
rem--;
remmask|= 0x80>>rem; //0x80 is the highest bit in a byte set
}
offset++;
(((char*)ipv6)+offset) &= remmask;
while(offset<16)
{
(((char*)ipv6)+offset=0;
offset++;
}
Wrote the code right here, so it hasn't been tested but I am thinking you could use something like this
Upvotes: 0