diff --git a/.taprc b/.taprc index 2188e32..eb6eb3e 100644 --- a/.taprc +++ b/.taprc @@ -1,4 +1,2 @@ -ts: false -jsx: false -flow: false -coverage: true +files: + - test/**/*.test.js diff --git a/README.md b/README.md index 5757412..ceeb7b5 100644 --- a/README.md +++ b/README.md @@ -34,10 +34,9 @@ returned. ```js proxyaddr(req, function (addr) { return addr === '127.0.0.1' }) -proxyaddr(req, function (addr, i) { return i < 1 }) ``` -The `trust` arugment may also be a single IP address string or an +The `trust` argument may also be a single IP address string or an array of trusted addresses, as plain IP addresses, CIDR-formatted strings, or IP/netmask strings. diff --git a/benchmark/compiling.js b/benchmark/compiling.js index 1bf7420..7638727 100644 --- a/benchmark/compiling.js +++ b/benchmark/compiling.js @@ -1,3 +1,4 @@ +'use strict' /** * Globals for benchmark.js diff --git a/benchmark/kind.js b/benchmark/kind.js index 3f254c5..239ccf9 100644 --- a/benchmark/kind.js +++ b/benchmark/kind.js @@ -1,3 +1,4 @@ +'use strict' /** * Globals for benchmark.js diff --git a/benchmark/matching.js b/benchmark/matching.js index 0ca7fb2..d584c68 100644 --- a/benchmark/matching.js +++ b/benchmark/matching.js @@ -1,3 +1,4 @@ +'use strict' /** * Globals for benchmark.js diff --git a/benchmark/prefix-length-from-subnet-mask.js b/benchmark/prefix-length-from-subnet-mask.js new file mode 100644 index 0000000..990bd71 --- /dev/null +++ b/benchmark/prefix-length-from-subnet-mask.js @@ -0,0 +1,25 @@ +'use strict' + +/** + * Globals for benchmark.js + */ +const { prefixLengthFromSubnetMask } = require('../lib/prefix-length-from-subnet-mask') + +/** + * Module dependencies. + */ +const benchmark = require('benchmark') + +const suite = new benchmark.Suite() + +const ip = '255.255.254.0' + +suite.add('prefixLengthFromSubnetMask', function () { + prefixLengthFromSubnetMask(ip) +}) + +suite.on('cycle', function onCycle (event) { + console.log(String(event.target)) +}) + +suite.run({ async: false }) diff --git a/index.js b/index.js index aaa7671..d386c8c 100644 --- a/index.js +++ b/index.js @@ -7,33 +7,17 @@ 'use strict' -/** - * Module exports. - * @public - */ - -module.exports = proxyaddr -module.exports.default = proxyaddr -module.exports.proxyaddr = proxyaddr -module.exports.all = alladdrs -module.exports.compile = compile - /** * Module dependencies. * @private */ -const forwarded = require('@fastify/forwarded') -const ipaddr = require('ipaddr.js') - -/** - * Variables. - * @private - */ - -const DIGIT_REGEXP = /^[0-9]+$/ -const isip = ipaddr.isValid -const parseip = ipaddr.parse +const { isIP } = require('node:net') +const { forwarded } = require('@fastify/forwarded') +const { parse: parseIp } = require('ipaddr.js') +const { isIPv4MappedIPv6Address } = require('./lib/is-ipv4-mapped-ipv6-address') +const { prefixLengthFromSubnetMask } = require('./lib/prefix-length-from-subnet-mask') +const { isInteger } = require('./lib/is-integer') /** * Pre-defined IP ranges. @@ -41,38 +25,46 @@ const parseip = ipaddr.parse */ const IP_RANGES = { + __proto__: null, linklocal: ['169.254.0.0/16', 'fe80::/10'], loopback: ['127.0.0.1/8', '::1/128'], uniquelocal: ['10.0.0.0/8', '172.16.0.0/12', '192.168.0.0/16', 'fc00::/7'] } +/** + * @callback TrustFunction + * @param {string} addr + * @param {number} i + * @returns {boolean} + */ + /** * Get all addresses in the request, optionally stopping * at the first untrusted. * * @param {Object} request - * @param {Function|Array|String} [trust] + * @param {TrustFunction|Array|String} [trust] * @public */ -function alladdrs (req, trust) { - // get addresses - const addrs = forwarded(req) - +function alladdrs (request, trust) { if (!trust) { // Return all addresses - return addrs + return forwarded(request) } - if (typeof trust !== 'function') { - trust = compile(trust) - } + typeof trust !== 'function' && (trust = compile(trust)) - /* eslint-disable no-var */ - for (var i = 0; i < addrs.length - 1; i++) { - if (trust(addrs[i], i)) continue + // get addresses + const addrs = forwarded(request) - addrs.length = i + 1 + const len = addrs.length - 1 + /* eslint-disable no-var */ + for (var i = 0; i < len; i++) { + if (trust(addrs[i], i) === false) { + addrs.length = i + 1 + break + } } return addrs @@ -82,6 +74,7 @@ function alladdrs (req, trust) { * Compile argument into trust function. * * @param {Array|String} val + * @returns {TrustFunction} * @private */ @@ -90,6 +83,9 @@ function compile (val) { throw new TypeError('argument is required') } + /** + * @type {string[]} + */ let trust if (typeof val === 'string') { @@ -104,14 +100,12 @@ function compile (val) { for (var i = 0; i < trust.length; i++) { val = trust[i] - if (!Object.prototype.hasOwnProperty.call(IP_RANGES, val)) { - continue + if (val in IP_RANGES) { + // Splice in pre-defined range + val = IP_RANGES[val] + trust.splice.apply(trust, [i, 1].concat(val)) + i += val.length - 1 } - - // Splice in pre-defined range - val = IP_RANGES[val] - trust.splice.apply(trust, [i, 1].concat(val)) - i += val.length - 1 } return compileTrust(compileRangeSubnets(trust)) @@ -120,16 +114,18 @@ function compile (val) { /** * Compile `arr` elements into range subnets. * - * @param {Array} arr * @private + * + * @param {Array} arr */ function compileRangeSubnets (arr) { - const rangeSubnets = new Array(arr.length) + const len = arr.length + const rangeSubnets = new Array(len) /* eslint-disable no-var */ - for (var i = 0; i < arr.length; i++) { - rangeSubnets[i] = parseipNotation(arr[i]) + for (var i = 0; i < len; i++) { + rangeSubnets[i] = parseIpNotation(arr[i]) } return rangeSubnets @@ -138,8 +134,10 @@ function compileRangeSubnets (arr) { /** * Compile range subnet array into trust function. * - * @param {Array} rangeSubnets * @private + * + * @param {Array} rangeSubnets + * @returns {TrustFunction} */ function compileTrust (rangeSubnets) { @@ -155,41 +153,47 @@ function compileTrust (rangeSubnets) { /** * Parse IP notation string into range subnet. * - * @param {String} note * @private + * + * @param {String} note */ -function parseipNotation (note) { +function parseIpNotation (note) { const pos = note.lastIndexOf('/') const str = pos !== -1 ? note.substring(0, pos) : note - if (!isip(str)) { + if (isIP(str) === 0) { throw new TypeError('invalid IP address: ' + str) } - let ip = parseip(str) + let ip = parseIp(str) - if (pos === -1 && ip.kind() === 'ipv6' && ip.isIPv4MappedAddress()) { + if (pos === -1 && ip.kind() === 'ipv6' && isIPv4MappedIPv6Address(str)) { // Store as IPv4 ip = ip.toIPv4Address() } - const max = ip.kind() === 'ipv6' + const kind = ip.kind() + + const max = kind === 'ipv6' ? 128 : 32 + /** + * @type {number|string} + */ let range = pos !== -1 ? note.substring(pos + 1, note.length) : null if (range === null) { range = max - } else if (DIGIT_REGEXP.test(range)) { + } else if (isInteger(range)) { range = parseInt(range, 10) - } else if (ip.kind() === 'ipv4' && isip(range)) { - range = parseNetmask(range) + } else if (kind === 'ipv4' && isIP(range) === 4) { + range = prefixLengthFromSubnetMask(range) } else { range = null } @@ -198,35 +202,20 @@ function parseipNotation (note) { throw new TypeError('invalid range on address: ' + note) } - return [ip, range] -} - -/** - * Parse netmask string into CIDR range. - * - * @param {String} netmask - * @private - */ - -function parseNetmask (netmask) { - const ip = parseip(netmask) - const kind = ip.kind() - - return kind === 'ipv4' - ? ip.prefixLengthFromSubnetMask() - : null + return [ip, range, kind] } /** * Determine address of proxied request. * - * @param {Object} request - * @param {Function|Array|String} trust * @public + * + * @param {Object} request + * @param {TrustFunction|Array|String} trust */ -function proxyaddr (req, trust) { - if (!req) { +function proxyaddr (request, trust) { + if (!request) { throw new TypeError('req argument is required') } @@ -234,16 +223,37 @@ function proxyaddr (req, trust) { throw new TypeError('trust argument is required') } - const addrs = alladdrs(req, trust) - const addr = addrs[addrs.length - 1] + typeof trust !== 'function' && (trust = compile(trust)) - return addr + // get addresses + const addrs = forwarded(request) + + switch (addrs.length) { + case 1: + return addrs[0] + case 2: + return trust(addrs[0], 0) + ? addrs[1] + : addrs[0] + default: { + /* eslint-disable no-var */ + for (var i = 0; i < addrs.length - 1; i++) { + if (trust(addrs[i], i) === false) { + return addrs[i] + } + } + + return addrs[addrs.length - 1] + } + } } /** * Static trust function to trust nothing. * * @private + * + * @type {TrustFunction} */ function trustNone () { @@ -253,28 +263,33 @@ function trustNone () { /** * Compile trust function for multiple subnets. * - * @param {Array} subnets * @private + * + * @param {Array} subnets + * @returns {TrustFunction} */ function trustMulti (subnets) { return function trust (addr) { - if (!isip(addr)) return false + if (isIP(addr) === 0) return false - const ip = parseip(addr) + const ip = parseIp(addr) let ipconv const kind = ip.kind() + const isIPv4MappedAddress = kind === 'ipv6' + ? isIPv4MappedIPv6Address(addr) + : false /* eslint-disable no-var */ for (var i = 0; i < subnets.length; i++) { const subnet = subnets[i] const subnetip = subnet[0] - const subnetkind = subnetip.kind() const subnetrange = subnet[1] + const subnetkind = subnet[2] let trusted = ip if (kind !== subnetkind) { - if (subnetkind === 'ipv4' && !ip.isIPv4MappedAddress()) { + if (subnetkind === 'ipv4' && !isIPv4MappedAddress) { // Incompatible IP addresses continue } @@ -301,24 +316,29 @@ function trustMulti (subnets) { /** * Compile trust function for single subnet. * - * @param {Object} subnet * @private + * + * @param {Object} subnet + * @returns {TrustFunction} */ function trustSingle (subnet) { const subnetip = subnet[0] - const subnetkind = subnetip.kind() - const subnetisipv4 = subnetkind === 'ipv4' const subnetrange = subnet[1] + const subnetkind = subnet[2] + const subnetisipv4 = subnetkind === 'ipv4' return function trust (addr) { - if (!isip(addr)) return false + if (isIP(addr) === 0) return false - let ip = parseip(addr) + let ip = parseIp(addr) const kind = ip.kind() + const isIPv4MappedAddress = kind === 'ipv6' + ? isIPv4MappedIPv6Address(addr) + : false if (kind !== subnetkind) { - if (subnetisipv4 && !ip.isIPv4MappedAddress()) { + if (subnetisipv4 && !isIPv4MappedAddress) { // Incompatible IP addresses return false } @@ -332,3 +352,14 @@ function trustSingle (subnet) { return ip.match(subnetip, subnetrange) } } + +/** + * Module exports. + * @public + */ + +module.exports = proxyaddr +module.exports.default = proxyaddr +module.exports.proxyaddr = proxyaddr +module.exports.all = alladdrs +module.exports.compile = compile diff --git a/lib/is-integer.js b/lib/is-integer.js new file mode 100644 index 0000000..26b0e6c --- /dev/null +++ b/lib/is-integer.js @@ -0,0 +1,15 @@ +'use strict' + +const isIntegerRE = /^[0-9]+$/ + +/** + * @function isInteger + * @description Test if a string is an integer + * @param {string} str + * @returns {boolean} + */ +const isInteger = isIntegerRE.test.bind(isIntegerRE) + +module.exports = { + isInteger +} diff --git a/lib/is-ipv4-mapped-ipv6-address.js b/lib/is-ipv4-mapped-ipv6-address.js new file mode 100644 index 0000000..78fc65c --- /dev/null +++ b/lib/is-ipv4-mapped-ipv6-address.js @@ -0,0 +1,58 @@ +'use strict' + +const { isIP } = require('node:net') + +/** + * Determine if `addr` is a IPv4-mapped IPv6 address. + * + * @param {string} addr + * @returns {boolean} + */ +function isIPv4MappedIPv6Address (addr) { + if ( + addr[0] === ':' && + addr[1] === ':' + ) { + if ( + addr[2] === 'f' && + addr[3] === 'f' && + addr[4] === 'f' && + addr[5] === 'f' && + addr[6] === ':' + ) { + return true + } else if (isIP(addr.slice(2)) === 4) { + return true + } + } + + let group = 0 + for (let i = 0; i < addr.length; ++i) { + switch (addr[i]) { + case ':': + if (group === 5) { + return true + } + ++group + break + case '0': + if (group === 5) { + return false + } + break + case 'f': + if (group !== 5) { + return false + } + break + default: + return false + } + } + + return false +} + +module.exports = { + isIPv4MappedIPv6Address +} diff --git a/lib/prefix-length-from-subnet-mask.js b/lib/prefix-length-from-subnet-mask.js new file mode 100644 index 0000000..73b4a02 --- /dev/null +++ b/lib/prefix-length-from-subnet-mask.js @@ -0,0 +1,75 @@ +'use strict' + +/** + * Parse netmask string into CIDR range. + * + * @param {String} netmask + * @returns {Number} + */ +function prefixLengthFromSubnetMask (netmask) { + let stop = false + let cidr = 0 + + let end = netmask.length + let start = netmask.lastIndexOf('.', end - 1) + let zeros = octetToZeros(netmask, start + 1, end) + if (zeros === 0) return null + zeros !== 9 && (stop = true) + cidr += zeros + + end = start + start = netmask.lastIndexOf('.', end - 1) + zeros = octetToZeros(netmask, start + 1, end) + if (zeros !== 1 && (stop === true || zeros === 0)) return null + stop === false && zeros !== 9 && (stop = true) + cidr += zeros + + end = start + start = netmask.lastIndexOf('.', end - 1) + zeros = octetToZeros(netmask, start + 1, end) + if (zeros !== 1 && (stop === true || zeros === 0)) return null + stop === false && zeros !== 9 && (stop = true) + cidr += zeros + + end = start + zeros = octetToZeros(netmask, 0, end) + if (zeros !== 1 && (stop === true || zeros === 0)) return null + stop === false && zeros !== 9 && (stop = true) + cidr += zeros + + return 36 - cidr +} + +/** + * @param {String} ip + * @param {Number} start + * @param {Number} end + * @returns {Number} + */ +function octetToZeros (ip, start, end) { + return ( + (ip[start] === '0' && 9) || + ((end - start) === 3 && ( + (ip[start] === '1' && ( + (ip[start + 1] === '2' && ip[start + 2] === '8' && 8) || + (ip[start + 1] === '9' && ip[start + 2] === '2' && 7) + )) || + (ip[start] === '2' && ( + (ip[start + 1] === '2' && ip[start + 2] === '4' && 6) || + (ip[start + 1] === '4' && ( + (ip[start + 2] === '0' && 5) || + (ip[start + 2] === '8' && 4) + )) || + (ip[start + 1] === '5' && ( + (ip[start + 2] === '2' && 3) || + (ip[start + 2] === '4' && 2) || + (ip[start + 2] === '5' && 1) + )) + )) + )) + ) || 0 +} + +module.exports = { + prefixLengthFromSubnetMask +} diff --git a/package.json b/package.json index 66fb887..e0ea164 100644 --- a/package.json +++ b/package.json @@ -34,8 +34,10 @@ "scripts": { "bench": "node benchmark/index.js", "lint": "standard", + "lint:fix": "standard --fix", "test": "npm run test:unit && npm run test:typescript", "test:typescript": "tsd", - "test:unit": "tap" + "test:unit": "tap", + "test:coverage": "tap --coverage-report=html" } } diff --git a/test/.eslintrc.yml b/test/.eslintrc.yml deleted file mode 100644 index 9808c3b..0000000 --- a/test/.eslintrc.yml +++ /dev/null @@ -1,2 +0,0 @@ -env: - mocha: true diff --git a/test/all.js b/test/all.test.js similarity index 83% rename from test/all.js rename to test/all.test.js index 1942dc4..cc7cba6 100644 --- a/test/all.js +++ b/test/all.test.js @@ -8,7 +8,7 @@ test('argument req should be required', function (t) { t.end() }) -test('argument trustshould be optional', function (t) { +test('argument trust should be optional', function (t) { const req = createReq('127.0.0.1') t.doesNotThrow(proxyaddr.all.bind(null, req)) t.end() @@ -28,6 +28,14 @@ test('with x-forwarded-for header should include x-forwarded-for', function (t) t.end() }) +test('with x-forwarded-for header should include x-forwarded-for, using trust function', function (t) { + const req = createReq('127.0.0.1', { + 'x-forwarded-for': '10.0.0.1' + }) + t.same(proxyaddr.all(req, () => true), ['127.0.0.1', '10.0.0.1']) + t.end() +}) + test('with x-forwarded-for header should include x-forwarded-for in correct order', function (t) { const req = createReq('127.0.0.1', { 'x-forwarded-for': '10.0.0.1, 10.0.0.2' diff --git a/test/base.js b/test/base.test.js similarity index 93% rename from test/base.js rename to test/base.test.js index 2a31bba..003cca4 100644 --- a/test/base.js +++ b/test/base.test.js @@ -123,7 +123,7 @@ test('trust should be invoked as trust(addr, i)', function (t) { t.end() }) -test('with all trusted should return socket address wtesth no headers', function (t) { +test('with all trusted should return socket address with no headers', function (t) { const req = createReq('127.0.0.1') t.equal(proxyaddr(req, all), '127.0.0.1') t.end() @@ -145,13 +145,13 @@ test('with all trusted should return furthest header value', function (t) { t.end() }) -test('with none trusted should return socket address wtesth no headers', function (t) { +test('with none trusted should return socket address with no headers', function (t) { const req = createReq('127.0.0.1') t.equal(proxyaddr(req, none), '127.0.0.1') t.end() }) -test('with none trusted should return socket address wtesth headers', function (t) { +test('with none trusted should return socket address with headers', function (t) { const req = createReq('127.0.0.1', { 'x-forwarded-for': '10.0.0.1, 10.0.0.2' }) @@ -159,7 +159,7 @@ test('with none trusted should return socket address wtesth headers', function ( t.end() }) -test('with some trusted should return socket address wtesth no headers', function (t) { +test('with some trusted should return socket address with no headers', function (t) { const req = createReq('127.0.0.1') t.equal(proxyaddr(req, trust10x), '127.0.0.1') t.end() @@ -197,7 +197,7 @@ test('with some trusted should not skip untrusted', function (t) { t.end() }) -test('when given array should accept ltesteral IP addresses', function (t) { +test('when given array should accept literal IP addresses', function (t) { const req = createReq('10.0.0.1', { 'x-forwarded-for': '192.168.0.1, 10.0.0.2' }) @@ -227,7 +227,7 @@ test('when array empty should return socket address ', function (t) { t.end() }) -test('when array empty should return socket address wtesth headers', function (t) { +test('when array empty should return socket address with headers', function (t) { const req = createReq('127.0.0.1', { 'x-forwarded-for': '10.0.0.1, 10.0.0.2' }) @@ -235,7 +235,7 @@ test('when array empty should return socket address wtesth headers', function (t t.end() }) -test('when given IPv4 addresses should accept ltesteral IP addresses', function (t) { +test('when given IPv4 addresses should accept literal IP addresses', function (t) { const req = createReq('10.0.0.1', { 'x-forwarded-for': '192.168.0.1, 10.0.0.2' }) @@ -259,7 +259,7 @@ test('when given IPv4 addresses should accept netmask notation', function (t) { t.end() }) -test('when given IPv6 addresses should accept ltesteral IP addresses', function (t) { +test('when given IPv6 addresses should accept literal IP addresses', function (t) { const req = createReq('fe80::1', { 'x-forwarded-for': '2002:c000:203::1, fe80::2' }) @@ -323,7 +323,7 @@ test('when IPv4-mapped IPv6 addresses should match CIDR notation for IPv4-mapped t.end() }) -test('when IPv4-mapped IPv6 addresses should match CIDR notation for IPv4-mapped address mixed wtesth IPv6 CIDR', function (t) { +test('when IPv4-mapped IPv6 addresses should match CIDR notation for IPv4-mapped address mixed with IPv6 CIDR', function (t) { const req = createReq('10.0.0.1', { 'x-forwarded-for': '192.168.0.1, 10.0.0.200' }) @@ -331,7 +331,7 @@ test('when IPv4-mapped IPv6 addresses should match CIDR notation for IPv4-mapped t.end() }) -test('when IPv4-mapped IPv6 addresses should match CIDR notation for IPv4-mapped address mixed wtesth IPv4 addresses', function (t) { +test('when IPv4-mapped IPv6 addresses should match CIDR notation for IPv4-mapped address mixed with IPv4 addresses', function (t) { const req = createReq('10.0.0.1', { 'x-forwarded-for': '192.168.0.1, 10.0.0.200' }) @@ -395,7 +395,7 @@ test('when socket address undefined should return undefined as address', functio t.end() }) -test('when socket address undefined should return undefined even wtesth trusted headers', function (t) { +test('when socket address undefined should return undefined even with trusted headers', function (t) { const req = createReq(undefined, { 'x-forwarded-for': '127.0.0.1, 10.0.0.1' }) diff --git a/test/compile.js b/test/compile.test.js similarity index 100% rename from test/compile.js rename to test/compile.test.js diff --git a/test/is-integer.test.js b/test/is-integer.test.js new file mode 100644 index 0000000..2896cf0 --- /dev/null +++ b/test/is-integer.test.js @@ -0,0 +1,13 @@ +'use strict' + +const { test } = require('tap') +const { isInteger } = require('../lib/is-integer.js') + +test('isInteger', function (t) { + t.plan(3) + + t.ok(isInteger('1')) + t.ok(isInteger('1337')) + + t.notOk(isInteger('1.1')) +}) diff --git a/test/is-ipv4-mapped-ipv6-address.test.js b/test/is-ipv4-mapped-ipv6-address.test.js new file mode 100644 index 0000000..c8b3ad0 --- /dev/null +++ b/test/is-ipv4-mapped-ipv6-address.test.js @@ -0,0 +1,35 @@ +'use strict' + +const { test } = require('tap') +const { isIPv4MappedIPv6Address } = require('../lib/is-ipv4-mapped-ipv6-address.js') + +test('isIPv4MappedIPv6Address', (t) => { + t.plan(23) + + // positive cases + t.equal(isIPv4MappedIPv6Address('::ffff:'), true) + t.equal(isIPv4MappedIPv6Address('::ffff:192.168.1.10'), true) + t.equal(isIPv4MappedIPv6Address('::ffff:101:101'), true) + t.equal(isIPv4MappedIPv6Address('0:0:0:0:0:ffff:0101:0101'), true) + t.equal(isIPv4MappedIPv6Address('0000:0000:0000:0000:0000:ffff:0101:0101'), true) + t.equal(isIPv4MappedIPv6Address('::8.8.8.8'), true) + t.equal(isIPv4MappedIPv6Address('0:0:0:0:0:ffff:0:0'), true) + t.equal(isIPv4MappedIPv6Address('::ffff:c0a8:101'), true) + t.equal(isIPv4MappedIPv6Address('::ffff:192.168.1.1'), true) + + // negative cases + t.equal(isIPv4MappedIPv6Address('0000:0000:0000:0000:ffff:ffff:0101:0101'), false) + t.equal(isIPv4MappedIPv6Address('0000:0000:0000:ffff:ffff:ffff:0101:0101'), false) + t.equal(isIPv4MappedIPv6Address('f000:0000:0000:ffff:ffff:ffff:0101:0101'), false) + t.equal(isIPv4MappedIPv6Address('2001:0db8:85a3:0000:0000:8a2e:0370:7334'), false) + t.equal(isIPv4MappedIPv6Address('2001:db8:ff:abc:def:123b:456c:78d'), false) + t.equal(isIPv4MappedIPv6Address('invalid'), false) + t.equal(isIPv4MappedIPv6Address(''), false) + t.equal(isIPv4MappedIPv6Address('::'), false) + t.equal(isIPv4MappedIPv6Address('::1'), false) + t.equal(isIPv4MappedIPv6Address('0:ff::'), false) + t.equal(isIPv4MappedIPv6Address('::ff:0'), false) + t.equal(isIPv4MappedIPv6Address('::ff:0:0'), false) + t.equal(isIPv4MappedIPv6Address('0:0:0:0:0:0:ffff:0'), false) + t.equal(isIPv4MappedIPv6Address('::ff:ff:0:0:0'), false) +}) diff --git a/test/prefix-length-from-subnet-mask.test.js b/test/prefix-length-from-subnet-mask.test.js new file mode 100644 index 0000000..ac5356e --- /dev/null +++ b/test/prefix-length-from-subnet-mask.test.js @@ -0,0 +1,56 @@ +'use strict' + +const { test } = require('tap') +const { prefixLengthFromSubnetMask } = require('../lib/prefix-length-from-subnet-mask') + +test('prefixLengthFromSubnetMask returns proper CIDR notation for standard IPv4 masks', (t) => { + t.plan(44) + + // positive cases + t.equal(prefixLengthFromSubnetMask('255.255.255.255'), 32) + t.equal(prefixLengthFromSubnetMask('255.255.255.254'), 31) + t.equal(prefixLengthFromSubnetMask('255.255.255.252'), 30) + t.equal(prefixLengthFromSubnetMask('255.255.255.248'), 29) + t.equal(prefixLengthFromSubnetMask('255.255.255.240'), 28) + t.equal(prefixLengthFromSubnetMask('255.255.255.224'), 27) + t.equal(prefixLengthFromSubnetMask('255.255.255.192'), 26) + t.equal(prefixLengthFromSubnetMask('255.255.255.128'), 25) + t.equal(prefixLengthFromSubnetMask('255.255.255.0'), 24) + t.equal(prefixLengthFromSubnetMask('255.255.254.0'), 23) + t.equal(prefixLengthFromSubnetMask('255.255.252.0'), 22) + t.equal(prefixLengthFromSubnetMask('255.255.248.0'), 21) + t.equal(prefixLengthFromSubnetMask('255.255.240.0'), 20) + t.equal(prefixLengthFromSubnetMask('255.255.224.0'), 19) + t.equal(prefixLengthFromSubnetMask('255.255.192.0'), 18) + t.equal(prefixLengthFromSubnetMask('255.255.128.0'), 17) + t.equal(prefixLengthFromSubnetMask('255.255.0.0'), 16) + t.equal(prefixLengthFromSubnetMask('255.254.0.0'), 15) + t.equal(prefixLengthFromSubnetMask('255.252.0.0'), 14) + t.equal(prefixLengthFromSubnetMask('255.248.0.0'), 13) + t.equal(prefixLengthFromSubnetMask('255.240.0.0'), 12) + t.equal(prefixLengthFromSubnetMask('255.224.0.0'), 11) + t.equal(prefixLengthFromSubnetMask('255.192.0.0'), 10) + t.equal(prefixLengthFromSubnetMask('255.128.0.0'), 9) + t.equal(prefixLengthFromSubnetMask('255.0.0.0'), 8) + t.equal(prefixLengthFromSubnetMask('254.0.0.0'), 7) + t.equal(prefixLengthFromSubnetMask('252.0.0.0'), 6) + t.equal(prefixLengthFromSubnetMask('248.0.0.0'), 5) + t.equal(prefixLengthFromSubnetMask('240.0.0.0'), 4) + t.equal(prefixLengthFromSubnetMask('224.0.0.0'), 3) + t.equal(prefixLengthFromSubnetMask('192.0.0.0'), 2) + t.equal(prefixLengthFromSubnetMask('128.0.0.0'), 1) + t.equal(prefixLengthFromSubnetMask('0.0.0.0'), 0) + + // negative cases + t.equal(prefixLengthFromSubnetMask('255.255.255.253'), null) + t.equal(prefixLengthFromSubnetMask('168.192.255.0'), null) + t.equal(prefixLengthFromSubnetMask('192.168.255.0'), null) + t.equal(prefixLengthFromSubnetMask('255.0.255.0'), null) + t.equal(prefixLengthFromSubnetMask('255.0.255.42'), null) + t.equal(prefixLengthFromSubnetMask('255.0.42.0'), null) + t.equal(prefixLengthFromSubnetMask('255.42.0.0'), null) + t.equal(prefixLengthFromSubnetMask('42.0.0.0'), null) + t.equal(prefixLengthFromSubnetMask('255.255.42.254'), null) + t.equal(prefixLengthFromSubnetMask('255.255.192.255'), null) + t.equal(prefixLengthFromSubnetMask('192.255.255.255'), null) +})