Skip to content

Instantly share code, notes, and snippets.

@peelman
Last active September 28, 2021 13:16
Show Gist options
  • Save peelman/6a0008cb709ab65ab4cd24546846a972 to your computer and use it in GitHub Desktop.
Save peelman/6a0008cb709ab65ab4cd24546846a972 to your computer and use it in GitHub Desktop.
Subnet Utility Class for Javascript

Borrows liberally from the Netmask codebase, but structured to be used more readily in a browser context.

Example:

Subnet.isIP('10.0.0.0')          // true
Subnet.isIP('10.0.0.257')        // false

var subnet = new Subnet('10.0.0.0',12);
subnet.base;                     // 10.0.0.0
subnet.mask;                     // 255.240.0.0
subnet.bitmask;                  // 12
subnet.hostmask;                 // 0.15.255.255
subnet.broadcast;                // 10.15.255.255
subnet.size;                     // 1048576
subnet.first;                    // 10.0.0.1
subnet.last;                     // 10.15.255.254

subnet.contains('10.0.8.10');    // true
subnet.contains('10.8.0.10');    // true
subnet.contains('192.168.1.20'); // false

subnet.forEach(function(ip, long, index));

subnet.next()                    // Subnet('10.16.0.0/12')
function long2ip(long) {
var a, b, c, d;
a = (long & (0xff << 24)) >>> 24;
b = (long & (0xff << 16)) >>> 16;
c = (long & (0xff << 8)) >>> 8;
d = long & 0xff;
return [a, b, c, d].join('.');
};
function ip2long(ip) {
var b, c, i, j, n, ref;
b = [];
for (i = j = 0; j <= 3; i = ++j) {
if (ip.length === 0) {
break;
}
if (i > 0) {
if (ip[0] !== '.') {
throw new Error('Invalid IP');
}
ip = ip.substring(1);
}
ref = atob(ip), n = ref[0], c = ref[1];
ip = ip.substring(c);
b.push(n);
}
if (ip.length !== 0) {
throw new Error('Invalid IP');
}
switch (b.length) {
case 1:
if (b[0] > 0xFFFFFFFF) {
throw new Error('Invalid IP');
}
return b[0] >>> 0;
case 2:
if (b[0] > 0xFF || b[1] > 0xFFFFFF) {
throw new Error('Invalid IP');
}
return (b[0] << 24 | b[1]) >>> 0;
case 3:
if (b[0] > 0xFF || b[1] > 0xFF || b[2] > 0xFFFF) {
throw new Error('Invalid IP');
}
return (b[0] << 24 | b[1] << 16 | b[2]) >>> 0;
case 4:
if (b[0] > 0xFF || b[1] > 0xFF || b[2] > 0xFF || b[3] > 0xFF) {
throw new Error('Invalid IP');
}
return (b[0] << 24 | b[1] << 16 | b[2] << 8 | b[3]) >>> 0;
default:
throw new Error('Invalid IP');
}
}
function chr(b) {
return b.charCodeAt(0);
}
chr0 = chr('0');
chra = chr('a');
chrA = chr('A');
function atob(s) {
var base, dmax, i, n, start;
n = 0;
base = 10;
dmax = '9';
i = 0;
if (s.length > 1 && s[i] === '0') {
if (s[i + 1] === 'x' || s[i + 1] === 'X') {
i += 2;
base = 16;
} else if ('0' <= s[i + 1] && s[i + 1] <= '9') {
i++;
base = 8;
dmax = '7';
}
}
start = i;
while (i < s.length) {
if ('0' <= s[i] && s[i] <= dmax) {
n = (n * base + (chr(s[i]) - chr0)) >>> 0;
} else if (base === 16) {
if ('a' <= s[i] && s[i] <= 'f') {
n = (n * base + (10 + chr(s[i]) - chra)) >>> 0;
} else if ('A' <= s[i] && s[i] <= 'F') {
n = (n * base + (10 + chr(s[i]) - chrA)) >>> 0;
} else {
break;
}
} else {
break;
}
if (n > 0xFFFFFFFF) {
throw new Error('too large');
}
i++;
}
if (i === start) {
throw new Error('empty octet');
}
return [n, i];
}
class Subnet {
constructor(net, mask) {
var error, i, j, ref;
if (typeof net !== 'string') {
throw new Error("Missing `net' parameter");
}
if (!mask) {
ref = net.split('/', 2), net = ref[0], mask = ref[1];
}
if (!mask) {
mask = 32;
}
if (typeof mask === 'string' && mask.indexOf('.') > -1) {
try {
this.maskLong = ip2long(mask);
} catch (error1) {
error = error1;
throw new Error("Invalid mask: " + mask);
}
for (i = j = 32; j >= 0; i = --j) {
if (this.maskLong === (0xffffffff << (32 - i)) >>> 0) {
this.bitmask = i;
break;
}
}
} else if (mask || mask === 0) {
this.bitmask = parseInt(mask, 10);
this.maskLong = 0;
if (this.bitmask > 0) {
this.maskLong = (0xffffffff << (32 - this.bitmask)) >>> 0;
}
} else {
throw new Error("Invalid mask: empty");
}
try {
this.netLong = (ip2long(net) & this.maskLong) >>> 0;
} catch (error1) {
error = error1;
throw new Error("Invalid net address: " + net);
}
if (this.bitmask > 32 || this.bitmask < 0) {
throw new Error("Invalid mask for ip4: " + mask);
}
this.size = Math.pow(2, 32 - this.bitmask);
this.base = long2ip(this.netLong);
this.mask = long2ip(this.maskLong);
this.hostmask = long2ip(~this.maskLong);
this.first = this.bitmask <= 30 ? long2ip(this.netLong + 1) : this.base;
this.last = this.bitmask <= 30 ? long2ip(this.netLong + this.size - 2) : long2ip(this.netLong + this.size - 1);
this.broadcast = this.bitmask <= 30 ? long2ip(this.netLong + this.size - 1) : void 0;
}
static isIP(ip) {
if (typeof ip !== 'string') {
return false;
}
var parts = ip.match(/^([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})$/);
if (parts === null) {
return false;
}
for (var i = 1; i <= 4; i++) {
var n = parseInt(parts[i], 10);
if ((n > 255) || (n < 0)) {
return false;
}
}
return true;
}
contains(ip) {
if (typeof ip === 'string' && (ip.indexOf('/') > 0 || ip.split('.').length !== 4)) {
ip = new Subnet(ip);
}
if (ip instanceof Subnet) {
return this.contains(ip.base) && this.contains(ip.broadcast || ip.last);
} else {
return (ip2long(ip) & this.maskLong) >>> 0 === (this.netLong & this.maskLong) >>> 0;
}
};
next(count) {
if (count == null) {
count = 1;
}
return new Subnet(long2ip(this.netLong + (this.size * count)), this.mask);
};
forEach(fn) {
var index, lastLong, long;
long = ip2long(this.first);
lastLong = ip2long(this.last);
index = 0;
while (long <= lastLong) {
fn(long2ip(long), long, index);
index++;
long++;
}
};
toString() {
return this.base + "/" + this.bitmask;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment