Mirror: Best-effort discovery of the machine's local network using just Node.js dgram sockets
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

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