Mirror: Best-effort discovery of the machine's local network using just Node.js dgram sockets
at v0.1.6 3.2 kB view raw
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};