// dnsmasq's own code, living in the module (novox/hq ADR 0044). dnsmasq here is a pure resolver: it // answers the mesh's generated wildcard names (..) and forwards nothing. So // its code reads what it was told to answer, and can resolve through itself to prove that it does. import { readFile } from "node:fs/promises"; import { Resolver } from "node:dns/promises"; export interface AnsweredName { /** A machine's internal name, e.g. "anchor.internal" — it and everything under it resolve here. */ name: string; address: string; } export class DnsmasqClient { constructor( private readonly resolverPath: string, private readonly address: string, ) {} /** * Build from the environment. Both values are node-local facts with mesh-chosen defaults — the * wildcard file the module is sent, and the loopback address its config listens on — so there is * nothing to be unconfigured about; it never throws. */ static fromEnv(env: NodeJS.ProcessEnv = process.env): DnsmasqClient { return new DnsmasqClient( env.MESH_DNSMASQ_RESOLVER_PATH ?? "/etc/mesh-resolver/nodes.conf", env.MESH_DNSMASQ_ADDRESS ?? "127.0.0.55", ); } /** The names this resolver answers, read from the mesh-generated wildcard file. */ async answeredNames(): Promise { let text: string; try { text = await readFile(this.resolverPath, "utf8"); } catch { return []; // not yet on the network, or the file has not been written — no names, not an error } const names: AnsweredName[] = []; for (const line of text.split("\n")) { // dnsmasq wildcard syntax the mesh writes: address=/./
const match = line.match(/^address=\/([^/]+)\/(.+)$/); if (match) names.push({ name: match[1], address: match[2] }); } return names; } /** Resolve a name through this node's own resolver — the check that wildcard-resolution answers. */ async resolve(name: string): Promise { const resolver = new Resolver(); resolver.setServers([this.address]); try { return await resolver.resolve4(name); } catch { return await resolver.resolve6(name); } } }