cidr-tools
Advanced tools
Comparing version 1.2.2 to 2.0.0
355
index.js
"use strict"; | ||
const execa = require("execa"); | ||
const fs = require("mz/fs"); | ||
const path = require("path"); | ||
const tempfile = require("tempfile"); | ||
const cidrTools = module.exports = {}; | ||
const IPCIDR = require("ip-cidr"); | ||
const isCidr = require("is-cidr"); | ||
const ipv6Normalize = require("ipv6-normalize"); | ||
const naturalCompare = require("string-natural-compare"); | ||
const Address4 = require("ip-address").Address4; | ||
const Address6 = require("ip-address").Address6; | ||
const BigInteger = require("jsbn").BigInteger; | ||
const net = require("net"); | ||
const merge = path.join(__dirname, "cidr-merge.py"); | ||
const exclude = path.join(__dirname, "cidr-exclude.py"); | ||
const expand = path.join(__dirname, "cidr-expand.py"); | ||
const bits = { | ||
"v4": 32, | ||
"v6": 128, | ||
}; | ||
function parseNets(nets) { | ||
if (typeof nets === "string") { | ||
return nets; | ||
} else if (Array.isArray(nets)) { | ||
return nets.join("\n"); | ||
function bigint(numberstring) { | ||
return new BigInteger(numberstring); | ||
} | ||
function parse(str) { | ||
if (isCidr(str)) { | ||
return new IPCIDR(cidrTools.normalize(str)); | ||
} else { | ||
const version = net.isIP(str); | ||
if (version) { | ||
return new IPCIDR(cidrTools.normalize(`${str}/${bits[`v${version}`]}`)); | ||
} else { | ||
throw new Error(`Network is not a CIDR or IP: ${str}`); | ||
} | ||
} | ||
} | ||
module.exports.merge = function(nets) { | ||
return new Promise(function(resolve, reject) { | ||
if (!Array.isArray(nets) && typeof nets !== "string") { | ||
return reject(new Error("Expected an array or string, not " + nets)); | ||
function format(number, v) { | ||
const cls = v === "v6" ? Address6 : Address4; | ||
if (number.constructor.name !== "BigInteger") number = bigint(number); | ||
return cidrTools.normalize(cls.fromBigInteger(number).address); | ||
} | ||
function prefix(size, v) { | ||
return bits[v] - (bigint(String(size)).toString(2).match(/0/g) || []).length; | ||
} | ||
function overlap(a, b) { | ||
const aStart = a.start({type: "bigInteger"}); | ||
const bStart = b.start({type: "bigInteger"}); | ||
const aEnd = a.end({type: "bigInteger"}); | ||
const bEnd = b.end({type: "bigInteger"}); | ||
// aaa | ||
// bbb | ||
if (aStart.compareTo(bEnd) > 0) return false; // a starts after b | ||
// aaa | ||
// bbb | ||
if (bStart.compareTo(aEnd) > 0) return false; // b starts after a | ||
return true; | ||
} | ||
// exclude b from a end return remainder numbers | ||
function exclude(a, b, v) { | ||
const aStart = a.start({type: "bigInteger"}); | ||
const bStart = b.start({type: "bigInteger"}); | ||
const aEnd = a.end({type: "bigInteger"}); | ||
const bEnd = b.end({type: "bigInteger"}); | ||
// compareTo returns negative if left is less than right | ||
const parts = []; | ||
// aaa | ||
// bbb | ||
// aaa | ||
// bbb | ||
if (aEnd.compareTo(bStart) < 0 || aStart.compareTo(bEnd) > 0) { | ||
return [a.cidr]; | ||
} | ||
// aaa | ||
// bbb | ||
if (aStart.compareTo(bStart) === 0 && aEnd.compareTo(bEnd) === 0) { | ||
return []; | ||
} | ||
// aa | ||
// bbbb | ||
if (aStart.compareTo(bStart) > 0 && aEnd.compareTo(bEnd) < 0) { | ||
return []; | ||
} | ||
// aaaa | ||
// bbbb | ||
// aaaa | ||
// bb | ||
if (aStart.compareTo(bStart) < 0 && aEnd.compareTo(bEnd) <= 0) { | ||
parts.push({ | ||
start: aStart, | ||
end: bStart.subtract(bigint("1")), | ||
}); | ||
} | ||
// aaa | ||
// bbb | ||
// aaaa | ||
// bbb | ||
if (aStart.compareTo(bStart) >= 0 && aEnd.compareTo(bEnd) > 0) { | ||
parts.push({ | ||
start: bEnd.add(bigint("1")), | ||
end: aEnd, | ||
}); | ||
} | ||
// aaaa | ||
// bb | ||
if (aStart.compareTo(bStart) < 0 && aEnd.compareTo(bEnd) > 0) { | ||
parts.push({ | ||
start: aStart, | ||
end: bStart.subtract(bigint("1")), | ||
}); | ||
parts.push({ | ||
start: bEnd.add(bigint("1")), | ||
end: aEnd, | ||
}); | ||
} | ||
const remaining = []; | ||
for (const part of parts) { | ||
for (const subpart of subparts(part, v)) { | ||
remaining.push(formatPart(subpart, v)); | ||
} | ||
} | ||
const netfile = tempfile(".net"); | ||
// return remaining; | ||
return cidrTools.merge(remaining); | ||
} | ||
fs.writeFile(netfile, parseNets(nets)).then(function() { | ||
execa.stdout(merge, [netfile]).then(function(stdout) { | ||
resolve(stdout.split("\n").filter(net => Boolean(net))); | ||
fs.unlink(netfile); | ||
}).catch(function(err) { | ||
resolve(err); | ||
fs.unlink(netfile); | ||
}); | ||
}).catch(reject); | ||
}); | ||
function biggestPowerOfTwo(num) { | ||
if (num === 0) return 0; | ||
return Math.pow(2, num.toString(2).length - 1); | ||
} | ||
function subparts(part) { | ||
const size = bigint(String(diff(part.end, part.start))); | ||
const biggest = bigint(String(biggestPowerOfTwo(Number(size.toString())))); | ||
if (size.equals(biggest)) return [part]; | ||
const start = part.start.add(biggest).divide(biggest).multiply(biggest); | ||
const end = start.add(biggest).subtract(bigint("1")); | ||
let parts = [{start, end}]; | ||
// // additional subnets on left side | ||
if (!start.equals(part.start)) { | ||
parts = parts.concat(subparts({start: part.start, end: start.subtract(bigint("1"))})); | ||
} | ||
// additional subnets on right side | ||
if (!end.equals(part.end)) { | ||
parts = parts.concat(subparts({start: part.start, end: start.subtract(bigint("1"))})); | ||
} | ||
return parts; | ||
} | ||
function diff(a, b) { | ||
if (a.constructor.name !== "BigInteger") a = bigint(a); | ||
if (b.constructor.name !== "BigInteger") b = bigint(b); | ||
return Number((a.add(bigint("1"))).subtract(b).toString()); | ||
} | ||
function formatPart(part, v) { | ||
const d = diff(part.end, part.start); | ||
return format(part.start, v) + "/" + prefix(d, v); | ||
} | ||
cidrTools.normalize = (cidr) => { | ||
const cidrVersion = isCidr(cidr); | ||
if (cidrVersion === 4) { | ||
return cidr; | ||
} else if (cidrVersion === 6) { | ||
const [ip, prefix] = cidr.split("/"); | ||
return `${ipv6Normalize(ip)}/${prefix}`; | ||
} | ||
const ipVersion = net.isIP(cidr); | ||
if (ipVersion === 4) { | ||
return cidr; | ||
} else if (ipVersion === 6) { | ||
return ipv6Normalize(cidr); | ||
} | ||
throw new Error(`Invalid network: ${cidr}`); | ||
}; | ||
module.exports.exclude = function(basenets, excludenets) { | ||
return new Promise(function(resolve, reject) { | ||
if (!Array.isArray(basenets) && typeof basenets !== "string") { | ||
return reject(new Error("Expected an array or string, not " + basenets)); | ||
function mapNets(nets) { | ||
const maps = {v4: {}, v6: {}}; | ||
for (const net of nets) { | ||
const start = net.start({type: "bigInteger"}).toString(); | ||
const end = net.end({type: "bigInteger"}).toString(); | ||
const v = `v${isCidr(net)}`; | ||
if (!maps[v][start]) maps[v][start] = {}; | ||
if (!maps[v][end]) maps[v][end] = {}; | ||
if (maps[v][start].start) { | ||
maps[v][start].start += 1; | ||
} else { | ||
maps[v][start].start = 1; | ||
} | ||
if (!Array.isArray(excludenets) && typeof excludenets !== "string") { | ||
return reject(new Error("Expected an array or string, not " + excludenets)); | ||
if (maps[v][end].end) { | ||
maps[v][end].end += 1; | ||
} else { | ||
maps[v][end].end = 1; | ||
} | ||
} | ||
return maps; | ||
} | ||
const basefile = tempfile(".net"); | ||
const excludefile = tempfile(".net"); | ||
cidrTools.merge = function(nets) { | ||
nets = (Array.isArray(nets) ? nets : [nets]).map(parse); | ||
const maps = mapNets(nets); | ||
fs.writeFile(basefile, parseNets(basenets)).then(function() { | ||
fs.writeFile(excludefile, parseNets(excludenets)).then(function() { | ||
execa.stdout(exclude, [basefile, excludefile]).then(function(stdout) { | ||
resolve(stdout.split("\n").filter(net => Boolean(net))); | ||
fs.unlink(basefile); | ||
fs.unlink(excludefile); | ||
}).catch(function(err) { | ||
resolve(err); | ||
fs.unlink(basefile); | ||
fs.unlink(excludefile); | ||
}); | ||
}).catch(reject); | ||
}).catch(reject); | ||
}); | ||
const merged = {v4: [], v6: []}; | ||
const start = {v4: null, v6: null}; | ||
const end = {v4: null, v6: null}; | ||
for (const v of ["v4", "v6"]) { | ||
const numbers = Object.keys(maps[v]).sort(naturalCompare); | ||
let depth = 0; | ||
for (const [index, number] of numbers.entries()) { | ||
const marker = maps[v][number]; | ||
if (!start[v] && marker.start) { | ||
start[v] = bigint(number); | ||
} | ||
if (marker.end) { | ||
end[v] = bigint(number); | ||
} | ||
if (marker.start) depth += marker.start; | ||
if (marker.end) depth -= marker.end; | ||
if (marker.end && depth === 0 && ((numbers[index + 1] - numbers[index]) > 1)) { | ||
for (const sub of subparts({start: start[v], end: end[v]})) { | ||
merged[v].push(formatPart(sub, v)); | ||
} | ||
start[v] = null; | ||
end[v] = null; | ||
} else if (index === (numbers.length - 1)) { | ||
for (const sub of subparts({start: start[v], end: end[v]})) { | ||
merged[v].push(formatPart(sub, v)); | ||
} | ||
} | ||
} | ||
} | ||
merged.v4 = merged.v4.sort(naturalCompare); | ||
merged.v6 = merged.v6.sort(naturalCompare); | ||
return merged.v4.concat(merged.v6); | ||
}; | ||
module.exports.expand = function(nets) { | ||
return new Promise(function(resolve, reject) { | ||
if (!Array.isArray(nets) && typeof nets !== "string") { | ||
return reject(new Error("Expected an array or string, not " + nets)); | ||
cidrTools.exclude = function(basenets, exclnets) { | ||
basenets = Array.isArray(basenets) ? basenets : [basenets]; | ||
exclnets = Array.isArray(exclnets) ? exclnets : [exclnets]; | ||
basenets = cidrTools.merge(basenets); | ||
exclnets = cidrTools.merge(exclnets); | ||
const bases = {v4: [], v6: []}; | ||
const excls = {v4: [], v6: []}; | ||
for (const basenet of basenets) { | ||
bases[`v${isCidr(basenet)}`].push(basenet); | ||
} | ||
for (const exclnet of exclnets) { | ||
excls[`v${isCidr(exclnet)}`].push(exclnet); | ||
} | ||
for (const v of ["v4", "v6"]) { | ||
for (const exclcidr of excls[v]) { | ||
const excl = parse(exclcidr); | ||
for (const [index, basecidr] of bases[v].entries()) { | ||
const base = parse(basecidr); | ||
const remainders = exclude(base, excl, v); | ||
bases[v] = bases[v].concat(remainders); | ||
bases[v].splice(index, 1); | ||
} | ||
} | ||
} | ||
const netfile = tempfile(".net"); | ||
return bases.v4.concat(bases.v6); | ||
}; | ||
fs.writeFile(netfile, parseNets(nets)).then(function() { | ||
execa.stdout(expand, [netfile]).then(function(stdout) { | ||
resolve(stdout.split("\n").filter(net => Boolean(net))); | ||
fs.unlink(netfile); | ||
}).catch(function(err) { | ||
resolve(err); | ||
fs.unlink(netfile); | ||
}); | ||
}).catch(reject); | ||
}); | ||
cidrTools.expand = function(nets) { | ||
nets = Array.isArray(nets) ? nets : [nets]; | ||
let ips = []; | ||
for (const net of cidrTools.merge(nets)) { | ||
ips = ips.concat((new IPCIDR(net)).toArray()); | ||
} | ||
return ips.map(cidrTools.normalize); | ||
}; | ||
cidrTools.overlap = (a, b) => { | ||
a = parse(a); | ||
b = parse(b); | ||
if (a.address.v4 !== b.address.v4) return false; | ||
return overlap(a, b); | ||
}; |
{ | ||
"name": "cidr-tools", | ||
"version": "1.2.2", | ||
"version": "2.0.0", | ||
"author": "silverwind <me@silverwind.io>", | ||
@@ -18,26 +18,27 @@ "description": "Tools to work with IPv4 and IPv6 CIDR network lists", | ||
"ipv4", | ||
"ipv6" | ||
"ipv6", | ||
"merge", | ||
"exclude", | ||
"expand" | ||
], | ||
"engines": { | ||
"node": ">=4" | ||
"node": ">=8" | ||
}, | ||
"files": [ | ||
"index.js", | ||
"cidr-merge.py", | ||
"cidr-exclude.py", | ||
"cidr-expand.py", | ||
"install.sh" | ||
"index.js" | ||
], | ||
"dependencies": { | ||
"execa": "^0.9.0", | ||
"ipaddr.js": "^1.6.0", | ||
"mz": "^2.7.0", | ||
"tempfile": "^2.0.0" | ||
"ip-address": "^5.8.9", | ||
"ip-cidr": "^2.0.0", | ||
"ipv6-normalize": "^1.0.1", | ||
"is-cidr": "^3.0.0", | ||
"jsbn": "^1.1.0", | ||
"string-natural-compare": "^2.0.2" | ||
}, | ||
"devDependencies": { | ||
"ava": "^0.25.0", | ||
"eslint": "^4.18.2", | ||
"eslint-config-silverwind": "^1.0.37", | ||
"updates": "^2.3.1" | ||
"eslint": "^5.7.0", | ||
"eslint-config-silverwind": "^2.0.9", | ||
"updates": "^4.5.2", | ||
"ver": "^3.0.0" | ||
} | ||
} |
@@ -7,6 +7,6 @@ # cidr-tools | ||
With `python3`, `node` and `npm` available: | ||
## Install | ||
```bash | ||
$ npm i --save cidr-tools | ||
$ npm i cidr-tools | ||
``` | ||
@@ -18,16 +18,7 @@ ## Example | ||
cidrTools.merge(['1.0.0.0/24', '1.0.1.0/24']).then(r => { | ||
console.log(r); | ||
//=> ['1.0.0.0/23'] | ||
}); | ||
cidrTools.exclude(['::1/127'], ['::1/128']).then(r => { | ||
console.log(r); | ||
//=> ['::/128'] | ||
}); | ||
cidrTools.expand(['2001:db8::/126']).then(r => { | ||
console.log(r); | ||
//=> ['2001:db8::', '2001:db8::1', '2001:db8::2', '2001:db8::3'] | ||
}); | ||
cidrTools.merge(['1.0.0.0/24', '1.0.1.0/24']); //=> ['1.0.0.0/23'] | ||
cidrTools.exclude(['::1/127'], ['::1/128']) //=> ['::/128'] | ||
cidrTools.expand(['2001:db8::/126']) //=> ['2001:db8::', '2001:db8::1', '2001:db8::2', '2001:db8::3'] | ||
cidrTools.overlap('1.0.0.0/24', '1.0.0.128/25') //=> true | ||
cidrTools.normalize('0:0:0:0:0:0:0:0/0') //=> '::/0' | ||
``` | ||
@@ -37,19 +28,36 @@ | ||
All functions take CIDR addresses or single IP addresses. On single addresses, a prefix of `/32` or `/128` is assumed. Function that return networks will return a merged and properly sorted set of networks with IPv4 sorted before IPv6. | ||
### cidrTools.merge(networks) | ||
- `networks` *String* or *Array*: A list of IPv4 and IPv6 networks. | ||
- Returns: A promise that resolves to an array of merged networks. | ||
- `networks` *String* or *Array*: One or more CIDR or IP addresses. | ||
Returns an array of merged networks. | ||
### cidrTools.exclude(baseNetworks, excludeNetworks) | ||
- `baseNetworks` *String* or *Array*: A list of IPv4 and IPv6 networks. | ||
- `excludeNetworks` *Array*: A list of IPv4 and IPv6 networks to exclude from `baseNetworks`. | ||
- Returns: A promise that resolves to an array of merged remaining networks. | ||
- `baseNetworks` *String* or *Array*: One or more CIDR or IP addresses. | ||
- `excludeNetworks` *String* or *Array*: One or more CIDR or IP addresses to exclude from `baseNetworks`. | ||
Returns an array of merged remaining networks. | ||
### cidrTools.expand(networks) | ||
- `networks` *String* or *Array*: A list of IPv4 and IPv6 networks. | ||
- Returns: A promise that resolves to an array of individual IPs contained in the networks. | ||
- `networks` *String* or *Array*: One or more CIDR or IP addresses. | ||
Returns an array of individual IPs contained in the networks. | ||
### cidrTools.overlap(networkA, networkB) | ||
- `networkA` *String*: A CIDR or IP addresses. | ||
- `networkB` *String*: A CIDR or IP addresses. | ||
Returns a boolean that indicates if the networks overlap (intersect) each other. | ||
### cidrTools.normalize(network) | ||
- `network` *String*: A CIDR or IP addresses. | ||
Returns a normalized representation of a IP or CIDR. Will not include a prefix on IPs. | ||
© [silverwind](https://github.com/silverwind), distributed under BSD licence. |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
12163
259
61
6
4
2
+ Addedip-address@^5.8.9
+ Addedip-cidr@^2.0.0
+ Addedipv6-normalize@^1.0.1
+ Addedis-cidr@^3.0.0
+ Addedjsbn@^1.1.0
+ Addedcidr-regex@2.0.10(transitive)
+ Addedip-address@5.9.46.4.0(transitive)
+ Addedip-cidr@2.1.5(transitive)
+ Addedip-regex@2.1.0(transitive)
+ Addedipv6-normalize@1.0.1(transitive)
+ Addedis-cidr@3.1.1(transitive)
+ Addedjsbn@1.1.0(transitive)
+ Addedlodash@4.17.21(transitive)
+ Addedlodash.find@4.6.0(transitive)
+ Addedlodash.max@4.0.1(transitive)
+ Addedlodash.merge@4.6.2(transitive)
+ Addedlodash.padstart@4.6.1(transitive)
+ Addedlodash.repeat@4.1.0(transitive)
+ Addedsprintf-js@1.1.2(transitive)
+ Addedstring-natural-compare@2.0.3(transitive)
- Removedexeca@^0.9.0
- Removedipaddr.js@^1.6.0
- Removedmz@^2.7.0
- Removedtempfile@^2.0.0
- Removedany-promise@1.3.0(transitive)
- Removedcross-spawn@5.1.0(transitive)
- Removedexeca@0.9.0(transitive)
- Removedget-stream@3.0.0(transitive)
- Removedipaddr.js@1.9.1(transitive)
- Removedis-stream@1.1.0(transitive)
- Removedisexe@2.0.0(transitive)
- Removedlru-cache@4.1.5(transitive)
- Removedmz@2.7.0(transitive)
- Removednpm-run-path@2.0.2(transitive)
- Removedobject-assign@4.1.1(transitive)
- Removedp-finally@1.0.0(transitive)
- Removedpath-key@2.0.1(transitive)
- Removedpseudomap@1.0.2(transitive)
- Removedshebang-command@1.2.0(transitive)
- Removedshebang-regex@1.0.0(transitive)
- Removedsignal-exit@3.0.7(transitive)
- Removedstrip-eof@1.0.0(transitive)
- Removedtemp-dir@1.0.0(transitive)
- Removedtempfile@2.0.0(transitive)
- Removedthenify@3.3.1(transitive)
- Removedthenify-all@1.6.0(transitive)
- Removeduuid@3.4.0(transitive)
- Removedwhich@1.3.1(transitive)
- Removedyallist@2.1.2(transitive)