Mirror: Best-effort discovery of the machine's local network using just Node.js dgram sockets
at v0.1.7 3.5 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 (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};