Skip to content

Commit

Permalink
Merge pull request #1 from demskie/summarize-networks
Browse files Browse the repository at this point in the history
Summarize networks
  • Loading branch information
demskie committed May 20, 2019
2 parents bcca791 + baa1675 commit 2e8aef5
Show file tree
Hide file tree
Showing 3 changed files with 225 additions and 2 deletions.
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];
}

0 comments on commit 2e8aef5

Please sign in to comment.