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.

feat: Add CLI for testing (#3)

* Remove default assignment

* Add test CLI

* Add changeset

authored by

Phil Pluckthun and committed by
GitHub
e5785771 3a03fb7b

+194 -13
+5
.changeset/plenty-ears-laugh.md
··· 1 + --- 2 + 'lan-network': patch 3 + --- 4 + 5 + Add CLI for testing
+15
cli/package.json
··· 1 + { 2 + "name": "lan-network-cli", 3 + "private": true, 4 + "version": "0.0.0", 5 + "main": "../dist/lan-network-cli.js", 6 + "types": "../dist/lan-network-cli.d.ts", 7 + "source": "../src/cli.ts", 8 + "exports": { 9 + ".": { 10 + "types": "../dist/lan-network-cli.d.ts", 11 + "require": "../dist/lan-network-cli.js", 12 + "source": "../src/cli.ts" 13 + } 14 + } 15 + }
+8
package.json
··· 24 24 "changeset:version": "changeset version && pnpm install --lockfile-only", 25 25 "changeset:publish": "changeset publish" 26 26 }, 27 + "bin": { 28 + "lan-network": "./dist/lan-network-cli.js" 29 + }, 27 30 "exports": { 28 31 ".": { 29 32 "types": "./dist/lan-network.d.ts", ··· 34 37 "types": "./dist/lan-network-subprocess.d.ts", 35 38 "require": "./dist/lan-network-subprocess.js", 36 39 "source": "./src/subprocess.ts" 40 + }, 41 + "./cli": { 42 + "types": "./dist/lan-network-cli.d.ts", 43 + "require": "./dist/lan-network-cli.js", 44 + "source": "./src/cli.ts" 37 45 }, 38 46 "./package.json": "./package.json" 39 47 },
+149
src/cli.ts
··· 1 + #!/usr/bin/env node 2 + 3 + import type { GatewayAssignment } from './types'; 4 + import { 5 + DEFAULT_ASSIGNMENT, 6 + interfaceAssignments, 7 + matchAssignment, 8 + } from './network'; 9 + import { probeDefaultRoute } from './route'; 10 + import { dhcpDiscover } from './dhcp'; 11 + import { lanNetwork } from './index'; 12 + 13 + type Mode = 'help' | 'dhcp' | 'probe' | 'fallback' | 'default'; 14 + 15 + function 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 + 33 + async 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 + 65 + async 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 + 88 + async 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 + 99 + async 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 + 110 + function 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 + 149 + cli();
+1 -1
src/dhcp.ts
··· 69 69 socket.close(); 70 70 socket.unref(); 71 71 }); 72 - socket.bind(DHCP_CLIENT_PORT, assignment.address, () => { 72 + socket.bind(DHCP_CLIENT_PORT, () => { 73 73 socket.setBroadcast(true); 74 74 socket.setSendBufferSize(packet.length); 75 75 socket.send(
+5 -12
src/index.ts
··· 1 1 import { spawnSync } from 'child_process'; 2 2 import { dhcpDiscover } from './dhcp'; 3 3 import { probeDefaultRoute } from './route'; 4 - import { interfaceAssignments, matchAssignment } from './network'; 4 + import { 5 + DEFAULT_ASSIGNMENT, 6 + interfaceAssignments, 7 + matchAssignment, 8 + } from './network'; 5 9 import type { GatewayAssignment } from './types'; 6 - 7 - const DEFAULT_ASSIGNMENT: GatewayAssignment = { 8 - iname: 'lo0', 9 - address: '127.0.0.1', 10 - netmask: '255.0.0.0', 11 - family: 'IPv4', 12 - mac: '00:00:00:00:00:00', 13 - internal: true, 14 - cidr: '127.0.0.1/8', 15 - gateway: null, 16 - }; 17 10 18 11 export async function lanNetwork(): Promise<GatewayAssignment> { 19 12 // Get IPv4 network assignments, sorted by:
+11
src/network.ts
··· 1 1 import os from 'node:os'; 2 2 import type { GatewayAssignment, NetworkAssignment } from './types'; 3 3 4 + export const DEFAULT_ASSIGNMENT: GatewayAssignment = { 5 + iname: 'lo0', 6 + address: '127.0.0.1', 7 + netmask: '255.0.0.0', 8 + family: 'IPv4', 9 + mac: '00:00:00:00:00:00', 10 + internal: true, 11 + cidr: '127.0.0.1/8', 12 + gateway: null, 13 + }; 14 + 4 15 export const parseMacStr = (macStr: string): number[] => 5 16 macStr 6 17 .split(':')