Mirror: Best-effort discovery of the machine's local network using just Node.js dgram sockets
1import { networkInterfaces } from 'node:os';
2import type { GatewayAssignment, NetworkAssignment } from './types';
3
4export const parseMacStr = (macStr: string): number[] =>
5 macStr
6 .split(':')
7 .slice(0, 16)
8 .map(seq => parseInt(seq, 16));
9
10export const parseIpStr = (ipStr: string): number => {
11 const addr = ipStr
12 .split('.')
13 .slice(0, 4)
14 .map(seq => parseInt(seq, 10));
15 return addr[3] | (addr[2] << 8) | (addr[1] << 16) | (addr[0] << 24);
16};
17
18export const toIpStr = (addr: number): string => {
19 const MASK = (1 << 8) - 1;
20 let ipStr = '';
21 ipStr += `${((addr >>> 24) & MASK).toString(10)}.`;
22 ipStr += `${((addr >>> 16) & MASK).toString(10)}.`;
23 ipStr += `${((addr >>> 8) & MASK).toString(10)}.`;
24 ipStr += (addr & MASK).toString(10);
25 return ipStr;
26};
27
28const getSubnetPriority = (addr: string): number => {
29 if (addr.startsWith('192.')) return 5;
30 else if (addr.startsWith('172.')) return 4;
31 else if (addr.startsWith('10.')) return 3;
32 else if (addr.startsWith('100.')) return 2;
33 else if (addr.startsWith('127.')) return 1;
34 else return 0;
35};
36
37export const interfaceAssignments = (): NetworkAssignment[] => {
38 const candidates: NetworkAssignment[] = [];
39 const interfaces = networkInterfaces();
40 for (const iname in interfaces) {
41 const assignments = interfaces[iname];
42 if (!assignments) continue;
43 for (const assignment of assignments) {
44 if (assignment.family !== 'IPv4') continue;
45 candidates.push({ ...assignment, iname });
46 }
47 }
48 return candidates.sort((a, b) => {
49 const priorityA = getSubnetPriority(a.address);
50 const priorityB = getSubnetPriority(b.address);
51 // Prioritise external interfaces, then sort by priority,
52 // when priority is equal, sort by raw IP values
53 const sortBy =
54 +a.internal - +b.internal ||
55 priorityB - priorityA ||
56 parseIpStr(b.address) - parseIpStr(a.address);
57 return sortBy;
58 });
59};
60
61export const matchAssignment = (
62 candidates: NetworkAssignment[],
63 addr: string
64): GatewayAssignment | null => {
65 const rawAddr = parseIpStr(addr);
66 for (const candidate of candidates) {
67 const candidateAddr = parseIpStr(candidate.address);
68 if (rawAddr === candidateAddr) return { ...candidate, gateway: null };
69 const mask = parseIpStr(candidate.netmask);
70 if ((rawAddr & mask) === (candidateAddr & mask))
71 return { ...candidate, gateway: addr };
72 }
73 return null;
74};