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
48/** Determines if an assignment is internal (indicated by the flag or by a zeroed mac address) */
49export const isInternal = (assignment: NetworkAssignment) =>
50 assignment.internal || parseMacStr(assignment.mac).every(x => !x);
51
52export const interfaceAssignments = (): NetworkAssignment[] => {
53 const candidates: NetworkAssignment[] = [];
54 const interfaces = os.networkInterfaces();
55 for (const iname in interfaces) {
56 const assignments = interfaces[iname];
57 if (!assignments) continue;
58 for (const assignment of assignments) {
59 if (assignment.family !== 'IPv4') continue;
60 candidates.push({ ...assignment, iname });
61 }
62 }
63 return candidates.sort((a, b) => {
64 const priorityA = getSubnetPriority(a.address);
65 const priorityB = getSubnetPriority(b.address);
66 // Prioritise external interfaces, then sort by priority,
67 // when priority is equal, sort by raw IP values
68 const sortBy =
69 +isInternal(a) - +isInternal(b) ||
70 priorityB - priorityA ||
71 parseIpStr(b.address) - parseIpStr(a.address);
72 return sortBy;
73 });
74};
75
76export const matchAssignment = (
77 candidates: NetworkAssignment[],
78 addr: string
79): GatewayAssignment | null => {
80 const rawAddr = parseIpStr(addr);
81 for (const candidate of candidates) {
82 const candidateAddr = parseIpStr(candidate.address);
83 if (rawAddr === candidateAddr) return { ...candidate, gateway: null };
84 const mask = parseIpStr(candidate.netmask);
85 if ((rawAddr & mask) === (candidateAddr & mask))
86 return { ...candidate, gateway: addr };
87 }
88 return null;
89};