-
Notifications
You must be signed in to change notification settings - Fork 52
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(utils): add util functions for IP address validation MAASENG-2980 (
- Loading branch information
Showing
2 changed files
with
127 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
import { getIpRangeFromCidr, isIpInSubnet } from "./subnetIpRange"; | ||
|
||
describe("getIpRangeFromCidr", () => { | ||
it("returns the start and end IP of a subnet", () => { | ||
expect(getIpRangeFromCidr("10.0.0.0/26")).toEqual([ | ||
"10.0.0.1", | ||
"10.0.0.62", | ||
]); | ||
|
||
expect(getIpRangeFromCidr("10.0.0.0/25")).toEqual([ | ||
"10.0.0.1", | ||
"10.0.0.126", | ||
]); | ||
|
||
expect(getIpRangeFromCidr("10.0.0.0/24")).toEqual([ | ||
"10.0.0.1", | ||
"10.0.0.254", | ||
]); | ||
|
||
expect(getIpRangeFromCidr("10.0.0.0/23")).toEqual([ | ||
"10.0.0.1", | ||
"10.0.1.254", | ||
]); | ||
|
||
expect(getIpRangeFromCidr("10.0.0.0/22")).toEqual([ | ||
"10.0.0.1", | ||
"10.0.3.254", | ||
]); | ||
}); | ||
}); | ||
|
||
describe("isIpInSubnet", () => { | ||
it("returns true if an IP is in a subnet", () => { | ||
expect(isIpInSubnet("10.0.0.1", "10.0.0.0/24")).toBe(true); | ||
expect(isIpInSubnet("10.0.0.254", "10.0.0.0/24")).toBe(true); | ||
expect(isIpInSubnet("192.168.0.1", "192.168.0.0/24")).toBe(true); | ||
expect(isIpInSubnet("192.168.0.254", "192.168.0.0/24")).toBe(true); | ||
expect(isIpInSubnet("192.168.1.1", "192.168.0.0/23")).toBe(true); | ||
}); | ||
|
||
it("returns false if an IP is not in a subnet", () => { | ||
expect(isIpInSubnet("10.0.1.0", "10.0.0.0/24")).toBe(false); | ||
expect(isIpInSubnet("10.1.0.0", "10.0.0.0/24")).toBe(false); | ||
expect(isIpInSubnet("11.0.0.0", "10.0.0.0/24")).toBe(false); | ||
expect(isIpInSubnet("192.168.1.255", "192.168.0.0/23")).toBe(false); | ||
expect(isIpInSubnet("10.0.0.1", "192.168.0.0/24")).toBe(false); | ||
expect(isIpInSubnet("192.168.2.1", "192.168.0.0/24")).toBe(false); | ||
expect(isIpInSubnet("172.16.0.1", "192.168.0.0/24")).toBe(false); | ||
}); | ||
|
||
it("returns false for the network and broadcast addresses", () => { | ||
expect(isIpInSubnet("10.0.0.0", "10.0.0.0/24")).toBe(false); | ||
expect(isIpInSubnet("10.0.0.255", "10.0.0.0/24")).toBe(false); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
import type { Subnet } from "../store/subnet/types"; | ||
|
||
/** | ||
* Takes a subnet CIDR notation (IPv4) and returns the first and last IP of the subnet. | ||
* The network and host addresses are excluded. | ||
* | ||
* @param cidr The CIDR notation of the subnet | ||
* @returns The first and last valid IP addresses as two strings in a list. | ||
*/ | ||
export const getIpRangeFromCidr = (cidr: Subnet["cidr"]) => { | ||
// https://gist.github.com/binarymax/6114792 | ||
|
||
// Get start IP and number of valid addresses | ||
const [startIp, mask] = cidr.split("/"); | ||
const numberOfAddresses = (1 << (32 - parseInt(mask))) - 1; | ||
|
||
// IPv4 can be represented by an unsigned 32-bit integer, so we can use a Uint32Array to store the IP | ||
const buffer = new ArrayBuffer(4); //4 octets | ||
const int32 = new Uint32Array(buffer); | ||
|
||
// Convert starting IP to Uint32 and add the number of addresses to get the end IP. | ||
// Subtract 1 from the number of addresses to exclude the broadcast address. | ||
int32[0] = convertIpToUint32(startIp) + numberOfAddresses - 1; | ||
|
||
// Convert the buffer to a Uint8Array to get the octets, then convert it to an array | ||
const arrayApplyBuffer = Array.from(new Uint8Array(buffer)); | ||
|
||
// Reverse the octets and join them with "." to get the end IP | ||
const endIp = arrayApplyBuffer.reverse().join("."); | ||
|
||
const firstValidIp = getFirstValidIp(startIp); | ||
|
||
return [firstValidIp, endIp]; | ||
}; | ||
|
||
const getFirstValidIp = (ip: string) => { | ||
const buffer = new ArrayBuffer(4); //4 octets | ||
const int32 = new Uint32Array(buffer); | ||
|
||
// add 1 because the first IP is the network address | ||
int32[0] = convertIpToUint32(ip) + 1; | ||
|
||
const arrayApplyBuffer = Array.from(new Uint8Array(buffer)); | ||
|
||
return arrayApplyBuffer.reverse().join("."); | ||
}; | ||
|
||
const convertIpToUint32 = (ip: string) => { | ||
const octets = ip.split(".").map((a) => parseInt(a)); | ||
const buffer = new ArrayBuffer(4); | ||
const int32 = new Uint32Array(buffer); | ||
int32[0] = | ||
(octets[0] << 24) + (octets[1] << 16) + (octets[2] << 8) + octets[3]; | ||
return int32[0]; | ||
}; | ||
|
||
/** | ||
* Checks if an IPv4 address is valid for the given subnet. | ||
* | ||
* @param ip The IPv4 address to check, as a string | ||
* @param cidr The subnet's CIDR notation e.g. 192.168.0.0/24 | ||
* @returns True if the IP is in the subnet, false otherwise | ||
*/ | ||
export const isIpInSubnet = (ip: string, cidr: Subnet["cidr"]) => { | ||
const [startIP, endIP] = getIpRangeFromCidr(cidr); | ||
|
||
const ipUint32 = convertIpToUint32(ip); | ||
const startIPUint32 = convertIpToUint32(startIP); | ||
const endIPUint32 = convertIpToUint32(endIP); | ||
|
||
return ipUint32 >= startIPUint32 && ipUint32 <= endIPUint32; | ||
}; |