import { get_network_interfaces, ping_subnetwork } from "networking"; import { exit, argv } from "process"; import { readdir } from "fs/promises"; import { join } from "path"; import { NetworkInterfaceBase } from "os"; import { fastify } from "fastify"; import fastify_static from "@fastify/static"; import { toIP as mac_to_ip_address } from "@network-utils/arp-lookup"; import { IPMask, IPv4 } from "ip-matching"; const minion_mac_addresses = [ "30:83:98:a4:68:09", "bc:ff:4d:17:62:75" ]; const minion_positions: Record = { "30:83:98:a4:68:09": 13, "bc:ff:4d:17:62:75": 1 }; async function mac_to_ip_addresses(mac_addresses: string[]) { let failed = []; let succeeded = []; for(const mac_address of mac_addresses) { const ip_address = await mac_to_ip_address(mac_address); if(ip_address === null) { failed.push(mac_address); continue; } succeeded.push({ mac_address: mac_address, ip_address: ip_address }); } return { succeeded: succeeded, failed: failed }; } interface NWInterfaceInfo extends NetworkInterfaceBase { family: number | string scopeid?: number; } function get_ipv4_nw_interface_info() { const nw_interfaces = get_network_interfaces(); const nw_interface_name = Object.keys(nw_interfaces)[0]; const nw_interface_info_arr = nw_interfaces[nw_interface_name] as NWInterfaceInfo[]; const nw_interface_info = nw_interface_info_arr.find(info => { return info.family === "IPv4" || info.family === 4; }); if(nw_interface_info === undefined) { console.error( `Error: Network interface "${nw_interface_name}" doesn't support IPv4"` ); exit(1); } return nw_interface_info; } function get_current_subnetwork(nw_interface_info: NWInterfaceInfo) { const ip_mask = new IPMask( new IPv4(nw_interface_info.address), new IPv4(nw_interface_info.netmask) ); const subnetwork = ip_mask.convertToSubnet(); if(subnetwork === undefined) { console.error(`Error: Failed to convert ${ip_mask.input} to subnet`); exit(1); } return subnetwork; } async function get_minion_ip_addresses(nw_interface_info: NWInterfaceInfo) { let mac_to_ip_result = await mac_to_ip_addresses(minion_mac_addresses); if(mac_to_ip_result.failed.length === 0) { return mac_to_ip_result.succeeded; } console.log( "Warning: Couldn't find all minion IP addresses in ARP table. " + "Updating ARP table entries..." ); const subnetwork = get_current_subnetwork(nw_interface_info); await ping_subnetwork(subnetwork); console.log("Retrying finding minion IP addresses in ARP table..."); mac_to_ip_result = await mac_to_ip_addresses(minion_mac_addresses); if(mac_to_ip_result.failed.length !== 0) { console.error( "Error: Failed to find all minion IP addresses in ARP table " + "even after updating ARP table entries" ); exit(1); } return mac_to_ip_result.succeeded; } async function main() { const nw_interface_info = get_ipv4_nw_interface_info(); const minion_info = (await get_minion_ip_addresses(nw_interface_info)).map( ({ mac_address, ip_address }) => { return { ip_address: ip_address, position: minion_positions[mac_address] }; } ); const app = fastify(); const is_prod = argv.length !== 0 && argv.includes("--prod"); if(is_prod) { const dist_dir_path = join(__dirname, "/../../client/dist"); await readdir(dist_dir_path).catch(() => { console.error("Error: The client dist directory doesn't exist"); exit(1); }); app.register(fastify_static, { root: dist_dir_path }); app.route({ method: "GET", url: "/", handler: (req, reply) => { reply.sendFile("index.html"); } }); } app.route({ method: "GET", url: "/api/v1/minions", handler: (req, reply) => { reply.send({ data: minion_info }); } }); const local_address = nw_interface_info.address; const port = 80; const address = is_prod ? local_address : "localhost"; await app.listen(port, address); console.log(`Listening on address http://${address}:${port}`); } main();