Skip to content

Commit 136eea4

Browse files
zeusdeuxMylesBorins
authored andcommitted
os: add CIDR support
This patch adds support for CIDR notation to the output of the `networkInterfaces()` method PR-URL: #14307 Fixes: #14006 Reviewed-By: Roman Reiss <[email protected]> Reviewed-By: James M Snell <[email protected]> Reviewed-By: Refael Ackermann <[email protected]> Reviewed-By: Timothy Gu <[email protected]>
1 parent 4205648 commit 136eea4

File tree

6 files changed

+125
-7
lines changed

6 files changed

+125
-7
lines changed

doc/api/os.md

+11-4
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,9 @@ The properties available on the assigned network address object include:
253253
similar interface that is not remotely accessible; otherwise `false`
254254
* `scopeid` {number} The numeric IPv6 scope ID (only specified when `family`
255255
is `IPv6`)
256+
* `cidr` {string} The assigned IPv4 or IPv6 address with the routing prefix
257+
in CIDR notation. If the `netmask` is invalid, this property is set
258+
to `null`
256259

257260
<!-- eslint-skip -->
258261
```js
@@ -263,14 +266,16 @@ The properties available on the assigned network address object include:
263266
netmask: '255.0.0.0',
264267
family: 'IPv4',
265268
mac: '00:00:00:00:00:00',
266-
internal: true
269+
internal: true,
270+
cidr: '127.0.0.1/8'
267271
},
268272
{
269273
address: '::1',
270274
netmask: 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff',
271275
family: 'IPv6',
272276
mac: '00:00:00:00:00:00',
273-
internal: true
277+
internal: true,
278+
cidr: '::1/128'
274279
}
275280
],
276281
eth0: [
@@ -279,14 +284,16 @@ The properties available on the assigned network address object include:
279284
netmask: '255.255.255.0',
280285
family: 'IPv4',
281286
mac: '01:02:03:0a:0b:0c',
282-
internal: false
287+
internal: false,
288+
cidr: '192.168.1.108/24'
283289
},
284290
{
285291
address: 'fe80::a00:27ff:fe4e:66a1',
286292
netmask: 'ffff:ffff:ffff:ffff::',
287293
family: 'IPv6',
288294
mac: '01:02:03:0a:0b:0c',
289-
internal: false
295+
internal: false,
296+
cidr: 'fe80::a00:27ff:fe4e:66a1/64'
290297
}
291298
]
292299
}

lib/internal/os.js

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
'use strict';
2+
3+
function getCIDRSuffix(mask, protocol = 'ipv4') {
4+
const isV6 = protocol === 'ipv6';
5+
const bitsString = mask
6+
.split(isV6 ? ':' : '.')
7+
.filter((v) => !!v)
8+
.map((v) => pad(parseInt(v, isV6 ? 16 : 10).toString(2), isV6))
9+
.join('');
10+
11+
if (isValidMask(bitsString)) {
12+
return countOnes(bitsString);
13+
} else {
14+
return null;
15+
}
16+
}
17+
18+
function pad(binaryString, isV6) {
19+
const groupLength = isV6 ? 16 : 8;
20+
const binLen = binaryString.length;
21+
22+
return binLen < groupLength ?
23+
`${'0'.repeat(groupLength - binLen)}${binaryString}` : binaryString;
24+
}
25+
26+
function isValidMask(bitsString) {
27+
const firstIndexOfZero = bitsString.indexOf(0);
28+
const lastIndexOfOne = bitsString.lastIndexOf(1);
29+
30+
return firstIndexOfZero < 0 || firstIndexOfZero > lastIndexOfOne;
31+
}
32+
33+
function countOnes(bitsString) {
34+
return bitsString
35+
.split('')
36+
.reduce((acc, bit) => acc += parseInt(bit, 10), 0);
37+
}
38+
39+
module.exports = {
40+
getCIDRSuffix
41+
};

lib/os.js

+17-1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
const pushValToArrayMax = process.binding('util').pushValToArrayMax;
2525
const constants = process.binding('constants').os;
2626
const deprecate = require('internal/util').deprecate;
27+
const getCIDRSuffix = require('internal/os').getCIDRSuffix;
2728
const isWindows = process.platform === 'win32';
2829

2930
const {
@@ -121,6 +122,21 @@ function endianness() {
121122
}
122123
endianness[Symbol.toPrimitive] = () => kEndianness;
123124

125+
function networkInterfaces() {
126+
const interfaceAddresses = getInterfaceAddresses();
127+
128+
return Object.entries(interfaceAddresses).reduce((acc, [key, val]) => {
129+
acc[key] = val.map((v) => {
130+
const protocol = v.family.toLowerCase();
131+
const suffix = getCIDRSuffix(v.netmask, protocol);
132+
const cidr = suffix ? `${v.address}/${suffix}` : null;
133+
134+
return Object.assign({}, v, { cidr });
135+
});
136+
return acc;
137+
}, {});
138+
}
139+
124140
module.exports = exports = {
125141
arch,
126142
cpus,
@@ -130,7 +146,7 @@ module.exports = exports = {
130146
homedir: getHomeDirectory,
131147
hostname: getHostname,
132148
loadavg,
133-
networkInterfaces: getInterfaceAddresses,
149+
networkInterfaces,
134150
platform,
135151
release: getOSRelease,
136152
tmpdir,

node.gyp

+1
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@
9191
'lib/internal/linkedlist.js',
9292
'lib/internal/net.js',
9393
'lib/internal/module.js',
94+
'lib/internal/os.js',
9495
'lib/internal/process/next_tick.js',
9596
'lib/internal/process/promises.js',
9697
'lib/internal/process/stdio.js',

test/parallel/test-internal-os.js

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// Flags: --expose-internals
2+
'use strict';
3+
4+
require('../common');
5+
const assert = require('assert');
6+
const getCIDRSuffix = require('internal/os').getCIDRSuffix;
7+
8+
const specs = [
9+
// valid
10+
['128.0.0.0', 'ipv4', 1],
11+
['255.0.0.0', 'ipv4', 8],
12+
['255.255.255.128', 'ipv4', 25],
13+
['255.255.255.255', 'ipv4', 32],
14+
['ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff', 'ipv6', 128],
15+
['ffff:ffff:ffff:ffff::', 'ipv6', 64],
16+
['ffff:ffff:ffff:ff80::', 'ipv6', 57],
17+
// invalid
18+
['255.0.0.1', 'ipv4', null],
19+
['255.255.9.0', 'ipv4', null],
20+
['255.255.1.0', 'ipv4', null],
21+
['ffff:ffff:43::', 'ipv6', null],
22+
['ffff:ffff:ffff:1::', 'ipv6', null]
23+
];
24+
25+
specs.forEach(([mask, protocol, expectedSuffix]) => {
26+
const actualSuffix = getCIDRSuffix(mask, protocol);
27+
28+
assert.strictEqual(
29+
actualSuffix, expectedSuffix,
30+
`Mask: ${mask}, expected: ${expectedSuffix}, actual: ${actualSuffix}`
31+
);
32+
});

test/parallel/test-os.js

+23-2
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ const common = require('../common');
2424
const assert = require('assert');
2525
const os = require('os');
2626
const path = require('path');
27+
const { inspect } = require('util');
2728

2829
const is = {
2930
string: (value) => { assert.strictEqual(typeof value, 'string'); },
@@ -121,7 +122,7 @@ switch (platform) {
121122
const actual = interfaces.lo.filter(filter);
122123
const expected = [{ address: '127.0.0.1', netmask: '255.0.0.0',
123124
mac: '00:00:00:00:00:00', family: 'IPv4',
124-
internal: true }];
125+
internal: true, cidr: '127.0.0.1/8' }];
125126
assert.deepStrictEqual(actual, expected);
126127
break;
127128
}
@@ -131,11 +132,31 @@ switch (platform) {
131132
const actual = interfaces['Loopback Pseudo-Interface 1'].filter(filter);
132133
const expected = [{ address: '127.0.0.1', netmask: '255.0.0.0',
133134
mac: '00:00:00:00:00:00', family: 'IPv4',
134-
internal: true }];
135+
internal: true, cidr: '127.0.0.1/8' }];
135136
assert.deepStrictEqual(actual, expected);
136137
break;
137138
}
138139
}
140+
function flatten(arr) {
141+
return arr.reduce(
142+
(acc, c) => acc.concat(Array.isArray(c) ? flatten(c) : c),
143+
[]
144+
);
145+
}
146+
const netmaskToCIDRSuffixMap = new Map(Object.entries({
147+
'255.0.0.0': 8,
148+
'255.255.255.0': 24,
149+
'ffff:ffff:ffff:ffff::': 64,
150+
'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff': 128
151+
}));
152+
flatten(Object.values(interfaces))
153+
.map((v) => ({ v, mask: netmaskToCIDRSuffixMap.get(v.netmask) }))
154+
.forEach(({ v, mask }) => {
155+
assert.ok('cidr' in v, `"cidr" prop not found in ${inspect(v)}`);
156+
if (mask) {
157+
assert.strictEqual(v.cidr, `${v.address}/${mask}`);
158+
}
159+
});
139160

140161
const EOL = os.EOL;
141162
assert.ok(EOL.length > 0);

0 commit comments

Comments
 (0)