Skip to content

Commit de823fa

Browse files
committed
Added more details to NIC infos
1 parent 7df499d commit de823fa

File tree

3 files changed

+119
-25
lines changed

3 files changed

+119
-25
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "lup-system",
3-
"version": "1.5.2",
3+
"version": "1.5.3",
44
"description": "NodeJS library to retrieve system information and utilization.",
55
"main": "./lib/index",
66
"types": "./lib/index.d.ts",

src/__tests__/Net.test.ts

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,27 @@
11
import { start } from 'repl';
2-
import { isPortInUse, getNetworkInterfaces, stopNetworkUtilizationComputation, canConnect, isPortListendedOn } from '../net';
2+
import {
3+
isPortInUse,
4+
getNetworkInterfaces,
5+
stopNetworkUtilizationComputation,
6+
canConnect,
7+
isPortListendedOn,
8+
getPrimaryIp,
9+
} from '../net';
310
import net from 'net';
411

5-
6-
712
test('getNetworkInterfaces', async () => {
813
const nics = await getNetworkInterfaces();
9-
//console.log(nics); // TODO REMOVE
14+
console.log(JSON.stringify(nics, null, 2));
1015
expect(nics).toBeDefined();
1116
expect(nics.length).toBeGreaterThan(0);
1217
}, 10000);
1318

19+
test('getPrimaryIp()', async () => {
20+
const ip = await getPrimaryIp();
21+
console.log('Primary IP:', ip);
22+
expect(ip).toBeDefined();
23+
expect(ip).toMatch(/\d+\.\d+\.\d+\.\d+/);
24+
});
1425

1526
test('isPortInUse(12345)', async () => {
1627
const port = 12345;
@@ -31,11 +42,8 @@ test('isPortInUse(12345)', async () => {
3142
server.close();
3243
});
3344

34-
35-
3645
test('isPortListenedOn not affecting canConnect', async () => {
37-
for(let i=0; i < 10; i++){
38-
46+
for (let i = 0; i < 10; i++) {
3947
const canConn = await canConnect(12345);
4048
expect(canConn).toBe(false);
4149

@@ -44,7 +52,6 @@ test('isPortListenedOn not affecting canConnect', async () => {
4452
}
4553
});
4654

47-
4855
/*
4956
test('canConnect vs isPortListenedOn', async () => {
5057
const iterations = 50;

src/net.ts

Lines changed: 102 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { execCommand } from './utils';
2+
import dgram from 'dgram';
23
import fs from 'fs/promises';
34
import net from 'net';
45
import os from 'os';
@@ -34,19 +35,19 @@ export type NICInfo = {
3435

3536
/** If the address is internal (e.g. loopback). */
3637
internal: boolean;
37-
}[];
3838

39-
/** Status information about the network interface. */
40-
status: {
41-
/** Operational status of the interface (e.g. up, down). */
42-
operational: 'up' | 'down' | 'dormant' | 'notpresent' | 'lowerlayerdown' | 'testing' | 'unknown';
39+
/** If the address is publicly reachable. */
40+
public: boolean;
4341

44-
/** If the interface is enabled in the settings. */
45-
admin: boolean;
42+
/** If the address is the primary address for the system. */
43+
primary: boolean;
44+
}[];
4645

47-
/** If a network cable is plugged into the interface. */
48-
cable: boolean;
49-
};
46+
/**
47+
* Checks if the network interface is up, has a speed is defined,
48+
* and is either physical or has the primary address.
49+
*/
50+
primary: boolean;
5051

5152
/** If the interface is a physical (e.g. Ethernet) or virtual (e.g. Loopback, VPN, Hyper-V) interface. */
5253
physical: boolean;
@@ -66,6 +67,18 @@ export type NICInfo = {
6667
bytes: number;
6768
};
6869

70+
/** Status information about the network interface. */
71+
status: {
72+
/** Operational status of the interface (e.g. up, down). */
73+
operational: 'up' | 'down' | 'dormant' | 'notpresent' | 'lowerlayerdown' | 'testing' | 'unknown';
74+
75+
/** If the interface is enabled in the settings. */
76+
admin: boolean;
77+
78+
/** If a network cable is plugged into the interface. */
79+
cable: boolean;
80+
};
81+
6982
/**
7083
* Current link utilization in percentage (0.0-1.0) of the maximum bandwidth (speed).
7184
*
@@ -168,7 +181,6 @@ export function stopNetworkUtilizationComputation() {
168181
NET_COMPUTE_RUNNING = false;
169182
}
170183

171-
172184
/**
173185
* Tries to connect to a given server over TCP.
174186
*
@@ -188,6 +200,32 @@ export async function canConnect(port: number, host: string = '127.0.0.1'): Prom
188200
});
189201
}
190202

203+
/**
204+
* Returns the primary IP address that is configured and
205+
* which is most likely to also be the public IP.
206+
*
207+
* @returns Primary IP address.
208+
*/
209+
export async function getPrimaryIp(): Promise<string> {
210+
return new Promise((resolve, reject) => {
211+
const socket = dgram.createSocket('udp4');
212+
// Destination doesn't need to be reachable – just needs routing
213+
socket.connect(80, '8.8.8.8', () => {
214+
try {
215+
const address = socket.address();
216+
socket.close();
217+
if (typeof address === 'string') {
218+
reject(new Error('Unexpected address format'));
219+
} else {
220+
resolve(address.address); // local IP used for routing
221+
}
222+
} catch (err) {
223+
reject(err);
224+
}
225+
});
226+
});
227+
}
228+
191229
/**
192230
* Checks if a process is listening on a given port.
193231
*
@@ -204,7 +242,7 @@ export async function isPortListendedOn(port: number, bindAddress: string = '0.0
204242
server.unref();
205243
server.once('error', (err) => {
206244
server.close();
207-
if((err as any).code === 'EADDRINUSE') {
245+
if ((err as any).code === 'EADDRINUSE') {
208246
resolve(true);
209247
} else {
210248
resolve(false);
@@ -217,7 +255,6 @@ export async function isPortListendedOn(port: number, bindAddress: string = '0.0
217255
});
218256
}
219257

220-
221258
/**
222259
* Uses isPortListenedOn() and canConnect() to determine if a port is in use.
223260
* @param port Port number to check.
@@ -226,15 +263,49 @@ export async function isPortListendedOn(port: number, bindAddress: string = '0.0
226263
export async function isPortInUse(port: number, bindAddress: string = '0.0.0.0'): Promise<boolean> {
227264
// check first if port can be listened on (way faster if port is really used because just a kernel check needed).
228265
const isListenedOn = await isPortListendedOn(port, bindAddress);
229-
if(isListenedOn) return true;
266+
if (isListenedOn) return true;
230267

231268
// as backup check if can connect
232269
const canConn = await canConnect(port, bindAddress);
233-
if(canConn) return true;
270+
if (canConn) return true;
271+
272+
return false;
273+
}
274+
275+
/**
276+
* Checks if the given IPv4 or IPv6 address is a public IP address.
277+
* @param ip The IP address to check.
278+
* @returns True if the IP address is public, false otherwise.
279+
*/
280+
export function isPublicIp(ip: string): boolean {
281+
if (!ip) return false;
282+
283+
const ipv4Parts = ip.split('.');
284+
if (ipv4Parts.length === 4) {
285+
const [a, b] = ipv4Parts.map(Number);
286+
// Check for private IP ranges
287+
return !(a === 10 || a === 127 || (a === 172 && b >= 16 && b <= 31) || (a === 192 && b === 168));
288+
}
289+
290+
const ipv6Parts = ip.split(':');
291+
if (ipv6Parts.length > 2) {
292+
const [first, second] = ipv6Parts;
293+
// Check for unique IPv6 ranges
294+
return !(
295+
first === '::1' || // Loopback
296+
first === 'fc00' ||
297+
first === 'fd00' || // Unique Local Addresses
298+
(first === 'fe80' && second === '::') // Link-Local
299+
);
300+
}
234301

235302
return false;
236303
}
237304

305+
export async function getRealNetworkInterfaces(): Promise<NICInfo[]> {
306+
const allNics = await getNetworkInterfaces();
307+
return allNics.filter((nic) => true);
308+
}
238309

239310
/**
240311
* Returns information about the network interfaces on the system.
@@ -246,6 +317,7 @@ export async function getNetworkInterfaces(): Promise<NICInfo[]> {
246317
await runNetComputeInterval(); // runs the first computation immediately
247318
await computeNetworkUtilization(); // run second computation immediately to get initial values
248319
}
320+
const primaryIp = await getPrimaryIp();
249321

250322
const nics: { [name: string]: NICInfo } = {};
251323
for (const [name, addresses] of Object.entries(os.networkInterfaces())) {
@@ -259,7 +331,10 @@ export async function getNetworkInterfaces(): Promise<NICInfo[]> {
259331
netmask: addr.netmask,
260332
cidr: addr.cidr,
261333
internal: addr.internal,
334+
public: isPublicIp(addr.address),
335+
primary: addr.address === primaryIp,
262336
})) || [],
337+
primary: false,
263338
status: {
264339
operational: 'unknown',
265340
admin: true, // Default to true, will be updated based on platform
@@ -328,6 +403,7 @@ export async function getNetworkInterfaces(): Promise<NICInfo[]> {
328403
nics[currNic] = {
329404
name: currNic,
330405
addresses: [],
406+
primary: false,
331407
status: {
332408
operational: 'unknown',
333409
admin: true, // Default to true, will be updated based on platform
@@ -370,5 +446,16 @@ export async function getNetworkInterfaces(): Promise<NICInfo[]> {
370446
break;
371447
}
372448
}
449+
450+
// post process
451+
for (const key of Object.keys(nics)) {
452+
nics[key].primary =
453+
nics[key].status.operational === 'up' &&
454+
(nics[key].physical || nics[key].addresses.find((addr) => addr.primary)) &&
455+
nics[key].speed
456+
? true
457+
: false;
458+
}
459+
373460
return Object.values(nics);
374461
}

0 commit comments

Comments
 (0)