Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Summarize networks #1

Merged
merged 7 commits into from
May 20, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,3 +125,13 @@ test("sanity check rangeOfNetworks IPv6", () => {
"2001:440:ffff:ffff::/65"
]);
});

test("sanity check sort", () => {
const output = index.sort(["192.168.2.3/31", "255.255.255.255", "192.168.0.0/16"], true);
expect(output).toEqual(["192.168.0.0/16", "192.168.2.3/31", "255.255.255.255/32"]);
});

test("sanity check summarize", () => {
const output = index.summarize(["192.168.0.0/16", "192.168.1.1", "192.168.2.3/31"], true);
expect(output).toEqual(["192.168.0.0/16"]);
});
106 changes: 105 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,108 @@ export function rangeOfNetworks(startAddress: string, stopAddress: string, throw
return results;
}

/**
* Sort returns an array of sorted networks
*
* @example
* netparser.sort(["255.255.255.255", "192.168.2.3/31", "192.168.0.0/16"]) // returns ["192.168.0.0/16", "192.168.2.3/31", "255.255.255.255/32"]
*
* @param networkAddresses - An array of addresses or subnets
* @param throwErrors - Stop the library from failing silently
*
* @returns An array of networks or null in case of error
*/
export function sort(networkAddresses: string[], throwErrors?: boolean) {
let subnets = new Array(networkAddresses.length) as shared.Network[];
let foundCIDR = false;
for (let i = 0; i < networkAddresses.length; i++) {
const netString = networkAddresses[i];
const addr = shared.parseAddressString(netString, throwErrors);
if (!addr) return null;
let cidr = shared.getCIDR(netString);
if (!cidr) {
if (addr.length == 4) {
cidr = 32;
} else {
cidr = 128;
}
} else {
foundCIDR = true;
}
subnets[i] = { bytes: addr, cidr: cidr };
}
subnets = shared.sortNetworks(subnets);
const results = new Array(subnets.length) as string[];
for (let i = 0; i < subnets.length; i++) {
let s = shared.bytesToAddr(subnets[i].bytes, throwErrors);
if (!s) return null;
results[i] = foundCIDR ? `${s}/${subnets[i].cidr}` : `${s}`;
}
return results;
}

/**
* Summarize returns an array of aggregates given a list of networks
*
* @example
* netparser.summarize(["192.168.1.1", "192.168.0.0/16", "192.168.2.3/31"]) // returns ["192.168.0.0/16"]
*
* @param networks - An array of addresses or subnets
* @param strict - Do not automatically mask addresses to baseAddresses
* @param throwErrors - Stop the library from failing silently
*
* @returns An array of networks or null in case of error
*/
export function summarize(networks: string[], strict?: boolean, throwErrors?: boolean) {
let subnets = [] as shared.Network[];
for (let i = 0; i < networks.length; i++) {
const netString = networks[i];
let net = shared.parseNetworkString(netString, strict, false);
if (!net) {
const addr = shared.parseAddressString(netString, throwErrors);
if (!addr) return null;
if (addr.length == 4) {
net = { bytes: addr, cidr: 32 };
} else {
net = { bytes: addr, cidr: 128 };
}
if (subnets.length > 0 && subnets[0].bytes.length !== net.bytes.length) {
if (throwErrors) throw errors.MixingIPv4AndIPv6;
return null;
}
}
subnets[i] = net;
}
subnets = shared.sortNetworks(subnets);
const aggregates = [] as shared.Network[];
for (let idx = 0; idx < subnets.length; idx++) {
aggregates.push(subnets[idx]);
let skipped = 0;
for (let i = idx + 1; i < subnets.length; i++) {
if (shared.networkContainsSubnet(subnets[idx], subnets[i])) {
skipped++;
continue;
}
if (subnets[idx].cidr === subnets[i].cidr) {
if (shared.networksAreAdjacent(subnets[idx], subnets[i])) {
subnets[idx].cidr--;
skipped++;
continue;
}
}
break;
}
idx += skipped;
}
const results = new Array(aggregates.length) as string[];
for (let i = 0; i < aggregates.length; i++) {
let s = shared.bytesToAddr(aggregates[i].bytes, throwErrors);
if (!s) return null;
results[i] = `${s}/${aggregates[i].cidr}`;
}
return results;
}

module.exports = {
baseAddress,
broadcastAddress,
Expand All @@ -339,7 +441,9 @@ module.exports = {
networksIntersect,
nextAddress,
nextNetwork,
rangeOfNetworks
rangeOfNetworks,
sort,
summarize
};

// The following functions are pending an implementation:
Expand Down
111 changes: 110 additions & 1 deletion src/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,7 @@ export function networkContainsSubnet(net: Network, subnet: Network, throwErrors
const netBytesEnd = duplicateAddress(net.bytes);
if (!increaseAddressWithCIDR(netBytesEnd, net.cidr, throwErrors)) return false;
const subnetBytesEnd = duplicateAddress(subnet.bytes);
if (!increaseAddressWithCIDR(subnet.bytes, subnet.cidr, throwErrors)) return false;
if (!increaseAddressWithCIDR(subnetBytesEnd, subnet.cidr, throwErrors)) return false;
if (compareAddresses(netBytesEnd, subnetBytesEnd) < 0) return false;
return true;
}
Expand Down Expand Up @@ -266,6 +266,14 @@ export function networksIntersect(net: Network, otherNet: Network, throwErrors?:
return true;
}

export function networksAreAdjacent(net: Network, otherNet: Network, throwErrors?: boolean) {
if (net.bytes.length !== otherNet.bytes.length) return false;
const netBytes = duplicateAddress(net.bytes);
if (!increaseAddressWithCIDR(netBytes, net.cidr, throwErrors)) return false;
if (compareAddresses(netBytes, otherNet.bytes) === 0) return true;
return false;
}

export function findNetworkIntersection(network: Network, otherNetworks: Network[]) {
for (var otherNet of otherNetworks) {
if (networksIntersect(network, otherNet)) {
Expand Down Expand Up @@ -297,3 +305,104 @@ export function findNetworkWithoutIntersection(network: Network, otherNetworks:
}
return null;
}

enum IPVersion {
v4,
v6
}

function specificNetworks(networks: Network[], version: IPVersion) {
const results = [] as Network[];
if (version === IPVersion.v4) {
for (let network of networks) {
if (network.bytes.length === 4) {
results.push(network);
}
}
} else if (version === IPVersion.v6) {
for (let network of networks) {
if (network.bytes.length === 16) {
results.push(network);
}
}
}
return results;
}

function radixSortNetworks(networks: Network[], version: IPVersion) {
if (networks.length > 0 || version === IPVersion.v4 || version === IPVersion.v6) {
const counts = new Array(256) as number[];
const offsetPrefixSum = new Array(256) as number[];
const byteLength = version === IPVersion.v4 ? 4 : 16;
const maxCIDR = version === IPVersion.v4 ? 32 : 128;

// in place swap and sort for every byte (including CIDR)
for (let byteIndex = 0; byteIndex <= byteLength; byteIndex++) {
for (let i = 0; i < counts.length; i++) {
counts[i] = 0;
}

// count each occurance of byte value
for (let net of networks) {
if (byteIndex < byteLength) {
net.bytes[byteIndex] = Math.min(Math.max(0, net.bytes[byteIndex]), 255);
counts[net.bytes[byteIndex]]++;
} else {
net.cidr = Math.min(Math.max(0, net.cidr), maxCIDR);
counts[net.cidr]++;
}
}

// initialize runningPrefixSum
let total = 0;
let oldCount = 0;
const runningPrefixSum = counts;
for (let i = 0; i < 256; i++) {
oldCount = counts[i];
runningPrefixSum[i] = total;
total += oldCount;
}

// initialize offsetPrefixSum (american flag sort)
for (let i = 0; i < 256; i++) {
if (i < 255) {
offsetPrefixSum[i] = runningPrefixSum[i + 1];
} else {
offsetPrefixSum[i] = runningPrefixSum[i];
}
}

// in place swap and sort by value
let idx = 0;
let value = 0;
while (idx < networks.length) {
if (byteIndex < byteLength) {
value = networks[idx].bytes[byteIndex];
} else {
value = networks[idx].cidr;
}
if (runningPrefixSum[value] !== idx) {
if (runningPrefixSum[value] < offsetPrefixSum[value]) {
let x = networks[runningPrefixSum[value]];
networks[runningPrefixSum[value]] = networks[idx];
networks[idx] = x;
} else {
idx++;
}
} else {
idx++;
}
runningPrefixSum[value]++;
}
}
}
return networks;
}

export function sortNetworks(networks: Network[]) {
const v4 = specificNetworks(networks, IPVersion.v4);
const v6 = specificNetworks(networks, IPVersion.v6);
radixSortNetworks(v4, IPVersion.v4);
radixSortNetworks(v6, IPVersion.v6);
return [...v4, ...v6];
}