import * as plugins from './plugins.js'; // IPC command map for type-safe bridge communication export type TDnsCommands = { start: { params: { config: IRustDnsConfig }; result: Record; }; stop: { params: Record; result: Record; }; dnsQueryResult: { params: { correlationId: string; answers: IIpcDnsAnswer[]; answered: boolean; }; result: { resolved: boolean }; }; updateCerts: { params: { httpsKey: string; httpsCert: string }; result: Record; }; processPacket: { params: { packet: string }; // base64-encoded DNS packet result: { packet: string }; // base64-encoded DNS response }; ping: { params: Record; result: { pong: boolean }; }; }; export interface IRustDnsConfig { udpPort: number; httpsPort: number; udpBindInterface: string; httpsBindInterface: string; httpsKey: string; httpsCert: string; dnssecZone: string; dnssecAlgorithm: string; primaryNameserver: string; enableLocalhostHandling: boolean; manualUdpMode: boolean; manualHttpsMode: boolean; } export interface IIpcDnsQuestion { name: string; type: string; class: string; } export interface IIpcDnsAnswer { name: string; type: string; class: string; ttl: number; data: any; } export interface IDnsQueryEvent { correlationId: string; questions: IIpcDnsQuestion[]; dnssecRequested: boolean; } /** * Bridge to the Rust DNS binary via smartrust IPC. */ export class RustDnsBridge extends plugins.events.EventEmitter { private bridge: InstanceType>; constructor() { super(); const packageDir = plugins.path.resolve( plugins.path.dirname(new URL(import.meta.url).pathname), '..' ); // Determine platform suffix for dist_rust binaries (matches tsrust naming) const platformSuffix = getPlatformSuffix(); const localPaths: string[] = []; // dist_rust/ candidates (tsrust cross-compiled output, platform-specific) if (platformSuffix) { localPaths.push(plugins.path.join(packageDir, 'dist_rust', `rustdns_${platformSuffix}`)); } // dist_rust/ without suffix (native build) localPaths.push(plugins.path.join(packageDir, 'dist_rust', 'rustdns')); // Local dev build paths localPaths.push(plugins.path.join(packageDir, 'rust', 'target', 'release', 'rustdns')); localPaths.push(plugins.path.join(packageDir, 'rust', 'target', 'debug', 'rustdns')); this.bridge = new plugins.smartrust.RustBridge({ binaryName: 'rustdns', cliArgs: ['--management'], requestTimeoutMs: 30_000, readyTimeoutMs: 10_000, localPaths, searchSystemPath: false, }); // Forward events from inner bridge this.bridge.on('management:dnsQuery', (data: IDnsQueryEvent) => { this.emit('dnsQuery', data); }); this.bridge.on('management:started', () => { this.emit('started'); }); this.bridge.on('management:stopped', () => { this.emit('stopped'); }); this.bridge.on('management:error', (data: { message: string }) => { this.emit('error', new Error(data.message)); }); this.bridge.on('stderr', (line: string) => { // Forward Rust tracing output as debug logs if (line.trim()) { console.log(`[rustdns] ${line}`); } }); } /** * Spawn the Rust binary and wait for readiness. */ public async spawn(): Promise { return this.bridge.spawn(); } /** * Start the DNS server with given config. */ public async startServer(config: IRustDnsConfig): Promise { await this.bridge.sendCommand('start', { config }); } /** * Stop the DNS server. */ public async stopServer(): Promise { await this.bridge.sendCommand('stop', {}); } /** * Send a DNS query result back to Rust. */ public async sendQueryResult( correlationId: string, answers: IIpcDnsAnswer[], answered: boolean ): Promise { await this.bridge.sendCommand('dnsQueryResult', { correlationId, answers, answered, }); } /** * Update TLS certificates. */ public async updateCerts(httpsKey: string, httpsCert: string): Promise { await this.bridge.sendCommand('updateCerts', { httpsKey, httpsCert }); } /** * Process a raw DNS packet via IPC (for manual/passthrough mode). * Returns the DNS response as a Buffer. */ public async processPacket(packet: Buffer): Promise { const result = await this.bridge.sendCommand('processPacket', { packet: packet.toString('base64'), }); return Buffer.from(result.packet, 'base64'); } /** * Ping the Rust binary for health check. */ public async ping(): Promise { const result = await this.bridge.sendCommand('ping', {}); return result.pong; } /** * Kill the Rust process. */ public kill(): void { this.bridge.kill(); } /** * Whether the bridge is running. */ public get running(): boolean { return this.bridge.running; } } /** * Get the tsrust platform suffix for the current platform. * Matches the naming convention used by @git.zone/tsrust. */ function getPlatformSuffix(): string | null { const platform = process.platform; const arch = process.arch; const platformMap: Record = { 'linux': 'linux', 'darwin': 'macos', 'win32': 'windows', }; const archMap: Record = { 'x64': 'amd64', 'arm64': 'arm64', }; const p = platformMap[platform]; const a = archMap[arch]; if (p && a) { return `${p}_${a}`; } return null; }