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