Mirror: Best-effort discovery of the machine's local network using just Node.js dgram sockets
at v0.1.1 2.9 kB view raw
1import { spawnSync } from 'child_process'; 2import { dhcpDiscover } from './dhcp'; 3import { probeDefaultRoute } from './route'; 4import { interfaceAssignments, matchAssignment } from './network'; 5import type { GatewayAssignment } from './types'; 6 7const DEFAULT_ASSIGNMENT: GatewayAssignment = { 8 iname: 'lo0', 9 address: '127.0.0.1', 10 netmask: '255.0.0.0', 11 family: 'IPv4', 12 mac: '00:00:00:00:00:00', 13 internal: true, 14 cidr: '127.0.0.1/8', 15 gateway: null, 16}; 17 18export async function lanNetwork(): Promise<GatewayAssignment> { 19 // Get IPv4 network assignments, sorted by: 20 // - external first 21 // - LAN-reserved IP range priority 22 // - address value 23 const assignments = interfaceAssignments(); 24 if (!assignments.length) { 25 // If we have no assignments (which shouldn't ever happen, we make up a loopback interface) 26 return DEFAULT_ASSIGNMENT; 27 } 28 29 let assignment: GatewayAssignment | null; 30 31 // First, we attempt to probe the default route to a publicly routed IP 32 // This will generally fail if there's no route, e.g. if the network is offline 33 try { 34 const defaultRoute = await probeDefaultRoute(); 35 // If this route matches a known assignment, return it without a gateway 36 if ((assignment = matchAssignment(assignments, defaultRoute))) { 37 return assignment; 38 } 39 } catch { 40 // Ignore errors, since we have a fallback method 41 } 42 43 // Second, attempt to discover a gateway's DHCP network 44 // Because without a gateway we won't get a reply, we do this in parallel 45 const discoveries = await Promise.allSettled( 46 assignments.map(assignment => { 47 // For each assignment, we send a DHCPDISCOVER packet to its network mask 48 return dhcpDiscover(assignment); 49 }) 50 ); 51 for (const discovery of discoveries) { 52 // The first discovered gateway is returned, if it matches an assignment 53 if (discovery.status === 'fulfilled' && discovery.value) { 54 const dhcpRoute = discovery.value; 55 if ((assignment = matchAssignment(assignments, dhcpRoute))) { 56 return assignment; 57 } 58 } 59 } 60 61 // As a fallback, we choose the first assignment, since they're ordered by likely candidates 62 // This may return 127.0.0.1, typically as a last resort 63 return { ...assignments[0], gateway: null }; 64} 65 66export function lanNetworkSync(): GatewayAssignment { 67 const subprocessPath = require.resolve('lan-network/subprocess'); 68 const { error, status, stdout } = spawnSync( 69 process.execPath, 70 [subprocessPath], 71 { 72 shell: false, 73 timeout: 500, 74 encoding: 'utf8', 75 windowsVerbatimArguments: false, 76 windowsHide: true, 77 } 78 ); 79 if (status || error) { 80 return DEFAULT_ASSIGNMENT; 81 } else if (!status && typeof stdout === 'string') { 82 const json = JSON.parse(stdout.trim()) as GatewayAssignment; 83 return typeof json === 'object' && json && 'address' in json 84 ? json 85 : DEFAULT_ASSIGNMENT; 86 } else { 87 return DEFAULT_ASSIGNMENT; 88 } 89}