IPv6 Mask / Prefix Conversion
Functions to convert and validate IPv6 Mask
In our SDWAN solution, I have just started to implement IPv6 Support for the upcoming release. For all IP validations I'm using ip-address.js NPM library, however it doesn't have all the functionalities.
So I had to extend it and build our own NPM library so that it could be consumed by our products.
One of the main functionality which was missing was prefix - mask conversion and validation. As usual, I googled it, Unfortunately I didn't find any solution. So I had to implement it on my own and i thought it could be useful for the community.
Prefix to Mask Conversion
Input will be a number between 0 and 128 and the function should return the corresponding IPv6 Mask.
Explanation:
- Create a 128 bit binary string
- Fill the first n (prefix) bits with 1s and the remaining with 0s.
- Chunk them with a size of 16 bits each
- Convert each chuck to a hex value and join them with (:)
For Example:
118 => 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:fc00'
const HEX = 16;
const BINARY = 2;
const MAX_PREFIX = 128;
const MIN_PREFIX = 0;
static isValidPrefix(prefix: number): boolean {
return Number.isInteger(prefix) && prefix >= MIN_PREFIX && prefix <= MAX_PREFIX;
}
/**
*
* @param prefix
* @returns ipv6 netmask address
*
* Fill an array with 1s for given number of prefix bits
* Fill the remaining bits with 0s
* chunk it with 16 elements and covert each chunk to hex
*
*/
static getNetmaskForPrefix(prefix: number): string {
if (!IPV6.isValidPrefix(prefix)) {
throw new TypeError('Invalid IPv6 Prefix');
}
const prefixArr: number[] = new Array(prefix).fill(1);
const chunkArr = Array.from({
length: Math.ceil(prefixArr.length / HEX),
}, (_v, i) => prefixArr.slice(i * HEX, i * HEX + HEX));
// Converting from binary to hex
let subnet = chunkArr.map((item) => {
return parseInt(item.join('').padEnd(HEX, '0'), BINARY).toString(HEX);
}).join(':');
if (subnet.length < 35) {
subnet = `${subnet}::`;
}
return new Address6(subnet).canonicalForm();
}
Mask to Prefix Conversion
This is the opposite of the previous function. With a given mask it should return the corresponding prefix.
Ex: expect(IPV6.getPrefixForNetmask('ffff:ffff:ffff:ffff:ffff:fffc:0000:0000')).toBe(94);
Explanation:
- Convert the mask to binary using
ip-address.js
function. - Count the number of 1's and return the count as result.
/**
*
* @param {string} netmask
* @returns {number} prefix
*
* Returns exception if its an invalid mask
*/
static getPrefixForNetmask(netmask: string): number {
if (IPV6.isValidMask(netmask)) {
const bits = new Address6(netmask).getBitsBase2(0, 128); // Note : Address6 from ip-address.js
const masked = bits.match(/1/g);
return masked ? masked.length : 0;
}
throw new TypeError('Invalid IPv6 Mask');
}
Valid Mask
But before attempting to get the prefix out of the mask, we also need to check whether it is a valid mask, else it might return the wrong results.
Ideally, a valid mask can have a maximum of one flip bit in it and starts with 1 with trailing 0s or starts with 0 and no trailing 1s.
Example: 1100 => valid // starts with 1 with trailing 0s 0000 => valid // with 0 and no trailing 1s 1010 => Invalid 0001 => Invalid
Explanation:
- Convert the mask to binary string
- Iterate through each bit to count the flips.
- If the count is more than 1, then it's not a valid mask.
static isValidMask(mask: string): boolean {
try {
const bits = new Address6(mask).getBitsBase2(0, 128);
let flips = 0;
let oldBit = '1';
for (let i = 0; i < bits.length; i++) {
if (oldBit !== bits.charAt(i)) {
flips += 1;
oldBit = bits.charAt(i);
}
if (flips > 1) {
return false;
}
}
return flips <= 1;
} catch (e) {
return false;
}
}
Bonus : Random IPv6 Address Generator ( used for testing)
Sometimes I needed a random IPv6 address for testing.
Explanation:
- Create 7 * 4 chars chunks of individual sets with
Math.random()
and valid characters from IPv6 char set [0-9][a-f]. - Append them with the given prefix and join it with the colon(:).
/**
*
* @param ipPrefix defaults to 2000:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx
* @returns a random IPv6 address with the given prefix
*/
static generateRandomIPv6Address(ipPrefix = '2000'): string {
const items = '0123456789abcdef'.split('');
const randomised = [];
for (let i = 0; i < 28; i++) {
randomised[i] = items[Math.floor(Math.random() * items.length)];
}
const ipBlock: string[][] = [];
for (let i = 0; i < randomised.length; i++) {
const blockIndex = Math.floor(i / 4);
if (!Array.isArray(ipBlock[blockIndex])) {
ipBlock.push([]);
}
if (!(ipBlock[blockIndex].length === 0) || randomised[i] !== '0') {
ipBlock[blockIndex].push(randomised[i]);
}
}
const ip: string[][] = [];
for (let i = 0; i < ipBlock.length; i++) {
const ipIndex = Math.floor(i / 8);
if (!Array.isArray(ip[ipIndex])) {
ip.push([]);
}
ip[ipIndex].push(ipBlock[i].join(''));
}
let suffixIp = '';
for (let i = 0; i < ip.length; i++) {
suffixIp += ip[i].join(':');
}
return `${ipPrefix}:${suffixIp}`;
}
Did you find this article valuable?
Support Sedhu by becoming a sponsor. Any amount is appreciated!