dns-resolver.ts•6.84 kB
import * as dns from 'dns/promises';
import { Resolver } from 'dns/promises';
import { DnsRecord, DnsRecordType, DnsLookupResult, DnsError, DnsServerConfig } from './types/dns';
export class DnsResolver {
private resolver: Resolver;
private config: DnsServerConfig;
constructor(config?: DnsServerConfig) {
this.resolver = new Resolver();
this.config = config || {
servers: ['8.8.8.8', '8.8.4.4', '1.1.1.1', '1.0.0.1'],
timeout: 5000,
retries: 2
};
if (this.config.servers && this.config.servers.length > 0) {
this.resolver.setServers(this.config.servers);
}
}
async lookup(domain: string, recordType: DnsRecordType, timeout?: number): Promise<DnsLookupResult> {
const startTime = Date.now();
const records: DnsRecord[] = [];
try {
switch (recordType) {
case 'A':
const aRecords = await this.resolver.resolve4(domain, { ttl: true });
records.push(...aRecords.map(r => ({
type: 'A' as DnsRecordType,
name: domain,
value: r.address,
ttl: r.ttl
})));
break;
case 'AAAA':
const aaaaRecords = await this.resolver.resolve6(domain, { ttl: true });
records.push(...aaaaRecords.map(r => ({
type: 'AAAA' as DnsRecordType,
name: domain,
value: r.address,
ttl: r.ttl
})));
break;
case 'CNAME':
const cnameRecords = await this.resolver.resolveCname(domain);
records.push(...cnameRecords.map(r => ({
type: 'CNAME' as DnsRecordType,
name: domain,
value: r
})));
break;
case 'MX':
const mxRecords = await this.resolver.resolveMx(domain);
records.push(...mxRecords.map(r => ({
type: 'MX' as DnsRecordType,
name: domain,
value: r.exchange,
priority: r.priority
})));
break;
case 'TXT':
const txtRecords = await this.resolver.resolveTxt(domain);
records.push(...txtRecords.map(r => ({
type: 'TXT' as DnsRecordType,
name: domain,
value: r.join('')
})));
break;
case 'NS':
const nsRecords = await this.resolver.resolveNs(domain);
records.push(...nsRecords.map(r => ({
type: 'NS' as DnsRecordType,
name: domain,
value: r
})));
break;
case 'SOA':
const soaRecord = await this.resolver.resolveSoa(domain);
if (soaRecord) {
records.push({
type: 'SOA' as DnsRecordType,
name: domain,
value: JSON.stringify(soaRecord)
});
}
break;
case 'PTR':
const ptrRecords = await this.resolver.resolvePtr(domain);
records.push(...ptrRecords.map(r => ({
type: 'PTR' as DnsRecordType,
name: domain,
value: r
})));
break;
case 'SRV':
const srvRecords = await this.resolver.resolveSrv(domain);
records.push(...srvRecords.map(r => ({
type: 'SRV' as DnsRecordType,
name: domain,
value: r.name,
priority: r.priority,
weight: r.weight,
port: r.port
})));
break;
case 'CAA':
const caaRecords = await this.resolver.resolveCaa(domain);
records.push(...caaRecords.map(r => ({
type: 'CAA' as DnsRecordType,
name: domain,
value: `${r.critical ? '128' : '0'} ${r.issue || r.issuewild || r.iodef || ''}`
})));
break;
default:
throw new Error(`Unsupported record type: ${recordType}`);
}
return {
domain,
recordType,
records,
queryTime: Date.now() - startTime,
server: this.config.servers[0],
timestamp: new Date().toISOString()
};
} catch (error: any) {
throw {
code: error.code || 'UNKNOWN_ERROR',
message: error.message || 'An unknown error occurred',
domain,
recordType
} as DnsError;
}
}
async reverseLookup(ipAddress: string): Promise<string[]> {
try {
return await this.resolver.reverse(ipAddress);
} catch (error: any) {
throw {
code: error.code || 'REVERSE_LOOKUP_FAILED',
message: error.message || 'Reverse lookup failed',
domain: ipAddress
} as DnsError;
}
}
async batchLookup(
queries: { domain: string; recordTypes: DnsRecordType[] }[],
parallel: boolean = true
): Promise<{ results: DnsLookupResult[]; errors: DnsError[] }> {
const results: DnsLookupResult[] = [];
const errors: DnsError[] = [];
const performQuery = async (domain: string, recordType: DnsRecordType) => {
try {
const result = await this.lookup(domain, recordType);
results.push(result);
} catch (error) {
errors.push(error as DnsError);
}
};
const allQueries = queries.flatMap(q =>
q.recordTypes.map(rt => ({ domain: q.domain, recordType: rt }))
);
if (parallel) {
await Promise.all(
allQueries.map(q => performQuery(q.domain, q.recordType))
);
} else {
for (const q of allQueries) {
await performQuery(q.domain, q.recordType);
}
}
return { results, errors };
}
async traceDns(domain: string, recordType: DnsRecordType = 'A'): Promise<any> {
const trace: any[] = [];
try {
const rootServers = await this.resolver.resolveNs('.');
trace.push({ level: 'root', servers: rootServers });
const parts = domain.split('.').reverse();
let currentDomain = '';
for (const part of parts) {
currentDomain = currentDomain ? `${part}.${currentDomain}` : part;
try {
const nsRecords = await this.resolver.resolveNs(currentDomain);
trace.push({ level: currentDomain, servers: nsRecords });
} catch (error) {
break;
}
}
const finalResult = await this.lookup(domain, recordType);
trace.push({ level: 'final', result: finalResult });
return trace;
} catch (error: any) {
throw {
code: 'TRACE_FAILED',
message: error.message || 'DNS trace failed',
domain,
recordType
} as DnsError;
}
}
updateServers(servers: string[]) {
this.config.servers = servers;
this.resolver.setServers(servers);
}
}