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 isSameSubnet = (
30 addrA: string,
31 addrB: string,
32 netmask: string
33): boolean => {
34 const rawAddrA = parseIpStr(addrA);
35 const rawAddrB = parseIpStr(addrB);
36 const rawMask = parseIpStr(netmask);
37 return (rawAddrA & rawMask) === (rawAddrB & rawMask);
38};
39
40export const toIpStr = (addr: number): string => {
41 const MASK = (1 << 8) - 1;
42 let ipStr = '';
43 ipStr += `${((addr >>> 24) & MASK).toString(10)}.`;
44 ipStr += `${((addr >>> 16) & MASK).toString(10)}.`;
45 ipStr += `${((addr >>> 8) & MASK).toString(10)}.`;
46 ipStr += (addr & MASK).toString(10);
47 return ipStr;
48};
49
50const getSubnetPriority = (addr: string): number => {
51 if (addr.startsWith('192.')) return 5;
52 else if (addr.startsWith('172.')) return 4;
53 else if (addr.startsWith('10.')) return 3;
54 else if (addr.startsWith('100.')) return 2;
55 else if (addr.startsWith('127.')) return 1;
56 else return 0;
57};
58
59/** Determines if an assignment is internal (indicated by the flag or by a zeroed mac address) */
60export const isInternal = (assignment: NetworkAssignment) => {
61 if (assignment.internal) {
62 return true;
63 }
64 const mac = parseMacStr(assignment.mac);
65 if (mac.every(x => !x)) {
66 return true;
67 } else if (mac[0] === 0 && mac[1] === 21 && mac[2] === 93) {
68 // NOTE(@kitten): Microsoft virtual interface
69 return true;
70 } else if (assignment.iname.includes('vEthernet')) {
71 // NOTE(@kitten): Other Windows virtual interfaces
72 return true;
73 } else {
74 return false;
75 }
76};
77
78export const interfaceAssignments = (): NetworkAssignment[] => {
79 const candidates: NetworkAssignment[] = [];
80 const interfaces = os.networkInterfaces();
81 for (const iname in interfaces) {
82 const assignments = interfaces[iname];
83 if (!assignments) continue;
84 for (const assignment of assignments) {
85 if (assignment.family !== 'IPv4') continue;
86 candidates.push({ ...assignment, iname });
87 }
88 }
89 return candidates.sort((a, b) => {
90 const priorityA = getSubnetPriority(a.address);
91 const priorityB = getSubnetPriority(b.address);
92 // Prioritise external interfaces, then sort by priority,
93 // when priority is equal, sort by raw IP values
94 const sortBy =
95 +isInternal(a) - +isInternal(b) ||
96 priorityB - priorityA ||
97 parseIpStr(b.address) - parseIpStr(a.address);
98 return sortBy;
99 });
100};
101
102export const matchAssignment = (
103 candidates: NetworkAssignment[],
104 addr: string
105): GatewayAssignment | null => {
106 const rawAddr = parseIpStr(addr);
107 for (const candidate of candidates) {
108 const candidateAddr = parseIpStr(candidate.address);
109 if (rawAddr === candidateAddr) return { ...candidate, gateway: null };
110 const mask = parseIpStr(candidate.netmask);
111 if ((rawAddr & mask) === (candidateAddr & mask))
112 return { ...candidate, gateway: addr };
113 }
114 return null;
115};