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 console.log(discoveries);
46 let assignment: GatewayAssignment | null = null;
47 for (const discovery of discoveries) {
48 // The first discovered gateway is returned, if it matches an assignment
49 if (discovery.status === 'fulfilled' && discovery.value) {
50 const dhcpRoute = discovery.value;
51 if ((assignment = matchAssignment(assignments, dhcpRoute))) {
52 break;
53 }
54 }
55 }
56 if (assignment && assignment !== DEFAULT_ASSIGNMENT) {
57 console.log(JSON.stringify(assignment, null, 2));
58 process.exit(0);
59 } else {
60 console.error('No DHCP router was discoverable');
61 process.exit(1);
62 }
63}
64
65async function probe() {
66 const assignments = interfaceAssignments();
67 if (!assignments.length) {
68 console.error('No available network interface assignments');
69 process.exit(1);
70 }
71 try {
72 const defaultRoute = await probeDefaultRoute();
73 const assignment = matchAssignment(assignments, defaultRoute);
74 if (assignment && assignment !== DEFAULT_ASSIGNMENT) {
75 console.log(JSON.stringify(assignment, null, 2));
76 process.exit(0);
77 } else {
78 console.error('No default gateway or route');
79 process.exit(1);
80 }
81 } catch (error) {
82 console.error('No default gateway or route');
83 console.error(error);
84 process.exit(1);
85 }
86}
87
88async function fallback() {
89 const assignments = interfaceAssignments();
90 if (!assignments.length) {
91 console.error('No available network interface assignments');
92 process.exit(1);
93 }
94 const assignment = { ...assignments[0], gateway: null };
95 console.log(JSON.stringify(assignment, null, 2));
96 process.exit(0);
97}
98
99async function main() {
100 const assignment = await lanNetwork();
101 if (assignment !== DEFAULT_ASSIGNMENT) {
102 console.log(JSON.stringify(assignment, null, 2));
103 process.exit(0);
104 } else {
105 console.error('No default gateway, route, or DHCP router');
106 process.exit(1);
107 }
108}
109
110function cli() {
111 let mode: Mode = 'default';
112 parseArgs: for (let i = 1; i < process.argv.length; i++) {
113 const arg = process.argv[i].trim().toLowerCase();
114 switch (arg) {
115 case '-h':
116 case '--help':
117 mode = 'help';
118 break parseArgs;
119 case '-d':
120 case '--dhcp':
121 mode = 'dhcp';
122 break;
123 case '-p':
124 case '--probe':
125 mode = 'probe';
126 break;
127 case '-f':
128 case '--fallback':
129 mode = 'fallback';
130 break;
131 default:
132 if (arg.startsWith('-')) throw new TypeError(`Invalid flag: ${arg}`);
133 }
134 }
135 switch (mode) {
136 case 'help':
137 return help();
138 case 'dhcp':
139 return dhcp();
140 case 'probe':
141 return probe();
142 case 'fallback':
143 return fallback();
144 case 'default':
145 return main();
146 }
147}
148
149cli();