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 if (assignment.internal) {
51 return true;
52 }
53 const mac = parseMacStr(assignment.mac);
54 if (mac.every(x => !x)) {
55 return true;
56 } else if (mac[0] === 0 && mac[1] === 21 && mac[2] === 93) {
57 // NOTE(@kitten): Microsoft virtual interface
58 return true;
59 } else if (assignment.iname.includes('vEthernet')) {
60 // NOTE(@kitten): Other Windows virtual interfaces
61 return true;
62 } else {
63 return false;
64 }
65};
66
67export const interfaceAssignments = (): NetworkAssignment[] => {
68 const candidates: NetworkAssignment[] = [];
69 const interfaces = os.networkInterfaces();
70 for (const iname in interfaces) {
71 const assignments = interfaces[iname];
72 if (!assignments) continue;
73 for (const assignment of assignments) {
74 if (assignment.family !== 'IPv4') continue;
75 candidates.push({ ...assignment, iname });
76 }
77 }
78 return candidates.sort((a, b) => {
79 const priorityA = getSubnetPriority(a.address);
80 const priorityB = getSubnetPriority(b.address);
81 // Prioritise external interfaces, then sort by priority,
82 // when priority is equal, sort by raw IP values
83 const sortBy =
84 +isInternal(a) - +isInternal(b) ||
85 priorityB - priorityA ||
86 parseIpStr(b.address) - parseIpStr(a.address);
87 return sortBy;
88 });
89};
90
91export const matchAssignment = (
92 candidates: NetworkAssignment[],
93 addr: string
94): GatewayAssignment | null => {
95 const rawAddr = parseIpStr(addr);
96 for (const candidate of candidates) {
97 const candidateAddr = parseIpStr(candidate.address);
98 if (rawAddr === candidateAddr) return { ...candidate, gateway: null };
99 const mask = parseIpStr(candidate.netmask);
100 if ((rawAddr & mask) === (candidateAddr & mask))
101 return { ...candidate, gateway: addr };
102 }
103 return null;
104};