From 5e9d3a3370c7c2a3abfedf222d53e7aabfe2148b Mon Sep 17 00:00:00 2001 From: Andrei Date: Thu, 23 May 2024 10:58:46 +0200 Subject: [PATCH] added: node-dns resolver, resolver for runtime --- dist/dns-resolvers.d.ts | 20 ++++ dist/dns-resolvers.js | 198 ++++++++++++++++++++++++++++++++++ dist/index.d.ts | 10 +- dist/index.js | 93 ++++++---------- src/dns-resolvers.ts | 229 ++++++++++++++++++++++++++++++++++++++++ src/index.ts | 100 +++++------------- src/node-resolvers.ts | 76 ------------- test/test.js | 22 ++-- 8 files changed, 521 insertions(+), 227 deletions(-) create mode 100644 dist/dns-resolvers.d.ts create mode 100644 dist/dns-resolvers.js create mode 100644 src/dns-resolvers.ts delete mode 100644 src/node-resolvers.ts diff --git a/dist/dns-resolvers.d.ts b/dist/dns-resolvers.d.ts new file mode 100644 index 0000000..9d2988b --- /dev/null +++ b/dist/dns-resolvers.d.ts @@ -0,0 +1,20 @@ +import { DnsRecord } from './index.js'; +export declare function dnsRecordsCloudflare(name: string, type?: string): Promise; +export declare function dnsRecordsGoogle(name: string, type?: string): Promise; +/** + * Get DNS records using the `dig` command in Node.js + * + * @param names The name(s) to query + * @param types The DNS type(s) to query + * @param server The DNS server to query. If not provided, the default DNS server on the network will be used + * @returns The DNS records + */ +export declare function dnsRecordsNodeDig(names: string | string[], types?: string | string[], server?: string): Promise; +/** + * Get DNS records using the Node.js DNS module + * + * @param names The name to query + * @param types The DNS type to query + * @returns The DNS records + */ +export declare function dnsRecordsNodeDns(name: string, type?: string): Promise; diff --git a/dist/dns-resolvers.js b/dist/dns-resolvers.js new file mode 100644 index 0000000..ecab730 --- /dev/null +++ b/dist/dns-resolvers.js @@ -0,0 +1,198 @@ +import { toASCII } from 'punycode'; +const dnsTypeNumbers = { + 1: 'A', + 2: 'NS', + 5: 'CNAME', + 6: 'SOA', + 12: 'PTR', + 15: 'MX', + 16: 'TXT', + 24: 'SIG', + 25: 'KEY', + 28: 'AAAA', + 33: 'SRV', + 257: 'CAA', +}; +export async function dnsRecordsCloudflare(name, type = 'A') { + const re = await fetch(`https://cloudflare-dns.com/dns-query?name=${toASCII(name)}&type=${type}&cd=1`, { + headers: { + accept: 'application/dns-json', + } + }); + if (!re.ok) { + throw new Error(`Error fetching DNS records for ${name}: ${re.status} ${re.statusText}`); + } + const json = await re.json(); + const records = (json.Answer || []).map((record) => { + const type = dnsTypeNumbers[record.type] || String(record.type); + let data = record.data; + if (['CNAME', 'NS'].includes(type) && record.data.endsWith('.')) { + data = data.slice(0, -1); + } + return { name: record.name, type, ttl: record.TTL, data }; + }); + return records; +} +export async function dnsRecordsGoogle(name, type = 'A') { + const re = await fetch(`https://dns.google/resolve?name=${toASCII(name)}&type=${type}&cd=1`); + if (!re.ok) { + throw new Error(`Error fetching DNS records for ${name}: ${re.status} ${re.statusText}`); + } + const json = await re.json(); + const records = (json.Answer || []).map((record) => { + const type = dnsTypeNumbers[record.type] || String(record.type); + let data = record.data; + if (['CNAME', 'NS'].includes(type) && record.data.endsWith('.')) { + data = data.slice(0, -1); + } + return { + name: record.name, + type, + ttl: record.TTL, + data, + }; + }); + return records; +} +/** + * Get DNS records using the `dig` command in Node.js + * + * @param names The name(s) to query + * @param types The DNS type(s) to query + * @param server The DNS server to query. If not provided, the default DNS server on the network will be used + * @returns The DNS records + */ +export async function dnsRecordsNodeDig(names, types = 'A', server) { + // start building the arguments list for the `dig` command + const args = []; + // append @ to server if not present + if (server) { + if (!server.startsWith('@')) { + server = `@${server}`; + } + args.push(server); + } + if (!Array.isArray(names)) { + names = [names]; + } + names.forEach(name => { + name = toASCII(name); + if (Array.isArray(types) && types.length) { + types.forEach(type => { + args.push(name, type); + }); + } + else if (types && typeof types === 'string') { + args.push(name, types); + } + else { + args.push(name); + } + }); + // +noall' // don't display any texts (authority, question, stats, etc) in response, + // +answer // except the answer + // +cdflag // no DNSSEC check, faster + // https://linux.die.net/man/1/dig + const { spawnSync } = await import('node:child_process'); + const dig = spawnSync('dig', [...args, '+noall', '+answer', '+cdflag']); + let re = dig.stdout.toString(); + const dnsRecords = []; + // split lines & ignore comments or empty + re.split("\n") + .filter(line => line.length && !line.startsWith(';')) + .forEach(line => { + // replace tab(s) with space, then split by space + const parts = line.replace(/[\t]+/g, " ").split(" "); + let name = String(parts[0]); + if (name.endsWith('.')) { + name = name.slice(0, -1); + } + dnsRecords.push({ + name, + ttl: Number(parts[1]), + type: String(parts[3]), + data: parts.slice(4).join(" ") + }); + }); + return dnsRecords; +} +/** + * Get DNS records using the Node.js DNS module + * + * @param names The name to query + * @param types The DNS type to query + * @returns The DNS records + */ +export async function dnsRecordsNodeDns(name, type = 'A') { + const { promises: dns } = await import('node:dns'); + type = type.toUpperCase(); + const dnsRecords = []; + try { + if (['A', 'AAAA'].includes(type)) { + const foundRecords = type === 'A' ? await dns.resolve4(name, { ttl: true }) : await dns.resolve6(name, { ttl: true }); + foundRecords.forEach(record => { + dnsRecords.push({ + name, + type, + ttl: record.ttl, + data: record.address, + }); + }); + } + else if (type === 'CNAME') { + const foundRecords = await dns.resolveCname(name); + foundRecords.forEach(record => { + dnsRecords.push({ + name, + type, + ttl: 0, + data: record, + }); + }); + } + else if (type === 'MX') { + const foundRecords = await dns.resolveMx(name); + foundRecords.forEach(record => { + dnsRecords.push({ + name, + type, + ttl: 0, + data: `${record.priority} ${record.exchange}`, + }); + }); + } + else if (type === 'NS') { + const foundRecords = await dns.resolveNs(name); + foundRecords.forEach(record => { + dnsRecords.push({ + name, + type, + ttl: 0, + data: record, + }); + }); + } + else if (type === 'SOA') { + const foundRecords = await dns.resolveSoa(name); + dnsRecords.push({ + name, + type, + ttl: 0, + data: Object.values(foundRecords).join(' '), + }); + } + else if (type === 'TXT') { + const foundRecords = await dns.resolveTxt(name); + foundRecords.forEach(record => { + dnsRecords.push({ + name, + type, + ttl: 0, + data: record.join(' '), + }); + }); + } + } + catch (e) { } + return dnsRecords; +} diff --git a/dist/index.d.ts b/dist/index.d.ts index e92775d..5d64af4 100644 --- a/dist/index.d.ts +++ b/dist/index.d.ts @@ -14,7 +14,7 @@ export interface DnsRecord { * * @param name Fully qualified domain name. * @param type DNS record type: A, AAAA, TXT, CNAME, MX, etc. - * @param resolver DNS resolver to use. Default: cloudflare-dns. + * @param resolver Which DNS resolver to use. If not specified, the best DNS resolver for this runtime will be used. * @returns Array of discovered `DnsRecord` objects. * * @example Get TXT records for example.com @@ -31,17 +31,15 @@ export interface DnsRecord { * const mxRecords = await getDnsRecords('android.com', 'MX', 'google-dns') * ``` */ -export declare function getDnsRecords(name: string, type?: string, resolver?: string | Function): Promise; +export declare function getDnsRecords(name: string, type?: string, resolver?: string): Promise; /** Options for discovering DNS records. */ export type GetAllDnsRecordsOptions = { /** * Which DNS resolver to use for DNS lookup. * - * Options: cloudflare-dns, google-dns, custom resolver `Function`. - * - * @default 'cloudflare-dns' + * Options: cloudflare-dns, google-dns, node-dns, node-dig, deno-dns * */ - resolver?: string | Function; + resolver?: string; /** List of extra subdomains to check for */ subdomains?: string[]; }; diff --git a/dist/index.js b/dist/index.js index 72c2ad2..4b02adf 100644 --- a/dist/index.js +++ b/dist/index.js @@ -1,4 +1,5 @@ import { toASCII } from 'punycode'; +import { dnsRecordsCloudflare, dnsRecordsGoogle, dnsRecordsNodeDig, dnsRecordsNodeDns } from './dns-resolvers.js'; import { subdomainsRecords } from './subdomains.js'; const isTld = (tld) => { if (tld.startsWith('.')) { @@ -22,64 +23,23 @@ const isDomain = (domain) => { return index ? !label.startsWith('-') && !label.endsWith('-') && labelTest.test(label) : isTld(label); }); }; -const dnsTypeNumbers = { - 1: 'A', - 2: 'NS', - 5: 'CNAME', - 6: 'SOA', - 12: 'PTR', - 15: 'MX', - 16: 'TXT', - 24: 'SIG', - 25: 'KEY', - 28: 'AAAA', - 33: 'SRV', - 257: 'CAA', -}; -const dnsResolvers = { - 'cloudflare-dns': async (name, type = 'A') => { - const re = await fetch(`https://cloudflare-dns.com/dns-query?name=${toASCII(name)}&type=${type}&cd=1`, { - headers: { - accept: 'application/dns-json', - } - }); - if (!re.ok) { - throw new Error(`Error fetching DNS records for ${name}: ${re.status} ${re.statusText}`); - } - const json = await re.json(); - const records = (json.Answer || []).map((record) => { - return { - name: record.name, - type: dnsTypeNumbers[record.type] || String(record.type), - ttl: record.TTL, - data: record.data, - }; - }); - return records; - }, - 'google-dns': async (name, type = 'A') => { - const re = await fetch(`https://dns.google/resolve?name=${toASCII(name)}&type=${type}&cd=1`); - if (!re.ok) { - throw new Error(`Error fetching DNS records for ${name}: ${re.status} ${re.statusText}`); - } - const json = await re.json(); - const records = (json.Answer || []).map((record) => { - return { - name: record.name, - type: dnsTypeNumbers[record.type] || String(record.type), - ttl: record.TTL, - data: record.data, - }; - }); - return records; - }, -}; +function bestDnsResolverForThisRuntime() { + if (navigator.userAgent === 'Cloudflare-Workers') { + return 'cloudflare-dns'; + } + else if (navigator.userAgent.startsWith('Node.js/')) { + return 'node-dns'; + } + else { + return 'google-dns'; + } +} /** * Get DNS records of a given type for a FQDN. * * @param name Fully qualified domain name. * @param type DNS record type: A, AAAA, TXT, CNAME, MX, etc. - * @param resolver DNS resolver to use. Default: cloudflare-dns. + * @param resolver Which DNS resolver to use. If not specified, the best DNS resolver for this runtime will be used. * @returns Array of discovered `DnsRecord` objects. * * @example Get TXT records for example.com @@ -96,19 +56,27 @@ const dnsResolvers = { * const mxRecords = await getDnsRecords('android.com', 'MX', 'google-dns') * ``` */ -export async function getDnsRecords(name, type = 'A', resolver = 'cloudflare-dns') { +export async function getDnsRecords(name, type = 'A', resolver) { if (!isDomain(name)) { throw new Error(`"${name}" is not a valid domain name`); } - if (typeof resolver === 'string' && resolver in dnsResolvers) { - const fn = dnsResolvers[resolver]; - if (typeof fn !== 'function') { - throw new Error(`Invalid DNS resolver: ${resolver}`); - } - return fn(name, type); + if (!resolver) { + resolver = bestDnsResolverForThisRuntime(); + } + if (resolver === 'cloudflare-dns') { + return dnsRecordsCloudflare(name, type); + } + else if (resolver === 'google-dns') { + return dnsRecordsGoogle(name, type); + } + else if (resolver === 'node-dig') { + return dnsRecordsNodeDig(name, type); + } + else if (resolver === 'node-dns') { + return dnsRecordsNodeDns(name, type); } - if (typeof resolver === 'function') { - return resolver(name, type); + else if (resolver === 'deno-dns') { + throw new Error('Deno DNS not yet implemented'); } throw new Error(`Invalid DNS resolver: ${resolver}`); } @@ -121,7 +89,6 @@ export async function getDnsRecords(name, type = 'A', resolver = 'cloudflare-dns */ export function getAllDnsRecordsStream(domain, options = {}) { options = { - resolver: 'cloudflare-dns', subdomains: [], ...options, }; diff --git a/src/dns-resolvers.ts b/src/dns-resolvers.ts new file mode 100644 index 0000000..cc05cb7 --- /dev/null +++ b/src/dns-resolvers.ts @@ -0,0 +1,229 @@ +import { toASCII } from 'punycode' +import { DnsRecord } from './index.js' + +const dnsTypeNumbers: { [key: number]: string } = { + 1: 'A', + 2: 'NS', + 5: 'CNAME', + 6: 'SOA', + 12: 'PTR', + 15: 'MX', + 16: 'TXT', + 24: 'SIG', + 25: 'KEY', + 28: 'AAAA', + 33: 'SRV', + 257: 'CAA', +} + +export async function dnsRecordsCloudflare(name: string, type: string = 'A'): Promise { + const re = await fetch(`https://cloudflare-dns.com/dns-query?name=${toASCII(name)}&type=${type}&cd=1`, { + headers: { + accept: 'application/dns-json', + } + }) + + if (!re.ok) { + throw new Error(`Error fetching DNS records for ${name}: ${re.status} ${re.statusText}`) + } + + const json: any = await re.json() + const records: DnsRecord[] = (json.Answer || []).map((record: any) => { + const type = dnsTypeNumbers[record.type] || String(record.type) + let data = record.data + + if (['CNAME', 'NS'].includes(type) && record.data.endsWith('.')) { + data = data.slice(0, -1) + } + + return { name: record.name, type, ttl: record.TTL, data } + }) + + return records +} + +export async function dnsRecordsGoogle(name: string, type: string = 'A'): Promise { + const re = await fetch(`https://dns.google/resolve?name=${toASCII(name)}&type=${type}&cd=1`) + + if (!re.ok) { + throw new Error(`Error fetching DNS records for ${name}: ${re.status} ${re.statusText}`) + } + + const json: any = await re.json() + const records: DnsRecord[] = (json.Answer || []).map((record: any) => { + const type = dnsTypeNumbers[record.type] || String(record.type) + let data = record.data + + if (['CNAME', 'NS'].includes(type) && record.data.endsWith('.')) { + data = data.slice(0, -1) + } + + return { + name: record.name, + type, + ttl: record.TTL, + data, + } + }) + + return records +} + +/** + * Get DNS records using the `dig` command in Node.js + * + * @param names The name(s) to query + * @param types The DNS type(s) to query + * @param server The DNS server to query. If not provided, the default DNS server on the network will be used + * @returns The DNS records + */ +export async function dnsRecordsNodeDig(names: string | string[], types: string | string[] = 'A', server?: string): Promise { + // start building the arguments list for the `dig` command + const args = [] + + // append @ to server if not present + if (server) { + if (!server.startsWith('@')) { + server = `@${server}` + } + + args.push(server) + } + + if (!Array.isArray(names)) { + names = [names] + } + + names.forEach(name => { + name = toASCII(name) + + if (Array.isArray(types) && types.length) { + types.forEach(type => { + args.push(name, type) + }) + } else if (types && typeof types === 'string') { + args.push(name, types) + } else { + args.push(name) + } + }) + + // +noall' // don't display any texts (authority, question, stats, etc) in response, + // +answer // except the answer + // +cdflag // no DNSSEC check, faster + // https://linux.die.net/man/1/dig + + const { spawnSync } = await import('node:child_process') + + const dig = spawnSync('dig', [...args, '+noall', '+answer', '+cdflag']) + let re = dig.stdout.toString() + + const dnsRecords: DnsRecord[] = [] + + // split lines & ignore comments or empty + re.split("\n") + .filter(line => line.length && !line.startsWith(';')) + .forEach(line => { + // replace tab(s) with space, then split by space + const parts = line.replace(/[\t]+/g, " ").split(" ") + + let name = String(parts[0]) + + if (name.endsWith('.')) { + name = name.slice(0, -1) + } + + dnsRecords.push({ + name, + ttl: Number(parts[1]), + type: String(parts[3]), + data: parts.slice(4).join(" ") + }) + }) + + return dnsRecords +} + +/** + * Get DNS records using the Node.js DNS module + * + * @param names The name to query + * @param types The DNS type to query + * @returns The DNS records + */ +export async function dnsRecordsNodeDns(name: string, type: string = 'A'): Promise { + const { promises: dns } = await import('node:dns') + type = type.toUpperCase() + + const dnsRecords: DnsRecord[] = [] + + try { + if (['A', 'AAAA'].includes(type)) { + const foundRecords = type === 'A' ? await dns.resolve4(name, { ttl: true }) : await dns.resolve6(name, { ttl: true }) + + foundRecords.forEach(record => { + dnsRecords.push({ + name, + type, + ttl: record.ttl, + data: record.address, + }) + }) + } else if (type === 'CNAME') { + const foundRecords = await dns.resolveCname(name) + + foundRecords.forEach(record => { + dnsRecords.push({ + name, + type, + ttl: 0, + data: record, + }) + }) + } else if (type === 'MX') { + const foundRecords = await dns.resolveMx(name) + + foundRecords.forEach(record => { + dnsRecords.push({ + name, + type, + ttl: 0, + data: `${record.priority} ${record.exchange}`, + }) + }) + } else if (type === 'NS') { + const foundRecords = await dns.resolveNs(name) + + foundRecords.forEach(record => { + dnsRecords.push({ + name, + type, + ttl: 0, + data: record, + }) + }) + } else if (type === 'SOA') { + const foundRecords = await dns.resolveSoa(name) + + dnsRecords.push({ + name, + type, + ttl: 0, + data: Object.values(foundRecords).join(' '), + }) + } else if (type === 'TXT') { + const foundRecords = await dns.resolveTxt(name) + + foundRecords.forEach(record => { + dnsRecords.push({ + name, + type, + ttl: 0, + data: record.join(' '), + }) + }) + } + } catch (e) {} + + return dnsRecords +} diff --git a/src/index.ts b/src/index.ts index 715cb16..866b6f4 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,5 @@ import { toASCII } from 'punycode' +import { dnsRecordsCloudflare, dnsRecordsGoogle, dnsRecordsNodeDig, dnsRecordsNodeDns } from './dns-resolvers.js' import { subdomainsRecords } from './subdomains.js' /** DNS Record object, with type, ttl and value */ @@ -40,64 +41,14 @@ const isDomain = (domain: string): boolean => { }) } -const dnsTypeNumbers: { [key: number]: string } = { - 1: 'A', - 2: 'NS', - 5: 'CNAME', - 6: 'SOA', - 12: 'PTR', - 15: 'MX', - 16: 'TXT', - 24: 'SIG', - 25: 'KEY', - 28: 'AAAA', - 33: 'SRV', - 257: 'CAA', -} - -const dnsResolvers: { [key: string]: Function } = { - 'cloudflare-dns': async (name: string, type: string = 'A'): Promise => { - const re = await fetch(`https://cloudflare-dns.com/dns-query?name=${toASCII(name)}&type=${type}&cd=1`, { - headers: { - accept: 'application/dns-json', - } - }) - - if (!re.ok) { - throw new Error(`Error fetching DNS records for ${name}: ${re.status} ${re.statusText}`) - } - - const json: any = await re.json() - const records: DnsRecord[] = (json.Answer || []).map((record: any) => { - return { - name: record.name, - type: dnsTypeNumbers[record.type] || String(record.type), - ttl: record.TTL, - data: record.data, - } - }) - - return records - }, - 'google-dns': async (name: string, type: string = 'A'): Promise => { - const re = await fetch(`https://dns.google/resolve?name=${toASCII(name)}&type=${type}&cd=1`) - - if (!re.ok) { - throw new Error(`Error fetching DNS records for ${name}: ${re.status} ${re.statusText}`) - } - - const json: any = await re.json() - const records: DnsRecord[] = (json.Answer || []).map((record: any) => { - return { - name: record.name, - type: dnsTypeNumbers[record.type] || String(record.type), - ttl: record.TTL, - data: record.data, - } - }) - - return records - }, +function bestDnsResolverForThisRuntime(): string { + if (navigator.userAgent === 'Cloudflare-Workers') { + return 'cloudflare-dns' + } else if (navigator.userAgent.startsWith('Node.js/')) { + return 'node-dns' + } else { + return 'google-dns' + } } /** @@ -105,7 +56,7 @@ const dnsResolvers: { [key: string]: Function } = { * * @param name Fully qualified domain name. * @param type DNS record type: A, AAAA, TXT, CNAME, MX, etc. - * @param resolver DNS resolver to use. Default: cloudflare-dns. + * @param resolver Which DNS resolver to use. If not specified, the best DNS resolver for this runtime will be used. * @returns Array of discovered `DnsRecord` objects. * * @example Get TXT records for example.com @@ -122,21 +73,25 @@ const dnsResolvers: { [key: string]: Function } = { * const mxRecords = await getDnsRecords('android.com', 'MX', 'google-dns') * ``` */ -export async function getDnsRecords(name: string, type: string = 'A', resolver: string|Function = 'cloudflare-dns'): Promise { +export async function getDnsRecords(name: string, type: string = 'A', resolver?: string): Promise { if (!isDomain(name)) { throw new Error(`"${name}" is not a valid domain name`) } - if (typeof resolver === 'string' && resolver in dnsResolvers) { - const fn = dnsResolvers[resolver] - - if (typeof fn !== 'function') { - throw new Error(`Invalid DNS resolver: ${resolver}`) - } + if (!resolver) { + resolver = bestDnsResolverForThisRuntime() + } - return fn(name, type) - } if (typeof resolver === 'function') { - return resolver(name, type) + if (resolver === 'cloudflare-dns') { + return dnsRecordsCloudflare(name, type) + } else if (resolver === 'google-dns') { + return dnsRecordsGoogle(name, type) + } else if (resolver === 'node-dig') { + return dnsRecordsNodeDig(name, type) + } else if (resolver === 'node-dns') { + return dnsRecordsNodeDns(name, type) + } else if (resolver === 'deno-dns') { + throw new Error('Deno DNS not yet implemented') } throw new Error(`Invalid DNS resolver: ${resolver}`) @@ -147,11 +102,9 @@ export type GetAllDnsRecordsOptions = { /** * Which DNS resolver to use for DNS lookup. * - * Options: cloudflare-dns, google-dns, custom resolver `Function`. - * - * @default 'cloudflare-dns' + * Options: cloudflare-dns, google-dns, node-dns, node-dig, deno-dns * */ - resolver?: string|Function + resolver?: string /** List of extra subdomains to check for */ subdomains?: string[] } @@ -165,7 +118,6 @@ export type GetAllDnsRecordsOptions = { */ export function getAllDnsRecordsStream(domain: string, options: Partial = {}): ReadableStream { options = { - resolver: 'cloudflare-dns', subdomains: [], ...options, } diff --git a/src/node-resolvers.ts b/src/node-resolvers.ts deleted file mode 100644 index 649d30b..0000000 --- a/src/node-resolvers.ts +++ /dev/null @@ -1,76 +0,0 @@ -import url from 'node:url' -import { spawnSync } from 'node:child_process' -import { DnsRecord } from './index.js' - -/** - * Get DNS records using the `dig` command - * - * @param names The name(s) to query - * @param types The DNS type(s) to query - * @param server The DNS server to query. If not provided, the default DNS server on the network will be used - * @returns The DNS records - */ -export async function getDnsRecordsDig(names: string | string[], types: string | string[] = 'A', server?: string): Promise { - // start building the arguments list for the `dig` command - const args = [] - - // append @ to server if not present - if (server) { - if (!server.startsWith('@')) { - server = `@${server}` - } - - args.push(server) - } - - if (!Array.isArray(names)) { - names = [names] - } - - names.forEach(name => { - name = url.domainToASCII(name) - - if (Array.isArray(types) && types.length) { - types.forEach(type => { - args.push(name, type) - }) - } else if (types && typeof types === 'string') { - args.push(name, types) - } else { - args.push(name) - } - }) - - // +noall' // don't display any texts (authority, question, stats, etc) in response, - // +answer // except the answer - // +cdflag // no DNSSEC check, faster - // https://linux.die.net/man/1/dig - - const dig = spawnSync('dig', [...args, '+noall', '+answer', '+cdflag']) - let re = dig.stdout.toString() - - const dnsRecords: DnsRecord[] = [] - - // split lines & ignore comments or empty - re.split("\n") - .filter(line => line.length && !line.startsWith(';')) - .forEach(line => { - // replace tab(s) with space, then split by space - const parts = line.replace(/[\t]+/g, " ").split(" ") - - let name = String(parts[0]) - - if (name.endsWith('.')) { - name = name.slice(0, -1) - } - - dnsRecords.push({ - name, - ttl: Number(parts[1]), - type: String(parts[3]), - data: parts.slice(4).join(" ") - }) - }) - - return dnsRecords -} diff --git a/test/test.js b/test/test.js index d912e01..3f54c23 100644 --- a/test/test.js +++ b/test/test.js @@ -2,38 +2,44 @@ import { strict as assert } from 'node:assert' import test from 'node:test' import { getDnsRecords, getAllDnsRecords } from '../dist/index.js' -import { getDnsRecordsDig } from '../dist/node-resolvers.js' test('get name servers for google.com (NS)', async () => { - const expectedNs = ['ns1.google.com.', 'ns2.google.com.', 'ns3.google.com.', 'ns4.google.com.'] + const expectedNs = ['ns1.google.com', 'ns2.google.com', 'ns3.google.com', 'ns4.google.com'] - const [ nsRecordsWithCloudflareDns, nsRecordsWithGoogleDns, nsRecordsWithNodeDig ] = await Promise.all([ + const [ nsRecordsWithCloudflareDns, nsRecordsWithGoogleDns, nsRecordsWithNodeDns, nsRecordsWithNodeDig ] = await Promise.all([ getDnsRecords('google.com', 'NS', 'cloudflare-dns'), getDnsRecords('google.com', 'NS', 'google-dns'), - getDnsRecords('google.com', 'NS', getDnsRecordsDig), + getDnsRecords('google.com', 'NS', 'node-dns'), + getDnsRecords('google.com', 'NS', 'node-dig'), ]) - assert.equal(nsRecordsWithCloudflareDns.length, expectedNs.length, 'Number of NameServers doesn\'t match') + assert.equal(nsRecordsWithCloudflareDns.length, expectedNs.length, 'CloudFlare DNS, Number of NameServers doesn\'t match') assert.ok(expectedNs.some(ns => ns === nsRecordsWithCloudflareDns[1].data), 'Returned NS doesn\'t match') assert.ok(expectedNs.some(ns => ns === nsRecordsWithCloudflareDns[1].data), 'Returned NS doesn\'t match') - assert.equal(nsRecordsWithGoogleDns.length, expectedNs.length, 'Number of NameServers doesn\'t match') + assert.equal(nsRecordsWithGoogleDns.length, expectedNs.length, 'Google DNS, Number of NameServers doesn\'t match') assert.ok(expectedNs.some(ns => ns === nsRecordsWithGoogleDns[1].data), 'Returned NS doesn\'t match') assert.ok(expectedNs.some(ns => ns === nsRecordsWithGoogleDns[1].data), 'Returned NS doesn\'t match') - assert.equal(nsRecordsWithNodeDig.length, expectedNs.length, 'Number of NameServers doesn\'t match') + assert.equal(nsRecordsWithNodeDns.length, expectedNs.length, 'Node DNS, Number of NameServers doesn\'t match') + assert.ok(expectedNs.some(ns => ns === nsRecordsWithNodeDns[1].data), 'Returned NS doesn\'t match') + assert.ok(expectedNs.some(ns => ns === nsRecordsWithNodeDns[1].data), 'Returned NS doesn\'t match') + + assert.equal(nsRecordsWithNodeDig.length, expectedNs.length, 'Node dig, Number of NameServers doesn\'t match') assert.ok(expectedNs.some(ns => ns === nsRecordsWithNodeDig[1].data), 'Returned NS doesn\'t match') assert.ok(expectedNs.some(ns => ns === nsRecordsWithNodeDig[1].data), 'Returned NS doesn\'t match') }); test('get A records for "mañana.com" (IDN)', async () => { - const [ aRecordsWithCloudflareDns, aRecordsWithGoogleDns ] = await Promise.all([ + const [ aRecordsWithCloudflareDns, aRecordsWithGoogleDns, aRecordsWithNodeDns ] = await Promise.all([ getDnsRecords('mañana.com', 'A', 'cloudflare-dns'), getDnsRecords('mañana.com', 'A', 'google-dns'), + getDnsRecords('mañana.com', 'A', 'node-dns'), ]) assert.notEqual(aRecordsWithCloudflareDns.length, 0, 'No A records returned') assert.equal(aRecordsWithCloudflareDns.length, aRecordsWithGoogleDns.length, 'A records length between `google-dns` and `cloudflare-dns` doesn\'t match') + assert.equal(aRecordsWithGoogleDns.length, aRecordsWithNodeDns.length, 'A records length between `google-dns` and `cloudflare-dns` doesn\'t match') }); test('get TXT records for "cloudflare.com"', async () => {