Mirror: Best-effort discovery of the machine's local network using just Node.js dgram sockets
1#!/usr/bin/env node
2
3import type { GatewayAssignment } from './types';
4import {
5 DEFAULT_ASSIGNMENT,
6 interfaceAssignments,
7 matchAssignment,
8} from './network';
9import { probeDefaultRoute } from './route';
10import { dhcpDiscover } from './dhcp';
11import { lanNetwork } from './index';
12
13type Mode = 'help' | 'dhcp' | 'probe' | 'fallback' | 'default';
14
15function help() {
16 const output = [
17 "Discover the machine's default gateway and local network IP (test utility)",
18 '',
19 'Usage',
20 ' $ lan-network',
21 ' $ lan-network --default',
22 '',
23 'Modes',
24 ' --probe Discover gateway via UDP4 socket to publicly routed address',
25 ' --dhcp Discover gateway via DHCPv4 discover broadcast',
26 ' --fallback Return highest-priority IPv4 network interface assignment',
27 ' --default Try the three above modes in order',
28 ' --help Print help output',
29 ].join('\n');
30 console.log(output);
31}
32
33async function dhcp() {
34 const assignments = interfaceAssignments();
35 if (!assignments.length) {
36 console.error('No available network interface assignments');
37 process.exit(1);
38 }
39 const discoveries = await Promise.allSettled(
40 assignments.map(assignment => {
41 // For each assignment, we send a DHCPDISCOVER packet to its network mask
42 return dhcpDiscover(assignment);
43 })
44 );
45 let assignment: GatewayAssignment | null = null;
46 for (const discovery of discoveries) {
47 // The first discovered gateway is returned, if it matches an assignment
48 if (discovery.status === 'fulfilled' && discovery.value) {
49 const dhcpRoute = discovery.value;
50 if ((assignment = matchAssignment(assignments, dhcpRoute))) {
51 break;
52 }
53 }
54 }
55 if (assignment && assignment !== DEFAULT_ASSIGNMENT) {
56 console.log(JSON.stringify(assignment, null, 2));
57 process.exit(0);
58 } else {
59 console.error('No DHCP router was discoverable');
60 process.exit(1);
61 }
62}
63
64async function probe() {
65 const assignments = interfaceAssignments();
66 if (!assignments.length) {
67 console.error('No available network interface assignments');
68 process.exit(1);
69 }
70 try {
71 const defaultRoute = await probeDefaultRoute();
72 const assignment = matchAssignment(assignments, defaultRoute);
73 if (assignment && assignment !== DEFAULT_ASSIGNMENT) {
74 console.log(JSON.stringify(assignment, null, 2));
75 process.exit(0);
76 } else {
77 console.error('No default gateway or route');
78 process.exit(1);
79 }
80 } catch (error) {
81 console.error('No default gateway or route');
82 console.error(error);
83 process.exit(1);
84 }
85}
86
87async function fallback() {
88 const assignments = interfaceAssignments();
89 if (!assignments.length) {
90 console.error('No available network interface assignments');
91 process.exit(1);
92 }
93 const assignment = { ...assignments[0], gateway: null };
94 console.log(JSON.stringify(assignment, null, 2));
95 process.exit(0);
96}
97
98async function main() {
99 const assignment = await lanNetwork();
100 if (assignment !== DEFAULT_ASSIGNMENT) {
101 console.log(JSON.stringify(assignment, null, 2));
102 process.exit(0);
103 } else {
104 console.error('No default gateway, route, or DHCP router');
105 process.exit(1);
106 }
107}
108
109function cli() {
110 let mode: Mode = 'default';
111 parseArgs: for (let i = 1; i < process.argv.length; i++) {
112 const arg = process.argv[i].trim().toLowerCase();
113 switch (arg) {
114 case '-h':
115 case '--help':
116 mode = 'help';
117 break parseArgs;
118 case '-d':
119 case '--dhcp':
120 mode = 'dhcp';
121 break;
122 case '-p':
123 case '--probe':
124 mode = 'probe';
125 break;
126 case '-f':
127 case '--fallback':
128 mode = 'fallback';
129 break;
130 default:
131 if (arg.startsWith('-')) throw new TypeError(`Invalid flag: ${arg}`);
132 }
133 }
134 switch (mode) {
135 case 'help':
136 return help();
137 case 'dhcp':
138 return dhcp();
139 case 'probe':
140 return probe();
141 case 'fallback':
142 return fallback();
143 case 'default':
144 return main();
145 }
146}
147
148cli();