237 lines
5.6 KiB
TypeScript
237 lines
5.6 KiB
TypeScript
import * as plugins from './plugins.js';
|
|
|
|
// IPC command map for type-safe bridge communication
|
|
export type TDnsCommands = {
|
|
start: {
|
|
params: { config: IRustDnsConfig };
|
|
result: Record<string, never>;
|
|
};
|
|
stop: {
|
|
params: Record<string, never>;
|
|
result: Record<string, never>;
|
|
};
|
|
dnsQueryResult: {
|
|
params: {
|
|
correlationId: string;
|
|
answers: IIpcDnsAnswer[];
|
|
answered: boolean;
|
|
};
|
|
result: { resolved: boolean };
|
|
};
|
|
updateCerts: {
|
|
params: { httpsKey: string; httpsCert: string };
|
|
result: Record<string, never>;
|
|
};
|
|
processPacket: {
|
|
params: { packet: string }; // base64-encoded DNS packet
|
|
result: { packet: string }; // base64-encoded DNS response
|
|
};
|
|
ping: {
|
|
params: Record<string, never>;
|
|
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<typeof plugins.smartrust.RustBridge<TDnsCommands>>;
|
|
|
|
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<TDnsCommands>({
|
|
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<boolean> {
|
|
return this.bridge.spawn();
|
|
}
|
|
|
|
/**
|
|
* Start the DNS server with given config.
|
|
*/
|
|
public async startServer(config: IRustDnsConfig): Promise<void> {
|
|
await this.bridge.sendCommand('start', { config });
|
|
}
|
|
|
|
/**
|
|
* Stop the DNS server.
|
|
*/
|
|
public async stopServer(): Promise<void> {
|
|
await this.bridge.sendCommand('stop', {});
|
|
}
|
|
|
|
/**
|
|
* Send a DNS query result back to Rust.
|
|
*/
|
|
public async sendQueryResult(
|
|
correlationId: string,
|
|
answers: IIpcDnsAnswer[],
|
|
answered: boolean
|
|
): Promise<void> {
|
|
await this.bridge.sendCommand('dnsQueryResult', {
|
|
correlationId,
|
|
answers,
|
|
answered,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Update TLS certificates.
|
|
*/
|
|
public async updateCerts(httpsKey: string, httpsCert: string): Promise<void> {
|
|
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<Buffer> {
|
|
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<boolean> {
|
|
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<string, string> = {
|
|
'linux': 'linux',
|
|
'darwin': 'macos',
|
|
'win32': 'windows',
|
|
};
|
|
|
|
const archMap: Record<string, string> = {
|
|
'x64': 'amd64',
|
|
'arm64': 'arm64',
|
|
};
|
|
|
|
const p = platformMap[platform];
|
|
const a = archMap[arch];
|
|
|
|
if (p && a) {
|
|
return `${p}_${a}`;
|
|
}
|
|
|
|
return null;
|
|
}
|