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