Mirror: Best-effort discovery of the machine's local network using just Node.js dgram sockets
at v0.1.1 2.5 kB view raw
1import { randomBytes } from 'node:crypto'; 2import { createSocket } from 'node:dgram'; 3import { parseIpStr, toIpStr, parseMacStr } from './network'; 4import type { NetworkAssignment } from './types'; 5 6class DHCPTimeoutError extends TypeError { 7 code = 'ETIMEDOUT'; 8} 9 10const computeBroadcastAddress = (assignment: NetworkAssignment) => { 11 const address = parseIpStr(assignment.address); 12 const netmask = parseIpStr(assignment.netmask); 13 return toIpStr(address | ~netmask); 14}; 15 16const dhcpDiscoverPacket = (macStr: string) => { 17 const MAC_ADDRESS = new Uint8Array(16); 18 MAC_ADDRESS.set(parseMacStr(macStr)); 19 const packet = new Uint8Array(244); 20 const XID = randomBytes(4); 21 packet[0] = 1; // op = request 22 packet[1] = 1; // hw_type = ethernet 23 packet[2] = 6; // hw_len = ethernet 24 packet[3] = 0; // hops = 0 25 packet.set(XID, 4); 26 // elapsed = 0 seconds [2 bytes] 27 packet[10] = 0x80; // flags = broadcast discovery [2 bytes] 28 // client IP = null [4 bytes] 29 // own IP = null [4 bytes] 30 // server IP = null [4 bytes] 31 // gateway IP = null [4 bytes] 32 packet.set(MAC_ADDRESS, 28); 33 // sname = null [64 bytes] 34 // boot file = null [128 bytes] 35 packet.set([0x63, 0x82, 0x53, 0x63], 236); // Magic cookie 36 packet.set([0x35, 0x01, 0x01, 0xff], 240); // Trailer 37 return packet; 38}; 39 40const DHCP_TIMEOUT = 250; 41const DHCP_CLIENT_PORT = 68; 42const DHCP_SERVER_PORT = 67; 43 44export const dhcpDiscover = ( 45 assignment: NetworkAssignment 46): Promise<string> => { 47 return new Promise((resolve, reject) => { 48 const broadcastAddress = computeBroadcastAddress(assignment); 49 const packet = dhcpDiscoverPacket(assignment.mac); 50 const timeout = setTimeout(() => { 51 reject( 52 new DHCPTimeoutError( 53 `Received no reply to DHCPDISCOVER in ${DHCP_TIMEOUT}ms` 54 ) 55 ); 56 }, DHCP_TIMEOUT); 57 const socket = createSocket( 58 { type: 'udp4', reuseAddr: true }, 59 (_msg, rinfo) => { 60 clearTimeout(timeout); 61 resolve(rinfo.address); 62 socket.close(); 63 socket.unref(); 64 } 65 ); 66 socket.on('error', error => { 67 clearTimeout(timeout); 68 reject(error); 69 socket.close(); 70 socket.unref(); 71 }); 72 socket.bind(DHCP_CLIENT_PORT, assignment.address, () => { 73 socket.setBroadcast(true); 74 socket.setSendBufferSize(packet.length); 75 socket.send( 76 packet, 77 0, 78 packet.length, 79 DHCP_SERVER_PORT, 80 broadcastAddress, 81 error => { 82 if (error) reject(error); 83 } 84 ); 85 }); 86 }); 87};