From 9a275c31fca43380e0a4e9a49597da2d75a02d5e Mon Sep 17 00:00:00 2001 From: silverwind Date: Tue, 4 Jul 2017 19:41:17 +0200 Subject: [PATCH] add IPv6 prefixLengthFromSubnetMask Fixes #59 --- ipaddr.min.js | 2 +- lib/ipaddr.js | 41 +++++++++++++++++++++++++++++++++++++++++ src/ipaddr.coffee | 40 ++++++++++++++++++++++++++++++++++++++++ test/ipaddr.test.coffee | 17 ++++++++++++++--- 4 files changed, 96 insertions(+), 4 deletions(-) diff --git a/ipaddr.min.js b/ipaddr.min.js index 235489b..9472242 100644 --- a/ipaddr.min.js +++ b/ipaddr.min.js @@ -1 +1 @@ -(function(){var r,t,e,n,i,o,a,s;t={},s=this,"undefined"!=typeof module&&null!==module&&module.exports?module.exports=t:s.ipaddr=t,a=function(r,t,e,n){var i,o;if(r.length!==t.length)throw new Error("ipaddr: cannot match CIDR for objects with different lengths");for(i=0;n>0;){if((o=e-n)<0&&(o=0),r[i]>>o!=t[i]>>o)return!1;n-=e,i+=1}return!0},t.subnetMatch=function(r,t,e){var n,i,o,a,s;null==e&&(e="unicast");for(o in t)for(!(a=t[o])[0]||a[0]instanceof Array||(a=[a]),n=0,i=a.length;n=0;t=e+=-1){if(!((n=this.octets[t])in a))return null;if(o=a[n],i&&0!==o)return null;8!==o&&(i=!0),r+=o}return 32-r},r}(),e="(0?\\d+|0x[a-f0-9]+)",n={fourOctet:new RegExp("^"+e+"\\."+e+"\\."+e+"\\."+e+"$","i"),longValue:new RegExp("^"+e+"$","i")},t.IPv4.parser=function(r){var t,e,i,o,a;if(e=function(r){return"0"===r[0]&&"x"!==r[1]?parseInt(r,8):parseInt(r)},t=r.match(n.fourOctet))return function(){var r,n,o,a;for(a=[],r=0,n=(o=t.slice(1,6)).length;r4294967295||a<0)throw new Error("ipaddr: address outside defined range");return function(){var r,t;for(t=[],o=r=0;r<=24;o=r+=8)t.push(a>>o&255);return t}().reverse()}return null},t.IPv6=function(){function r(r){var t,e,n,i,o,a;if(16===r.length)for(this.parts=[],t=e=0;e<=14;t=e+=2)this.parts.push(r[t]<<8|r[t+1]);else{if(8!==r.length)throw new Error("ipaddr: ipv6 part count should be 8 or 16");this.parts=r}for(n=0,i=(a=this.parts).length;n>8),r.push(255&n);return r},r.prototype.toNormalizedString=function(){var r;return function(){var t,e,n,i;for(i=[],t=0,e=(n=this.parts).length;t>8,255&r,e>>8,255&e])},r}(),i="(?:[0-9a-f]+::?)+",o={native:new RegExp("^(::)?("+i+")?([0-9a-f]+)?(::)?$","i"),transitional:new RegExp("^((?:"+i+")|(?:::)(?:"+i+")?)"+e+"\\."+e+"\\."+e+"\\."+e+"$","i")},r=function(r,t){var e,n,i,o,a;if(r.indexOf("::")!==r.lastIndexOf("::"))return null;for(e=0,n=-1;(n=r.indexOf(":",n+1))>=0;)e++;if("::"===r.substr(0,2)&&e--,"::"===r.substr(-2,2)&&e--,e>t)return null;for(a=t-e,o=":";a--;)o+="0:";return":"===(r=r.replace("::",o))[0]&&(r=r.slice(1)),":"===r[r.length-1]&&(r=r.slice(0,-1)),function(){var t,e,n,o;for(o=[],t=0,e=(n=r.split(":")).length;t=0&&t<=32)return[this.parse(e[1]),t];throw new Error("ipaddr: string is not formatted like an IPv4 CIDR range")},t.IPv4.subnetMaskFromPrefixLength=function(r){var e,n;if(r<0||r>32)throw new Error("ipaddr: invalid prefix length");for(n=Array(4).fill(0),e=0;e=0&&t<=128)return[this.parse(e[1]),t];throw new Error("ipaddr: string is not formatted like an IPv6 CIDR range")},t.isValid=function(r){return t.IPv6.isValid(r)||t.IPv4.isValid(r)},t.parse=function(r){if(t.IPv6.isValid(r))return t.IPv6.parse(r);if(t.IPv4.isValid(r))return t.IPv4.parse(r);throw new Error("ipaddr: the address has neither IPv6 nor IPv4 format")},t.parseCIDR=function(r){try{return t.IPv6.parseCIDR(r)}catch(e){e;try{return t.IPv4.parseCIDR(r)}catch(r){throw r,new Error("ipaddr: the address has neither IPv6 nor IPv4 CIDR format")}}},t.fromByteArray=function(r){var e;if(4===(e=r.length))return new t.IPv4(r);if(16===e)return new t.IPv6(r);throw new Error("ipaddr: the binary input is neither an IPv6 nor IPv4 address")},t.process=function(r){var t;return t=this.parse(r),"ipv6"===t.kind()&&t.isIPv4MappedAddress()?t.toIPv4Address():t}}).call(this); \ No newline at end of file +(function(){var r,t,n,e,i,o,a,s;t={},s=this,"undefined"!=typeof module&&null!==module&&module.exports?module.exports=t:s.ipaddr=t,a=function(r,t,n,e){var i,o;if(r.length!==t.length)throw new Error("ipaddr: cannot match CIDR for objects with different lengths");for(i=0;e>0;){if((o=n-e)<0&&(o=0),r[i]>>o!=t[i]>>o)return!1;e-=n,i+=1}return!0},t.subnetMatch=function(r,t,n){var e,i,o,a,s;null==n&&(n="unicast");for(o in t)for(!(a=t[o])[0]||a[0]instanceof Array||(a=[a]),e=0,i=a.length;e=0;t=n+=-1){if(!((e=this.octets[t])in a))return null;if(o=a[e],i&&0!==o)return null;8!==o&&(i=!0),r+=o}return 32-r},r}(),n="(0?\\d+|0x[a-f0-9]+)",e={fourOctet:new RegExp("^"+n+"\\."+n+"\\."+n+"\\."+n+"$","i"),longValue:new RegExp("^"+n+"$","i")},t.IPv4.parser=function(r){var t,n,i,o,a;if(n=function(r){return"0"===r[0]&&"x"!==r[1]?parseInt(r,8):parseInt(r)},t=r.match(e.fourOctet))return function(){var r,e,o,a;for(a=[],r=0,e=(o=t.slice(1,6)).length;r4294967295||a<0)throw new Error("ipaddr: address outside defined range");return function(){var r,t;for(t=[],o=r=0;r<=24;o=r+=8)t.push(a>>o&255);return t}().reverse()}return null},t.IPv6=function(){function r(r){var t,n,e,i,o,a;if(16===r.length)for(this.parts=[],t=n=0;n<=14;t=n+=2)this.parts.push(r[t]<<8|r[t+1]);else{if(8!==r.length)throw new Error("ipaddr: ipv6 part count should be 8 or 16");this.parts=r}for(e=0,i=(a=this.parts).length;e>8),r.push(255&e);return r},r.prototype.toNormalizedString=function(){var r;return function(){var t,n,e,i;for(i=[],t=0,n=(e=this.parts).length;t>8,255&r,n>>8,255&n])},r.prototype.prefixLengthFromSubnetMask=function(){var r,t,n,e,i,o,a;for(a={0:16,32768:15,49152:14,57344:13,61440:12,63488:11,64512:10,65024:9,65280:8,65408:7,65472:6,65504:5,65520:4,65528:3,65532:2,65534:1,65535:0},r=0,i=!1,t=n=7;n>=0;t=n+=-1){if(!((e=this.parts[t])in a))return null;if(o=a[e],i&&0!==o)return null;16!==o&&(i=!0),r+=o}return 128-r},r}(),i="(?:[0-9a-f]+::?)+",o={native:new RegExp("^(::)?("+i+")?([0-9a-f]+)?(::)?$","i"),transitional:new RegExp("^((?:"+i+")|(?:::)(?:"+i+")?)"+n+"\\."+n+"\\."+n+"\\."+n+"$","i")},r=function(r,t){var n,e,i,o,a;if(r.indexOf("::")!==r.lastIndexOf("::"))return null;for(n=0,e=-1;(e=r.indexOf(":",e+1))>=0;)n++;if("::"===r.substr(0,2)&&n--,"::"===r.substr(-2,2)&&n--,n>t)return null;for(a=t-n,o=":";a--;)o+="0:";return":"===(r=r.replace("::",o))[0]&&(r=r.slice(1)),":"===r[r.length-1]&&(r=r.slice(0,-1)),function(){var t,n,e,o;for(o=[],t=0,n=(e=r.split(":")).length;t=0&&t<=32)return[this.parse(n[1]),t];throw new Error("ipaddr: string is not formatted like an IPv4 CIDR range")},t.IPv4.subnetMaskFromPrefixLength=function(r){var n,e;if(r<0||r>32)throw new Error("ipaddr: invalid prefix length");for(e=Array(4).fill(0),n=0;n=0&&t<=128)return[this.parse(n[1]),t];throw new Error("ipaddr: string is not formatted like an IPv6 CIDR range")},t.isValid=function(r){return t.IPv6.isValid(r)||t.IPv4.isValid(r)},t.parse=function(r){if(t.IPv6.isValid(r))return t.IPv6.parse(r);if(t.IPv4.isValid(r))return t.IPv4.parse(r);throw new Error("ipaddr: the address has neither IPv6 nor IPv4 format")},t.parseCIDR=function(r){try{return t.IPv6.parseCIDR(r)}catch(n){n;try{return t.IPv4.parseCIDR(r)}catch(r){throw r,new Error("ipaddr: the address has neither IPv6 nor IPv4 CIDR format")}}},t.fromByteArray=function(r){var n;if(4===(n=r.length))return new t.IPv4(r);if(16===n)return new t.IPv6(r);throw new Error("ipaddr: the binary input is neither an IPv6 nor IPv4 address")},t.process=function(r){var t;return"ipv6"===(t=this.parse(r)).kind()&&t.isIPv4MappedAddress()?t.toIPv4Address():t}}).call(this); \ No newline at end of file diff --git a/lib/ipaddr.js b/lib/ipaddr.js index 51d7c76..841df0e 100644 --- a/lib/ipaddr.js +++ b/lib/ipaddr.js @@ -339,6 +339,47 @@ return new ipaddr.IPv4([high >> 8, high & 0xff, low >> 8, low & 0xff]); }; + IPv6.prototype.prefixLengthFromSubnetMask = function() { + var cidr, i, k, part, stop, zeros, zerotable; + zerotable = { + 0: 16, + 32768: 15, + 49152: 14, + 57344: 13, + 61440: 12, + 63488: 11, + 64512: 10, + 65024: 9, + 65280: 8, + 65408: 7, + 65472: 6, + 65504: 5, + 65520: 4, + 65528: 3, + 65532: 2, + 65534: 1, + 65535: 0 + }; + cidr = 0; + stop = false; + for (i = k = 7; k >= 0; i = k += -1) { + part = this.parts[i]; + if (part in zerotable) { + zeros = zerotable[part]; + if (stop && zeros !== 0) { + return null; + } + if (zeros !== 16) { + stop = true; + } + cidr += zeros; + } else { + return null; + } + } + return 128 - cidr; + }; + return IPv6; })(); diff --git a/src/ipaddr.coffee b/src/ipaddr.coffee index adb7b6e..c07d14d 100644 --- a/src/ipaddr.coffee +++ b/src/ipaddr.coffee @@ -302,6 +302,46 @@ class ipaddr.IPv6 return new ipaddr.IPv4([high >> 8, high & 0xff, low >> 8, low & 0xff]) + # returns a number of leading ones in IPv6 address, making sure that + # the rest is a solid sequence of 0's (valid netmask) + # returns either the CIDR length or null if mask is not valid + prefixLengthFromSubnetMask: -> + # number of zeroes in octet + zerotable = + 0 : 16 + 32768: 15 + 49152: 14 + 57344: 13 + 61440: 12 + 63488: 11 + 64512: 10 + 65024: 9 + 65280: 8 + 65408: 7 + 65472: 6 + 65504: 5 + 65520: 4 + 65528: 3 + 65532: 2 + 65534: 1 + 65535: 0 + + cidr = 0 + # non-zero encountered stop scanning for zeroes + stop = false + for i in [7..0] by -1 + part = @parts[i] + if part of zerotable + zeros = zerotable[part] + if stop and zeros != 0 + return null + unless zeros == 16 + stop = true + cidr += zeros + else + return null + return 128 - cidr + # IPv6-matching regular expressions. # For IPv6, the task is simpler: it is enough to match the colon-delimited # hexadecimal IPv6 and a transitional variant with dotted-decimal IPv4 at diff --git a/test/ipaddr.test.coffee b/test/ipaddr.test.coffee index 63830da..3e7724d 100644 --- a/test/ipaddr.test.coffee +++ b/test/ipaddr.test.coffee @@ -360,8 +360,19 @@ module.exports = test.equal(ipaddr.IPv4.parse('255.0.255.0').prefixLengthFromSubnetMask(), null) test.done() + 'prefixLengthFromSubnetMask returns proper CIDR notation for standard IPv6 masks': (test) -> + test.equal(ipaddr.IPv6.parse('ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff').prefixLengthFromSubnetMask(), 128) + test.equal(ipaddr.IPv6.parse('ffff:ffff:ffff:ffff::').prefixLengthFromSubnetMask(), 64) + test.equal(ipaddr.IPv6.parse('ffff:ffff:ffff:ff80::').prefixLengthFromSubnetMask(), 57) + test.equal(ipaddr.IPv6.parse('ffff:ffff:ffff::').prefixLengthFromSubnetMask(), 48) + test.equal(ipaddr.IPv6.parse('::').prefixLengthFromSubnetMask(), 0) + # negative cases + test.equal(ipaddr.IPv6.parse('2001:db8::').prefixLengthFromSubnetMask(), null) + test.equal(ipaddr.IPv6.parse('ffff:0:0:ffff::').prefixLengthFromSubnetMask(), null) + test.done() + 'subnetMaskFromPrefixLength returns correct subnet mask given prefix length': (test) -> - + test.equal(ipaddr.IPv4.subnetMaskFromPrefixLength("0"), "0.0.0.0"); test.equal(ipaddr.IPv4.subnetMaskFromPrefixLength("1"), "128.0.0.0") test.equal(ipaddr.IPv4.subnetMaskFromPrefixLength("2"), "192.0.0.0") @@ -395,12 +406,12 @@ module.exports = test.equal(ipaddr.IPv4.subnetMaskFromPrefixLength("30"), "255.255.255.252") test.equal(ipaddr.IPv4.subnetMaskFromPrefixLength("31"), "255.255.255.254") test.done() - + 'broadcastAddressFromCIDR returns correct broadcast address': (test) -> test.equal(ipaddr.IPv4.broadcastAddressFromCIDR("172.0.0.1/24"), "172.0.0.255") test.equal(ipaddr.IPv4.broadcastAddressFromCIDR("172.0.0.1/26"), "172.0.0.63") test.done() - + 'networkAddressFromCIDR returns correct network address': (test) -> test.equal(ipaddr.IPv4.networkAddressFromCIDR("172.0.0.1/24"), "172.0.0.0") test.equal(ipaddr.IPv4.networkAddressFromCIDR("172.0.0.1/5"), "168.0.0.0")