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