summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/api/os.md15
-rw-r--r--lib/internal/os.js41
-rw-r--r--lib/os.js18
-rw-r--r--node.gyp1
-rw-r--r--test/parallel/test-internal-os.js32
-rw-r--r--test/parallel/test-os.js25
6 files changed, 125 insertions, 7 deletions
diff --git a/doc/api/os.md b/doc/api/os.md
index 3b4b9526cb..9dc0fcf308 100644
--- a/doc/api/os.md
+++ b/doc/api/os.md
@@ -253,6 +253,9 @@ The properties available on the assigned network address object include:
similar interface that is not remotely accessible; otherwise `false`
* `scopeid` {number} The numeric IPv6 scope ID (only specified when `family`
is `IPv6`)
+* `cidr` {string} The assigned IPv4 or IPv6 address with the routing prefix
+ in CIDR notation. If the `netmask` is invalid, this property is set
+ to `null`
<!-- eslint-skip -->
```js
@@ -263,14 +266,16 @@ The properties available on the assigned network address object include:
netmask: '255.0.0.0',
family: 'IPv4',
mac: '00:00:00:00:00:00',
- internal: true
+ internal: true,
+ cidr: '127.0.0.1/8'
},
{
address: '::1',
netmask: 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff',
family: 'IPv6',
mac: '00:00:00:00:00:00',
- internal: true
+ internal: true,
+ cidr: '::1/128'
}
],
eth0: [
@@ -279,14 +284,16 @@ The properties available on the assigned network address object include:
netmask: '255.255.255.0',
family: 'IPv4',
mac: '01:02:03:0a:0b:0c',
- internal: false
+ internal: false,
+ cidr: '192.168.1.108/24'
},
{
address: 'fe80::a00:27ff:fe4e:66a1',
netmask: 'ffff:ffff:ffff:ffff::',
family: 'IPv6',
mac: '01:02:03:0a:0b:0c',
- internal: false
+ internal: false,
+ cidr: 'fe80::a00:27ff:fe4e:66a1/64'
}
]
}
diff --git a/lib/internal/os.js b/lib/internal/os.js
new file mode 100644
index 0000000000..74ed6e767e
--- /dev/null
+++ b/lib/internal/os.js
@@ -0,0 +1,41 @@
+'use strict';
+
+function getCIDRSuffix(mask, protocol = 'ipv4') {
+ const isV6 = protocol === 'ipv6';
+ const bitsString = mask
+ .split(isV6 ? ':' : '.')
+ .filter((v) => !!v)
+ .map((v) => pad(parseInt(v, isV6 ? 16 : 10).toString(2), isV6))
+ .join('');
+
+ if (isValidMask(bitsString)) {
+ return countOnes(bitsString);
+ } else {
+ return null;
+ }
+}
+
+function pad(binaryString, isV6) {
+ const groupLength = isV6 ? 16 : 8;
+ const binLen = binaryString.length;
+
+ return binLen < groupLength ?
+ `${'0'.repeat(groupLength - binLen)}${binaryString}` : binaryString;
+}
+
+function isValidMask(bitsString) {
+ const firstIndexOfZero = bitsString.indexOf(0);
+ const lastIndexOfOne = bitsString.lastIndexOf(1);
+
+ return firstIndexOfZero < 0 || firstIndexOfZero > lastIndexOfOne;
+}
+
+function countOnes(bitsString) {
+ return bitsString
+ .split('')
+ .reduce((acc, bit) => acc += parseInt(bit, 10), 0);
+}
+
+module.exports = {
+ getCIDRSuffix
+};
diff --git a/lib/os.js b/lib/os.js
index 4a99cab81e..078dba3fcc 100644
--- a/lib/os.js
+++ b/lib/os.js
@@ -24,6 +24,7 @@
const pushValToArrayMax = process.binding('util').pushValToArrayMax;
const constants = process.binding('constants').os;
const deprecate = require('internal/util').deprecate;
+const getCIDRSuffix = require('internal/os').getCIDRSuffix;
const isWindows = process.platform === 'win32';
const {
@@ -121,6 +122,21 @@ function endianness() {
}
endianness[Symbol.toPrimitive] = () => kEndianness;
+function networkInterfaces() {
+ const interfaceAddresses = getInterfaceAddresses();
+
+ return Object.entries(interfaceAddresses).reduce((acc, [key, val]) => {
+ acc[key] = val.map((v) => {
+ const protocol = v.family.toLowerCase();
+ const suffix = getCIDRSuffix(v.netmask, protocol);
+ const cidr = suffix ? `${v.address}/${suffix}` : null;
+
+ return Object.assign({}, v, { cidr });
+ });
+ return acc;
+ }, {});
+}
+
module.exports = exports = {
arch,
cpus,
@@ -130,7 +146,7 @@ module.exports = exports = {
homedir: getHomeDirectory,
hostname: getHostname,
loadavg,
- networkInterfaces: getInterfaceAddresses,
+ networkInterfaces,
platform,
release: getOSRelease,
tmpdir,
diff --git a/node.gyp b/node.gyp
index 81f549f8b6..040e38223d 100644
--- a/node.gyp
+++ b/node.gyp
@@ -91,6 +91,7 @@
'lib/internal/linkedlist.js',
'lib/internal/net.js',
'lib/internal/module.js',
+ 'lib/internal/os.js',
'lib/internal/process/next_tick.js',
'lib/internal/process/promises.js',
'lib/internal/process/stdio.js',
diff --git a/test/parallel/test-internal-os.js b/test/parallel/test-internal-os.js
new file mode 100644
index 0000000000..c4014abc5b
--- /dev/null
+++ b/test/parallel/test-internal-os.js
@@ -0,0 +1,32 @@
+// Flags: --expose-internals
+'use strict';
+
+require('../common');
+const assert = require('assert');
+const getCIDRSuffix = require('internal/os').getCIDRSuffix;
+
+const specs = [
+ // valid
+ ['128.0.0.0', 'ipv4', 1],
+ ['255.0.0.0', 'ipv4', 8],
+ ['255.255.255.128', 'ipv4', 25],
+ ['255.255.255.255', 'ipv4', 32],
+ ['ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff', 'ipv6', 128],
+ ['ffff:ffff:ffff:ffff::', 'ipv6', 64],
+ ['ffff:ffff:ffff:ff80::', 'ipv6', 57],
+ // invalid
+ ['255.0.0.1', 'ipv4', null],
+ ['255.255.9.0', 'ipv4', null],
+ ['255.255.1.0', 'ipv4', null],
+ ['ffff:ffff:43::', 'ipv6', null],
+ ['ffff:ffff:ffff:1::', 'ipv6', null]
+];
+
+specs.forEach(([mask, protocol, expectedSuffix]) => {
+ const actualSuffix = getCIDRSuffix(mask, protocol);
+
+ assert.strictEqual(
+ actualSuffix, expectedSuffix,
+ `Mask: ${mask}, expected: ${expectedSuffix}, actual: ${actualSuffix}`
+ );
+});
diff --git a/test/parallel/test-os.js b/test/parallel/test-os.js
index 180d869001..afff23b2e4 100644
--- a/test/parallel/test-os.js
+++ b/test/parallel/test-os.js
@@ -24,6 +24,7 @@ const common = require('../common');
const assert = require('assert');
const os = require('os');
const path = require('path');
+const { inspect } = require('util');
const is = {
string: (value) => { assert.strictEqual(typeof value, 'string'); },
@@ -121,7 +122,7 @@ switch (platform) {
const actual = interfaces.lo.filter(filter);
const expected = [{ address: '127.0.0.1', netmask: '255.0.0.0',
mac: '00:00:00:00:00:00', family: 'IPv4',
- internal: true }];
+ internal: true, cidr: '127.0.0.1/8' }];
assert.deepStrictEqual(actual, expected);
break;
}
@@ -131,11 +132,31 @@ switch (platform) {
const actual = interfaces['Loopback Pseudo-Interface 1'].filter(filter);
const expected = [{ address: '127.0.0.1', netmask: '255.0.0.0',
mac: '00:00:00:00:00:00', family: 'IPv4',
- internal: true }];
+ internal: true, cidr: '127.0.0.1/8' }];
assert.deepStrictEqual(actual, expected);
break;
}
}
+function flatten(arr) {
+ return arr.reduce(
+ (acc, c) => acc.concat(Array.isArray(c) ? flatten(c) : c),
+ []
+ );
+}
+const netmaskToCIDRSuffixMap = new Map(Object.entries({
+ '255.0.0.0': 8,
+ '255.255.255.0': 24,
+ 'ffff:ffff:ffff:ffff::': 64,
+ 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff': 128
+}));
+flatten(Object.values(interfaces))
+ .map((v) => ({ v, mask: netmaskToCIDRSuffixMap.get(v.netmask) }))
+ .forEach(({ v, mask }) => {
+ assert.ok('cidr' in v, `"cidr" prop not found in ${inspect(v)}`);
+ if (mask) {
+ assert.strictEqual(v.cidr, `${v.address}/${mask}`);
+ }
+ });
const EOL = os.EOL;
assert.ok(EOL.length > 0);