diff --git a/src/command/dns-command.ts b/src/command/dns-command.ts index 5681b960..8a0814a2 100644 --- a/src/command/dns-command.ts +++ b/src/command/dns-command.ts @@ -5,50 +5,23 @@ import type {CommandInterface} from '../types.js'; import {isExecaError} from '../helper/execa-error-check.js'; import {InvalidOptionsException} from './exception/invalid-options-exception.js'; +import ClassicDigParser from './handlers/dig/classic.js'; +import type {DnsParseResponse as DnsParseResponseClassic} from './handlers/dig/classic.js'; +import TraceDigParser from './handlers/dig/trace.js'; +import type {DnsParseResponse as DnsParseResponseTrace} from './handlers/dig/trace.js'; + type DnsOptions = { type: 'dns'; target: string; query: { - type?: string[]; + type?: string; resolver?: string; protocol?: string; port?: number; + trace?: boolean; }; }; -type DnsParseLoopResponse = { - [key: string]: any; - question?: any[]; - header: any[]; - answer: any[]; - time: number; - server: string; -}; - -type DnsParseResponse = DnsParseLoopResponse & { - rawOutput: string; -}; - -type DnsValueType = string | { - priority: number; - server: string; -}; - -type DnsSection = Record | { - domain: string; - type: string; - ttl: number; - class: string; - value: DnsValueType; -}; - -/* eslint-disable @typescript-eslint/naming-convention */ -const SECTION_REG_EXP = /(;; )(\S+)( SECTION:)/g; -const NEW_LINE_REG_EXP = /\r?\n/; -const QUERY_TIME_REG_EXP = /Query\s+time:\s+(\d+)/g; -const RESOLVER_REG_EXP = /SERVER:.*\((.*?)\)/g; -/* eslint-enable @typescript-eslint/naming-convention */ - const allowedTypes = ['A', 'AAAA', 'ANY', 'CNAME', 'DNSKEY', 'DS', 'MX', 'NS', 'NSEC', 'PTR', 'RRSIG', 'SOA', 'TXT', 'SRV']; const allowedProtocols = ['UDP', 'TCP']; @@ -60,12 +33,14 @@ const dnsOptionsSchema = Joi.object({ resolver: Joi.string().optional(), protocol: Joi.string().valid(...allowedProtocols).optional().default('udp'), port: Joi.number().optional().default('53'), + trace: Joi.boolean().optional(), }), }); export const dnsCmd = (options: DnsOptions): ExecaChildProcess => { const protocolArg = options.query.protocol?.toLowerCase() === 'tcp' ? '+tcp' : []; const resolverArg = options.query.resolver ? `@${options.query.resolver}` : []; + const traceArg = options.query.trace ? '+trace' : []; const args = [ options.target, @@ -76,6 +51,7 @@ export const dnsCmd = (options: DnsOptions): ExecaChildProcess => { '+timeout=3', '+tries=2', '+nocookie', + traceArg, protocolArg, ].flat() as string[]; @@ -85,7 +61,7 @@ export const dnsCmd = (options: DnsOptions): ExecaChildProcess => { export class DnsCommand implements CommandInterface { constructor(private readonly cmd: typeof dnsCmd) {} - async run(socket: Socket, measurementId: string, testId: string, options: unknown): Promise { + async run(socket: Socket, measurementId: string, testId: string, options: DnsOptions): Promise { const {value: cmdOptions, error} = dnsOptionsSchema.validate(options); if (error) { @@ -105,16 +81,13 @@ export class DnsCommand implements CommandInterface { try { const cmdResult = await cmd; - const parsedResult = this.parse(cmdResult.stdout); + const parsedResult = this.parse(cmdResult.stdout, Boolean(options.query.trace)); if (parsedResult instanceof Error) { throw parsedResult; } - const {answer, time, server, rawOutput} = parsedResult; - result = { - answer, time, server, rawOutput, - }; + result = parsedResult; } catch (error: unknown) { const output = isExecaError(error) ? error.stderr.toString() : ''; result = { @@ -129,127 +102,11 @@ export class DnsCommand implements CommandInterface { }); } - private parse(rawOutput: string): Error | DnsParseResponse { - const lines = rawOutput.split(NEW_LINE_REG_EXP); - - if (lines.length < 6) { - const message = lines[lines.length - 2]; - - if (!message || message.length < 2) { - return new Error(rawOutput); - } - - return new Error(message); - } - - return { - ...this.parseLoop(lines), - rawOutput, - }; - } - - private parseLoop(lines: string[]): DnsParseLoopResponse { - const result: DnsParseLoopResponse = { - header: [], - answer: [], - server: '', - time: 0, - }; - - let section = 'header'; - for (const line of lines) { - const time = this.getQueryTime(line); - if (time !== undefined) { - result.time = time; - } - - const serverMatch = this.getResolverServer(line); - if (serverMatch) { - result.server = serverMatch; - } - - let sectionChanged = false; - if (line.length === 0) { - section = ''; - } else { - const sectionMatch = SECTION_REG_EXP.exec(line); - - if (sectionMatch && sectionMatch.length >= 2) { - section = String(sectionMatch[2]).toLowerCase(); - sectionChanged = true; - } - } - - if (!section) { - continue; - } - - if (!result[section]) { - result[section] = []; - } - - if (!sectionChanged && line) { - if (section === 'header') { - result[section].push(line); - } else { - const sectionResult = this.parseSection(line.split(/\s+/g), section); - (result[section] as DnsSection[]).push(sectionResult); - } - } - } - - return result; - } - - private parseSection(values: string[], section: string): DnsSection { - if (!['answer', 'additional'].includes(section)) { - return {}; - } - - return { - domain: values[0], - type: values[3], - ttl: values[1], - class: values[2], - value: this.parseValue(values), - }; - } - - private getQueryTime(line: string): number | undefined { - const result = QUERY_TIME_REG_EXP.exec(line); - - if (!result) { - return; - } - - return Number(result[1]); - } - - private getResolverServer(line: string): string | undefined { - const result = RESOLVER_REG_EXP.exec(line); - - if (!result) { - return; - } - - return String(result[1]); - } - - private parseValue(values: string[]): DnsValueType { - const type = String(values[3]).toUpperCase(); - - if (type === 'SOA') { - return String(values.slice(4)).replace(/,/g, ' '); - } - - if (type === 'MX') { - return {priority: Number(values[4]), server: String(values[5])}; - } - - if (type === 'TXT') { - return String(values.slice(4).join(' ')); + private parse(rawOutput: string, trace: boolean): Error | DnsParseResponseClassic | DnsParseResponseTrace { + if (!trace) { + return ClassicDigParser.parse(rawOutput); } - return String(values[values.length - 1]); + return TraceDigParser.parse(rawOutput); } } diff --git a/src/command/handlers/dig/classic.ts b/src/command/handlers/dig/classic.ts new file mode 100644 index 00000000..b1dfe616 --- /dev/null +++ b/src/command/handlers/dig/classic.ts @@ -0,0 +1,125 @@ +import { + SECTION_REG_EXP, + NEW_LINE_REG_EXP, + SharedDigParser, + DnsSection, + DnsParseLoopResponse, +} from './shared.js'; + +export type DnsParseResponse = DnsParseLoopResponse & { + rawOutput: string; +}; + +/* eslint-disable @typescript-eslint/naming-convention */ +const QUERY_TIME_REG_EXP = /Query\s+time:\s+(\d+)/g; +const RESOLVER_REG_EXP = /SERVER:.*\((.*?)\)/g; +/* eslint-enable @typescript-eslint/naming-convention */ + +// eslint-disable-next-line @typescript-eslint/naming-convention +export const ClassicDigParser = { + parse(rawOutput: string): Error | DnsParseResponse { + const lines = rawOutput.split(NEW_LINE_REG_EXP); + + if (lines.length < 6) { + const message = lines[lines.length - 2]; + + if (!message || message.length < 2) { + return new Error(rawOutput); + } + + return new Error(message); + } + + return { + ...ClassicDigParser.parseLoop(lines), + rawOutput, + }; + }, + + parseLoop(lines: string[]): DnsParseLoopResponse { + const result: DnsParseLoopResponse = { + header: [], + answer: [], + server: '', + time: 0, + }; + + let section = 'header'; + for (const line of lines) { + const time = ClassicDigParser.getQueryTime(line); + if (time !== undefined) { + result.time = time; + } + + const serverMatch = ClassicDigParser.getResolverServer(line); + if (serverMatch) { + result.server = serverMatch; + } + + let sectionChanged = false; + if (line.length === 0) { + section = ''; + } else { + const sectionMatch = SECTION_REG_EXP.exec(line); + + if (sectionMatch && sectionMatch.length >= 2) { + section = String(sectionMatch[2]).toLowerCase(); + sectionChanged = true; + } + } + + if (!section) { + continue; + } + + if (!result[section]) { + result[section] = []; + } + + if (!sectionChanged && line) { + if (section === 'header') { + result[section]!.push(line); + } else { + const sectionResult = ClassicDigParser.parseSection(line.split(/\s+/g), section); + (result[section] as DnsSection[]).push(sectionResult); + } + } + } + + return { + answer: result.answer, + server: result.server, + time: result.time, + }; + }, + + parseSection(values: string[], section: string): DnsSection { + if (!['answer', 'additional'].includes(section)) { + return {}; + } + + return SharedDigParser.parseSection(values); + }, + + getQueryTime(line: string): number | undefined { + const result = QUERY_TIME_REG_EXP.exec(line); + + if (!result) { + return; + } + + return Number(result[1]); + }, + + getResolverServer(line: string): string | undefined { + const result = RESOLVER_REG_EXP.exec(line); + + if (!result) { + return; + } + + return String(result[1]); + }, +}; + +export default ClassicDigParser; diff --git a/src/command/handlers/dig/shared.ts b/src/command/handlers/dig/shared.ts new file mode 100644 index 00000000..75874c65 --- /dev/null +++ b/src/command/handlers/dig/shared.ts @@ -0,0 +1,57 @@ +export type DnsValueType = string | { + priority: number; + server: string; +}; + +export type DnsSection = Record | { + domain: string; + type: string; + ttl: number; + class: string; + value: DnsValueType; +}; + +export type DnsParseLoopResponse = { + [key: string]: any; + question?: any[]; + header?: any[]; + answer: DnsSection[]; + time: number; + server: string; +}; + +/* eslint-disable @typescript-eslint/naming-convention */ +export const SECTION_REG_EXP = /(;; )(\S+)( SECTION:)/g; +export const NEW_LINE_REG_EXP = /\r?\n/; +/* eslint-enable @typescript-eslint/naming-convention */ + +// eslint-disable-next-line @typescript-eslint/naming-convention +export const SharedDigParser = { + parseSection(values: string[]): DnsSection { + return { + domain: values[0], + type: values[3], + ttl: values[1], + class: values[2], + value: SharedDigParser.parseValue(values), + }; + }, + + parseValue(values: string[]): DnsValueType { + const type = String(values[3]).toUpperCase(); + + if (type === 'SOA') { + return String(values.slice(4)).replace(/,/g, ' '); + } + + if (type === 'MX') { + return {priority: Number(values[4]), server: String(values[5])}; + } + + if (type === 'TXT') { + return String(values.slice(4).join(' ')); + } + + return String(values[values.length - 1]); + }, +}; diff --git a/src/command/handlers/dig/trace.ts b/src/command/handlers/dig/trace.ts new file mode 100644 index 00000000..7fc8e678 --- /dev/null +++ b/src/command/handlers/dig/trace.ts @@ -0,0 +1,72 @@ +import { + NEW_LINE_REG_EXP, + SharedDigParser, + DnsSection, + DnsParseLoopResponse, +} from './shared.js'; + +export type DnsParseResponse = { + result: DnsParseLoopResponse[]; + rawOutput: string; +}; + +/* eslint-disable @typescript-eslint/naming-convention */ +const RESOLVER_REG_EXP = /from.*\((.*?)\)/; +const QUERY_TIME_REG_EXP = /in\s+(\d+)\s+ms/; +/* eslint-enable @typescript-eslint/naming-convention */ + +// eslint-disable-next-line @typescript-eslint/naming-convention +export const TraceDigParser = { + parse(rawOutput: string): Error | DnsParseResponse { + const lines = rawOutput.split(NEW_LINE_REG_EXP); + + if (lines.length < 3) { + const message = lines[lines.length - 2]; + + if (!message || message.length < 2) { + return new Error(rawOutput); + } + + return new Error(message); + } + + return { + result: TraceDigParser.parseLoop(lines.slice(2)), + rawOutput, + }; + }, + + parseLoop(lines: string[]): DnsParseLoopResponse[] { + const groups = []; + + for (let i = 0; i < lines.length - 1; i++) { + const line = lines[lines.length - 1 - i]; + + if (!line) { + continue; + } + + if (line.startsWith(';;')) { + const resolver = RESOLVER_REG_EXP.exec(line); + const queryTime = QUERY_TIME_REG_EXP.exec(line); + + groups.push({ + time: queryTime ? Number(queryTime[1]) : 0, + server: resolver ? String(resolver[1]) : '', + answer: [] as DnsSection[], + }); + + continue; + } + + const groupIndex = groups.length - 1; + + const answer = SharedDigParser.parseSection(line.split(/\s+/g)); + groups[groupIndex]!.answer.push(answer); + } + + return groups.reverse().map(item => ({...item, answer: item.answer.reverse()})); + }, +}; + +export default TraceDigParser; diff --git a/test/mocks/dig/trace.success.txt b/test/mocks/dig/trace.success.txt new file mode 100644 index 00000000..e9f9173e --- /dev/null +++ b/test/mocks/dig/trace.success.txt @@ -0,0 +1,50 @@ +; <<>> DiG 9.18.1-1ubuntu1-Ubuntu <<>> +trace +nocookie +tries +timeout cdn.jsdelivr.net +;; global options: +cmd +. 6593 IN NS j.root-servers.net. +. 6593 IN NS c.root-servers.net. +. 6593 IN NS g.root-servers.net. +. 6593 IN NS b.root-servers.net. +. 6593 IN NS i.root-servers.net. +. 6593 IN NS a.root-servers.net. +. 6593 IN NS e.root-servers.net. +. 6593 IN NS l.root-servers.net. +. 6593 IN NS k.root-servers.net. +. 6593 IN NS f.root-servers.net. +. 6593 IN NS m.root-servers.net. +. 6593 IN NS d.root-servers.net. +. 6593 IN NS h.root-servers.net. +;; Received 811 bytes from 127.0.0.53#53(127.0.0.53) in 4 ms + +net. 172800 IN NS a.gtld-servers.net. +net. 172800 IN NS b.gtld-servers.net. +net. 172800 IN NS c.gtld-servers.net. +net. 172800 IN NS d.gtld-servers.net. +net. 172800 IN NS e.gtld-servers.net. +net. 172800 IN NS f.gtld-servers.net. +net. 172800 IN NS g.gtld-servers.net. +net. 172800 IN NS h.gtld-servers.net. +net. 172800 IN NS i.gtld-servers.net. +net. 172800 IN NS j.gtld-servers.net. +net. 172800 IN NS k.gtld-servers.net. +net. 172800 IN NS l.gtld-servers.net. +net. 172800 IN NS m.gtld-servers.net. +net. 86400 IN DS 35886 8 2 7862B27F5F516EBE19680444D4CE5E762981931842C465F00236401D 8BD973EE +net. 86400 IN RRSIG DS 8 1 86400 20220509170000 20220426160000 47671 . IbbmgURsOFU02lEF33VZIt90+xd+DSAy6n+LowQlVMbxAxB6BsF5nNi1 n0Xsfixgxk06JOsQOLeMnTSX6xGZ5baCHa8pWGlS2CZ3wpmWt9Fg5Y/r Vqpneq9sBXuvcyLZ4OOzxqY8Xvqnj5EBqx2wegOxqOzbw4I2MLPeFWS4 hRvHcodnVkAHaWbDWLi3olY+8nIdWMLRdMxA1VkzliQn0MOPNn6mhKeG HTh3uOo7FSm+adbefRhC6X8QDoSFQ6VKYhd3mVJ7HGJ2JsvpVsJlG5Ff WNBztAw7W5Tg9aIVxPwfl3tNvlkpyvDqgurJLXVqmB7F+t3f3+8QKDMb nStNJg== +;; Received 1173 bytes from 199.7.91.13#53(d.root-servers.net) in 24 ms + +jsdelivr.net. 172800 IN NS dns1.p03.nsone.net. +jsdelivr.net. 172800 IN NS dns2.p03.nsone.net. +jsdelivr.net. 172800 IN NS dns3.p03.nsone.net. +jsdelivr.net. 172800 IN NS dns4.p03.nsone.net. +jsdelivr.net. 172800 IN NS gns1.cloudns.net. +jsdelivr.net. 172800 IN NS gns2.cloudns.net. +jsdelivr.net. 172800 IN NS gns3.cloudns.net. +jsdelivr.net. 172800 IN NS gns4.cloudns.net. +A1RT98BS5QGC9NFI51S9HCI47ULJG6JH.net. 86400 IN NSEC3 1 1 0 - A1RTLNPGULOGN7B9A62SHJE1U3TTP8DR NS SOA RRSIG DNSKEY NSEC3PARAM +A1RT98BS5QGC9NFI51S9HCI47ULJG6JH.net. 86400 IN RRSIG NSEC3 8 2 86400 20220503055829 20220426044829 45728 net. kqtegTsTwSJ9OJ/4UpoKnOzaSfaEaSxd03SERi2nhwhL1Dd/xjXF+Oy+ gB2NxI8IHBdT0Za1PadKRYefjjI+phvYB2Z2s7LqLE5iLju+2R6mVMQu TfkTO8GJWxBDMcXdcX2cjTxSan7y8m4kbzeGvqFHwiWtodDnT2lFQBvg QKqFhOv3D/NZtRua5mWeuy78rB3MIZQmGQ7rwapaz4h4eg== +GBMGFDMMHIENHS2RNSDAQ541H88GB5IO.net. 86400 IN NSEC3 1 1 0 - GBMKRB78QIII3C3NIFGFSK27G1IBHMM0 NS DS RRSIG +GBMGFDMMHIENHS2RNSDAQ541H88GB5IO.net. 86400 IN RRSIG NSEC3 8 2 86400 20220430055643 20220423044643 45728 net. JIFFMnHeaG97gcZYao5JGWkTJ4zGKDAKrfLOi9KrKVmBroFmnjfOytBo NgTxIl17GlLW2kaq1doKHpDHUu8y4u/56OdJmeBgL+OYqGbQjmICztpK dU+p9eoXUPLMkDFTqrwFhN1dm51Q9sle4MiDHMeLHBrs76jlpBcR+PQn NO9LoUolBGhvxHSQfhwCyAi0slPURsAkC4DBUS1WrpipCQ== +;; Received 1116 bytes from 192.26.92.30#53(c.gtld-servers.net) in 32 ms + +cdn.jsdelivr.net. 900 IN CNAME jsdelivr.map.fastly.net. +;; Received 79 bytes from 185.136.98.122#53(gns3.cloudns.net) in 28 ms diff --git a/test/mocks/dns-trace-success.json b/test/mocks/dns-trace-success.json new file mode 100644 index 00000000..66d30179 --- /dev/null +++ b/test/mocks/dns-trace-success.json @@ -0,0 +1,313 @@ +{ + "testId": "test", + "measurementId": "measurement", + "result": { + "result": [ + { + "time": 4, + "server": "127.0.0.53", + "answer": [ + { + "domain": ".", + "type": "NS", + "ttl": "6593", + "class": "IN", + "value": "c.root-servers.net." + }, + { + "domain": ".", + "type": "NS", + "ttl": "6593", + "class": "IN", + "value": "g.root-servers.net." + }, + { + "domain": ".", + "type": "NS", + "ttl": "6593", + "class": "IN", + "value": "b.root-servers.net." + }, + { + "domain": ".", + "type": "NS", + "ttl": "6593", + "class": "IN", + "value": "i.root-servers.net." + }, + { + "domain": ".", + "type": "NS", + "ttl": "6593", + "class": "IN", + "value": "a.root-servers.net." + }, + { + "domain": ".", + "type": "NS", + "ttl": "6593", + "class": "IN", + "value": "e.root-servers.net." + }, + { + "domain": ".", + "type": "NS", + "ttl": "6593", + "class": "IN", + "value": "l.root-servers.net." + }, + { + "domain": ".", + "type": "NS", + "ttl": "6593", + "class": "IN", + "value": "k.root-servers.net." + }, + { + "domain": ".", + "type": "NS", + "ttl": "6593", + "class": "IN", + "value": "f.root-servers.net." + }, + { + "domain": ".", + "type": "NS", + "ttl": "6593", + "class": "IN", + "value": "m.root-servers.net." + }, + { + "domain": ".", + "type": "NS", + "ttl": "6593", + "class": "IN", + "value": "d.root-servers.net." + }, + { + "domain": ".", + "type": "NS", + "ttl": "6593", + "class": "IN", + "value": "h.root-servers.net." + } + ] + }, + { + "time": 24, + "server": "d.root-servers.net", + "answer": [ + { + "domain": "net.", + "type": "NS", + "ttl": "172800", + "class": "IN", + "value": "a.gtld-servers.net." + }, + { + "domain": "net.", + "type": "NS", + "ttl": "172800", + "class": "IN", + "value": "b.gtld-servers.net." + }, + { + "domain": "net.", + "type": "NS", + "ttl": "172800", + "class": "IN", + "value": "c.gtld-servers.net." + }, + { + "domain": "net.", + "type": "NS", + "ttl": "172800", + "class": "IN", + "value": "d.gtld-servers.net." + }, + { + "domain": "net.", + "type": "NS", + "ttl": "172800", + "class": "IN", + "value": "e.gtld-servers.net." + }, + { + "domain": "net.", + "type": "NS", + "ttl": "172800", + "class": "IN", + "value": "f.gtld-servers.net." + }, + { + "domain": "net.", + "type": "NS", + "ttl": "172800", + "class": "IN", + "value": "g.gtld-servers.net." + }, + { + "domain": "net.", + "type": "NS", + "ttl": "172800", + "class": "IN", + "value": "h.gtld-servers.net." + }, + { + "domain": "net.", + "type": "NS", + "ttl": "172800", + "class": "IN", + "value": "i.gtld-servers.net." + }, + { + "domain": "net.", + "type": "NS", + "ttl": "172800", + "class": "IN", + "value": "j.gtld-servers.net." + }, + { + "domain": "net.", + "type": "NS", + "ttl": "172800", + "class": "IN", + "value": "k.gtld-servers.net." + }, + { + "domain": "net.", + "type": "NS", + "ttl": "172800", + "class": "IN", + "value": "l.gtld-servers.net." + }, + { + "domain": "net.", + "type": "NS", + "ttl": "172800", + "class": "IN", + "value": "m.gtld-servers.net." + }, + { + "domain": "net.", + "type": "DS", + "ttl": "86400", + "class": "IN", + "value": "8BD973EE" + }, + { + "domain": "net.", + "type": "RRSIG", + "ttl": "86400", + "class": "IN", + "value": "nStNJg==" + } + ] + }, + { + "time": 32, + "server": "c.gtld-servers.net", + "answer": [ + { + "domain": "jsdelivr.net.", + "type": "NS", + "ttl": "172800", + "class": "IN", + "value": "dns1.p03.nsone.net." + }, + { + "domain": "jsdelivr.net.", + "type": "NS", + "ttl": "172800", + "class": "IN", + "value": "dns2.p03.nsone.net." + }, + { + "domain": "jsdelivr.net.", + "type": "NS", + "ttl": "172800", + "class": "IN", + "value": "dns3.p03.nsone.net." + }, + { + "domain": "jsdelivr.net.", + "type": "NS", + "ttl": "172800", + "class": "IN", + "value": "dns4.p03.nsone.net." + }, + { + "domain": "jsdelivr.net.", + "type": "NS", + "ttl": "172800", + "class": "IN", + "value": "gns1.cloudns.net." + }, + { + "domain": "jsdelivr.net.", + "type": "NS", + "ttl": "172800", + "class": "IN", + "value": "gns2.cloudns.net." + }, + { + "domain": "jsdelivr.net.", + "type": "NS", + "ttl": "172800", + "class": "IN", + "value": "gns3.cloudns.net." + }, + { + "domain": "jsdelivr.net.", + "type": "NS", + "ttl": "172800", + "class": "IN", + "value": "gns4.cloudns.net." + }, + { + "domain": "A1RT98BS5QGC9NFI51S9HCI47ULJG6JH.net.", + "type": "NSEC3", + "ttl": "86400", + "class": "IN", + "value": "NSEC3PARAM" + }, + { + "domain": "A1RT98BS5QGC9NFI51S9HCI47ULJG6JH.net.", + "type": "RRSIG", + "ttl": "86400", + "class": "IN", + "value": "QKqFhOv3D/NZtRua5mWeuy78rB3MIZQmGQ7rwapaz4h4eg==" + }, + { + "domain": "GBMGFDMMHIENHS2RNSDAQ541H88GB5IO.net.", + "type": "NSEC3", + "ttl": "86400", + "class": "IN", + "value": "RRSIG" + }, + { + "domain": "GBMGFDMMHIENHS2RNSDAQ541H88GB5IO.net.", + "type": "RRSIG", + "ttl": "86400", + "class": "IN", + "value": "NO9LoUolBGhvxHSQfhwCyAi0slPURsAkC4DBUS1WrpipCQ==" + } + ] + }, + { + "time": 28, + "server": "gns3.cloudns.net", + "answer": [ + { + "domain": "cdn.jsdelivr.net.", + "type": "CNAME", + "ttl": "900", + "class": "IN", + "value": "jsdelivr.map.fastly.net." + } + ] + } + ], + "rawOutput": "; <<>> DiG 9.18.1-1ubuntu1-Ubuntu <<>> +trace +nocookie +tries +timeout cdn.jsdelivr.net\n;; global options: +cmd\n.\t\t\t6593\tIN\tNS\tj.root-servers.net.\n.\t\t\t6593\tIN\tNS\tc.root-servers.net.\n.\t\t\t6593\tIN\tNS\tg.root-servers.net.\n.\t\t\t6593\tIN\tNS\tb.root-servers.net.\n.\t\t\t6593\tIN\tNS\ti.root-servers.net.\n.\t\t\t6593\tIN\tNS\ta.root-servers.net.\n.\t\t\t6593\tIN\tNS\te.root-servers.net.\n.\t\t\t6593\tIN\tNS\tl.root-servers.net.\n.\t\t\t6593\tIN\tNS\tk.root-servers.net.\n.\t\t\t6593\tIN\tNS\tf.root-servers.net.\n.\t\t\t6593\tIN\tNS\tm.root-servers.net.\n.\t\t\t6593\tIN\tNS\td.root-servers.net.\n.\t\t\t6593\tIN\tNS\th.root-servers.net.\n;; Received 811 bytes from 127.0.0.53#53(127.0.0.53) in 4 ms\n\nnet.\t\t\t172800\tIN\tNS\ta.gtld-servers.net.\nnet.\t\t\t172800\tIN\tNS\tb.gtld-servers.net.\nnet.\t\t\t172800\tIN\tNS\tc.gtld-servers.net.\nnet.\t\t\t172800\tIN\tNS\td.gtld-servers.net.\nnet.\t\t\t172800\tIN\tNS\te.gtld-servers.net.\nnet.\t\t\t172800\tIN\tNS\tf.gtld-servers.net.\nnet.\t\t\t172800\tIN\tNS\tg.gtld-servers.net.\nnet.\t\t\t172800\tIN\tNS\th.gtld-servers.net.\nnet.\t\t\t172800\tIN\tNS\ti.gtld-servers.net.\nnet.\t\t\t172800\tIN\tNS\tj.gtld-servers.net.\nnet.\t\t\t172800\tIN\tNS\tk.gtld-servers.net.\nnet.\t\t\t172800\tIN\tNS\tl.gtld-servers.net.\nnet.\t\t\t172800\tIN\tNS\tm.gtld-servers.net.\nnet.\t\t\t86400\tIN\tDS\t35886 8 2 7862B27F5F516EBE19680444D4CE5E762981931842C465F00236401D 8BD973EE\nnet.\t\t\t86400\tIN\tRRSIG\tDS 8 1 86400 20220509170000 20220426160000 47671 . IbbmgURsOFU02lEF33VZIt90+xd+DSAy6n+LowQlVMbxAxB6BsF5nNi1 n0Xsfixgxk06JOsQOLeMnTSX6xGZ5baCHa8pWGlS2CZ3wpmWt9Fg5Y/r Vqpneq9sBXuvcyLZ4OOzxqY8Xvqnj5EBqx2wegOxqOzbw4I2MLPeFWS4 hRvHcodnVkAHaWbDWLi3olY+8nIdWMLRdMxA1VkzliQn0MOPNn6mhKeG HTh3uOo7FSm+adbefRhC6X8QDoSFQ6VKYhd3mVJ7HGJ2JsvpVsJlG5Ff WNBztAw7W5Tg9aIVxPwfl3tNvlkpyvDqgurJLXVqmB7F+t3f3+8QKDMb nStNJg==\n;; Received 1173 bytes from 199.7.91.13#53(d.root-servers.net) in 24 ms\n\njsdelivr.net.\t\t172800\tIN\tNS\tdns1.p03.nsone.net.\njsdelivr.net.\t\t172800\tIN\tNS\tdns2.p03.nsone.net.\njsdelivr.net.\t\t172800\tIN\tNS\tdns3.p03.nsone.net.\njsdelivr.net.\t\t172800\tIN\tNS\tdns4.p03.nsone.net.\njsdelivr.net.\t\t172800\tIN\tNS\tgns1.cloudns.net.\njsdelivr.net.\t\t172800\tIN\tNS\tgns2.cloudns.net.\njsdelivr.net.\t\t172800\tIN\tNS\tgns3.cloudns.net.\njsdelivr.net.\t\t172800\tIN\tNS\tgns4.cloudns.net.\nA1RT98BS5QGC9NFI51S9HCI47ULJG6JH.net. 86400 IN NSEC3 1 1 0 - A1RTLNPGULOGN7B9A62SHJE1U3TTP8DR NS SOA RRSIG DNSKEY NSEC3PARAM\nA1RT98BS5QGC9NFI51S9HCI47ULJG6JH.net. 86400 IN RRSIG NSEC3 8 2 86400 20220503055829 20220426044829 45728 net. kqtegTsTwSJ9OJ/4UpoKnOzaSfaEaSxd03SERi2nhwhL1Dd/xjXF+Oy+ gB2NxI8IHBdT0Za1PadKRYefjjI+phvYB2Z2s7LqLE5iLju+2R6mVMQu TfkTO8GJWxBDMcXdcX2cjTxSan7y8m4kbzeGvqFHwiWtodDnT2lFQBvg QKqFhOv3D/NZtRua5mWeuy78rB3MIZQmGQ7rwapaz4h4eg==\nGBMGFDMMHIENHS2RNSDAQ541H88GB5IO.net. 86400 IN NSEC3 1 1 0 - GBMKRB78QIII3C3NIFGFSK27G1IBHMM0 NS DS RRSIG\nGBMGFDMMHIENHS2RNSDAQ541H88GB5IO.net. 86400 IN RRSIG NSEC3 8 2 86400 20220430055643 20220423044643 45728 net. JIFFMnHeaG97gcZYao5JGWkTJ4zGKDAKrfLOi9KrKVmBroFmnjfOytBo NgTxIl17GlLW2kaq1doKHpDHUu8y4u/56OdJmeBgL+OYqGbQjmICztpK dU+p9eoXUPLMkDFTqrwFhN1dm51Q9sle4MiDHMeLHBrs76jlpBcR+PQn NO9LoUolBGhvxHSQfhwCyAi0slPURsAkC4DBUS1WrpipCQ==\n;; Received 1116 bytes from 192.26.92.30#53(c.gtld-servers.net) in 32 ms\n\ncdn.jsdelivr.net.\t900\tIN\tCNAME\tjsdelivr.map.fastly.net.\n;; Received 79 bytes from 185.136.98.122#53(gns3.cloudns.net) in 28 ms\n" + } +} diff --git a/test/mocks/dns-trace-success.txt b/test/mocks/dns-trace-success.txt new file mode 100644 index 00000000..e9f9173e --- /dev/null +++ b/test/mocks/dns-trace-success.txt @@ -0,0 +1,50 @@ +; <<>> DiG 9.18.1-1ubuntu1-Ubuntu <<>> +trace +nocookie +tries +timeout cdn.jsdelivr.net +;; global options: +cmd +. 6593 IN NS j.root-servers.net. +. 6593 IN NS c.root-servers.net. +. 6593 IN NS g.root-servers.net. +. 6593 IN NS b.root-servers.net. +. 6593 IN NS i.root-servers.net. +. 6593 IN NS a.root-servers.net. +. 6593 IN NS e.root-servers.net. +. 6593 IN NS l.root-servers.net. +. 6593 IN NS k.root-servers.net. +. 6593 IN NS f.root-servers.net. +. 6593 IN NS m.root-servers.net. +. 6593 IN NS d.root-servers.net. +. 6593 IN NS h.root-servers.net. +;; Received 811 bytes from 127.0.0.53#53(127.0.0.53) in 4 ms + +net. 172800 IN NS a.gtld-servers.net. +net. 172800 IN NS b.gtld-servers.net. +net. 172800 IN NS c.gtld-servers.net. +net. 172800 IN NS d.gtld-servers.net. +net. 172800 IN NS e.gtld-servers.net. +net. 172800 IN NS f.gtld-servers.net. +net. 172800 IN NS g.gtld-servers.net. +net. 172800 IN NS h.gtld-servers.net. +net. 172800 IN NS i.gtld-servers.net. +net. 172800 IN NS j.gtld-servers.net. +net. 172800 IN NS k.gtld-servers.net. +net. 172800 IN NS l.gtld-servers.net. +net. 172800 IN NS m.gtld-servers.net. +net. 86400 IN DS 35886 8 2 7862B27F5F516EBE19680444D4CE5E762981931842C465F00236401D 8BD973EE +net. 86400 IN RRSIG DS 8 1 86400 20220509170000 20220426160000 47671 . IbbmgURsOFU02lEF33VZIt90+xd+DSAy6n+LowQlVMbxAxB6BsF5nNi1 n0Xsfixgxk06JOsQOLeMnTSX6xGZ5baCHa8pWGlS2CZ3wpmWt9Fg5Y/r Vqpneq9sBXuvcyLZ4OOzxqY8Xvqnj5EBqx2wegOxqOzbw4I2MLPeFWS4 hRvHcodnVkAHaWbDWLi3olY+8nIdWMLRdMxA1VkzliQn0MOPNn6mhKeG HTh3uOo7FSm+adbefRhC6X8QDoSFQ6VKYhd3mVJ7HGJ2JsvpVsJlG5Ff WNBztAw7W5Tg9aIVxPwfl3tNvlkpyvDqgurJLXVqmB7F+t3f3+8QKDMb nStNJg== +;; Received 1173 bytes from 199.7.91.13#53(d.root-servers.net) in 24 ms + +jsdelivr.net. 172800 IN NS dns1.p03.nsone.net. +jsdelivr.net. 172800 IN NS dns2.p03.nsone.net. +jsdelivr.net. 172800 IN NS dns3.p03.nsone.net. +jsdelivr.net. 172800 IN NS dns4.p03.nsone.net. +jsdelivr.net. 172800 IN NS gns1.cloudns.net. +jsdelivr.net. 172800 IN NS gns2.cloudns.net. +jsdelivr.net. 172800 IN NS gns3.cloudns.net. +jsdelivr.net. 172800 IN NS gns4.cloudns.net. +A1RT98BS5QGC9NFI51S9HCI47ULJG6JH.net. 86400 IN NSEC3 1 1 0 - A1RTLNPGULOGN7B9A62SHJE1U3TTP8DR NS SOA RRSIG DNSKEY NSEC3PARAM +A1RT98BS5QGC9NFI51S9HCI47ULJG6JH.net. 86400 IN RRSIG NSEC3 8 2 86400 20220503055829 20220426044829 45728 net. kqtegTsTwSJ9OJ/4UpoKnOzaSfaEaSxd03SERi2nhwhL1Dd/xjXF+Oy+ gB2NxI8IHBdT0Za1PadKRYefjjI+phvYB2Z2s7LqLE5iLju+2R6mVMQu TfkTO8GJWxBDMcXdcX2cjTxSan7y8m4kbzeGvqFHwiWtodDnT2lFQBvg QKqFhOv3D/NZtRua5mWeuy78rB3MIZQmGQ7rwapaz4h4eg== +GBMGFDMMHIENHS2RNSDAQ541H88GB5IO.net. 86400 IN NSEC3 1 1 0 - GBMKRB78QIII3C3NIFGFSK27G1IBHMM0 NS DS RRSIG +GBMGFDMMHIENHS2RNSDAQ541H88GB5IO.net. 86400 IN RRSIG NSEC3 8 2 86400 20220430055643 20220423044643 45728 net. JIFFMnHeaG97gcZYao5JGWkTJ4zGKDAKrfLOi9KrKVmBroFmnjfOytBo NgTxIl17GlLW2kaq1doKHpDHUu8y4u/56OdJmeBgL+OYqGbQjmICztpK dU+p9eoXUPLMkDFTqrwFhN1dm51Q9sle4MiDHMeLHBrs76jlpBcR+PQn NO9LoUolBGhvxHSQfhwCyAi0slPURsAkC4DBUS1WrpipCQ== +;; Received 1116 bytes from 192.26.92.30#53(c.gtld-servers.net) in 32 ms + +cdn.jsdelivr.net. 900 IN CNAME jsdelivr.map.fastly.net. +;; Received 79 bytes from 185.136.98.122#53(gns3.cloudns.net) in 28 ms diff --git a/test/unit/cmd/dns.test.ts b/test/unit/cmd/dns.test.ts index 6694645f..2fbe0024 100644 --- a/test/unit/cmd/dns.test.ts +++ b/test/unit/cmd/dns.test.ts @@ -12,9 +12,33 @@ describe('dns command', () => { sandbox.reset(); }); - it('should parse trace - dns-success-linux', async () => { + it('should parse trace - dns-trace-success', async () => { + const testCase = 'dns-trace-success'; + const options = { + type: 'dns' as const, + target: 'cdn.jsdelivr.net', + query: { + trace: true, + }, + }; + + const rawOutput = getCmdMock(testCase); + const expectedResult = getCmdMockResult(testCase); + + const mockCmd = Promise.resolve({stdout: rawOutput}); + + const dns = new DnsCommand((): any => mockCmd); + await dns.run(mockSocket as any, 'measurement', 'test', options); + + expect(mockSocket.emit.calledOnce).to.be.true; + expect(mockSocket.emit.firstCall.args[0]).to.equal('probe:measurement:result'); + expect(mockSocket.emit.firstCall.args[1]).to.deep.equal(expectedResult); + }); + + it('should parse dns - dns-success-linux', async () => { const testCase = 'dns-success-linux'; const options = { + type: 'dns' as const, target: 'google.com', query: { type: 'TXT', @@ -37,6 +61,7 @@ describe('dns command', () => { it('should return ExecaError - dns-success-linux', async () => { const testCase = 'dns-resolver-error-linux'; const options = { + type: 'dns' as const, target: 'google.com', query: { type: 'TXT', diff --git a/test/unit/cmd/helpers/dig/trace.test.ts b/test/unit/cmd/helpers/dig/trace.test.ts new file mode 100644 index 00000000..cf165dbb --- /dev/null +++ b/test/unit/cmd/helpers/dig/trace.test.ts @@ -0,0 +1,18 @@ +import {expect} from 'chai'; +import { + TraceDigParser, + DnsParseResponse, +} from '../../../../../src/command/handlers/dig/trace.js'; +import {getCmdMock} from '../../../../utils.js'; + +describe('dig trace helper', () => { + describe('parse', () => { + it('should succeed', () => { + const rawOutput = getCmdMock('dig/trace.success'); + const parsedOutput = TraceDigParser.parse(rawOutput); + + expect(parsedOutput).to.not.be.instanceof(Error); + expect((parsedOutput as DnsParseResponse).result.length).to.equal(4); + }); + }); +});