Mirror: Best-effort discovery of the machine's local network using just Node.js dgram sockets
at v0.1.5 2.9 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 assignment.internal || parseMacStr(assignment.mac).every(x => !x); 51 52export const interfaceAssignments = (): NetworkAssignment[] => { 53 const candidates: NetworkAssignment[] = []; 54 const interfaces = os.networkInterfaces(); 55 for (const iname in interfaces) { 56 const assignments = interfaces[iname]; 57 if (!assignments) continue; 58 for (const assignment of assignments) { 59 if (assignment.family !== 'IPv4') continue; 60 candidates.push({ ...assignment, iname }); 61 } 62 } 63 return candidates.sort((a, b) => { 64 const priorityA = getSubnetPriority(a.address); 65 const priorityB = getSubnetPriority(b.address); 66 // Prioritise external interfaces, then sort by priority, 67 // when priority is equal, sort by raw IP values 68 const sortBy = 69 +isInternal(a) - +isInternal(b) || 70 priorityB - priorityA || 71 parseIpStr(b.address) - parseIpStr(a.address); 72 return sortBy; 73 }); 74}; 75 76export const matchAssignment = ( 77 candidates: NetworkAssignment[], 78 addr: string 79): GatewayAssignment | null => { 80 const rawAddr = parseIpStr(addr); 81 for (const candidate of candidates) { 82 const candidateAddr = parseIpStr(candidate.address); 83 if (rawAddr === candidateAddr) return { ...candidate, gateway: null }; 84 const mask = parseIpStr(candidate.netmask); 85 if ((rawAddr & mask) === (candidateAddr & mask)) 86 return { ...candidate, gateway: addr }; 87 } 88 return null; 89};