Mirror: Best-effort discovery of the machine's local network using just Node.js dgram sockets
at main 3.6 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 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 ( 71 assignment.iname.includes('vEthernet') || 72 /^bridge\d+$/.test(assignment.iname) 73 ) { 74 // NOTE(@kitten): Other Windows virtual interfaces, or Linux bridge interfaces 75 return true; 76 } else { 77 return false; 78 } 79}; 80 81export const interfaceAssignments = (): NetworkAssignment[] => { 82 const candidates: NetworkAssignment[] = []; 83 const interfaces = os.networkInterfaces(); 84 for (const iname in interfaces) { 85 const assignments = interfaces[iname]; 86 if (!assignments) continue; 87 for (const assignment of assignments) { 88 if (assignment.family !== 'IPv4') continue; 89 candidates.push({ ...assignment, iname }); 90 } 91 } 92 return candidates.sort((a, b) => { 93 const priorityA = getSubnetPriority(a.address); 94 const priorityB = getSubnetPriority(b.address); 95 // Prioritise external interfaces, then sort by priority, 96 // when priority is equal, sort by raw IP values 97 const sortBy = 98 +isInternal(a) - +isInternal(b) || 99 priorityB - priorityA || 100 parseIpStr(b.address) - parseIpStr(a.address); 101 return sortBy; 102 }); 103}; 104 105export const matchAssignment = ( 106 candidates: NetworkAssignment[], 107 addr: string 108): GatewayAssignment | null => { 109 const rawAddr = parseIpStr(addr); 110 for (const candidate of candidates) { 111 const candidateAddr = parseIpStr(candidate.address); 112 if (rawAddr === candidateAddr) return { ...candidate, gateway: null }; 113 const mask = parseIpStr(candidate.netmask); 114 if ((rawAddr & mask) === (candidateAddr & mask)) 115 return { ...candidate, gateway: addr }; 116 } 117 return null; 118};