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