├── .github ├── FUNDING.yml └── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── compilation_error.md │ ├── config.yml │ ├── feature_request.md │ └── question.md ├── .gitignore ├── .rustfmt.toml ├── .travis.yml ├── Cargo.lock ├── Cargo.toml ├── Dockerfile ├── Justfile ├── LICENCE ├── README.md ├── build.rs ├── completions ├── dog.bash ├── dog.fish ├── dog.ps1 └── dog.zsh ├── dns-transport ├── Cargo.toml └── src │ ├── auto.rs │ ├── error.rs │ ├── https.rs │ ├── lib.rs │ ├── tcp.rs │ ├── tls.rs │ ├── tls_stream.rs │ └── udp.rs ├── dns ├── Cargo.toml ├── fuzz │ ├── .gitignore │ ├── Cargo.lock │ ├── Cargo.toml │ └── fuzz_targets │ │ └── fuzz_parsing.rs ├── src │ ├── lib.rs │ ├── record │ │ ├── a.rs │ │ ├── aaaa.rs │ │ ├── caa.rs │ │ ├── cname.rs │ │ ├── eui48.rs │ │ ├── eui64.rs │ │ ├── hinfo.rs │ │ ├── loc.rs │ │ ├── mod.rs │ │ ├── mx.rs │ │ ├── naptr.rs │ │ ├── ns.rs │ │ ├── openpgpkey.rs │ │ ├── opt.rs │ │ ├── others.rs │ │ ├── ptr.rs │ │ ├── soa.rs │ │ ├── srv.rs │ │ ├── sshfp.rs │ │ ├── tlsa.rs │ │ ├── txt.rs │ │ └── uri.rs │ ├── strings.rs │ ├── types.rs │ └── wire.rs └── tests │ ├── wire_building_tests.rs │ └── wire_parsing_tests.rs ├── dog-screenshot.png ├── man └── dog.1.md ├── src ├── colours.rs ├── connect.rs ├── hints.rs ├── logger.rs ├── main.rs ├── options.rs ├── output.rs ├── requests.rs ├── resolve.rs ├── table.rs ├── txid.rs └── usage.txt └── xtests ├── README.md ├── features ├── none.toml └── outputs │ ├── disabled_https.txt │ └── disabled_tls.txt ├── live ├── badssl.toml ├── basics.toml ├── bins.toml ├── https.toml ├── json.toml ├── tcp.toml ├── tls.toml └── udp.toml ├── madns ├── a-records.toml ├── aaaa-records.toml ├── caa-records.toml ├── cname-records.toml ├── eui48-records.toml ├── eui64-records.toml ├── hinfo-records.toml ├── loc-records.toml ├── mx-records.toml ├── naptr-records.toml ├── ns-records.toml ├── openpgpkey-records.toml ├── opt-records.toml ├── outputs │ ├── a.example.ansitxt │ ├── a.example.json │ ├── aaaa.example.ansitxt │ ├── aaaa.example.json │ ├── ansi.str.example.ansitxt │ ├── ansi.str.example.json │ ├── bad-regex.naptr.example.ansitxt │ ├── bad-utf8.caa.example.ansitxt │ ├── bad-utf8.caa.example.json │ ├── bad-utf8.hinfo.example.ansitxt │ ├── bad-utf8.hinfo.example.json │ ├── bad-utf8.naptr.invalid.ansitxt │ ├── bad-utf8.naptr.invalid.json │ ├── bad-utf8.txt.example.ansitxt │ ├── bad-utf8.txt.example.json │ ├── bad-utf8.uri.example.ansitxt │ ├── bad-utf8.uri.example.json │ ├── caa.example.ansitxt │ ├── caa.example.json │ ├── cname.example.ansitxt │ ├── cname.example.json │ ├── critical.caa.example.ansitxt │ ├── critical.caa.example.json │ ├── do-flag.opt.example.ansitxt │ ├── do-flag.opt.example.json │ ├── eui48.example.ansitxt │ ├── eui48.example.json │ ├── eui64.example.ansitxt │ ├── eui64.example.json │ ├── far-negative-latitude.loc.invalid.ansitxt │ ├── far-negative-latitude.loc.invalid.json │ ├── far-negative-longitude.loc.invalid.ansitxt │ ├── far-negative-longitude.loc.invalid.json │ ├── far-positive-latitude.loc.invalid.ansitxt │ ├── far-positive-latitude.loc.invalid.json │ ├── far-positive-longitude.loc.invalid.ansitxt │ ├── far-positive-longitude.loc.invalid.json │ ├── hinfo.example.ansitxt │ ├── hinfo.example.json │ ├── loc.example.ansitxt │ ├── loc.example.json │ ├── mx.example.ansitxt │ ├── mx.example.json │ ├── named.opt.invalid.ansitxt │ ├── named.opt.invalid.json │ ├── naptr.example.ansitxt │ ├── naptr.example.json │ ├── newline.str.example.ansitxt │ ├── newline.str.example.json │ ├── ns.example.ansitxt │ ├── ns.example.json │ ├── null.str.example.ansitxt │ ├── null.str.example.json │ ├── openpgpkey.example.ansitxt │ ├── openpgpkey.example.json │ ├── opt.example.ansitxt │ ├── opt.example.json │ ├── other-flags.opt.example.ansitxt │ ├── other-flags.opt.example.json │ ├── others.caa.example.ansitxt │ ├── others.caa.example.json │ ├── ptr.example.ansitxt │ ├── ptr.example.json │ ├── slash.uri.example.ansitxt │ ├── soa.example.ansitxt │ ├── soa.example.json │ ├── srv.example.ansitxt │ ├── srv.example.json │ ├── sshfp.example.ansitxt │ ├── sshfp.example.json │ ├── tab.str.example.ansitxt │ ├── tab.str.example.json │ ├── tlsa.example.ansitxt │ ├── tlsa.example.json │ ├── txt.example.ansitxt │ ├── txt.example.json │ ├── upperbit.str.example.ansitxt │ ├── upperbit.str.example.json │ ├── uri.example.ansitxt │ ├── uri.example.json │ ├── utf8.caa.example.ansitxt │ ├── utf8.caa.example.json │ ├── utf8.hinfo.example.ansitxt │ ├── utf8.hinfo.example.json │ ├── utf8.naptr.invalid.ansitxt │ ├── utf8.naptr.invalid.json │ ├── utf8.txt.example.ansitxt │ ├── utf8.txt.example.json │ ├── utf8.uri.example.ansitxt │ └── utf8.uri.example.json ├── protocol-chars.toml ├── protocol-compression.toml ├── protocol-error-codes.toml ├── ptr-records.toml ├── soa-records.toml ├── srv-records.toml ├── sshfp-records.toml ├── tlsa-records.toml ├── txt-records.toml └── uri-records.toml └── options ├── errors.toml ├── help.toml └── outputs ├── huge-domain.txt ├── invalid-argument.txt ├── invalid-protocol-tweak.txt ├── invalid-query-class.txt ├── invalid-query-type.txt ├── missing-nameserver.txt ├── missing-parameter.txt └── opt-query.txt /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: ogham 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Report a crash, runtime error, or invalid output in dog 4 | --- 5 | 6 | If dog does something unexpected, or displays an error on the screen, or if it outright crashes, then please include the following information in your report: 7 | 8 | - The version of dog being used (`dog --version`) 9 | - The command-line arguments you are using 10 | - Your operating system and hardware platform 11 | 12 | If it’s a crash, please include the full text of the crash that gets printed to the screen. If you’re seeing unexpected behaviour, a screenshot of the issue will help a lot. 13 | 14 | --- 15 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/compilation_error.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Compilation error 3 | about: Report a problem compiling dog 4 | --- 5 | 6 | If dog fails to compile, or if there is a problem during the build process, then please include the following information in your report: 7 | 8 | - The exact dog commit you are building (`git rev-parse --short HEAD`) 9 | - The version of rustc you are compiling it with (`rustc --version`) 10 | - Your operating system and hardware platform 11 | - The Rust build target (the _exact_ output of `rustc --print cfg`) 12 | 13 | If you are seeing compilation errors, please include the output of the build process. 14 | 15 | --- 16 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: true 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Request a feature or enhancement to dog 4 | --- 5 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/question.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Question 3 | about: Ask a question about dog 4 | --- 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /tarpaulin-report.html 3 | fuzz-*.log 4 | /cargo-timing*.html 5 | -------------------------------------------------------------------------------- /.rustfmt.toml: -------------------------------------------------------------------------------- 1 | disable_all_formatting = true 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | rust: 3 | - 1.45.0 4 | - stable 5 | - beta 6 | - nightly 7 | 8 | script: 9 | - cargo build --verbose --workspace 10 | - cargo test --verbose --workspace --no-run 11 | - cargo test --verbose --workspace 12 | 13 | os: 14 | - windows 15 | - linux 16 | - osx 17 | 18 | jobs: 19 | fast_finish: true 20 | allow_failures: 21 | - rust: nightly 22 | 23 | include: 24 | - name: 'Rust: lint with Clippy' 25 | rust: stable 26 | install: 27 | - rustup component add clippy 28 | script: 29 | - cargo clippy 30 | 31 | - name: 'Rust: mutation testing' 32 | rust: nightly 33 | install: 34 | - git clone https://github.com/llogiq/mutagen.git 35 | - cd mutagen/mutagen-runner 36 | - cargo install --path . 37 | - cd ../.. 38 | script: 39 | - cargo test --package dns --features=dns/with_mutagen -- --quiet 40 | - cargo mutagen --package dns --features=dns/with_mutagen 41 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "dog" 3 | description = "A command-line DNS client" 4 | 5 | authors = ["Benjamin Sago "] 6 | categories = ["command-line-utilities"] 7 | edition = "2018" 8 | exclude = [ 9 | "/completions/*", "/man/*", "/xtests/*", 10 | "/dog-screenshot.png", "/Justfile", "/README.md", "/.rustfmt.toml", "/.travis.yml", 11 | ] 12 | homepage = "https://dns.lookup.dog/" 13 | license = "EUPL-1.2" 14 | version = "0.2.0-pre" 15 | 16 | 17 | [[bin]] 18 | name = "dog" 19 | path = "src/main.rs" 20 | doctest = false 21 | 22 | 23 | [workspace] 24 | members = [ 25 | "dns", 26 | "dns-transport", 27 | ] 28 | 29 | 30 | # make dev builds faster by excluding debug symbols 31 | [profile.dev] 32 | debug = false 33 | 34 | # use LTO for smaller binaries (that take longer to build) 35 | [profile.release] 36 | lto = true 37 | overflow-checks = true 38 | panic = "abort" 39 | 40 | 41 | [dependencies] 42 | 43 | # dns stuff 44 | dns = { path = "./dns" } 45 | dns-transport = { path = "./dns-transport" } 46 | 47 | # command-line 48 | ansi_term = "0.12" 49 | atty = "0.2" 50 | getopts = "0.2" 51 | 52 | # transaction ID generation 53 | rand = "0.8" 54 | 55 | # json output 56 | json = "0.12" 57 | 58 | # logging 59 | log = "0.4" 60 | 61 | # windows default nameserver determination 62 | [target.'cfg(windows)'.dependencies] 63 | ipconfig = { version = "0.2" } 64 | 65 | [build-dependencies] 66 | datetime = { version = "0.5.1", default_features = false } 67 | 68 | [dev-dependencies] 69 | pretty_assertions = "0.7" 70 | 71 | [features] 72 | default = ["with_idna", "with_tls", "with_https", "with_nativetls"] 73 | with_idna = ["dns/with_idna"] 74 | 75 | with_tls = ["dns-transport/with_tls"] 76 | with_https = ["dns-transport/with_https"] 77 | 78 | with_nativetls = ["dns-transport/with_nativetls"] 79 | with_nativetls_vendored = ["with_nativetls", "dns-transport/with_nativetls", "dns-transport/with_nativetls_vendored"] 80 | with_rustls = ["dns-transport/with_rustls"] 81 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM rust as build 2 | 3 | WORKDIR /build 4 | COPY /src /build/src 5 | COPY /dns /build/dns 6 | COPY /dns-transport /build/dns-transport 7 | COPY /man /build/man 8 | COPY build.rs Cargo.toml /build/ 9 | 10 | RUN cargo build --release 11 | 12 | FROM debian:buster-slim 13 | 14 | RUN apt update && apt install -y libssl1.1 ca-certificates && apt clean all 15 | 16 | COPY --from=build /build/target/release/dog /dog 17 | 18 | ENTRYPOINT ["/dog"] 19 | -------------------------------------------------------------------------------- /completions/dog.bash: -------------------------------------------------------------------------------- 1 | _dog() 2 | { 3 | cur=${COMP_WORDS[COMP_CWORD]} 4 | prev=${COMP_WORDS[COMP_CWORD-1]} 5 | 6 | case "$prev" in 7 | -'?'|--help|-v|--version) 8 | return 9 | ;; 10 | 11 | -t|--type) 12 | COMPREPLY=( $( compgen -W 'A AAAA CAA CNAME HINFO MX NS PTR SOA SRV TXT' -- "$cur" ) ) 13 | return 14 | ;; 15 | 16 | --edns) 17 | COMPREPLY=( $( compgen -W 'disable hide show' -- "$cur" ) ) 18 | return 19 | ;; 20 | 21 | -Z) 22 | COMPREPLY=( $( compgen -W 'aa ad bufsize= cd' -- "$cur" ) ) 23 | return 24 | ;; 25 | 26 | --class) 27 | COMPREPLY=( $( compgen -W 'IN CH HS' -- "$cur" ) ) 28 | return 29 | ;; 30 | 31 | --color|--colour) 32 | COMPREPLY=( $( compgen -W 'always automatic never' -- $cur ) ) 33 | return 34 | ;; 35 | esac 36 | 37 | case "$cur" in 38 | -*) 39 | COMPREPLY=( $( compgen -W '$( _parse_help "$1" )' -- "$cur" ) ) 40 | return 41 | ;; 42 | 43 | *) 44 | COMPREPLY=( $( compgen -W 'A AAAA CAA CNAME HINFO MX NS PTR SOA SRV TXT' -- "$cur" ) ) 45 | ;; 46 | esac 47 | } && 48 | complete -o bashdefault -F _dog dog 49 | -------------------------------------------------------------------------------- /completions/dog.fish: -------------------------------------------------------------------------------- 1 | # Meta options 2 | complete -c dog -s 'v' -l 'version' -d "Show version of dog" 3 | complete -c dog -s '?' -l 'help' -d "Show list of command-line options" 4 | 5 | # Query options 6 | complete -c dog -x -a "(__fish_print_hostnames) A AAAA CAA CNAME HINFO MX NS PTR SOA SRV TXT IN CH HS" 7 | complete -c dog -s 'q' -l 'query' -d "Host name or domain name to query" -x -a "(__fish_print_hostnames)" 8 | complete -c dog -s 't' -l 'type' -d "Type of the DNS record being queried" -x -a "A AAAA CAA CNAME HINFO MX NS PTR SOA SRV TXT" 9 | complete -c dog -s 'n' -l 'nameserver' -d "Address of the nameserver to send packets to" -x -a "(__fish_print_hostnames)" 10 | complete -c dog -l 'class' -d "Network class of the DNS record being queried" -x -a "IN CH HS" 11 | 12 | # Sending options 13 | complete -c dog -l 'edns' -d "Whether to OPT in to EDNS" -x -a " 14 | disable\t'Do not send an OPT query' 15 | hide\t'Send an OPT query, but hide the result' 16 | show\t'Send an OPT query, and show the result' 17 | " 18 | complete -c dog -l 'txid' -d "Set the transaction ID to a specific value" -x 19 | complete -c dog -s 'Z' -d "Configure uncommon protocol-level tweaks" -x -a " 20 | aa\t'Set the AA (Authoritative Answers) query bit' 21 | ad\t'Set the AD (Authentic Data) query bit' 22 | bufsize=\t'Set the UDP payload size' 23 | cd\t'Set the CD (Checking Disabled) query bit' 24 | " 25 | 26 | # Protocol options 27 | complete -c dog -s 'U' -l 'udp' -d "Use the DNS protocol over UDP" 28 | complete -c dog -s 'T' -l 'tcp' -d "Use the DNS protocol over TCP" 29 | complete -c dog -s 'S' -l 'tls' -d "Use the DNS-over-TLS protocol" 30 | complete -c dog -s 'H' -l 'https' -d "Use the DNS-over-HTTPS protocol" 31 | 32 | # Output options 33 | complete -c dog -s '1' -l 'short' -d "Display nothing but the first result" 34 | complete -c dog -s 'J' -l 'json' -d "Display the output as JSON" 35 | complete -c dog -l 'color' -d "When to colorise the output" -x -a " 36 | always\t'Always use colors' 37 | automatic\t'Use colors when printing to a terminal' 38 | never\t'Never use colors' 39 | " 40 | complete -c dog -l 'colour' -d "When to colourise the output" -x -a " 41 | always\t'Always use colours' 42 | automatic\t'Use colours when printing to a terminal' 43 | never\t'Never use colours' 44 | " 45 | complete -c dog -l 'seconds' -d "Do not format durations, display them as seconds" 46 | complete -c dog -l 'time' -d "Print how long the response took to arrive" 47 | -------------------------------------------------------------------------------- /completions/dog.ps1: -------------------------------------------------------------------------------- 1 | # Note: This works for both Windows PowerShell 5.1 and also PowerShell 7 (Core). 2 | # But beware that in Windows PowerShell 5.1, it has issues with completing args if they start with '-'. 3 | # For more information about the bug, see: https://github.com/PowerShell/PowerShell/issues/2912 4 | # In PowerShell 7+, it should work correctly. 5 | Register-ArgumentCompleter -Native -CommandName 'dog' -ScriptBlock { 6 | param($wordToComplete, $commandAst, $cursorPosition) 7 | 8 | [string]$argsString = $commandAst.ToString() 9 | 10 | # skip the "dog", split the args afterwards as array 11 | [string[]]$argsArray = $argsString.Split([char[]]@(' ', '=')) | Select-Object -Skip 1 12 | if ($argsArray -eq $null) { $argsArray = @() } 13 | 14 | # detect if starting a new arg (aka ending with space and asking for a completion) 15 | [bool]$isNewArg = $cursorPosition -gt $argsString.Length 16 | if ($isNewArg) { 17 | # if writing a new arg, add empty arg so that current and previous would be shifted 18 | $argsArray += '' 19 | } 20 | 21 | # get current arg (empty if starting new) 22 | [string]$currentArg = $argsArray[-1] 23 | if ([string]::IsNullOrEmpty($currentArg)) { 24 | $currentArg = '' 25 | } 26 | 27 | # get previous arg 28 | [string]$previousArg = $argsArray[-2] 29 | if ([string]::IsNullOrEmpty($previousArg)) { 30 | $previousArg = '' 31 | } 32 | 33 | [string[]]$dnsTypeValues = @('A', 'AAAA', 'CAA', 'CNAME', 'HINFO', 'MX', 'NS', 'PTR', 'SOA', 'SRV', 'TXT') 34 | 35 | [string[]]$completions = @() 36 | [bool]$isOptionValue = $argsString.EndsWith('=') 37 | 38 | # complete option value 39 | switch -Regex ($previousArg) { 40 | '^(-q|--query)' { $isOptionValue = $true } 41 | '^(-t|--type)' { $isOptionValue = $true; $completions += $dnsTypeValues } 42 | '^(-n|--nameserver)' { $isOptionValue = $true } 43 | '^(--class)' { $isOptionValue = $true; $completions += @('IN', 'CH', 'HS') } 44 | '^(--edns)' { $isOptionValue = $true; $completions += @('disable', 'hide', 'show') } 45 | '^(--txid)' { $isOptionValue = $true } 46 | '^(-Z)' { $isOptionValue = $true; $completions += @('aa', 'ad', 'bufsize=', 'cd') } 47 | '^(--color|--colour)' { $isOptionValue = $true; $completions += @('always', 'automatic', 'never') } 48 | } 49 | 50 | # detect whether to complete option value 51 | if ($isOptionValue) { 52 | if (!$isNewArg) { 53 | # if using =, complete including the option name and = 54 | $completions = $completions | ForEach-Object { "$previousArg=$_" } 55 | } 56 | } 57 | else { 58 | # if not completing option value, offer DNS type values first 59 | $completions += $dnsTypeValues 60 | 61 | # complete option name 62 | [string[]]$allOptions = @( 63 | '-q', '--query', 64 | '-t', '--type', 65 | '-n', '--nameserver', 66 | '--class', 67 | '--edns', 68 | '--txid', 69 | '-Z', 70 | '-U', '--udp', 71 | '-T', '--tcp', 72 | '-S', '--tls', 73 | '-H', '--https', 74 | '-1', '--short', 75 | '-J', '--json', 76 | '--color', '--colour', 77 | '--seconds', 78 | '--time', 79 | '-?', '--help', 80 | '-v', '--version' 81 | ) | Sort-Object 82 | 83 | $completions += $allOptions 84 | } 85 | 86 | if ($completions.Count -gt 0) { 87 | # narrow down completions by like* matching 88 | return $completions -like "$currentArg*" 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /completions/dog.zsh: -------------------------------------------------------------------------------- 1 | #compdef dog 2 | 3 | __dog() { 4 | _arguments \ 5 | "(- 1 *)"{-v,--version}"[Show version of dog]" \ 6 | "(- 1 *)"{-\?,--help}"[Show list of command-line options]" \ 7 | {-q,--query}"[Host name or domain name to query]::_hosts" \ 8 | {-t,--type}"[Type of the DNS record being queried]:(record type):(A AAAA CAA CNAME HINFO MX NS PTR SOA SRV TXT)" \ 9 | {-n,--nameserver}"[Address of the nameserver to send packets to]::_hosts;" \ 10 | --class"[Network class of the DNS record being queried]:(network class):(IN CH HS)" \ 11 | --edns"[Whether to OPT in to EDNS]:(edns setting):(disable hide show)" \ 12 | --txid"[Set the transaction ID to a specific value]" \ 13 | -Z"[Configure uncommon protocol-level tweaks]:(protocol tweak):(aa ad bufsize= cd)" \ 14 | {-U,--udp}"[Use the DNS protocol over UDP]" \ 15 | {-T,--tcp}"[Use the DNS protocol over TCP]" \ 16 | {-S,--tls}"[Use the DNS-over-TLS protocol]" \ 17 | {-H,--https}"[Use the DNS-over-HTTPS protocol]" \ 18 | {-1,--short}"[Display nothing but the finst result]" \ 19 | {-J,--json}"[Display the output as JSON]" \ 20 | {--color,--colour}"[When to use terminal colours]:(setting):(always automatic never)" \ 21 | --seconds"[Do not format durations, display them as seconds]" \ 22 | --time"[Print how long the response took to arrive"] \ 23 | '*:filename:_hosts' 24 | } 25 | 26 | __dog 27 | -------------------------------------------------------------------------------- /dns-transport/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "dns-transport" 3 | version = "0.2.0-pre" 4 | authors = ["Benjamin Sago "] 5 | edition = "2018" 6 | 7 | [lib] 8 | doctest = false 9 | test = false 10 | 11 | 12 | [dependencies] 13 | 14 | # dns wire protocol 15 | dns = { path = "../dns" } 16 | 17 | # logging 18 | log = "0.4" 19 | 20 | # tls networking 21 | native-tls = { version = "0.2", optional = true } 22 | 23 | # http response parsing 24 | httparse = { version = "1.3", optional = true } 25 | 26 | rustls = { version = "0.19", optional = true } 27 | 28 | webpki = { version = "0.21.0", optional = true } 29 | 30 | webpki-roots = { version = "0.21.0", optional = true } 31 | 32 | cfg-if = "1" 33 | 34 | [features] 35 | default = [] # these are enabled in the main dog crate 36 | 37 | with_tls = [] 38 | with_https = ["httparse"] 39 | 40 | with_nativetls = ["native-tls"] 41 | with_nativetls_vendored = ["native-tls", "native-tls/vendored"] 42 | with_rustls = ["rustls", "webpki-roots", "webpki"] 43 | -------------------------------------------------------------------------------- /dns-transport/src/auto.rs: -------------------------------------------------------------------------------- 1 | use log::*; 2 | 3 | use dns::{Request, Response}; 4 | use super::{Transport, Error, UdpTransport, TcpTransport}; 5 | 6 | 7 | /// The **automatic transport**, which sends DNS wire data using the UDP 8 | /// transport, then tries using the TCP transport if the first one fails 9 | /// because the response wouldn’t fit in a single UDP packet. 10 | /// 11 | /// This is the default behaviour for many DNS clients. 12 | pub struct AutoTransport { 13 | addr: String, 14 | } 15 | 16 | impl AutoTransport { 17 | 18 | /// Creates a new automatic transport that connects to the given host. 19 | pub fn new(addr: String) -> Self { 20 | Self { addr } 21 | } 22 | } 23 | 24 | 25 | impl Transport for AutoTransport { 26 | fn send(&self, request: &Request) -> Result { 27 | let udp_transport = UdpTransport::new(self.addr.clone()); 28 | let udp_response = udp_transport.send(&request)?; 29 | 30 | if ! udp_response.flags.truncated { 31 | return Ok(udp_response); 32 | } 33 | 34 | debug!("Truncated flag set, so switching to TCP"); 35 | 36 | let tcp_transport = TcpTransport::new(self.addr.clone()); 37 | let tcp_response = tcp_transport.send(&request)?; 38 | Ok(tcp_response) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /dns-transport/src/error.rs: -------------------------------------------------------------------------------- 1 | /// Something that can go wrong making a DNS request. 2 | #[derive(Debug)] 3 | pub enum Error { 4 | 5 | /// The data in the response did not parse correctly from the DNS wire 6 | /// protocol format. 7 | WireError(dns::WireError), 8 | 9 | /// There was a problem with the network making a TCP or UDP request. 10 | NetworkError(std::io::Error), 11 | 12 | /// Not enough information was received from the server before a `read` 13 | /// call returned zero bytes. 14 | TruncatedResponse, 15 | 16 | /// There was a problem making a TLS request. 17 | #[cfg(feature = "with_nativetls")] 18 | TlsError(native_tls::Error), 19 | 20 | /// There was a problem _establishing_ a TLS request. 21 | #[cfg(feature = "with_nativetls")] 22 | TlsHandshakeError(native_tls::HandshakeError), 23 | 24 | /// Provided dns name is not valid 25 | #[cfg(feature = "with_rustls")] 26 | RustlsInvalidDnsNameError(webpki::InvalidDNSNameError), 27 | 28 | /// There was a problem decoding the response HTTP headers or body. 29 | #[cfg(feature = "with_https")] 30 | HttpError(httparse::Error), 31 | 32 | /// The HTTP response code was something other than 200 OK, along with the 33 | /// response code text, if present. 34 | #[cfg(feature = "with_https")] 35 | WrongHttpStatus(u16, Option), 36 | } 37 | 38 | 39 | // From impls 40 | 41 | impl From for Error { 42 | fn from(inner: dns::WireError) -> Self { 43 | Self::WireError(inner) 44 | } 45 | } 46 | 47 | impl From for Error { 48 | fn from(inner: std::io::Error) -> Self { 49 | Self::NetworkError(inner) 50 | } 51 | } 52 | 53 | #[cfg(feature = "with_nativetls")] 54 | impl From for Error { 55 | fn from(inner: native_tls::Error) -> Self { 56 | Self::TlsError(inner) 57 | } 58 | } 59 | 60 | #[cfg(feature = "with_nativetls")] 61 | impl From> for Error { 62 | fn from(inner: native_tls::HandshakeError) -> Self { 63 | Self::TlsHandshakeError(inner) 64 | } 65 | } 66 | 67 | #[cfg(feature = "with_rustls")] 68 | impl From for Error { 69 | fn from(inner: webpki::InvalidDNSNameError) -> Self { 70 | Self::RustlsInvalidDnsNameError(inner) 71 | } 72 | } 73 | 74 | #[cfg(feature = "with_https")] 75 | impl From for Error { 76 | fn from(inner: httparse::Error) -> Self { 77 | Self::HttpError(inner) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /dns-transport/src/https.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(not(feature = "https"), allow(unused))] 2 | 3 | use std::io::{Read, Write}; 4 | use std::net::TcpStream; 5 | 6 | use log::*; 7 | 8 | use dns::{Request, Response, WireError}; 9 | use super::{Transport, Error}; 10 | 11 | use super::tls_stream; 12 | 13 | /// The **HTTPS transport**, which sends DNS wire data inside HTTP packets 14 | /// encrypted with TLS, using TCP. 15 | pub struct HttpsTransport { 16 | url: String, 17 | } 18 | 19 | impl HttpsTransport { 20 | 21 | /// Creates a new HTTPS transport that connects to the given URL. 22 | pub fn new(url: String) -> Self { 23 | Self { url } 24 | } 25 | } 26 | 27 | fn find_subsequence(haystack: &[u8], needle: &[u8]) -> Option { 28 | haystack.windows(needle.len()).position(|window| window == needle) 29 | } 30 | 31 | fn contains_header(buf: &[u8]) -> bool { 32 | let header_end: [u8; 4] = [ 13, 10, 13, 10 ]; 33 | find_subsequence(buf, &header_end).is_some() 34 | } 35 | 36 | use tls_stream::TlsStream; 37 | 38 | impl Transport for HttpsTransport { 39 | 40 | #[cfg(any(feature = "with_https"))] 41 | fn send(&self, request: &Request) -> Result { 42 | let (domain, path) = self.split_domain().expect("Invalid HTTPS nameserver"); 43 | 44 | info!("Opening TLS socket to {:?}", domain); 45 | let mut stream = Self::stream(&domain, 443)?; 46 | 47 | debug!("Connected"); 48 | 49 | let request_bytes = request.to_bytes().expect("failed to serialise request"); 50 | let mut bytes_to_send = format!("\ 51 | POST {} HTTP/1.1\r\n\ 52 | Host: {}\r\n\ 53 | Content-Type: application/dns-message\r\n\ 54 | Accept: application/dns-message\r\n\ 55 | User-Agent: {}\r\n\ 56 | Content-Length: {}\r\n\r\n", 57 | path, domain, USER_AGENT, request_bytes.len()).into_bytes(); 58 | bytes_to_send.extend(request_bytes); 59 | 60 | info!("Sending {} bytes of data to {:?} over HTTPS", bytes_to_send.len(), self.url); 61 | stream.write_all(&bytes_to_send)?; 62 | debug!("Wrote all bytes"); 63 | 64 | info!("Waiting to receive..."); 65 | let mut buf = [0; 4096]; 66 | let mut read_len = stream.read(&mut buf)?; 67 | while !contains_header(&buf[0..read_len]) { 68 | if read_len == buf.len() { 69 | return Err(Error::WireError(WireError::IO)); 70 | } 71 | read_len += stream.read(&mut buf[read_len..])?; 72 | } 73 | let mut expected_len = read_len; 74 | info!("Received {} bytes of data", read_len); 75 | 76 | let mut headers = [httparse::EMPTY_HEADER; 16]; 77 | let mut response = httparse::Response::new(&mut headers); 78 | let index: usize = response.parse(&buf)?.unwrap(); 79 | 80 | if response.code != Some(200) { 81 | let reason = response.reason.map(str::to_owned); 82 | return Err(Error::WrongHttpStatus(response.code.unwrap(), reason)); 83 | } 84 | 85 | for header in response.headers { 86 | let str_value = String::from_utf8_lossy(header.value); 87 | debug!("Header {:?} -> {:?}", header.name, str_value); 88 | if header.name == "Content-Length" { 89 | let content_length: usize = str_value.parse().unwrap(); 90 | expected_len = index + content_length; 91 | } 92 | } 93 | 94 | while read_len < expected_len { 95 | if read_len == buf.len() { 96 | return Err(Error::WireError(WireError::IO)); 97 | } 98 | read_len += stream.read(&mut buf[read_len..])?; 99 | } 100 | 101 | let body = &buf[index .. read_len]; 102 | debug!("HTTP body has {} bytes", body.len()); 103 | let response = Response::from_bytes(&body)?; 104 | Ok(response) 105 | } 106 | 107 | #[cfg(not(feature = "with_https"))] 108 | fn send(&self, request: &Request) -> Result { 109 | unreachable!("HTTPS feature disabled") 110 | } 111 | } 112 | 113 | impl HttpsTransport { 114 | fn split_domain(&self) -> Option<(&str, &str)> { 115 | if let Some(sp) = self.url.strip_prefix("https://") { 116 | if let Some(colon_index) = sp.find('/') { 117 | return Some((&sp[.. colon_index], &sp[colon_index ..])); 118 | } 119 | } 120 | 121 | None 122 | } 123 | } 124 | 125 | /// The User-Agent header sent with HTTPS requests. 126 | static USER_AGENT: &str = concat!("dog/", env!("CARGO_PKG_VERSION")); 127 | 128 | -------------------------------------------------------------------------------- /dns-transport/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! All the DNS transport types. 2 | 3 | #![warn(deprecated_in_future)] 4 | #![warn(future_incompatible)] 5 | #![warn(missing_copy_implementations)] 6 | #![warn(missing_docs)] 7 | #![warn(nonstandard_style)] 8 | #![warn(rust_2018_compatibility)] 9 | #![warn(rust_2018_idioms)] 10 | #![warn(single_use_lifetimes)] 11 | #![warn(trivial_casts, trivial_numeric_casts)] 12 | #![warn(unused)] 13 | 14 | #![warn(clippy::all, clippy::pedantic)] 15 | #![allow(clippy::module_name_repetitions)] 16 | #![allow(clippy::must_use_candidate)] 17 | #![allow(clippy::option_if_let_else)] 18 | #![allow(clippy::pub_enum_variant_names)] 19 | #![allow(clippy::wildcard_imports)] 20 | 21 | #![deny(clippy::cast_possible_truncation)] 22 | #![deny(clippy::cast_lossless)] 23 | #![deny(clippy::cast_possible_wrap)] 24 | #![deny(clippy::cast_sign_loss)] 25 | #![deny(unsafe_code)] 26 | 27 | 28 | mod auto; 29 | pub use self::auto::AutoTransport; 30 | 31 | mod udp; 32 | pub use self::udp::UdpTransport; 33 | 34 | mod tcp; 35 | pub use self::tcp::TcpTransport; 36 | 37 | mod tls; 38 | pub use self::tls::TlsTransport; 39 | 40 | mod https; 41 | pub use self::https::HttpsTransport; 42 | 43 | mod error; 44 | 45 | mod tls_stream; 46 | 47 | pub use self::error::Error; 48 | 49 | /// The trait implemented by all transport types. 50 | pub trait Transport { 51 | 52 | /// Convert the request to bytes, send it over the network, wait for a 53 | /// response, deserialise it from bytes, and return it, asynchronously. 54 | /// 55 | /// # Errors 56 | /// 57 | /// Returns an `Error` error if there’s an I/O error sending or 58 | /// receiving data, or the DNS packet in the response contained invalid 59 | /// bytes and failed to parse, or if there was a protocol-level error for 60 | /// the TLS and HTTPS transports. 61 | fn send(&self, request: &dns::Request) -> Result; 62 | } 63 | -------------------------------------------------------------------------------- /dns-transport/src/tls.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(not(feature = "tls"), allow(unused))] 2 | 3 | use std::net::TcpStream; 4 | use std::io::Write; 5 | 6 | use log::*; 7 | 8 | use dns::{Request, Response}; 9 | use super::{Transport, Error, TcpTransport}; 10 | use super::tls_stream::TlsStream; 11 | 12 | 13 | /// The **TLS transport**, which sends DNS wire data using TCP through an 14 | /// encrypted TLS connection. 15 | pub struct TlsTransport { 16 | addr: String, 17 | } 18 | 19 | impl TlsTransport { 20 | 21 | /// Creates a new TLS transport that connects to the given host. 22 | pub fn new(addr: String) -> Self { 23 | Self { addr } 24 | } 25 | } 26 | 27 | 28 | 29 | impl Transport for TlsTransport { 30 | 31 | #[cfg(feature = "with_tls")] 32 | fn send(&self, request: &Request) -> Result { 33 | info!("Opening TLS socket"); 34 | 35 | let domain = self.sni_domain(); 36 | info!("Connecting using domain {:?}", domain); 37 | let mut stream = 38 | if self.addr.contains(':') { 39 | let mut parts = self.addr.split(":"); 40 | let domain = parts.nth(0).unwrap(); 41 | let port = parts.last().unwrap().parse::().expect("Invalid port number"); 42 | 43 | Self::stream(domain, port)? 44 | } 45 | else { 46 | Self::stream(&*self.addr, 853)? 47 | }; 48 | 49 | 50 | debug!("Connected"); 51 | 52 | // The message is prepended with the length when sent over TCP, 53 | // so the server knows how long it is (RFC 1035 §4.2.2) 54 | let mut bytes_to_send = request.to_bytes().expect("failed to serialise request"); 55 | TcpTransport::prefix_with_length(&mut bytes_to_send); 56 | 57 | info!("Sending {} bytes of data to {} over TLS", bytes_to_send.len(), self.addr); 58 | stream.write_all(&bytes_to_send)?; 59 | debug!("Wrote all bytes"); 60 | 61 | let read_bytes = TcpTransport::length_prefixed_read(&mut stream)?; 62 | let response = Response::from_bytes(&read_bytes)?; 63 | Ok(response) 64 | } 65 | 66 | #[cfg(not(feature = "with_tls"))] 67 | fn send(&self, request: &Request) -> Result { 68 | unreachable!("TLS feature disabled") 69 | } 70 | } 71 | 72 | impl TlsTransport { 73 | fn sni_domain(&self) -> &str { 74 | if let Some(colon_index) = self.addr.find(':') { 75 | &self.addr[.. colon_index] 76 | } 77 | else { 78 | &self.addr[..] 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /dns-transport/src/tls_stream.rs: -------------------------------------------------------------------------------- 1 | use std::net::TcpStream; 2 | use super::Error; 3 | use super::HttpsTransport; 4 | use super::TlsTransport; 5 | 6 | #[cfg(any(feature = "with_nativetls", feature = "with_nativetls_vendored"))] 7 | fn stream_nativetls(domain: &str, port: u16) -> Result, Error> { 8 | let connector = native_tls::TlsConnector::new()?; 9 | let stream = TcpStream::connect((domain, port))?; 10 | Ok(connector.connect(domain, stream)?) 11 | } 12 | 13 | #[cfg(feature = "with_rustls")] 14 | fn stream_rustls(domain: &str, port: u16) -> Result, Error> { 15 | use std::sync::Arc; 16 | 17 | let mut config = rustls::ClientConfig::new(); 18 | 19 | config.root_store.add_server_trust_anchors(&webpki_roots::TLS_SERVER_ROOTS); 20 | 21 | let dns_name = webpki::DNSNameRef::try_from_ascii_str(domain)?; 22 | 23 | let conn = rustls::ClientSession::new(&Arc::new(config), dns_name); 24 | 25 | let sock = TcpStream::connect((domain, port))?; 26 | let tls = rustls::StreamOwned::new(conn, sock); 27 | 28 | Ok(tls) 29 | } 30 | 31 | pub trait TlsStream { 32 | fn stream(domain: &str, port: u16) -> Result; 33 | } 34 | 35 | #[cfg(any(feature = "with_tls", feature = "with_https"))] 36 | cfg_if::cfg_if! { 37 | if #[cfg(any(feature = "with_nativetls", feature = "with_nativetls_vendored"))] { 38 | 39 | impl TlsStream> for HttpsTransport { 40 | fn stream(domain: &str, port: u16) -> Result, Error> { 41 | stream_nativetls(domain, port) 42 | } 43 | } 44 | 45 | impl TlsStream> for TlsTransport { 46 | fn stream(domain: &str, port: u16) -> Result, Error> { 47 | stream_nativetls(domain, port) 48 | } 49 | } 50 | 51 | } else if #[cfg(feature = "with_rustls")] { 52 | 53 | impl TlsStream> for HttpsTransport { 54 | fn stream(domain: &str, port: u16) -> Result, Error> { 55 | stream_rustls(domain, port) 56 | } 57 | } 58 | 59 | impl TlsStream> for TlsTransport { 60 | fn stream(domain: &str, port: u16) -> Result, Error> { 61 | stream_rustls(domain, port) 62 | } 63 | } 64 | 65 | } else { 66 | unreachable!("tls/https enabled but no tls implementation provided") 67 | } 68 | } 69 | 70 | -------------------------------------------------------------------------------- /dns-transport/src/udp.rs: -------------------------------------------------------------------------------- 1 | use std::net::{Ipv4Addr, UdpSocket}; 2 | 3 | use log::*; 4 | 5 | use dns::{Request, Response}; 6 | use super::{Transport, Error}; 7 | 8 | 9 | /// The **UDP transport**, which sends DNS wire data inside a UDP datagram. 10 | /// 11 | /// # References 12 | /// 13 | /// - [RFC 1035 §4.2.1](https://tools.ietf.org/html/rfc1035) — Domain Names, 14 | /// Implementation and Specification (November 1987) 15 | pub struct UdpTransport { 16 | addr: String, 17 | } 18 | 19 | impl UdpTransport { 20 | 21 | /// Creates a new UDP transport that connects to the given host. 22 | pub fn new(addr: String) -> Self { 23 | Self { addr } 24 | } 25 | } 26 | 27 | 28 | impl Transport for UdpTransport { 29 | fn send(&self, request: &Request) -> Result { 30 | info!("Opening UDP socket"); 31 | // TODO: This will need to be changed for IPv6 support. 32 | let socket = UdpSocket::bind((Ipv4Addr::UNSPECIFIED, 0))?; 33 | 34 | if self.addr.contains(':') { 35 | socket.connect(&*self.addr)?; 36 | } 37 | else { 38 | socket.connect((&*self.addr, 53))?; 39 | } 40 | debug!("Opened"); 41 | 42 | let bytes_to_send = request.to_bytes().expect("failed to serialise request"); 43 | 44 | info!("Sending {} bytes of data to {} over UDP", bytes_to_send.len(), self.addr); 45 | let written_len = socket.send(&bytes_to_send)?; 46 | debug!("Wrote {} bytes", written_len); 47 | 48 | info!("Waiting to receive..."); 49 | let mut buf = vec![0; 4096]; 50 | let received_len = socket.recv(&mut buf)?; 51 | 52 | info!("Received {} bytes of data", received_len); 53 | let response = Response::from_bytes(&buf[.. received_len])?; 54 | Ok(response) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /dns/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "dns" 3 | version = "0.2.0-pre" 4 | authors = ["Benjamin Sago "] 5 | edition = "2018" 6 | 7 | [lib] 8 | doctest = false 9 | 10 | 11 | [dependencies] 12 | 13 | # logging 14 | log = "0.4" 15 | 16 | # protocol parsing helper 17 | byteorder = "1.3" 18 | 19 | # printing of certain packets 20 | base64 = "0.13" 21 | 22 | # idna encoding 23 | unic-idna = { version = "0.9.0", optional = true } 24 | 25 | # mutation testing 26 | mutagen = { git = "https://github.com/llogiq/mutagen", optional = true } 27 | 28 | [dev-dependencies] 29 | pretty_assertions = "0.7" 30 | 31 | [features] 32 | default = [] # idna is enabled in the main dog crate 33 | with_idna = ["unic-idna"] 34 | with_mutagen = ["mutagen"] # needs nightly 35 | -------------------------------------------------------------------------------- /dns/fuzz/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | target 3 | corpus 4 | artifacts 5 | -------------------------------------------------------------------------------- /dns/fuzz/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "arbitrary" 5 | version = "0.4.6" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | 8 | [[package]] 9 | name = "byteorder" 10 | version = "1.3.4" 11 | source = "registry+https://github.com/rust-lang/crates.io-index" 12 | 13 | [[package]] 14 | name = "cc" 15 | version = "1.0.60" 16 | source = "registry+https://github.com/rust-lang/crates.io-index" 17 | 18 | [[package]] 19 | name = "cfg-if" 20 | version = "0.1.10" 21 | source = "registry+https://github.com/rust-lang/crates.io-index" 22 | 23 | [[package]] 24 | name = "dns" 25 | version = "0.1.0" 26 | dependencies = [ 27 | "byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", 28 | "log 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)", 29 | ] 30 | 31 | [[package]] 32 | name = "dns-fuzz" 33 | version = "0.0.1" 34 | dependencies = [ 35 | "dns 0.1.0", 36 | "libfuzzer-sys 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", 37 | ] 38 | 39 | [[package]] 40 | name = "libfuzzer-sys" 41 | version = "0.3.4" 42 | source = "registry+https://github.com/rust-lang/crates.io-index" 43 | dependencies = [ 44 | "arbitrary 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", 45 | "cc 1.0.60 (registry+https://github.com/rust-lang/crates.io-index)", 46 | ] 47 | 48 | [[package]] 49 | name = "log" 50 | version = "0.4.11" 51 | source = "registry+https://github.com/rust-lang/crates.io-index" 52 | dependencies = [ 53 | "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", 54 | ] 55 | 56 | [metadata] 57 | "checksum arbitrary 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "0922a3e746b5a44e111e5603feb6704e5cc959116f66737f50bb5cbd264e9d87" 58 | "checksum byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" 59 | "checksum cc 1.0.60 (registry+https://github.com/rust-lang/crates.io-index)" = "ef611cc68ff783f18535d77ddd080185275713d852c4f5cbb6122c462a7a825c" 60 | "checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" 61 | "checksum libfuzzer-sys 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "ee8c42ab62f43795ed77a965ed07994c5584cdc94fd0ebf14b22ac1524077acc" 62 | "checksum log 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)" = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b" 63 | -------------------------------------------------------------------------------- /dns/fuzz/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "dns-fuzz" 3 | version = "0.0.1" 4 | authors = ["Automatically generated"] 5 | publish = false 6 | 7 | [package.metadata] 8 | cargo-fuzz = true 9 | 10 | [dependencies.dns] 11 | path = ".." 12 | 13 | [dependencies.libfuzzer-sys] 14 | version = "0.3.0" 15 | 16 | # Prevent this from interfering with workspaces 17 | [workspace] 18 | members = ["."] 19 | 20 | [[bin]] 21 | name = "fuzz_parsing" 22 | path = "fuzz_targets/fuzz_parsing.rs" 23 | -------------------------------------------------------------------------------- /dns/fuzz/fuzz_targets/fuzz_parsing.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | #[macro_use] extern crate libfuzzer_sys; 3 | extern crate dns; 4 | use dns::Response; 5 | 6 | fuzz_target!(|data: &[u8]| { 7 | let _ = Response::from_bytes(data); 8 | }); 9 | -------------------------------------------------------------------------------- /dns/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![warn(deprecated_in_future)] 2 | #![warn(future_incompatible)] 3 | #![warn(missing_copy_implementations)] 4 | #![warn(missing_docs)] 5 | #![warn(nonstandard_style)] 6 | #![warn(rust_2018_compatibility)] 7 | #![warn(rust_2018_idioms)] 8 | #![warn(single_use_lifetimes)] 9 | #![warn(trivial_casts, trivial_numeric_casts)] 10 | #![warn(unused)] 11 | 12 | #![warn(clippy::all, clippy::pedantic)] 13 | #![allow(clippy::doc_markdown)] 14 | #![allow(clippy::len_without_is_empty)] 15 | #![allow(clippy::missing_errors_doc)] 16 | #![allow(clippy::module_name_repetitions)] 17 | #![allow(clippy::must_use_candidate)] 18 | #![allow(clippy::non_ascii_literal)] 19 | #![allow(clippy::redundant_else)] 20 | #![allow(clippy::struct_excessive_bools)] 21 | #![allow(clippy::upper_case_acronyms)] 22 | #![allow(clippy::wildcard_imports)] 23 | 24 | #![deny(clippy::cast_possible_truncation)] 25 | #![deny(clippy::cast_lossless)] 26 | #![deny(clippy::cast_possible_wrap)] 27 | #![deny(clippy::cast_sign_loss)] 28 | #![deny(unsafe_code)] 29 | 30 | 31 | //! The DNS crate is the ‘library’ part of dog. It implements the DNS 32 | //! protocol: creating and decoding packets from their byte structure. 33 | 34 | 35 | mod types; 36 | pub use self::types::*; 37 | 38 | mod strings; 39 | pub use self::strings::Labels; 40 | 41 | mod wire; 42 | pub use self::wire::{Wire, WireError, MandatedLength}; 43 | 44 | pub mod record; 45 | -------------------------------------------------------------------------------- /dns/src/record/a.rs: -------------------------------------------------------------------------------- 1 | use std::net::Ipv4Addr; 2 | 3 | use log::*; 4 | 5 | use crate::wire::*; 6 | 7 | 8 | /// An **A** record type, which contains an `Ipv4Address`. 9 | /// 10 | /// # References 11 | /// 12 | /// - [RFC 1035 §3.4.1](https://tools.ietf.org/html/rfc1035) — Domain Names, 13 | /// Implementation and Specification (November 1987) 14 | #[derive(PartialEq, Debug, Copy, Clone)] 15 | pub struct A { 16 | 17 | /// The IPv4 address contained in the packet. 18 | pub address: Ipv4Addr, 19 | } 20 | 21 | impl Wire for A { 22 | const NAME: &'static str = "A"; 23 | const RR_TYPE: u16 = 1; 24 | 25 | #[cfg_attr(feature = "with_mutagen", ::mutagen::mutate)] 26 | fn read(stated_length: u16, c: &mut Cursor<&[u8]>) -> Result { 27 | if stated_length != 4 { 28 | warn!("Length is incorrect (record length {:?}, but should be four)", stated_length); 29 | let mandated_length = MandatedLength::Exactly(4); 30 | return Err(WireError::WrongRecordLength { stated_length, mandated_length }); 31 | } 32 | 33 | let mut buf = [0_u8; 4]; 34 | c.read_exact(&mut buf)?; 35 | 36 | let address = Ipv4Addr::from(buf); 37 | trace!("Parsed IPv4 address -> {:?}", address); 38 | 39 | Ok(Self { address }) 40 | } 41 | } 42 | 43 | 44 | #[cfg(test)] 45 | mod test { 46 | use super::*; 47 | use pretty_assertions::assert_eq; 48 | 49 | #[test] 50 | fn parses() { 51 | let buf = &[ 52 | 0x7F, 0x00, 0x00, 0x01, // IPv4 address 53 | ]; 54 | 55 | assert_eq!(A::read(buf.len() as _, &mut Cursor::new(buf)).unwrap(), 56 | A { address: Ipv4Addr::new(127, 0, 0, 1) }); 57 | } 58 | 59 | #[test] 60 | fn record_too_short() { 61 | let buf = &[ 62 | 0x7F, 0x00, 0x00, // Too short IPv4 address 63 | ]; 64 | 65 | assert_eq!(A::read(buf.len() as _, &mut Cursor::new(buf)), 66 | Err(WireError::WrongRecordLength { stated_length: 3, mandated_length: MandatedLength::Exactly(4) })); 67 | } 68 | 69 | #[test] 70 | fn record_too_long() { 71 | let buf = &[ 72 | 0x7F, 0x00, 0x00, 0x00, // IPv4 address 73 | 0x01, // Unexpected extra byte 74 | ]; 75 | 76 | assert_eq!(A::read(buf.len() as _, &mut Cursor::new(buf)), 77 | Err(WireError::WrongRecordLength { stated_length: 5, mandated_length: MandatedLength::Exactly(4) })); 78 | } 79 | 80 | #[test] 81 | fn record_empty() { 82 | assert_eq!(A::read(0, &mut Cursor::new(&[])), 83 | Err(WireError::WrongRecordLength { stated_length: 0, mandated_length: MandatedLength::Exactly(4) })); 84 | } 85 | 86 | #[test] 87 | fn buffer_ends_abruptly() { 88 | let buf = &[ 89 | 0x7F, 0x00, // Half an IPv4 address 90 | ]; 91 | 92 | assert_eq!(A::read(4, &mut Cursor::new(buf)), 93 | Err(WireError::IO)); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /dns/src/record/aaaa.rs: -------------------------------------------------------------------------------- 1 | use std::net::Ipv6Addr; 2 | 3 | use log::*; 4 | 5 | use crate::wire::*; 6 | 7 | 8 | /// A **AAAA** record, which contains an `Ipv6Address`. 9 | /// 10 | /// # References 11 | /// 12 | /// - [RFC 3596](https://tools.ietf.org/html/rfc3596) — DNS Extensions to 13 | /// Support IP Version 6 (October 2003) 14 | #[derive(PartialEq, Debug, Copy, Clone)] 15 | pub struct AAAA { 16 | 17 | /// The IPv6 address contained in the packet. 18 | pub address: Ipv6Addr, 19 | } 20 | 21 | impl Wire for AAAA { 22 | const NAME: &'static str = "AAAA"; 23 | const RR_TYPE: u16 = 28; 24 | 25 | #[cfg_attr(feature = "with_mutagen", ::mutagen::mutate)] 26 | fn read(stated_length: u16, c: &mut Cursor<&[u8]>) -> Result { 27 | if stated_length != 16 { 28 | warn!("Length is incorrect (stated length {:?}, but should be sixteen)", stated_length); 29 | let mandated_length = MandatedLength::Exactly(16); 30 | return Err(WireError::WrongRecordLength { stated_length, mandated_length }); 31 | } 32 | 33 | let mut buf = [0_u8; 16]; 34 | c.read_exact(&mut buf)?; 35 | 36 | let address = Ipv6Addr::from(buf); 37 | trace!("Parsed IPv6 address -> {:#x?}", address); 38 | 39 | Ok(Self { address }) 40 | } 41 | } 42 | 43 | 44 | #[cfg(test)] 45 | mod test { 46 | use super::*; 47 | use pretty_assertions::assert_eq; 48 | 49 | #[test] 50 | fn parses() { 51 | let buf = &[ 52 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 53 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // IPv6 address 54 | ]; 55 | 56 | assert_eq!(AAAA::read(buf.len() as _, &mut Cursor::new(buf)).unwrap(), 57 | AAAA { address: Ipv6Addr::new(0,0,0,0,0,0,0,0) }); 58 | } 59 | 60 | #[test] 61 | fn record_too_long() { 62 | let buf = &[ 63 | 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 64 | 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, // IPv6 address 65 | 0x09, // Unexpected extra byte 66 | ]; 67 | 68 | assert_eq!(AAAA::read(buf.len() as _, &mut Cursor::new(buf)), 69 | Err(WireError::WrongRecordLength { stated_length: 17, mandated_length: MandatedLength::Exactly(16) })); 70 | } 71 | 72 | #[test] 73 | fn record_too_short() { 74 | let buf = &[ 75 | 0x05, 0x05, 0x05, 0x05, 0x05, // Five arbitrary bytes 76 | ]; 77 | 78 | assert_eq!(AAAA::read(buf.len() as _, &mut Cursor::new(buf)), 79 | Err(WireError::WrongRecordLength { stated_length: 5, mandated_length: MandatedLength::Exactly(16) })); 80 | } 81 | 82 | #[test] 83 | fn record_empty() { 84 | assert_eq!(AAAA::read(0, &mut Cursor::new(&[])), 85 | Err(WireError::WrongRecordLength { stated_length: 0, mandated_length: MandatedLength::Exactly(16) })); 86 | } 87 | 88 | #[test] 89 | fn buffer_ends_abruptly() { 90 | let buf = &[ 91 | 0x05, 0x05, 0x05, 0x05, 0x05, // Five arbitrary bytes 92 | ]; 93 | 94 | assert_eq!(AAAA::read(16, &mut Cursor::new(buf)), 95 | Err(WireError::IO)); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /dns/src/record/caa.rs: -------------------------------------------------------------------------------- 1 | use log::*; 2 | 3 | use crate::wire::*; 4 | 5 | 6 | /// A **CAA** _(certification authority authorization)_ record. These allow 7 | /// domain names to specify which Certificate Authorities are allowed to issue 8 | /// certificates for the domain. 9 | /// 10 | /// # References 11 | /// 12 | /// - [RFC 6844](https://tools.ietf.org/html/rfc6844) — DNS Certification 13 | /// Authority Authorization Resource Record (January 2013) 14 | #[derive(PartialEq, Debug)] 15 | pub struct CAA { 16 | 17 | /// Whether this record is marked as “critical” or not. 18 | pub critical: bool, 19 | 20 | /// The “tag” part of the CAA record. 21 | pub tag: Box<[u8]>, 22 | 23 | /// The “value” part of the CAA record. 24 | pub value: Box<[u8]>, 25 | } 26 | 27 | impl Wire for CAA { 28 | const NAME: &'static str = "CAA"; 29 | const RR_TYPE: u16 = 257; 30 | 31 | #[cfg_attr(feature = "with_mutagen", ::mutagen::mutate)] 32 | fn read(stated_length: u16, c: &mut Cursor<&[u8]>) -> Result { 33 | 34 | // flags 35 | let flags = c.read_u8()?; 36 | trace!("Parsed flags -> {:#08b}", flags); 37 | 38 | let has_bit = |bit| { flags & bit == bit }; 39 | let critical = has_bit(0b_1000_0000); 40 | trace!("Parsed critical flag -> {:?}", critical); 41 | 42 | // tag 43 | let tag_length = c.read_u8()?; 44 | trace!("Parsed tag length -> {:?}", tag_length); 45 | 46 | let mut tag = vec![0_u8; usize::from(tag_length)].into_boxed_slice(); 47 | c.read_exact(&mut tag)?; 48 | trace!("Parsed tag -> {:?}", String::from_utf8_lossy(&tag)); 49 | 50 | // value 51 | let remaining_length = stated_length.saturating_sub(u16::from(tag_length)).saturating_sub(2); 52 | trace!("Remaining length -> {:?}", remaining_length); 53 | 54 | let mut value = vec![0_u8; usize::from(remaining_length)].into_boxed_slice(); 55 | c.read_exact(&mut value)?; 56 | trace!("Parsed value -> {:?}", String::from_utf8_lossy(&value)); 57 | 58 | Ok(Self { critical, tag, value }) 59 | } 60 | } 61 | 62 | 63 | #[cfg(test)] 64 | mod test { 65 | use super::*; 66 | use pretty_assertions::assert_eq; 67 | 68 | #[test] 69 | fn parses_non_critical() { 70 | let buf = &[ 71 | 0x00, // flags (all unset) 72 | 0x09, // tag length 73 | 0x69, 0x73, 0x73, 0x75, 0x65, 0x77, 0x69, 0x6c, 0x64, // tag 74 | 0x65, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e, 0x65, 0x74, // value 75 | ]; 76 | 77 | assert_eq!(CAA::read(buf.len() as _, &mut Cursor::new(buf)).unwrap(), 78 | CAA { 79 | critical: false, 80 | tag: Box::new(*b"issuewild"), 81 | value: Box::new(*b"entrust.net"), 82 | }); 83 | } 84 | 85 | #[test] 86 | fn parses_critical() { 87 | let buf = &[ 88 | 0x80, // flags (critical bit set) 89 | 0x09, // tag length 90 | 0x69, 0x73, 0x73, 0x75, 0x65, 0x77, 0x69, 0x6c, 0x64, // tag 91 | 0x65, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x6e, 0x65, 0x74, // value 92 | ]; 93 | 94 | assert_eq!(CAA::read(buf.len() as _, &mut Cursor::new(buf)).unwrap(), 95 | CAA { 96 | critical: true, 97 | tag: Box::new(*b"issuewild"), 98 | value: Box::new(*b"entrust.net"), 99 | }); 100 | } 101 | 102 | #[test] 103 | fn ignores_other_flags() { 104 | let buf = &[ 105 | 0x7F, // flags (all except critical bit set) 106 | 0x01, // tag length 107 | 0x65, // tag 108 | 0x45, // value 109 | ]; 110 | 111 | assert_eq!(CAA::read(buf.len() as _, &mut Cursor::new(buf)).unwrap(), 112 | CAA { 113 | critical: false, 114 | tag: Box::new(*b"e"), 115 | value: Box::new(*b"E"), 116 | }); 117 | } 118 | 119 | #[test] 120 | fn record_empty() { 121 | assert_eq!(CAA::read(0, &mut Cursor::new(&[])), 122 | Err(WireError::IO)); 123 | } 124 | 125 | #[test] 126 | fn buffer_ends_abruptly() { 127 | let buf = &[ 128 | 0x00, // flags 129 | ]; 130 | 131 | assert_eq!(CAA::read(23, &mut Cursor::new(buf)), 132 | Err(WireError::IO)); 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /dns/src/record/cname.rs: -------------------------------------------------------------------------------- 1 | use log::*; 2 | 3 | use crate::strings::{Labels, ReadLabels}; 4 | use crate::wire::*; 5 | 6 | 7 | /// A **CNAME** _(canonical name)_ record, which aliases one domain to another. 8 | /// 9 | /// # References 10 | /// 11 | /// - [RFC 1035 §3.3.1](https://tools.ietf.org/html/rfc1035) — Domain Names, 12 | /// Implementation and Specification (November 1987) 13 | #[derive(PartialEq, Debug)] 14 | pub struct CNAME { 15 | 16 | /// The domain name that this CNAME record is responding with. 17 | pub domain: Labels, 18 | } 19 | 20 | impl Wire for CNAME { 21 | const NAME: &'static str = "CNAME"; 22 | const RR_TYPE: u16 = 5; 23 | 24 | #[cfg_attr(feature = "with_mutagen", ::mutagen::mutate)] 25 | fn read(stated_length: u16, c: &mut Cursor<&[u8]>) -> Result { 26 | let (domain, domain_length) = c.read_labels()?; 27 | trace!("Parsed domain -> {:?}", domain); 28 | 29 | if stated_length == domain_length { 30 | trace!("Length is correct"); 31 | Ok(Self { domain }) 32 | } 33 | else { 34 | warn!("Length is incorrect (stated length {:?}, domain length {:?})", stated_length, domain_length); 35 | Err(WireError::WrongLabelLength { stated_length, length_after_labels: domain_length }) 36 | } 37 | } 38 | } 39 | 40 | 41 | #[cfg(test)] 42 | mod test { 43 | use super::*; 44 | use pretty_assertions::assert_eq; 45 | 46 | #[test] 47 | fn parses() { 48 | let buf = &[ 49 | 0x05, 0x62, 0x73, 0x61, 0x67, 0x6f, 0x02, 0x6d, 0x65, // domain 50 | 0x00, // domain terminator 51 | ]; 52 | 53 | assert_eq!(CNAME::read(buf.len() as _, &mut Cursor::new(buf)).unwrap(), 54 | CNAME { 55 | domain: Labels::encode("bsago.me").unwrap(), 56 | }); 57 | } 58 | 59 | #[test] 60 | fn incorrect_record_length() { 61 | let buf = &[ 62 | 0x03, 0x65, 0x66, 0x67, // domain 63 | 0x00, // domain terminator 64 | ]; 65 | 66 | assert_eq!(CNAME::read(6, &mut Cursor::new(buf)), 67 | Err(WireError::WrongLabelLength { stated_length: 6, length_after_labels: 5 })); 68 | } 69 | 70 | #[test] 71 | fn record_empty() { 72 | assert_eq!(CNAME::read(0, &mut Cursor::new(&[])), 73 | Err(WireError::IO)); 74 | } 75 | 76 | #[test] 77 | fn buffer_ends_abruptly() { 78 | let buf = &[ 79 | 0x05, 0x62, 0x73, // the stard of a string 80 | ]; 81 | 82 | assert_eq!(CNAME::read(23, &mut Cursor::new(buf)), 83 | Err(WireError::IO)); 84 | } 85 | } 86 | 87 | -------------------------------------------------------------------------------- /dns/src/record/eui48.rs: -------------------------------------------------------------------------------- 1 | use log::*; 2 | 3 | use crate::wire::*; 4 | 5 | 6 | /// A **EUI48** record, which holds a six-octet (48-bit) Extended Unique 7 | /// Identifier. These identifiers can be used as MAC addresses. 8 | /// 9 | /// # References 10 | /// 11 | /// - [RFC 7043](https://tools.ietf.org/html/rfc7043) — Resource Records for 12 | /// EUI-48 and EUI-64 Addresses in the DNS (October 2013) 13 | #[derive(PartialEq, Debug, Copy, Clone)] 14 | pub struct EUI48 { 15 | 16 | /// The six octets that make up the identifier. 17 | pub octets: [u8; 6], 18 | } 19 | 20 | impl Wire for EUI48 { 21 | const NAME: &'static str = "EUI48"; 22 | const RR_TYPE: u16 = 108; 23 | 24 | #[cfg_attr(feature = "with_mutagen", ::mutagen::mutate)] 25 | fn read(stated_length: u16, c: &mut Cursor<&[u8]>) -> Result { 26 | if stated_length != 6 { 27 | warn!("Length is incorrect (record length {:?}, but should be six)", stated_length); 28 | let mandated_length = MandatedLength::Exactly(6); 29 | return Err(WireError::WrongRecordLength { stated_length, mandated_length }); 30 | } 31 | 32 | let mut octets = [0_u8; 6]; 33 | c.read_exact(&mut octets)?; 34 | trace!("Parsed 6-byte address -> {:#x?}", octets); 35 | 36 | Ok(Self { octets }) 37 | } 38 | } 39 | 40 | 41 | impl EUI48 { 42 | 43 | /// Returns this EUI as hexadecimal numbers, separated by dashes. 44 | pub fn formatted_address(self) -> String { 45 | format!("{:02x}-{:02x}-{:02x}-{:02x}-{:02x}-{:02x}", 46 | self.octets[0], self.octets[1], self.octets[2], 47 | self.octets[3], self.octets[4], self.octets[5]) 48 | } 49 | } 50 | 51 | 52 | #[cfg(test)] 53 | mod test { 54 | use super::*; 55 | use pretty_assertions::assert_eq; 56 | 57 | #[test] 58 | fn parses() { 59 | let buf = &[ 60 | 0x00, 0x7F, 0x23, 0x12, 0x34, 0x56, // identifier 61 | ]; 62 | 63 | assert_eq!(EUI48::read(buf.len() as _, &mut Cursor::new(buf)).unwrap(), 64 | EUI48 { octets: [ 0x00, 0x7F, 0x23, 0x12, 0x34, 0x56 ] }); 65 | } 66 | 67 | #[test] 68 | fn record_too_short() { 69 | let buf = &[ 70 | 0x00, 0x7F, 0x23, // a mere OUI 71 | ]; 72 | 73 | assert_eq!(EUI48::read(buf.len() as _, &mut Cursor::new(buf)), 74 | Err(WireError::WrongRecordLength { stated_length: 3, mandated_length: MandatedLength::Exactly(6) })); 75 | } 76 | 77 | #[test] 78 | fn record_too_long() { 79 | let buf = &[ 80 | 0x00, 0x7F, 0x23, 0x12, 0x34, 0x56, // identifier 81 | 0x01, // an unexpected extra byte 82 | ]; 83 | 84 | assert_eq!(EUI48::read(buf.len() as _, &mut Cursor::new(buf)), 85 | Err(WireError::WrongRecordLength { stated_length: 7, mandated_length: MandatedLength::Exactly(6) })); 86 | } 87 | 88 | #[test] 89 | fn record_empty() { 90 | assert_eq!(EUI48::read(0, &mut Cursor::new(&[])), 91 | Err(WireError::WrongRecordLength { stated_length: 0, mandated_length: MandatedLength::Exactly(6) })); 92 | } 93 | 94 | #[test] 95 | fn buffer_ends_abruptly() { 96 | let buf = &[ 97 | 0x00, 0x7F, 0x23, // a mere OUI 98 | ]; 99 | 100 | assert_eq!(EUI48::read(6, &mut Cursor::new(buf)), 101 | Err(WireError::IO)); 102 | } 103 | 104 | #[test] 105 | fn hex_rep() { 106 | let record = EUI48 { octets: [ 0x00, 0x7F, 0x23, 0x12, 0x34, 0x56 ] }; 107 | 108 | assert_eq!(record.formatted_address(), 109 | "00-7f-23-12-34-56"); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /dns/src/record/eui64.rs: -------------------------------------------------------------------------------- 1 | use log::*; 2 | 3 | use crate::wire::*; 4 | 5 | 6 | /// A **EUI64** record, which holds an eight-octet (64-bit) Extended Unique 7 | /// Identifier. 8 | /// 9 | /// # References 10 | /// 11 | /// - [RFC 7043](https://tools.ietf.org/html/rfc7043) — Resource Records for 12 | /// EUI-48 and EUI-64 Addresses in the DNS (October 2013) 13 | #[derive(PartialEq, Debug, Copy, Clone)] 14 | pub struct EUI64 { 15 | 16 | /// The eight octets that make up the identifier. 17 | pub octets: [u8; 8], 18 | } 19 | 20 | impl Wire for EUI64 { 21 | const NAME: &'static str = "EUI64"; 22 | const RR_TYPE: u16 = 109; 23 | 24 | #[cfg_attr(feature = "with_mutagen", ::mutagen::mutate)] 25 | fn read(stated_length: u16, c: &mut Cursor<&[u8]>) -> Result { 26 | if stated_length != 8 { 27 | warn!("Length is incorrect (record length {:?}, but should be eight)", stated_length); 28 | let mandated_length = MandatedLength::Exactly(8); 29 | return Err(WireError::WrongRecordLength { stated_length, mandated_length }); 30 | } 31 | 32 | let mut octets = [0_u8; 8]; 33 | c.read_exact(&mut octets)?; 34 | trace!("Parsed 8-byte address -> {:#x?}", octets); 35 | 36 | Ok(Self { octets }) 37 | } 38 | } 39 | 40 | 41 | impl EUI64 { 42 | 43 | /// Returns this EUI as hexadecimal numbers, separated by dashes. 44 | pub fn formatted_address(self) -> String { 45 | format!("{:02x}-{:02x}-{:02x}-{:02x}-{:02x}-{:02x}-{:02x}-{:02x}", 46 | self.octets[0], self.octets[1], self.octets[2], self.octets[3], 47 | self.octets[4], self.octets[5], self.octets[6], self.octets[7]) 48 | } 49 | } 50 | 51 | 52 | #[cfg(test)] 53 | mod test { 54 | use super::*; 55 | use pretty_assertions::assert_eq; 56 | 57 | #[test] 58 | fn parses() { 59 | let buf = &[ 60 | 0x00, 0x7F, 0x23, 0x12, 0x34, 0x56, 0x78, 0x90, // identifier 61 | ]; 62 | 63 | assert_eq!(EUI64::read(buf.len() as _, &mut Cursor::new(buf)).unwrap(), 64 | EUI64 { octets: [ 0x00, 0x7F, 0x23, 0x12, 0x34, 0x56, 0x78, 0x90 ] }); 65 | } 66 | 67 | #[test] 68 | fn record_too_short() { 69 | let buf = &[ 70 | 0x00, 0x7F, 0x23, // a mere OUI 71 | ]; 72 | 73 | assert_eq!(EUI64::read(buf.len() as _, &mut Cursor::new(buf)), 74 | Err(WireError::WrongRecordLength { stated_length: 3, mandated_length: MandatedLength::Exactly(8) })); 75 | } 76 | 77 | #[test] 78 | fn record_too_long() { 79 | let buf = &[ 80 | 0x00, 0x7F, 0x23, 0x12, 0x34, 0x56, 0x78, 0x90, // identifier 81 | 0x01, // an unexpected extra byte 82 | ]; 83 | 84 | assert_eq!(EUI64::read(buf.len() as _, &mut Cursor::new(buf)), 85 | Err(WireError::WrongRecordLength { stated_length: 9, mandated_length: MandatedLength::Exactly(8) })); 86 | } 87 | 88 | #[test] 89 | fn record_empty() { 90 | assert_eq!(EUI64::read(0, &mut Cursor::new(&[])), 91 | Err(WireError::WrongRecordLength { stated_length: 0, mandated_length: MandatedLength::Exactly(8) })); 92 | } 93 | 94 | #[test] 95 | fn buffer_ends_abruptly() { 96 | let buf = &[ 97 | 0x00, 0x7F, 0x23, // a mere OUI 98 | ]; 99 | 100 | assert_eq!(EUI64::read(8, &mut Cursor::new(buf)), 101 | Err(WireError::IO)); 102 | } 103 | 104 | #[test] 105 | fn hex_rep() { 106 | let record = EUI64 { octets: [ 0x00, 0x7F, 0x23, 0x12, 0x34, 0x56, 0x78, 0x90 ] }; 107 | 108 | assert_eq!(record.formatted_address(), 109 | "00-7f-23-12-34-56-78-90"); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /dns/src/record/hinfo.rs: -------------------------------------------------------------------------------- 1 | use log::*; 2 | 3 | use crate::wire::*; 4 | 5 | 6 | /// A (an?) **HINFO** _(host information)_ record, which contains the CPU and 7 | /// OS information about a host. 8 | /// 9 | /// It also gets used as the response for an `ANY` query, if it is blocked. 10 | /// 11 | /// # References 12 | /// 13 | /// - [RFC 1035 §3.3.2](https://tools.ietf.org/html/rfc1035) — Domain Names, 14 | /// Implementation and Specification (November 1987) 15 | /// - [RFC 8482 §6](https://tools.ietf.org/html/rfc8482#section-6) — Providing 16 | /// Minimal-Sized Responses to DNS Queries That Have QTYPE=ANY (January 2019) 17 | #[derive(PartialEq, Debug)] 18 | pub struct HINFO { 19 | 20 | /// The CPU field, specifying the CPU type. 21 | pub cpu: Box<[u8]>, 22 | 23 | /// The OS field, specifying the operating system. 24 | pub os: Box<[u8]>, 25 | } 26 | 27 | impl Wire for HINFO { 28 | const NAME: &'static str = "HINFO"; 29 | const RR_TYPE: u16 = 13; 30 | 31 | #[cfg_attr(feature = "with_mutagen", ::mutagen::mutate)] 32 | fn read(stated_length: u16, c: &mut Cursor<&[u8]>) -> Result { 33 | 34 | let cpu_length = c.read_u8()?; 35 | trace!("Parsed CPU length -> {:?}", cpu_length); 36 | 37 | let mut cpu = vec![0_u8; usize::from(cpu_length)].into_boxed_slice(); 38 | c.read_exact(&mut cpu)?; 39 | trace!("Parsed CPU -> {:?}", String::from_utf8_lossy(&cpu)); 40 | 41 | let os_length = c.read_u8()?; 42 | trace!("Parsed OS length -> {:?}", os_length); 43 | 44 | let mut os = vec![0_u8; usize::from(os_length)].into_boxed_slice(); 45 | c.read_exact(&mut os)?; 46 | trace!("Parsed OS -> {:?}", String::from_utf8_lossy(&os)); 47 | 48 | let length_after_labels = 1 + u16::from(cpu_length) + 1 + u16::from(os_length); 49 | if stated_length == length_after_labels { 50 | trace!("Length is correct"); 51 | Ok(Self { cpu, os }) 52 | } 53 | else { 54 | warn!("Length is incorrect (stated length {:?}, cpu plus length {:?}", stated_length, length_after_labels); 55 | Err(WireError::WrongLabelLength { stated_length, length_after_labels }) 56 | } 57 | } 58 | } 59 | 60 | 61 | #[cfg(test)] 62 | mod test { 63 | use super::*; 64 | use pretty_assertions::assert_eq; 65 | 66 | #[test] 67 | fn parses() { 68 | let buf = &[ 69 | 0x0e, // cpu length 70 | 0x73, 0x6f, 0x6d, 0x65, 0x2d, 0x6b, 0x69, 0x6e, 0x64, 0x61, 0x2d, 71 | 0x63, 0x70, 0x75, // cpu 72 | 0x0d, // os length 73 | 0x73, 0x6f, 0x6d, 0x65, 0x2d, 0x6b, 0x69, 0x6e, 0x64, 0x61, 0x2d, 74 | 0x6f, 0x73, // os 75 | ]; 76 | 77 | assert_eq!(HINFO::read(buf.len() as _, &mut Cursor::new(buf)).unwrap(), 78 | HINFO { 79 | cpu: Box::new(*b"some-kinda-cpu"), 80 | os: Box::new(*b"some-kinda-os"), 81 | }); 82 | } 83 | 84 | #[test] 85 | fn incorrect_record_length() { 86 | let buf = &[ 87 | 0x03, // cpu length 88 | 0x65, 0x66, 0x67, // cpu 89 | 0x03, // os length 90 | 0x68, 0x69, 0x70, // os 91 | ]; 92 | 93 | assert_eq!(HINFO::read(6, &mut Cursor::new(buf)), 94 | Err(WireError::WrongLabelLength { stated_length: 6, length_after_labels: 8 })); 95 | } 96 | 97 | #[test] 98 | fn record_empty() { 99 | assert_eq!(HINFO::read(0, &mut Cursor::new(&[])), 100 | Err(WireError::IO)); 101 | } 102 | 103 | #[test] 104 | fn buffer_ends_abruptly() { 105 | let buf = &[ 106 | 0x14, 0x0A, 0x0B, 0x0C, // 32-bit CPU 107 | ]; 108 | 109 | assert_eq!(HINFO::read(23, &mut Cursor::new(buf)), 110 | Err(WireError::IO)); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /dns/src/record/mx.rs: -------------------------------------------------------------------------------- 1 | use log::*; 2 | 3 | use crate::strings::{Labels, ReadLabels}; 4 | use crate::wire::*; 5 | 6 | 7 | /// An **MX** _(mail exchange)_ record, which contains the hostnames for mail 8 | /// servers that handle mail sent to the domain. 9 | /// 10 | /// # References 11 | /// 12 | /// - [RFC 1035 §3.3.9](https://tools.ietf.org/html/rfc1035) — Domain Names, 13 | /// Implementation and Specification (November 1987) 14 | #[derive(PartialEq, Debug)] 15 | pub struct MX { 16 | 17 | /// The preference that clients should give to this MX record amongst all 18 | /// that get returned. 19 | pub preference: u16, 20 | 21 | /// The domain name of the mail exchange server. 22 | pub exchange: Labels, 23 | } 24 | 25 | impl Wire for MX { 26 | const NAME: &'static str = "MX"; 27 | const RR_TYPE: u16 = 15; 28 | 29 | #[cfg_attr(feature = "with_mutagen", ::mutagen::mutate)] 30 | fn read(stated_length: u16, c: &mut Cursor<&[u8]>) -> Result { 31 | let preference = c.read_u16::()?; 32 | trace!("Parsed preference -> {:?}", preference); 33 | 34 | let (exchange, exchange_length) = c.read_labels()?; 35 | trace!("Parsed exchange -> {:?}", exchange); 36 | 37 | let length_after_labels = 2 + exchange_length; 38 | if stated_length == length_after_labels { 39 | trace!("Length is correct"); 40 | Ok(Self { preference, exchange }) 41 | } 42 | else { 43 | warn!("Length is incorrect (stated length {:?}, preference plus exchange length {:?}", stated_length, length_after_labels); 44 | Err(WireError::WrongLabelLength { stated_length, length_after_labels }) 45 | } 46 | } 47 | } 48 | 49 | 50 | #[cfg(test)] 51 | mod test { 52 | use super::*; 53 | use pretty_assertions::assert_eq; 54 | 55 | #[test] 56 | fn parses() { 57 | let buf = &[ 58 | 0x00, 0x0A, // preference 59 | 0x05, 0x62, 0x73, 0x61, 0x67, 0x6f, 0x02, 0x6d, 0x65, // exchange 60 | 0x00, // exchange terminator 61 | ]; 62 | 63 | assert_eq!(MX::read(buf.len() as _, &mut Cursor::new(buf)).unwrap(), 64 | MX { 65 | preference: 10, 66 | exchange: Labels::encode("bsago.me").unwrap(), 67 | }); 68 | } 69 | 70 | #[test] 71 | fn incorrect_record_length() { 72 | let buf = &[ 73 | 0x00, 0x0A, // preference 74 | 0x03, 0x65, 0x66, 0x67, // domain 75 | 0x00, // domain terminator 76 | ]; 77 | 78 | assert_eq!(MX::read(6, &mut Cursor::new(buf)), 79 | Err(WireError::WrongLabelLength { stated_length: 6, length_after_labels: 7 })); 80 | } 81 | 82 | #[test] 83 | fn record_empty() { 84 | assert_eq!(MX::read(0, &mut Cursor::new(&[])), 85 | Err(WireError::IO)); 86 | } 87 | 88 | #[test] 89 | fn buffer_ends_abruptly() { 90 | let buf = &[ 91 | 0x00, 0x0A, // half a preference 92 | ]; 93 | 94 | assert_eq!(MX::read(23, &mut Cursor::new(buf)), 95 | Err(WireError::IO)); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /dns/src/record/ns.rs: -------------------------------------------------------------------------------- 1 | use log::*; 2 | 3 | use crate::strings::{Labels, ReadLabels}; 4 | use crate::wire::*; 5 | 6 | 7 | /// A **NS** _(name server)_ record, which is used to point domains to name 8 | /// servers. 9 | /// 10 | /// # References 11 | /// 12 | /// - [RFC 1035 §3.3.11](https://tools.ietf.org/html/rfc1035) — Domain Names, 13 | /// Implementation and Specification (November 1987) 14 | #[derive(PartialEq, Debug)] 15 | pub struct NS { 16 | 17 | /// The address of a nameserver that provides this DNS response. 18 | pub nameserver: Labels, 19 | } 20 | 21 | impl Wire for NS { 22 | const NAME: &'static str = "NS"; 23 | const RR_TYPE: u16 = 2; 24 | 25 | #[cfg_attr(feature = "with_mutagen", ::mutagen::mutate)] 26 | fn read(stated_length: u16, c: &mut Cursor<&[u8]>) -> Result { 27 | let (nameserver, nameserver_length) = c.read_labels()?; 28 | trace!("Parsed nameserver -> {:?}", nameserver); 29 | 30 | if stated_length == nameserver_length { 31 | trace!("Length is correct"); 32 | Ok(Self { nameserver }) 33 | } 34 | else { 35 | warn!("Length is incorrect (stated length {:?}, nameserver length {:?}", stated_length, nameserver_length); 36 | Err(WireError::WrongLabelLength { stated_length, length_after_labels: nameserver_length }) 37 | } 38 | } 39 | } 40 | 41 | 42 | #[cfg(test)] 43 | mod test { 44 | use super::*; 45 | use pretty_assertions::assert_eq; 46 | 47 | #[test] 48 | fn parses() { 49 | let buf = &[ 50 | 0x01, 0x61, 0x0c, 0x67, 0x74, 0x6c, 0x64, 0x2d, 0x73, 0x65, 0x72, 51 | 0x76, 0x65, 0x72, 0x73, 0x03, 0x6e, 0x65, 0x74, // nameserver 52 | 0x00, // nameserver terminator 53 | ]; 54 | 55 | assert_eq!(NS::read(buf.len() as _, &mut Cursor::new(buf)).unwrap(), 56 | NS { 57 | nameserver: Labels::encode("a.gtld-servers.net").unwrap(), 58 | }); 59 | } 60 | 61 | #[test] 62 | fn incorrect_record_length() { 63 | let buf = &[ 64 | 0x03, 0x65, 0x66, 0x67, // nameserver 65 | 0x00, // nameserver terminator 66 | ]; 67 | 68 | assert_eq!(NS::read(66, &mut Cursor::new(buf)), 69 | Err(WireError::WrongLabelLength { stated_length: 66, length_after_labels: 5 })); 70 | } 71 | 72 | #[test] 73 | fn record_empty() { 74 | assert_eq!(NS::read(0, &mut Cursor::new(&[])), 75 | Err(WireError::IO)); 76 | } 77 | 78 | #[test] 79 | fn buffer_ends_abruptly() { 80 | let buf = &[ 81 | 0x01, // the first byte of a string 82 | ]; 83 | 84 | assert_eq!(NS::read(23, &mut Cursor::new(buf)), 85 | Err(WireError::IO)); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /dns/src/record/openpgpkey.rs: -------------------------------------------------------------------------------- 1 | use log::*; 2 | 3 | use crate::wire::*; 4 | 5 | 6 | /// A **OPENPGPKEY** record, which holds a PGP key. 7 | /// 8 | /// # References 9 | /// 10 | /// - [RFC 1035 §3.3.14](https://tools.ietf.org/html/rfc7929) — DNS-Based 11 | /// Authentication of Named Entities Bindings for OpenPGP (August 2016) 12 | #[derive(PartialEq, Debug)] 13 | pub struct OPENPGPKEY { 14 | 15 | /// The PGP key, as unencoded bytes. 16 | pub key: Vec, 17 | } 18 | 19 | impl Wire for OPENPGPKEY { 20 | const NAME: &'static str = "OPENPGPKEY"; 21 | const RR_TYPE: u16 = 61; 22 | 23 | #[cfg_attr(feature = "with_mutagen", ::mutagen::mutate)] 24 | fn read(stated_length: u16, c: &mut Cursor<&[u8]>) -> Result { 25 | if stated_length == 0 { 26 | let mandated_length = MandatedLength::AtLeast(1); 27 | return Err(WireError::WrongRecordLength { stated_length, mandated_length }); 28 | } 29 | 30 | let mut key = vec![0_u8; usize::from(stated_length)]; 31 | c.read_exact(&mut key)?; 32 | trace!("Parsed key -> {:#x?}", key); 33 | 34 | Ok(Self { key }) 35 | } 36 | } 37 | 38 | impl OPENPGPKEY { 39 | 40 | /// The base64-encoded PGP key. 41 | pub fn base64_key(&self) -> String { 42 | base64::encode(&self.key) 43 | } 44 | } 45 | 46 | 47 | #[cfg(test)] 48 | mod test { 49 | use super::*; 50 | use pretty_assertions::assert_eq; 51 | 52 | #[test] 53 | fn parses() { 54 | let buf = &[ 55 | 0x12, 0x34, 0x56, 0x78, // key 56 | ]; 57 | 58 | assert_eq!(OPENPGPKEY::read(buf.len() as _, &mut Cursor::new(buf)).unwrap(), 59 | OPENPGPKEY { 60 | key: vec![ 0x12, 0x34, 0x56, 0x78 ], 61 | }); 62 | } 63 | 64 | #[test] 65 | fn one_byte_of_uri() { 66 | let buf = &[ 67 | 0x2b, // one byte of key 68 | ]; 69 | 70 | assert_eq!(OPENPGPKEY::read(buf.len() as _, &mut Cursor::new(buf)).unwrap(), 71 | OPENPGPKEY { 72 | key: vec![ 0x2b ], 73 | }); 74 | } 75 | 76 | #[test] 77 | fn record_empty() { 78 | assert_eq!(OPENPGPKEY::read(0, &mut Cursor::new(&[])), 79 | Err(WireError::WrongRecordLength { stated_length: 0, mandated_length: MandatedLength::AtLeast(1) })); 80 | } 81 | 82 | #[test] 83 | fn buffer_ends_abruptly() { 84 | let buf = &[ 85 | 0x12, 0x34, // the beginning of a key 86 | ]; 87 | 88 | assert_eq!(OPENPGPKEY::read(23, &mut Cursor::new(buf)), 89 | Err(WireError::IO)); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /dns/src/record/others.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | 4 | /// A number representing a record type dog can’t deal with. 5 | #[derive(PartialEq, Debug, Copy, Clone)] 6 | pub enum UnknownQtype { 7 | 8 | /// An rtype number that dog is aware of, but does not know how to parse. 9 | HeardOf(&'static str, u16), 10 | 11 | /// A completely unknown rtype number. 12 | UnheardOf(u16), 13 | } 14 | 15 | impl UnknownQtype { 16 | 17 | /// Searches the list for an unknown type with the given name, returning a 18 | /// `HeardOf` variant if one is found, and `None` otherwise. 19 | pub fn from_type_name(type_name: &str) -> Option { 20 | let (name, num) = TYPES.iter().find(|t| t.0.eq_ignore_ascii_case(type_name))?; 21 | Some(Self::HeardOf(name, *num)) 22 | } 23 | 24 | /// Returns the type number behind this unknown type. 25 | pub fn type_number(self) -> u16 { 26 | match self { 27 | Self::HeardOf(_, num) | 28 | Self::UnheardOf(num) => num, 29 | } 30 | } 31 | } 32 | 33 | impl From for UnknownQtype { 34 | fn from(qtype: u16) -> Self { 35 | match TYPES.iter().find(|t| t.1 == qtype) { 36 | Some(tuple) => Self::HeardOf(tuple.0, qtype), 37 | None => Self::UnheardOf(qtype), 38 | } 39 | } 40 | } 41 | 42 | impl fmt::Display for UnknownQtype { 43 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 44 | match self { 45 | Self::HeardOf(name, _) => write!(f, "{}", name), 46 | Self::UnheardOf(num) => write!(f, "{}", num), 47 | } 48 | } 49 | } 50 | 51 | 52 | /// Mapping of record type names to their assigned numbers. 53 | static TYPES: &[(&str, u16)] = &[ 54 | ("AFSDB", 18), 55 | ("ANY", 255), 56 | ("APL", 42), 57 | ("AXFR", 252), 58 | ("CDNSKEY", 60), 59 | ("CDS", 59), 60 | ("CERT", 37), 61 | ("CSYNC", 62), 62 | ("DHCID", 49), 63 | ("DLV", 32769), 64 | ("DNAME", 39), 65 | ("DNSKEEYE", 48), 66 | ("DS", 43), 67 | ("HIP", 55), 68 | ("IPSECKEY", 45), 69 | ("IXFR", 251), 70 | ("KEY", 25), 71 | ("KX", 36), 72 | ("NSEC", 47), 73 | ("NSEC3", 50), 74 | ("NSEC3PARAM", 51), 75 | ("OPENPGPKEY", 61), 76 | ("RRSIG", 46), 77 | ("RP", 17), 78 | ("SIG", 24), 79 | ("SMIMEA", 53), 80 | ("TA", 32768), 81 | ("TKEY", 249), 82 | ("TSIG", 250), 83 | ("URI", 256), 84 | ]; 85 | 86 | 87 | #[cfg(test)] 88 | mod test { 89 | use super::*; 90 | 91 | #[test] 92 | fn known() { 93 | assert_eq!(UnknownQtype::from(46).to_string(), 94 | String::from("RRSIG")); 95 | } 96 | 97 | #[test] 98 | fn unknown() { 99 | assert_eq!(UnknownQtype::from(4444).to_string(), 100 | String::from("4444")); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /dns/src/record/ptr.rs: -------------------------------------------------------------------------------- 1 | use log::*; 2 | 3 | use crate::strings::{Labels, ReadLabels}; 4 | use crate::wire::*; 5 | 6 | 7 | /// A **PTR** record, which holds a _pointer_ to a canonical name. This is 8 | /// most often used for reverse DNS lookups. 9 | /// 10 | /// # Encoding 11 | /// 12 | /// The text encoding is not specified, but this crate treats it as UTF-8. 13 | /// Invalid bytes are turned into the replacement character. 14 | /// 15 | /// # References 16 | /// 17 | /// - [RFC 1035 §3.3.14](https://tools.ietf.org/html/rfc1035) — Domain Names, 18 | /// Implementation and Specification (November 1987) 19 | #[derive(PartialEq, Debug)] 20 | pub struct PTR { 21 | 22 | /// The CNAME contained in the record. 23 | pub cname: Labels, 24 | } 25 | 26 | impl Wire for PTR { 27 | const NAME: &'static str = "PTR"; 28 | const RR_TYPE: u16 = 12; 29 | 30 | #[cfg_attr(feature = "with_mutagen", ::mutagen::mutate)] 31 | fn read(stated_length: u16, c: &mut Cursor<&[u8]>) -> Result { 32 | let (cname, cname_length) = c.read_labels()?; 33 | trace!("Parsed cname -> {:?}", cname); 34 | 35 | if stated_length == cname_length { 36 | trace!("Length is correct"); 37 | Ok(Self { cname }) 38 | } 39 | else { 40 | warn!("Length is incorrect (stated length {:?}, cname length {:?}", stated_length, cname_length); 41 | Err(WireError::WrongLabelLength { stated_length, length_after_labels: cname_length }) 42 | } 43 | } 44 | } 45 | 46 | 47 | #[cfg(test)] 48 | mod test { 49 | use super::*; 50 | use pretty_assertions::assert_eq; 51 | 52 | #[test] 53 | fn parses() { 54 | let buf = &[ 55 | 0x03, 0x64, 0x6e, 0x73, 0x06, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, // cname 56 | 0x00, // cname terminator 57 | ]; 58 | 59 | assert_eq!(PTR::read(buf.len() as _, &mut Cursor::new(buf)).unwrap(), 60 | PTR { 61 | cname: Labels::encode("dns.google").unwrap(), 62 | }); 63 | } 64 | 65 | #[test] 66 | fn incorrect_record_length() { 67 | let buf = &[ 68 | 0x03, 0x65, 0x66, 0x67, // cname 69 | 0x00, // cname terminator 70 | ]; 71 | 72 | assert_eq!(PTR::read(6, &mut Cursor::new(buf)), 73 | Err(WireError::WrongLabelLength { stated_length: 6, length_after_labels: 5 })); 74 | } 75 | 76 | #[test] 77 | fn record_empty() { 78 | assert_eq!(PTR::read(0, &mut Cursor::new(&[])), 79 | Err(WireError::IO)); 80 | } 81 | 82 | #[test] 83 | fn buffer_ends_abruptly() { 84 | let buf = &[ 85 | 0x03, 0x64, // the start of a cname 86 | ]; 87 | 88 | assert_eq!(PTR::read(23, &mut Cursor::new(buf)), 89 | Err(WireError::IO)); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /dns/src/record/srv.rs: -------------------------------------------------------------------------------- 1 | use log::*; 2 | 3 | use crate::strings::{Labels, ReadLabels}; 4 | use crate::wire::*; 5 | 6 | 7 | /// A **SRV** record, which contains an IP address as well as a port number, 8 | /// for specifying the location of services more precisely. 9 | /// 10 | /// # References 11 | /// 12 | /// - [RFC 2782](https://tools.ietf.org/html/rfc2782) — A DNS RR for 13 | /// specifying the location of services (February 2000) 14 | #[derive(PartialEq, Debug)] 15 | pub struct SRV { 16 | 17 | /// The priority of this host among all that get returned. Lower values 18 | /// are higher priority. 19 | pub priority: u16, 20 | 21 | /// A weight to choose among results with the same priority. Higher values 22 | /// are higher priority. 23 | pub weight: u16, 24 | 25 | /// The port the service is serving on. 26 | pub port: u16, 27 | 28 | /// The hostname of the machine the service is running on. 29 | pub target: Labels, 30 | } 31 | 32 | impl Wire for SRV { 33 | const NAME: &'static str = "SRV"; 34 | const RR_TYPE: u16 = 33; 35 | 36 | #[cfg_attr(feature = "with_mutagen", ::mutagen::mutate)] 37 | fn read(stated_length: u16, c: &mut Cursor<&[u8]>) -> Result { 38 | let priority = c.read_u16::()?; 39 | trace!("Parsed priority -> {:?}", priority); 40 | 41 | let weight = c.read_u16::()?; 42 | trace!("Parsed weight -> {:?}", weight); 43 | 44 | let port = c.read_u16::()?; 45 | trace!("Parsed port -> {:?}", port); 46 | 47 | let (target, target_length) = c.read_labels()?; 48 | trace!("Parsed target -> {:?}", target); 49 | 50 | let length_after_labels = 3 * 2 + target_length; 51 | if stated_length == length_after_labels { 52 | trace!("Length is correct"); 53 | Ok(Self { priority, weight, port, target }) 54 | } 55 | else { 56 | warn!("Length is incorrect (stated length {:?}, fields plus target length {:?})", stated_length, length_after_labels); 57 | Err(WireError::WrongLabelLength { stated_length, length_after_labels }) 58 | } 59 | } 60 | } 61 | 62 | 63 | #[cfg(test)] 64 | mod test { 65 | use super::*; 66 | use pretty_assertions::assert_eq; 67 | 68 | #[test] 69 | fn parses() { 70 | let buf = &[ 71 | 0x00, 0x01, // priority 72 | 0x00, 0x01, // weight 73 | 0x92, 0x7c, // port 74 | 0x03, 0x61, 0x74, 0x61, 0x05, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x04, 75 | 0x6e, 0x6f, 0x64, 0x65, 0x03, 0x64, 0x63, 0x31, 0x06, 0x63, 0x6f, 76 | 0x6e, 0x73, 0x75, 0x6c, // target 77 | 0x00, // target terminator 78 | ]; 79 | 80 | assert_eq!(SRV::read(buf.len() as _, &mut Cursor::new(buf)).unwrap(), 81 | SRV { 82 | priority: 1, 83 | weight: 1, 84 | port: 37500, 85 | target: Labels::encode("ata.local.node.dc1.consul").unwrap(), 86 | }); 87 | } 88 | 89 | #[test] 90 | fn incorrect_record_length() { 91 | let buf = &[ 92 | 0x00, 0x01, // priority 93 | 0x00, 0x01, // weight 94 | 0x92, 0x7c, // port 95 | 0x03, 0x61, 0x74, 0x61, // target 96 | 0x00, // target terminator 97 | ]; 98 | 99 | assert_eq!(SRV::read(16, &mut Cursor::new(buf)), 100 | Err(WireError::WrongLabelLength { stated_length: 16, length_after_labels: 11 })); 101 | } 102 | 103 | #[test] 104 | fn record_empty() { 105 | assert_eq!(SRV::read(0, &mut Cursor::new(&[])), 106 | Err(WireError::IO)); 107 | } 108 | 109 | #[test] 110 | fn buffer_ends_abruptly() { 111 | let buf = &[ 112 | 0x00, // half a priority 113 | ]; 114 | 115 | assert_eq!(SRV::read(23, &mut Cursor::new(buf)), 116 | Err(WireError::IO)); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /dns/src/record/sshfp.rs: -------------------------------------------------------------------------------- 1 | use log::*; 2 | 3 | use crate::wire::*; 4 | 5 | 6 | /// A **SSHFP** _(secure shell fingerprint)_ record, which contains the 7 | /// fingerprint of an SSH public key. 8 | /// 9 | /// # References 10 | /// 11 | /// - [RFC 4255](https://tools.ietf.org/html/rfc4255) — Using DNS to Securely 12 | /// Publish Secure Shell (SSH) Key Fingerprints (January 2006) 13 | #[derive(PartialEq, Debug)] 14 | pub struct SSHFP { 15 | 16 | /// The algorithm of the public key. This is a number with several defined 17 | /// mappings. 18 | pub algorithm: u8, 19 | 20 | /// The type of the fingerprint, which specifies the hashing algorithm 21 | /// used to derive the fingerprint. This is a number with several defined 22 | /// mappings. 23 | pub fingerprint_type: u8, 24 | 25 | /// The fingerprint of the public key. 26 | pub fingerprint: Vec, 27 | } 28 | 29 | impl Wire for SSHFP { 30 | const NAME: &'static str = "SSHFP"; 31 | const RR_TYPE: u16 = 44; 32 | 33 | #[cfg_attr(feature = "with_mutagen", ::mutagen::mutate)] 34 | fn read(stated_length: u16, c: &mut Cursor<&[u8]>) -> Result { 35 | let algorithm = c.read_u8()?; 36 | trace!("Parsed algorithm -> {:?}", algorithm); 37 | 38 | let fingerprint_type = c.read_u8()?; 39 | trace!("Parsed fingerprint type -> {:?}", fingerprint_type); 40 | 41 | if stated_length <= 2 { 42 | let mandated_length = MandatedLength::AtLeast(3); 43 | return Err(WireError::WrongRecordLength { stated_length, mandated_length }); 44 | } 45 | 46 | let fingerprint_length = stated_length - 1 - 1; 47 | let mut fingerprint = vec![0_u8; usize::from(fingerprint_length)]; 48 | c.read_exact(&mut fingerprint)?; 49 | trace!("Parsed fingerprint -> {:#x?}", fingerprint); 50 | 51 | Ok(Self { algorithm, fingerprint_type, fingerprint }) 52 | } 53 | } 54 | 55 | impl SSHFP { 56 | 57 | /// Returns the hexadecimal representation of the fingerprint. 58 | pub fn hex_fingerprint(&self) -> String { 59 | self.fingerprint.iter() 60 | .map(|byte| format!("{:02x}", byte)) 61 | .collect() 62 | } 63 | } 64 | 65 | 66 | #[cfg(test)] 67 | mod test { 68 | use super::*; 69 | 70 | #[test] 71 | fn parses() { 72 | let buf = &[ 73 | 0x01, // algorithm 74 | 0x01, // fingerprint type 75 | 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, // a short fingerprint 76 | ]; 77 | 78 | assert_eq!(SSHFP::read(buf.len() as _, &mut Cursor::new(buf)).unwrap(), 79 | SSHFP { 80 | algorithm: 1, 81 | fingerprint_type: 1, 82 | fingerprint: vec![ 0x21, 0x22, 0x23, 0x24, 0x25, 0x26 ], 83 | }); 84 | } 85 | 86 | #[test] 87 | fn one_byte_fingerprint() { 88 | let buf = &[ 89 | 0x01, // algorithm 90 | 0x01, // fingerprint type 91 | 0x21, // an extremely short fingerprint 92 | ]; 93 | 94 | assert_eq!(SSHFP::read(buf.len() as _, &mut Cursor::new(buf)).unwrap(), 95 | SSHFP { 96 | algorithm: 1, 97 | fingerprint_type: 1, 98 | fingerprint: vec![ 0x21 ], 99 | }); 100 | } 101 | 102 | #[test] 103 | fn record_too_short() { 104 | let buf = &[ 105 | 0x01, // algorithm 106 | 0x01, // fingerprint type 107 | ]; 108 | 109 | assert_eq!(SSHFP::read(buf.len() as _, &mut Cursor::new(buf)), 110 | Err(WireError::WrongRecordLength { stated_length: 2, mandated_length: MandatedLength::AtLeast(3) })); 111 | } 112 | 113 | #[test] 114 | fn record_empty() { 115 | assert_eq!(SSHFP::read(0, &mut Cursor::new(&[])), 116 | Err(WireError::IO)); 117 | } 118 | 119 | #[test] 120 | fn buffer_ends_abruptly() { 121 | let buf = &[ 122 | 0x01, // algorithm 123 | ]; 124 | 125 | assert_eq!(SSHFP::read(6, &mut Cursor::new(buf)), 126 | Err(WireError::IO)); 127 | } 128 | 129 | #[test] 130 | fn hex_rep() { 131 | let sshfp = SSHFP { 132 | algorithm: 1, 133 | fingerprint_type: 1, 134 | fingerprint: vec![ 0xf3, 0x48, 0xcd, 0xc9 ], 135 | }; 136 | 137 | assert_eq!(sshfp.hex_fingerprint(), 138 | String::from("f348cdc9")); 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /dns/src/record/uri.rs: -------------------------------------------------------------------------------- 1 | use log::*; 2 | 3 | use crate::wire::*; 4 | 5 | 6 | /// A **URI** record, which holds a URI along with weight and priority values 7 | /// to balance between several records. 8 | /// 9 | /// # References 10 | /// 11 | /// - [RFC 7553](https://tools.ietf.org/html/rfc7553) — The Uniform Resource 12 | /// Identifier (URI) DNS Resource Record (June 2015) 13 | /// - [RFC 3986](https://tools.ietf.org/html/rfc3986) — Uniform Resource 14 | /// Identifier (URI): Generic Syntax (January 2005) 15 | #[derive(PartialEq, Debug)] 16 | pub struct URI { 17 | 18 | /// The priority of the URI. Clients are supposed to contact the URI with 19 | /// the lowest priority out of all the ones it can reach. 20 | pub priority: u16, 21 | 22 | /// The weight of the URI, which specifies a relative weight for entries 23 | /// with the same priority. 24 | pub weight: u16, 25 | 26 | /// The URI contained in the record. Since all we are doing is displaying 27 | /// it to the user, we do not need to parse it for accuracy. 28 | pub target: Box<[u8]>, 29 | } 30 | 31 | impl Wire for URI { 32 | const NAME: &'static str = "URI"; 33 | const RR_TYPE: u16 = 256; 34 | 35 | #[cfg_attr(feature = "with_mutagen", ::mutagen::mutate)] 36 | fn read(stated_length: u16, c: &mut Cursor<&[u8]>) -> Result { 37 | let priority = c.read_u16::()?; 38 | trace!("Parsed priority -> {:?}", priority); 39 | 40 | let weight = c.read_u16::()?; 41 | trace!("Parsed weight -> {:?}", weight); 42 | 43 | // The target must not be empty. 44 | if stated_length <= 4 { 45 | let mandated_length = MandatedLength::AtLeast(5); 46 | return Err(WireError::WrongRecordLength { stated_length, mandated_length }); 47 | } 48 | 49 | let remaining_length = stated_length - 4; 50 | let mut target = vec![0_u8; usize::from(remaining_length)].into_boxed_slice(); 51 | c.read_exact(&mut target)?; 52 | trace!("Parsed target -> {:?}", String::from_utf8_lossy(&target)); 53 | 54 | Ok(Self { priority, weight, target }) 55 | } 56 | } 57 | 58 | 59 | #[cfg(test)] 60 | mod test { 61 | use super::*; 62 | use pretty_assertions::assert_eq; 63 | 64 | #[test] 65 | fn parses() { 66 | let buf = &[ 67 | 0x00, 0x0A, // priority 68 | 0x00, 0x10, // weight 69 | 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x72, 0x66, 0x63, 70 | 0x73, 0x2e, 0x69, 0x6f, 0x2f, // uri 71 | ]; 72 | 73 | assert_eq!(URI::read(buf.len() as _, &mut Cursor::new(buf)).unwrap(), 74 | URI { 75 | priority: 10, 76 | weight: 16, 77 | target: Box::new(*b"https://rfcs.io/"), 78 | }); 79 | } 80 | 81 | #[test] 82 | fn one_byte_of_uri() { 83 | let buf = &[ 84 | 0x00, 0x0A, // priority 85 | 0x00, 0x10, // weight 86 | 0x2f, // one byte of uri (invalid but still a legitimate DNS record) 87 | ]; 88 | 89 | assert_eq!(URI::read(buf.len() as _, &mut Cursor::new(buf)).unwrap(), 90 | URI { 91 | priority: 10, 92 | weight: 16, 93 | target: Box::new(*b"/"), 94 | }); 95 | } 96 | 97 | #[test] 98 | fn missing_any_data() { 99 | let buf = &[ 100 | 0x00, 0x0A, // priority 101 | 0x00, 0x10, // weight 102 | ]; 103 | 104 | assert_eq!(URI::read(buf.len() as _, &mut Cursor::new(buf)), 105 | Err(WireError::WrongRecordLength { stated_length: 4, mandated_length: MandatedLength::AtLeast(5) })); 106 | } 107 | 108 | #[test] 109 | fn record_empty() { 110 | assert_eq!(URI::read(0, &mut Cursor::new(&[])), 111 | Err(WireError::IO)); 112 | } 113 | 114 | #[test] 115 | fn buffer_ends_abruptly() { 116 | let buf = &[ 117 | 0x00, 0x0A, // half a priority 118 | ]; 119 | 120 | assert_eq!(URI::read(23, &mut Cursor::new(buf)), 121 | Err(WireError::IO)); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /dns/tests/wire_building_tests.rs: -------------------------------------------------------------------------------- 1 | use dns::{Request, Flags, Query, Labels, QClass}; 2 | use dns::record::RecordType; 3 | 4 | use pretty_assertions::assert_eq; 5 | 6 | 7 | #[test] 8 | fn build_request() { 9 | let request = Request { 10 | transaction_id: 0xceac, 11 | flags: Flags::query(), 12 | query: Query { 13 | qname: Labels::encode("rfcs.io").unwrap(), 14 | qclass: QClass::Other(0x42), 15 | qtype: RecordType::from(0x1234), 16 | }, 17 | additional: Some(Request::additional_record()), 18 | }; 19 | 20 | let result = vec![ 21 | 0xce, 0xac, // transaction ID 22 | 0x01, 0x00, // flags (standard query) 23 | 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, // counts (1, 0, 0, 1) 24 | 25 | // query: 26 | 0x04, 0x72, 0x66, 0x63, 0x73, 0x02, 0x69, 0x6f, 0x00, // qname 27 | 0x12, 0x34, // type 28 | 0x00, 0x42, // class 29 | 30 | // OPT record: 31 | 0x00, // name 32 | 0x00, 0x29, // type OPT 33 | 0x02, 0x00, // UDP payload size 34 | 0x00, // higher bits 35 | 0x00, // EDNS(0) version 36 | 0x00, 0x00, // more flags 37 | 0x00, 0x00, // no data 38 | ]; 39 | 40 | assert_eq!(request.to_bytes().unwrap(), result); 41 | } 42 | -------------------------------------------------------------------------------- /dog-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ogham/dog/721440b12ef01a812abe5dc6ced69af6e221fad5/dog-screenshot.png -------------------------------------------------------------------------------- /src/colours.rs: -------------------------------------------------------------------------------- 1 | //! Colours, colour schemes, and terminal styling. 2 | 3 | use ansi_term::Style; 4 | use ansi_term::Color::*; 5 | 6 | 7 | /// The **colours** are used to paint the input. 8 | #[derive(Debug, Default)] 9 | pub struct Colours { 10 | pub qname: Style, 11 | 12 | pub answer: Style, 13 | pub authority: Style, 14 | pub additional: Style, 15 | 16 | pub a: Style, 17 | pub aaaa: Style, 18 | pub caa: Style, 19 | pub cname: Style, 20 | pub eui48: Style, 21 | pub eui64: Style, 22 | pub hinfo: Style, 23 | pub loc: Style, 24 | pub mx: Style, 25 | pub ns: Style, 26 | pub naptr: Style, 27 | pub openpgpkey: Style, 28 | pub opt: Style, 29 | pub ptr: Style, 30 | pub sshfp: Style, 31 | pub soa: Style, 32 | pub srv: Style, 33 | pub tlsa: Style, 34 | pub txt: Style, 35 | pub uri: Style, 36 | pub unknown: Style, 37 | } 38 | 39 | impl Colours { 40 | 41 | /// Create a new colour palette that has a variety of different styles 42 | /// defined. This is used by default. 43 | pub fn pretty() -> Self { 44 | Self { 45 | qname: Blue.bold(), 46 | 47 | answer: Style::default(), 48 | authority: Cyan.normal(), 49 | additional: Green.normal(), 50 | 51 | a: Green.bold(), 52 | aaaa: Green.bold(), 53 | caa: Red.normal(), 54 | cname: Yellow.normal(), 55 | eui48: Yellow.normal(), 56 | eui64: Yellow.bold(), 57 | hinfo: Yellow.normal(), 58 | loc: Yellow.normal(), 59 | mx: Cyan.normal(), 60 | naptr: Green.normal(), 61 | ns: Red.normal(), 62 | openpgpkey: Cyan.normal(), 63 | opt: Purple.normal(), 64 | ptr: Red.normal(), 65 | sshfp: Cyan.normal(), 66 | soa: Purple.normal(), 67 | srv: Cyan.normal(), 68 | tlsa: Yellow.normal(), 69 | txt: Yellow.normal(), 70 | uri: Yellow.normal(), 71 | unknown: White.on(Red), 72 | } 73 | } 74 | 75 | /// Create a new colour palette where no styles are defined, causing 76 | /// output to be rendered as plain text without any formatting. 77 | /// This is used when output is not to a terminal. 78 | pub fn plain() -> Self { 79 | Self::default() 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/connect.rs: -------------------------------------------------------------------------------- 1 | //! Creating DNS transports based on the user’s input arguments. 2 | 3 | use dns_transport::*; 4 | 5 | 6 | /// A **transport type** creates a `Transport` that determines which protocols 7 | /// should be used to send and receive DNS wire data over the network. 8 | #[derive(PartialEq, Debug, Copy, Clone)] 9 | pub enum TransportType { 10 | 11 | /// Send packets over UDP or TCP. 12 | /// UDP is used by default. If the request packet would be too large, send 13 | /// a TCP packet instead; if a UDP _response_ packet is truncated, try 14 | /// again with TCP. 15 | Automatic, 16 | 17 | /// Send packets over UDP only. 18 | /// If the request packet is too large or the response packet is 19 | /// truncated, fail with an error. 20 | UDP, 21 | 22 | /// Send packets over TCP only. 23 | TCP, 24 | 25 | /// Send encrypted DNS-over-TLS packets. 26 | TLS, 27 | 28 | /// Send encrypted DNS-over-HTTPS packets. 29 | HTTPS, 30 | } 31 | 32 | impl TransportType { 33 | 34 | /// Creates a boxed `Transport` depending on the transport type. The 35 | /// parameter will be a URL for the HTTPS transport type, and a 36 | /// stringified address for the others. 37 | pub fn make_transport(self, param: String) -> Box { 38 | match self { 39 | Self::Automatic => Box::new(AutoTransport::new(param)), 40 | Self::UDP => Box::new(UdpTransport::new(param)), 41 | Self::TCP => Box::new(TcpTransport::new(param)), 42 | Self::TLS => Box::new(TlsTransport::new(param)), 43 | Self::HTTPS => Box::new(HttpsTransport::new(param)), 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/hints.rs: -------------------------------------------------------------------------------- 1 | //! Hints to the user made before a query is sent, in case the answer that 2 | //! comes back isn’t what they expect. 3 | 4 | use std::collections::BTreeSet; 5 | use std::fs::File; 6 | use std::io; 7 | 8 | use log::*; 9 | 10 | 11 | /// The set of hostnames that are configured to point to a specific host in 12 | /// the hosts file on the local machine. This gets queried before a request is 13 | /// made: because the running OS will consult the hosts file before looking up 14 | /// a hostname, but dog will not, it’s possible for dog to output one address 15 | /// while the OS is using another. dog displays a warning when this is the 16 | /// case, to prevent confusion. 17 | #[derive(Default)] 18 | pub struct LocalHosts { 19 | hostnames: BTreeSet, 20 | } 21 | 22 | impl LocalHosts { 23 | 24 | /// Loads the set of hostnames from the hosts file path on Unix. 25 | #[cfg(unix)] 26 | pub fn load() -> io::Result { 27 | debug!("Reading hints from /etc/hosts"); 28 | Self::load_from_file(File::open("/etc/hosts")?) 29 | } 30 | 31 | /// Loads the set of hostnames from the hosts file path on Windows. 32 | #[cfg(windows)] 33 | pub fn load() -> io::Result { 34 | debug!("Reading hints from /etc/hosts equivalent"); 35 | Self::load_from_file(File::open("C:\\Windows\\system32\\drivers\\etc\\hosts")?) 36 | } 37 | 38 | /// On other machines, load an empty set of hostnames that match nothing. 39 | #[cfg(all(not(windows), not(unix)))] 40 | pub fn load() -> io::Result { 41 | Ok(Self::default()) 42 | } 43 | 44 | /// Reads hostnames from the given file and returns them as a `LocalHosts` 45 | /// struct, or an I/O error if one occurs. The file should be in the 46 | /// standard `/etc/hosts` format, with one entry per line, separated by 47 | /// whitespace, where the first field is the address and the remaining 48 | /// fields are hostname aliases, and `#` signifies a comment. 49 | fn load_from_file(file: File) -> io::Result { 50 | use std::io::{BufRead, BufReader}; 51 | 52 | if cfg!(test) { 53 | panic!("load_from_file() called from test code"); 54 | } 55 | 56 | let reader = BufReader::new(file); 57 | 58 | let mut hostnames = BTreeSet::new(); 59 | for line in reader.lines() { 60 | let mut line = line?; 61 | 62 | if let Some(hash_index) = line.find('#') { 63 | line.truncate(hash_index); 64 | } 65 | 66 | for hostname in line.split_ascii_whitespace().skip(1) { 67 | match dns::Labels::encode(hostname) { 68 | Ok(hn) => { 69 | hostnames.insert(hn); 70 | } 71 | Err(e) => { 72 | warn!("Failed to encode local host hint {:?}: {}", hostname, e); 73 | } 74 | } 75 | } 76 | } 77 | 78 | trace!("{} hostname hints loaded OK.", hostnames.len()); 79 | Ok(Self { hostnames }) 80 | } 81 | 82 | /// Queries this set of hostnames to see if the given name, which is about 83 | /// to be queried for, exists within the file. 84 | pub fn contains(&self, hostname_in_query: &dns::Labels) -> bool { 85 | self.hostnames.contains(hostname_in_query) 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/logger.rs: -------------------------------------------------------------------------------- 1 | //! Debug error logging. 2 | 3 | use std::ffi::OsStr; 4 | 5 | use ansi_term::{Colour, ANSIString}; 6 | 7 | 8 | /// Sets the internal logger, changing the log level based on the value of an 9 | /// environment variable. 10 | pub fn configure>(ev: Option) { 11 | let ev = match ev { 12 | Some(v) => v, 13 | None => return, 14 | }; 15 | 16 | let env_var = ev.as_ref(); 17 | if env_var.is_empty() { 18 | return; 19 | } 20 | 21 | if env_var == "trace" { 22 | log::set_max_level(log::LevelFilter::Trace); 23 | } 24 | else { 25 | log::set_max_level(log::LevelFilter::Debug); 26 | } 27 | 28 | let result = log::set_logger(GLOBAL_LOGGER); 29 | if let Err(e) = result { 30 | eprintln!("Failed to initialise logger: {}", e); 31 | } 32 | } 33 | 34 | 35 | #[derive(Debug)] 36 | struct Logger; 37 | 38 | const GLOBAL_LOGGER: &Logger = &Logger; 39 | 40 | impl log::Log for Logger { 41 | fn enabled(&self, _: &log::Metadata<'_>) -> bool { 42 | true // no need to filter after using ‘set_max_level’. 43 | } 44 | 45 | fn log(&self, record: &log::Record<'_>) { 46 | let open = Colour::Fixed(243).paint("["); 47 | let level = level(record.level()); 48 | let close = Colour::Fixed(243).paint("]"); 49 | 50 | eprintln!("{}{} {}{} {}", open, level, record.target(), close, record.args()); 51 | } 52 | 53 | fn flush(&self) { 54 | // no need to flush with ‘eprintln!’. 55 | } 56 | } 57 | 58 | fn level(level: log::Level) -> ANSIString<'static> { 59 | match level { 60 | log::Level::Error => Colour::Red.paint("ERROR"), 61 | log::Level::Warn => Colour::Yellow.paint("WARN"), 62 | log::Level::Info => Colour::Cyan.paint("INFO"), 63 | log::Level::Debug => Colour::Blue.paint("DEBUG"), 64 | log::Level::Trace => Colour::Fixed(245).paint("TRACE"), 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/txid.rs: -------------------------------------------------------------------------------- 1 | //! Transaction ID generation. 2 | 3 | 4 | /// A **transaction ID generator** is used to create unique ID numbers to 5 | /// identify each packet, as part of the DNS protocol. 6 | #[derive(PartialEq, Debug, Copy, Clone)] 7 | pub enum TxidGenerator { 8 | 9 | /// Generate random transaction IDs each time. 10 | Random, 11 | 12 | /// Generate transaction IDs in a sequence, starting from the given value, 13 | /// wrapping around. 14 | Sequence(u16), 15 | } 16 | 17 | impl TxidGenerator { 18 | pub fn generate(self) -> u16 { 19 | match self { 20 | Self::Random => rand::random(), 21 | Self::Sequence(start) => start, // todo 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/usage.txt: -------------------------------------------------------------------------------- 1 | \4mUsage:\0m 2 | \1mdog\0m \1;33m[OPTIONS]\0m [--] \32m\0m 3 | 4 | \4mExamples:\0m 5 | \1mdog\0m \32mexample.net\0m Query a domain using default settings 6 | \1mdog\0m \32mexample.net MX\0m ...looking up MX records instead 7 | \1mdog\0m \32mexample.net MX @1.1.1.1\0m ...using a specific nameserver instead 8 | \1mdog\0m \32mexample.net MX @1.1.1.1\0m \1;33m-T\0m ...using TCP rather than UDP 9 | \1mdog\0m \1;33m-q\0m \33mexample.net\0m \1;33m-t\0m \33mMX\0m \1;33m-n\0m \33m1.1.1.1\0m \1;33m-T\0m As above, but using explicit arguments 10 | 11 | \4mQuery options:\0m 12 | \32m\0m Human-readable host names, nameservers, types, or classes 13 | \1;33m-q\0m, \1;33m--query\0m=\33mHOST\0m Host name or domain name to query 14 | \1;33m-t\0m, \1;33m--type\0m=\33mTYPE\0m Type of the DNS record being queried (A, MX, NS...) 15 | \1;33m-n\0m, \1;33m--nameserver\0m=\33mADDR\0m Address of the nameserver to send packets to 16 | \1;33m--class\0m=\33mCLASS\0m Network class of the DNS record being queried (IN, CH, HS) 17 | 18 | \4mSending options:\0m 19 | \1;33m--edns\0m=\33mSETTING\0m Whether to OPT in to EDNS (disable, hide, show) 20 | \1;33m--txid\0m=\33mNUMBER\0m Set the transaction ID to a specific value 21 | \1;33m-Z\0m=\33mTWEAKS\0m Set uncommon protocol-level tweaks 22 | 23 | \4mProtocol options:\0m 24 | \1;33m-U\0m, \1;33m--udp\0m Use the DNS protocol over UDP 25 | \1;33m-T\0m, \1;33m--tcp\0m Use the DNS protocol over TCP 26 | \1;33m-S\0m, \1;33m--tls\0m Use the DNS-over-TLS protocol 27 | \1;33m-H\0m, \1;33m--https\0m Use the DNS-over-HTTPS protocol 28 | 29 | \4mOutput options:\0m 30 | \1;33m-1\0m, \1;33m--short\0m Short mode: display nothing but the first result 31 | \1;33m-J\0m, \1;33m--json\0m Display the output as JSON 32 | \1;33m--color\0m, \1;33m--colour\0m=\33mWHEN\0m When to colourise the output (always, automatic, never) 33 | \1;33m--seconds\0m Do not format durations, display them as seconds 34 | \1;33m--time\0m Print how long the response took to arrive 35 | 36 | \4mMeta options:\0m 37 | \1;33m-?\0m, \1;33m--help\0m Print list of command-line options 38 | \1;33m-v\0m, \1;33m--version\0m Print version information 39 | -------------------------------------------------------------------------------- /xtests/README.md: -------------------------------------------------------------------------------- 1 | # dog › xtests 2 | 3 | This is dog’s extended test suite. The checks herein form a complete end-to-end set of tests, covering things like network connections, DNS protocol parsing, command-line options, error handling, and edge case behaviour. 4 | 5 | The checks are written as [Specsheet] documents, which you’ll need to have installed. For the JSON tests, you’ll also need [jq]. 6 | 7 | Because these tests make connections over the network, the outcome of the test suite will depend on your own machine‘s Internet connection! It also means that your own IP address will be recorded as making the requests. 8 | 9 | 10 | ### Test layout 11 | 12 | The tests have been divided into four sections: 13 | 14 | 1. **live**, which uses both your computer’s default resolver and the [public Cloudflare DNS resolver] to access records that have been created using a public-facing DNS host. This checks that dog works using whatever software is between you and those nameservers on the Internet right now. Because these are _live_ records, the output will vary as things like the TTL vary, so we cannot assert on the _exact_ output; nevertheless, it’s a good check to see if the basic functionality is working. 15 | 16 | 2. **madns**, which sends requests to the [madns resolver]. This resolver has been pre-programmed with deliberately incorrect responses to see how dog handles edge cases in the DNS specification. These are not live records, so things like the TTLs of the responses are fixed, meaning the output should never change over time; however, it does not mean dog will hold up against the network infrastructure of the real world. 17 | 18 | 3. **options**, which runs dog using various command-line options and checks that the correct output is returned. These tests should not make network requests when behaving correctly. 19 | 20 | 4. **features**, which checks dog does the right thing when certain features have been enabled or disabled at compile-time. These tests also should not make network requests when behaving correctly. 21 | 22 | All four categories of check are needed to ensure dog is working correctly. 23 | 24 | 25 | ### Tags 26 | 27 | To run a subset of the checks, you can filter with the following tags: 28 | 29 | - `cloudflare`: Tests that use the [public Cloudflare DNS resolver]. 30 | - `isp`: Tests that use your computer’s default resolver. 31 | - `madns`: Tests that use the [madns resolver]. 32 | - `options`: Tests that check the command-line options. 33 | 34 | You can also use a DNS record type as a tag to only run the checks for that particular type. 35 | 36 | [Specsheet]: https://specsheet.software/ 37 | [jq]: https://stedolan.github.io/jq/ 38 | [public Cloudflare DNS resolver]: https://developers.cloudflare.com/1.1.1.1/ 39 | [madns resolver]: https://madns.binarystar.systems/ 40 | -------------------------------------------------------------------------------- /xtests/features/none.toml: -------------------------------------------------------------------------------- 1 | # These tests are meant to be run against a dog binary compiled with 2 | # `--no-default-features`. They will fail otherwise. 3 | 4 | 5 | [[cmd]] 6 | name = "The missing features are documented in the version" 7 | shell = "dog --version" 8 | stdout = { string = "[-idna, -tls, -https]" } 9 | stderr = { empty = true } 10 | status = 0 11 | tags = [ 'features' ] 12 | 13 | [[cmd]] 14 | name = "The ‘--tls’ option is not accepted when the feature is disabled" 15 | shell = "dog --tls a.b.c.d" 16 | stdout = { empty = true } 17 | stderr = { file = "outputs/disabled_tls.txt" } 18 | status = 3 19 | tags = [ 'features' ] 20 | 21 | [[cmd]] 22 | name = "The ‘--https option is not accepted when the feature is disabled" 23 | shell = "dog --https a.b.c.d @name.server" 24 | stdout = { empty = true } 25 | stderr = { file = "outputs/disabled_https.txt" } 26 | status = 3 27 | tags = [ 'features' ] 28 | -------------------------------------------------------------------------------- /xtests/features/outputs/disabled_https.txt: -------------------------------------------------------------------------------- 1 | dog: Cannot use '--https': This version of dog has been compiled without HTTPS support 2 | -------------------------------------------------------------------------------- /xtests/features/outputs/disabled_tls.txt: -------------------------------------------------------------------------------- 1 | dog: Cannot use '--tls': This version of dog has been compiled without TLS support 2 | -------------------------------------------------------------------------------- /xtests/live/badssl.toml: -------------------------------------------------------------------------------- 1 | # Untrusted certificates 2 | 3 | [[cmd]] 4 | name = "Using a DNS-over-HTTPS server with an expired certificate" 5 | shell = "dog --https @https://expired.badssl.com/ lookup.dog" 6 | stdout = { empty = true } 7 | stderr = { string = "Error [tls]: The certificate was not trusted." } 8 | status = 1 9 | tags = [ 'live', 'badssl', 'https' ] 10 | 11 | [[cmd]] 12 | name = "Using a DNS-over-HTTPS server with the wrong host in the certificate" 13 | shell = "dog --https @https://wrong.host.badssl.com/ lookup.dog" 14 | stdout = { empty = true } 15 | stderr = { string = "Error [tls]: The certificate was not trusted." } 16 | status = 1 17 | tags = [ 'live', 'badssl', 'https' ] 18 | 19 | [[cmd]] 20 | name = "Using a DNS-over-HTTPS server with a self-signed certificate" 21 | shell = "dog --https @https://self-signed.badssl.com/ lookup.dog" 22 | stdout = { empty = true } 23 | stderr = { string = "Error [tls]: The certificate was not trusted." } 24 | status = 1 25 | tags = [ 'live', 'badssl', 'https' ] 26 | 27 | [[cmd]] 28 | name = "Using a DNS-over-HTTPS server with an untrusted root certificate" 29 | shell = "dog --https @https://untrusted-root.badssl.com/ lookup.dog" 30 | stdout = { empty = true } 31 | stderr = { string = "Error [tls]: The certificate was not trusted." } 32 | status = 1 33 | tags = [ 'live', 'badssl', 'https' ] 34 | 35 | [[cmd]] 36 | name = "Using a DNS-over-HTTPS server with a revoked certificate" 37 | shell = "dog --https @https://revoked.badssl.com/ lookup.dog" 38 | stdout = { empty = true } 39 | stderr = { string = "Error [tls]: The certificate was not trusted." } 40 | status = 1 41 | tags = [ 'live', 'badssl', 'https' ] 42 | 43 | [[cmd]] 44 | name = "Using a DNS-over-HTTPS server with a known bad certificate" 45 | shell = "dog --https @https://superfish.badssl.com/ lookup.dog" 46 | stdout = { empty = true } 47 | stderr = { string = "Error [tls]: The certificate was not trusted." } 48 | status = 1 49 | tags = [ 'live', 'badssl', 'https' ] 50 | 51 | 52 | # Handshake failures 53 | 54 | [[cmd]] 55 | name = "Using a DNS-over-HTTPS server that accepts the null cipher" 56 | shell = "dog --https @https://null.badssl.com/ lookup.dog" 57 | stdout = { empty = true } 58 | stderr = { string = "Error [tls]: handshake failure" } 59 | status = 1 60 | tags = [ 'live', 'badssl', 'https' ] 61 | 62 | [[cmd]] 63 | name = "Using a DNS-over-HTTPS server that accepts the rc4-md5 cipher" 64 | shell = "dog --https @https://rc4-md5.badssl.com/ lookup.dog" 65 | stdout = { empty = true } 66 | stderr = { string = "Error [tls]: handshake failure" } 67 | status = 1 68 | tags = [ 'live', 'badssl', 'https' ] 69 | -------------------------------------------------------------------------------- /xtests/live/basics.toml: -------------------------------------------------------------------------------- 1 | # Colour output 2 | 3 | [[cmd]] 4 | name = "Running dog with ‘--colour=always’ produces colourful output" 5 | shell = "dog dns.google --colour=always" 6 | stdout = { string = "\u001B[1;32mA\u001B[0m \u001B[1;34mdns.google.\u001B[0m" } 7 | stderr = { empty = true } 8 | status = 0 9 | tags = [ "live", "isp" ] 10 | 11 | [[cmd]] 12 | name = "Running dog produces an A record by default" 13 | shell = "dog dns.google" 14 | stdout = { string = "A dns.google." } 15 | stderr = { empty = true } 16 | status = 0 17 | tags = [ "live", "isp" ] 18 | 19 | [[cmd]] 20 | name = "Running dog with ‘--colour=never’ produces plain output" 21 | shell = "dog dns.google --colour=never" 22 | stdout = { string = "A dns.google." } 23 | stderr = { empty = true } 24 | status = 0 25 | tags = [ "live", "isp" ] 26 | 27 | 28 | # Default record type and transport 29 | 30 | [[cmd]] 31 | name = "Running dog with ‘-U’ produces no errors" 32 | shell = "dog dns.google -U" 33 | stdout = { string = "A dns.google." } 34 | stderr = { empty = true } 35 | status = 0 36 | tags = [ "live", "isp" ] 37 | 38 | [[cmd]] 39 | name = "Running dog with ‘A’ produces no errors" 40 | shell = "dog A dns.google" 41 | stdout = { string = "A dns.google." } 42 | stderr = { empty = true } 43 | status = 0 44 | tags = [ "live", "isp" ] 45 | 46 | [[cmd]] 47 | name = "Running dog with ‘--time’ outputs a duration" 48 | shell = "dog A dns.google --time" 49 | stdout = { string = "Ran in" } 50 | stderr = { empty = true } 51 | status = 0 52 | tags = [ "live", "isp" ] 53 | 54 | 55 | # Network errors 56 | 57 | [[cmd]] 58 | name = "Using a DNS server that does not exist on the network" 59 | shell = "dog A dns.google @non.exist --time" 60 | stdout = { string = "Ran in" } 61 | stderr = { string = "Error [network]" } 62 | status = 1 63 | tags = [ "live", "isp" ] 64 | -------------------------------------------------------------------------------- /xtests/live/bins.toml: -------------------------------------------------------------------------------- 1 | # HTTPS 2 | 3 | [[cmd]] 4 | name = "Using a DNS-over-HTTPS server that returns status 500" 5 | shell = "dog --https @https://eu.httpbin.org/status/500 lookup.dog" 6 | stdout = { empty = true } 7 | stderr = { string = "Error [http]: Nameserver returned HTTP 500 (INTERNAL SERVER ERROR)" } 8 | status = 1 9 | tags = [ 'live', 'httpbin', 'https' ] 10 | 11 | [[cmd]] 12 | name = "Using a DNS-over-HTTPS server that returns no content" 13 | shell = "dog --https @https://eu.httpbin.org/status/200 lookup.dog" 14 | stdout = { empty = true } 15 | stderr = { string = "Error [protocol]: Malformed packet: insufficient data" } 16 | status = 1 17 | tags = [ 'live', 'httpbin', 'https' ] 18 | 19 | 20 | # TCP 21 | 22 | # [[cmd]] 23 | # name = "Using a TCP server that returns an empty message" 24 | # shell = "dog --tcp @52.20.16.20:30000 lookup.dog" 25 | # stdout = { empty = true } 26 | # stderr = { string = "Error [network]: Truncated response" } 27 | # status = 1 28 | # tags = [ 'live', 'tcpbin', 'tcp' ] 29 | 30 | # The above test is flaky. It works correctly the first time, but produces a 31 | # different error message on subsequent runs. 32 | # 33 | # The ‘other’ tcpbin can be used to test the truncated response error 34 | # handling, but it requires waiting 60 seconds for their server to give up and 35 | # send us a FIN: 36 | # 37 | # - dog --tcp bsago.me @tcpbin.com:4242 38 | # - dog --tls bsago.me @tcpbin.com:4243 39 | -------------------------------------------------------------------------------- /xtests/madns/a-records.toml: -------------------------------------------------------------------------------- 1 | # A record successes 2 | 3 | [[cmd]] 4 | name = "Running with ‘a.example’ prints the correct A record" 5 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} A a.example" 6 | stdout = { file = "outputs/a.example.ansitxt" } 7 | stderr = { empty = true } 8 | status = 0 9 | tags = [ "a", "madns" ] 10 | 11 | 12 | # A record successes (JSON) 13 | 14 | [[cmd]] 15 | name = "Running with ‘a.example --json’ prints the correct A record structure" 16 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} A a.example --json | jq" 17 | stdout = { file = "outputs/a.example.json" } 18 | stderr = { empty = true } 19 | status = 0 20 | tags = [ "a", "madns", "json" ] 21 | 22 | 23 | # A record invalid packets 24 | 25 | [[cmd]] 26 | name = "Running with ‘too-long.a.invalid’ displays a record length error" 27 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} A too-long.a.invalid" 28 | stdout = { empty = true } 29 | stderr = { string = "Error [protocol]: Malformed packet: record length should be 4, got 5" } 30 | status = 1 31 | tags = [ "a", "madns" ] 32 | 33 | [[cmd]] 34 | name = "Running with ‘too-short.a.invalid’ displays a record length error" 35 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} A too-short.a.invalid" 36 | stdout = { empty = true } 37 | stderr = { string = "Error [protocol]: Malformed packet: record length should be 4, got 3" } 38 | status = 1 39 | tags = [ "a", "madns" ] 40 | 41 | [[cmd]] 42 | name = "Running with ‘empty.a.invalid’ displays a record length error" 43 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} A empty.a.invalid" 44 | stdout = { empty = true } 45 | stderr = { string = "Error [protocol]: Malformed packet: record length should be 4, got 0" } 46 | status = 1 47 | tags = [ "a", "madns" ] 48 | 49 | [[cmd]] 50 | name = "Running with ‘incomplete.a.invalid’ displays a protocol error" 51 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} A incomplete.a.invalid" 52 | stdout = { empty = true } 53 | stderr = { string = "Error [protocol]: Malformed packet: insufficient data" } 54 | status = 1 55 | tags = [ "a", "madns" ] 56 | -------------------------------------------------------------------------------- /xtests/madns/aaaa-records.toml: -------------------------------------------------------------------------------- 1 | # AAAA record successes 2 | 3 | [[cmd]] 4 | name = "Running with ‘aaaa.example’ prints the correct AAAA record" 5 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} AAAA aaaa.example" 6 | stdout = { file = "outputs/aaaa.example.ansitxt" } 7 | stderr = { empty = true } 8 | status = 0 9 | tags = [ "aaaa", "madns" ] 10 | 11 | 12 | # AAAA record successes (JSON) 13 | 14 | [[cmd]] 15 | name = "Running with ‘aaaa.example --json’ prints the correct AAAA record structure" 16 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} AAAA aaaa.example --json | jq" 17 | stdout = { file = "outputs/aaaa.example.json" } 18 | stderr = { empty = true } 19 | status = 0 20 | tags = [ "aaaa", "madns", "json" ] 21 | 22 | 23 | # AAAA record invalid packets 24 | 25 | [[cmd]] 26 | name = "Running with ‘too-long.aaaa.invalid’ displays a record length error" 27 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} AAAA too-long.aaaa.invalid" 28 | stdout = { empty = true } 29 | stderr = { string = "Error [protocol]: Malformed packet: record length should be 16, got 17" } 30 | status = 1 31 | tags = [ "aaaa", "madns" ] 32 | 33 | [[cmd]] 34 | name = "Running with ‘too-short.aaaa.invalid’ displays a record length error" 35 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} AAAA too-short.aaaa.invalid" 36 | stdout = { empty = true } 37 | stderr = { string = "Error [protocol]: Malformed packet: record length should be 16, got 8" } 38 | status = 1 39 | tags = [ "aaaa", "madns" ] 40 | 41 | [[cmd]] 42 | name = "Running with ‘empty.aaaa.invalid’ displays a record length error" 43 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} AAAA empty.aaaa.invalid" 44 | stdout = { empty = true } 45 | stderr = { string = "Error [protocol]: Malformed packet: record length should be 16, got 0" } 46 | status = 1 47 | tags = [ "aaaa", "madns" ] 48 | 49 | [[cmd]] 50 | name = "Running with ‘incomplete.aaaa.invalid’ displays a protocol error" 51 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} AAAA incomplete.aaaa.invalid" 52 | stdout = { empty = true } 53 | stderr = { string = "Error [protocol]: Malformed packet: insufficient data" } 54 | status = 1 55 | tags = [ "aaaa", "madns" ] 56 | -------------------------------------------------------------------------------- /xtests/madns/caa-records.toml: -------------------------------------------------------------------------------- 1 | # CAA record successes 2 | 3 | [[cmd]] 4 | name = "Running with ‘caa.example’ prints the correct CAA record" 5 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} CAA caa.example" 6 | stdout = { file = "outputs/caa.example.ansitxt" } 7 | stderr = { empty = true } 8 | status = 0 9 | tags = [ "caa", "madns" ] 10 | 11 | [[cmd]] 12 | name = "Running with ‘critical.caa.example’ prints the correct CAA record with the flag" 13 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} CAA critical.caa.example" 14 | stdout = { file = "outputs/critical.caa.example.ansitxt" } 15 | stderr = { empty = true } 16 | status = 0 17 | tags = [ "caa", "madns" ] 18 | 19 | [[cmd]] 20 | name = "Running with ‘others.caa.example’ prints the correct CAA record and ignores the flags" 21 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} CAA others.caa.example" 22 | stdout = { file = "outputs/others.caa.example.ansitxt" } 23 | stderr = { empty = true } 24 | status = 0 25 | tags = [ "caa", "madns" ] 26 | 27 | [[cmd]] 28 | name = "Running with ‘utf8.caa.example’ escapes characters in the fields" 29 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} CAA utf8.caa.example" 30 | stdout = { file = "outputs/utf8.caa.example.ansitxt" } 31 | stderr = { empty = true } 32 | status = 0 33 | tags = [ "caa", "madns", "chars" ] 34 | 35 | [[cmd]] 36 | name = "Running with ‘bad-utf8.caa.example’ escapes characters in the fields and does not crash" 37 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} CAA bad-utf8.caa.example" 38 | stdout = { file = "outputs/bad-utf8.caa.example.ansitxt" } 39 | stderr = { empty = true } 40 | status = 0 41 | tags = [ "caa", "madns", "chars" ] 42 | 43 | 44 | # CAA record successes (JSON) 45 | 46 | [[cmd]] 47 | name = "Running with ‘caa.example --json’ prints the correct CAA record structure" 48 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} CAA caa.example --json | jq" 49 | stdout = { file = "outputs/caa.example.json" } 50 | stderr = { empty = true } 51 | status = 0 52 | tags = [ "caa", "madns", "json" ] 53 | 54 | [[cmd]] 55 | name = "Running with ‘critical.caa.example --json’ prints the correct CAA record structurewith the flag" 56 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} CAA critical.caa.example --json | jq" 57 | stdout = { file = "outputs/critical.caa.example.json" } 58 | stderr = { empty = true } 59 | status = 0 60 | tags = [ "caa", "madns", "json" ] 61 | 62 | [[cmd]] 63 | name = "Running with ‘others.caa.example --json’ prints the correct CAA record structure and ignores the flags" 64 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} CAA others.caa.example --json | jq" 65 | stdout = { file = "outputs/others.caa.example.json" } 66 | stderr = { empty = true } 67 | status = 0 68 | tags = [ "caa", "madns", "json" ] 69 | 70 | [[cmd]] 71 | name = "Running with ‘utf8.caa.example --json’ interprets the response as UTF-8" 72 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} CAA utf8.caa.example --json | jq" 73 | stdout = { file = "outputs/utf8.caa.example.json" } 74 | stderr = { empty = true } 75 | status = 0 76 | tags = [ "caa", "madns", "chars", "json" ] 77 | 78 | [[cmd]] 79 | name = "Running with ‘bad-utf8.caa.example --json’ uses UTF-8 replacement characters" 80 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} CAA bad-utf8.caa.example --json | jq" 81 | stdout = { file = "outputs/bad-utf8.caa.example.json" } 82 | stderr = { empty = true } 83 | status = 0 84 | tags = [ "caa", "madns", "chars", "json" ] 85 | 86 | 87 | # CAA record invalid packets 88 | 89 | [[cmd]] 90 | name = "Running with ‘empty.caa.invalid’ displays a protocol error" 91 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} CAA empty.caa.invalid" 92 | stdout = { empty = true } 93 | stderr = { string = "Error [protocol]: Malformed packet: insufficient data" } 94 | status = 1 95 | tags = [ "caa", "madns" ] 96 | 97 | [[cmd]] 98 | name = "Running with ‘incomplete.caa.invalid’ displays a protocol error" 99 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} CAA incomplete.caa.invalid" 100 | stdout = { empty = true } 101 | stderr = { string = "Error [protocol]: Malformed packet: insufficient data" } 102 | status = 1 103 | tags = [ "caa", "madns" ] 104 | -------------------------------------------------------------------------------- /xtests/madns/cname-records.toml: -------------------------------------------------------------------------------- 1 | # CNAME record successes 2 | 3 | [[cmd]] 4 | name = "Running with ‘cname.example’ prints the correct CNAME record" 5 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} CNAME cname.example" 6 | stdout = { file = "outputs/cname.example.ansitxt" } 7 | stderr = { empty = true } 8 | status = 0 9 | tags = [ "cname", "madns" ] 10 | 11 | 12 | # CNAME record successes (JSON) 13 | 14 | [[cmd]] 15 | name = "Running with ‘cname.example --json’ prints the correct CNAME record structure" 16 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} CNAME cname.example --json | jq" 17 | stdout = { file = "outputs/cname.example.json" } 18 | stderr = { empty = true } 19 | status = 0 20 | tags = [ "cname", "madns", "json" ] 21 | 22 | 23 | # CNAME record invalid packets 24 | 25 | [[cmd]] 26 | name = "Running with ‘empty.cname.invalid’ displays a protocol error" 27 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} CNAME empty.cname.invalid" 28 | stdout = { empty = true } 29 | stderr = { string = "Error [protocol]: Malformed packet: insufficient data" } 30 | status = 1 31 | tags = [ "cname", "madns" ] 32 | 33 | [[cmd]] 34 | name = "Running with ‘incomplete.cname.invalid’ displays a protocol error" 35 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} CNAME incomplete.cname.invalid" 36 | stdout = { empty = true } 37 | stderr = { string = "Error [protocol]: Malformed packet: insufficient data" } 38 | status = 1 39 | tags = [ "cname", "madns" ] 40 | -------------------------------------------------------------------------------- /xtests/madns/eui48-records.toml: -------------------------------------------------------------------------------- 1 | # EUI48 record successes 2 | 3 | [[cmd]] 4 | name = "Running with ‘eui48.example’ prints the correct EUI48 record" 5 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} EUI48 eui48.example" 6 | stdout = { file = "outputs/eui48.example.ansitxt" } 7 | stderr = { empty = true } 8 | status = 0 9 | tags = [ "eui48", "madns" ] 10 | 11 | 12 | # EUI48 record successes (JSON) 13 | 14 | [[cmd]] 15 | name = "Running with ‘eui48.example --json’ prints the correct EUI48 record structure" 16 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} EUI48 eui48.example --json | jq" 17 | stdout = { file = "outputs/eui48.example.json" } 18 | stderr = { empty = true } 19 | status = 0 20 | tags = [ "eui48", "madns", "json" ] 21 | 22 | 23 | # EUI48 record invalid packets 24 | 25 | [[cmd]] 26 | name = "Running with ‘too-long.eui48.invalid’ displays a record length error" 27 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} EUI48 too-long.eui48.invalid" 28 | stdout = { empty = true } 29 | stderr = { string = "Error [protocol]: Malformed packet: record length should be 6, got 7" } 30 | status = 1 31 | tags = [ "eui48", "madns" ] 32 | 33 | [[cmd]] 34 | name = "Running with ‘too-short.eui48.invalid’ displays a record length error" 35 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} EUI48 too-short.eui48.invalid" 36 | stdout = { empty = true } 37 | stderr = { string = "Error [protocol]: Malformed packet: record length should be 6, got 5" } 38 | status = 1 39 | tags = [ "eui48", "madns" ] 40 | 41 | [[cmd]] 42 | name = "Running with ‘empty.eui48.invalid’ displays a record length error" 43 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} EUI48 empty.eui48.invalid" 44 | stdout = { empty = true } 45 | stderr = { string = "Error [protocol]: Malformed packet: record length should be 6, got 0" } 46 | status = 1 47 | tags = [ "eui48", "madns" ] 48 | 49 | [[cmd]] 50 | name = "Running with ‘incomplete.eui48.invalid’ displays a protocol error" 51 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} EUI48 incomplete.eui48.invalid" 52 | stdout = { empty = true } 53 | stderr = { string = "Error [protocol]: Malformed packet: insufficient data" } 54 | status = 1 55 | tags = [ "eui48", "madns" ] 56 | -------------------------------------------------------------------------------- /xtests/madns/eui64-records.toml: -------------------------------------------------------------------------------- 1 | # EUI64 record successes 2 | 3 | [[cmd]] 4 | name = "Running with ‘eui64.example’ prints the correct EUI64 record" 5 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} EUI64 eui64.example" 6 | stdout = { file = "outputs/eui64.example.ansitxt" } 7 | stderr = { empty = true } 8 | status = 0 9 | tags = [ "eui64", "madns" ] 10 | 11 | 12 | # EUI64 record successes (JSON) 13 | 14 | [[cmd]] 15 | name = "Running with ‘eui64.example --json’ prints the correct EUI64 record structure" 16 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} EUI64 eui64.example --json | jq" 17 | stdout = { file = "outputs/eui64.example.json" } 18 | stderr = { empty = true } 19 | status = 0 20 | tags = [ "eui64", "madns", "json" ] 21 | 22 | 23 | # EUI64 record invalid packets 24 | 25 | [[cmd]] 26 | name = "Running with ‘too-long.eui64.invalid’ displays a record length error" 27 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} EUI64 too-long.eui64.invalid" 28 | stdout = { empty = true } 29 | stderr = { string = "Error [protocol]: Malformed packet: record length should be 8, got 9" } 30 | status = 1 31 | tags = [ "eui64", "madns" ] 32 | 33 | [[cmd]] 34 | name = "Running with ‘too-short.eui64.invalid’ displays a record length error" 35 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} EUI64 too-short.eui64.invalid" 36 | stdout = { empty = true } 37 | stderr = { string = "Error [protocol]: Malformed packet: record length should be 8, got 7" } 38 | status = 1 39 | tags = [ "eui64", "madns" ] 40 | 41 | [[cmd]] 42 | name = "Running with ‘empty.eui64.invalid’ displays a record length error" 43 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} EUI64 empty.eui64.invalid" 44 | stdout = { empty = true } 45 | stderr = { string = "Error [protocol]: Malformed packet: record length should be 8, got 0" } 46 | status = 1 47 | tags = [ "eui64", "madns" ] 48 | 49 | [[cmd]] 50 | name = "Running with ‘incomplete.eui64.invalid’ displays a protocol error" 51 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} EUI64 incomplete.eui64.invalid" 52 | stdout = { empty = true } 53 | stderr = { string = "Error [protocol]: Malformed packet: insufficient data" } 54 | status = 1 55 | tags = [ "eui64", "madns" ] 56 | -------------------------------------------------------------------------------- /xtests/madns/hinfo-records.toml: -------------------------------------------------------------------------------- 1 | # HINFO record successes 2 | 3 | [[cmd]] 4 | name = "Running with ‘hinfo.example’ prints the correct HINFO record" 5 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} HINFO hinfo.example" 6 | stdout = { file = "outputs/hinfo.example.ansitxt" } 7 | stderr = { empty = true } 8 | status = 0 9 | tags = [ "hinfo", "madns" ] 10 | 11 | [[cmd]] 12 | name = "Running with ‘utf8.hinfo.example’ escapes characters in the fields" 13 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} HINFO utf8.hinfo.example" 14 | stdout = { file = "outputs/utf8.hinfo.example.ansitxt" } 15 | stderr = { empty = true } 16 | status = 0 17 | tags = [ "hinfo", "madns", "chars" ] 18 | 19 | [[cmd]] 20 | name = "Running with ‘bad-utf8.hinfo.example’ escapes characters in the fields and does not crash" 21 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} HINFO bad-utf8.hinfo.example" 22 | stdout = { file = "outputs/bad-utf8.hinfo.example.ansitxt" } 23 | stderr = { empty = true } 24 | status = 0 25 | tags = [ "hinfo", "madns", "chars" ] 26 | 27 | 28 | # HINFO record successes (JSON) 29 | 30 | [[cmd]] 31 | name = "Running with ‘hinfo.example --json’ prints the correct HINFO record structure" 32 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} HINFO hinfo.example --json | jq" 33 | stdout = { file = "outputs/hinfo.example.json" } 34 | stderr = { empty = true } 35 | status = 0 36 | tags = [ "hinfo", "madns", "json" ] 37 | 38 | [[cmd]] 39 | name = "Running with ‘utf8.hinfo.example --json’ interprets the response as UTF-8" 40 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} HINFO utf8.hinfo.example --json | jq" 41 | stdout = { file = "outputs/utf8.hinfo.example.json" } 42 | stderr = { empty = true } 43 | status = 0 44 | tags = [ "hinfo", "madns", "chars", "json" ] 45 | 46 | [[cmd]] 47 | name = "Running with ‘bad-utf8.hinfo.example --json’ uses UTF-8 replacement characters" 48 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} HINFO bad-utf8.hinfo.example --json | jq" 49 | stdout = { file = "outputs/bad-utf8.hinfo.example.json" } 50 | stderr = { empty = true } 51 | status = 0 52 | tags = [ "hinfo", "madns", "chars", "json" ] 53 | 54 | # HINFO record invalid packets 55 | 56 | [[cmd]] 57 | name = "Running with ‘empty.hinfo.invalid’ displays a protocol error" 58 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} HINFO empty.hinfo.invalid" 59 | stdout = { empty = true } 60 | stderr = { string = "Error [protocol]: Malformed packet: insufficient data" } 61 | status = 1 62 | tags = [ "hinfo", "madns" ] 63 | 64 | [[cmd]] 65 | name = "Running with ‘incomplete.hinfo.invalid’ displays a protocol error" 66 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} HINFO incomplete.hinfo.invalid" 67 | stdout = { empty = true } 68 | stderr = { string = "Error [protocol]: Malformed packet: insufficient data" } 69 | status = 1 70 | tags = [ "hinfo", "madns" ] 71 | -------------------------------------------------------------------------------- /xtests/madns/mx-records.toml: -------------------------------------------------------------------------------- 1 | # MX record successes 2 | 3 | [[cmd]] 4 | name = "Running with ‘mx.example’ prints the correct MX record" 5 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} MX mx.example" 6 | stdout = { file = "outputs/mx.example.ansitxt" } 7 | stderr = { empty = true } 8 | status = 0 9 | tags = [ "mx", "madns" ] 10 | 11 | 12 | # MX record successes (JSON) 13 | 14 | [[cmd]] 15 | name = "Running with ‘mx.example --json’ prints the correct MX record structure" 16 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} MX mx.example --json | jq" 17 | stdout = { file = "outputs/mx.example.json" } 18 | stderr = { empty = true } 19 | status = 0 20 | tags = [ "mx", "madns", "json" ] 21 | 22 | 23 | # MX record invalid packets 24 | 25 | [[cmd]] 26 | name = "Running with ‘empty.mx.invalid’ displays a protocol error" 27 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} MX empty.mx.invalid" 28 | stdout = { empty = true } 29 | stderr = { string = "Error [protocol]: Malformed packet: insufficient data" } 30 | status = 1 31 | tags = [ "mx", "madns" ] 32 | 33 | [[cmd]] 34 | name = "Running with ‘incomplete.mx.invalid’ displays a protocol error" 35 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} MX incomplete.mx.invalid" 36 | stdout = { empty = true } 37 | stderr = { string = "Error [protocol]: Malformed packet: insufficient data" } 38 | status = 1 39 | tags = [ "mx", "madns" ] 40 | -------------------------------------------------------------------------------- /xtests/madns/naptr-records.toml: -------------------------------------------------------------------------------- 1 | # NAPTR record successes 2 | 3 | [[cmd]] 4 | name = "Running with ‘naptr.example’ prints the correct NAPTR record" 5 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} NAPTR naptr.example" 6 | stdout = { file = "outputs/naptr.example.ansitxt" } 7 | stderr = { empty = true } 8 | status = 0 9 | tags = [ "naptr", "madns" ] 10 | 11 | [[cmd]] 12 | name = "Running with ‘bad-regex.naptr.example’ still prints the correct NAPTR record" 13 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} NAPTR bad-regex.naptr.example" 14 | stdout = { file = "outputs/bad-regex.naptr.example.ansitxt" } 15 | stderr = { empty = true } 16 | status = 0 17 | tags = [ "naptr", "madns" ] 18 | 19 | [[cmd]] 20 | name = "Running with ‘utf8.naptr.example’ escapes characters in the NAPTR" 21 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} NAPTR utf8.naptr.invalid" 22 | stdout = { file = "outputs/utf8.naptr.invalid.ansitxt" } 23 | stderr = { empty = true } 24 | status = 0 25 | tags = [ "naptr", "madns", "chars" ] 26 | 27 | [[cmd]] 28 | name = "Running with ‘bad-utf8.naptr.example’ escapes characters in the NAPTR and does not crash" 29 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} NAPTR bad-utf8.naptr.invalid" 30 | stdout = { file = "outputs/bad-utf8.naptr.invalid.ansitxt" } 31 | stderr = { empty = true } 32 | status = 0 33 | tags = [ "naptr", "madns", "chars" ] 34 | 35 | 36 | # NAPTR record successes (JSON) 37 | 38 | [[cmd]] 39 | name = "Running with ‘naptr.example --json’ prints the correct NAPTR record structure" 40 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} NAPTR naptr.example --json | jq" 41 | stdout = { file = "outputs/naptr.example.json" } 42 | stderr = { empty = true } 43 | status = 0 44 | tags = [ "naptr", "madns", "json" ] 45 | 46 | [[cmd]] 47 | name = "Running with ‘utf8.naptr.example --json’ interprets the response as UTF-8" 48 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} NAPTR utf8.naptr.invalid --json | jq" 49 | stdout = { file = "outputs/utf8.naptr.invalid.json" } 50 | stderr = { empty = true } 51 | status = 0 52 | tags = [ "naptr", "madns", "chars", "json" ] 53 | 54 | [[cmd]] 55 | name = "Running with ‘bad-utf8.naptr.example --json’ uses UTF-8 replacement characters" 56 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} NAPTR bad-utf8.naptr.invalid --json | jq" 57 | stdout = { file = "outputs/bad-utf8.naptr.invalid.json" } 58 | stderr = { empty = true } 59 | status = 0 60 | tags = [ "naptr", "madns", "chars", "json" ] 61 | 62 | 63 | # NAPTR record invalid packets 64 | 65 | [[cmd]] 66 | name = "Running with ‘empty.naptr.invalid’ displays a protocol error" 67 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} NAPTR empty.naptr.invalid" 68 | stdout = { empty = true } 69 | stderr = { string = "Error [protocol]: Malformed packet: insufficient data" } 70 | status = 1 71 | tags = [ "naptr", "madns" ] 72 | 73 | [[cmd]] 74 | name = "Running with ‘incomplete.naptr.invalid’ displays a protocol error" 75 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} NAPTR incomplete.naptr.invalid" 76 | stdout = { empty = true } 77 | stderr = { string = "Error [protocol]: Malformed packet: insufficient data" } 78 | status = 1 79 | tags = [ "naptr", "madns" ] 80 | -------------------------------------------------------------------------------- /xtests/madns/ns-records.toml: -------------------------------------------------------------------------------- 1 | # NS record successes 2 | 3 | [[cmd]] 4 | name = "Running with ‘ns.example’ prints the correct NS record" 5 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} NS ns.example" 6 | stdout = { file = "outputs/ns.example.ansitxt" } 7 | stderr = { empty = true } 8 | status = 0 9 | tags = [ "ns", "madns" ] 10 | 11 | 12 | # NS record successes (JSON) 13 | 14 | [[cmd]] 15 | name = "Running with ‘ns.example --json’ prints the correct NS record structure" 16 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} NS ns.example --json | jq" 17 | stdout = { file = "outputs/ns.example.json" } 18 | stderr = { empty = true } 19 | status = 0 20 | tags = [ "ns", "madns", "json" ] 21 | 22 | 23 | # NS record invalid packets 24 | 25 | [[cmd]] 26 | name = "Running with ‘empty.ns.invalid’ displays a protocol error" 27 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} NS empty.ns.invalid" 28 | stdout = { empty = true } 29 | stderr = { string = "Error [protocol]: Malformed packet: insufficient data" } 30 | status = 1 31 | tags = [ "ns", "madns" ] 32 | 33 | [[cmd]] 34 | name = "Running with ‘incomplete.ns.invalid’ displays a protocol error" 35 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} NS incomplete.ns.invalid" 36 | stdout = { empty = true } 37 | stderr = { string = "Error [protocol]: Malformed packet: insufficient data" } 38 | status = 1 39 | tags = [ "ns", "madns" ] 40 | -------------------------------------------------------------------------------- /xtests/madns/openpgpkey-records.toml: -------------------------------------------------------------------------------- 1 | # OPENPGPKEY record successes 2 | 3 | [[cmd]] 4 | name = "Running with ‘openpgpkey.example’ prints the correct OPENPGPKEY record" 5 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} OPENPGPKEY openpgpkey.example" 6 | stdout = { file = "outputs/openpgpkey.example.ansitxt" } 7 | stderr = { empty = true } 8 | status = 0 9 | tags = [ "openpgpkey", "madns" ] 10 | 11 | 12 | # OPENPGPKEY record successes (JSON) 13 | 14 | [[cmd]] 15 | name = "Running with ‘openpgpkey.example --json’ prints the correct OPENPGPKEY record structure" 16 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} OPENPGPKEY openpgpkey.example --json | jq" 17 | stdout = { file = "outputs/openpgpkey.example.json" } 18 | stderr = { empty = true } 19 | status = 0 20 | tags = [ "openpgpkey", "madns", "json" ] 21 | 22 | 23 | # OPENPGPKEY record invalid packets 24 | 25 | [[cmd]] 26 | name = "Running with ‘empty.openpgpkey.invalid’ displays a record length error" 27 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} OPENPGPKEY empty.openpgpkey.invalid" 28 | stdout = { empty = true } 29 | stderr = { string = "Error [protocol]: Malformed packet: record length should be at least 1, got 0" } 30 | status = 1 31 | tags = [ "openpgpkey", "madns" ] 32 | 33 | [[cmd]] 34 | name = "Running with ‘incomplete.openpgpkey.invalid’ displays a protocol error" 35 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} OPENPGPKEY incomplete.openpgpkey.invalid" 36 | stdout = { empty = true } 37 | stderr = { string = "Error [protocol]: Malformed packet: insufficient data" } 38 | status = 1 39 | tags = [ "openpgpkey", "madns" ] 40 | -------------------------------------------------------------------------------- /xtests/madns/opt-records.toml: -------------------------------------------------------------------------------- 1 | # OPT record successes 2 | 3 | [[cmd]] 4 | name = "Running with ‘opt.example’ prints the correct OPT record" 5 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} A opt.example --edns=show" 6 | stdout = { file = "outputs/opt.example.ansitxt" } 7 | stderr = { empty = true } 8 | status = 0 9 | tags = [ "opt", "madns" ] 10 | 11 | [[cmd]] 12 | name = "Running with ‘do-flag.opt.example’ prints the correct OPT record" 13 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} A do-flag.opt.example --edns=show" 14 | stdout = { file = "outputs/do-flag.opt.example.ansitxt" } 15 | stderr = { empty = true } 16 | status = 0 17 | tags = [ "opt", "madns" ] 18 | 19 | [[cmd]] 20 | name = "Running with ‘other-flags.opt.example’ prints the correct OPT record" 21 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} A other-flags.opt.example --edns=show" 22 | stdout = { file = "outputs/other-flags.opt.example.ansitxt" } 23 | stderr = { empty = true } 24 | status = 0 25 | tags = [ "opt", "madns" ] 26 | 27 | [[cmd]] 28 | name = "Running with ‘named.opt.invalid’ prints the correct OPT record" 29 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} A named.opt.invalid --edns=show" 30 | stdout = { file = "outputs/named.opt.invalid.ansitxt" } 31 | stderr = { empty = true } 32 | status = 0 33 | tags = [ "opt", "madns" ] 34 | 35 | 36 | # OPT record successes (JSON) 37 | 38 | [[cmd]] 39 | name = "Running with ‘opt.example --json’ prints the correct OPT record structure" 40 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} A opt.example --edns=show --json | jq" 41 | stdout = { file = "outputs/opt.example.json" } 42 | stderr = { empty = true } 43 | status = 0 44 | tags = [ "opt", "madns", "json" ] 45 | 46 | [[cmd]] 47 | name = "Running with ‘do-flag.opt.example --json’ prints the correct OPT record structure" 48 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} A do-flag.opt.example --edns=show --json | jq" 49 | stdout = { file = "outputs/do-flag.opt.example.json" } 50 | stderr = { empty = true } 51 | status = 0 52 | tags = [ "opt", "madns", "json" ] 53 | 54 | [[cmd]] 55 | name = "Running with ‘other-flags.opt.example --json’ prints the correct OPT record structure" 56 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} A other-flags.opt.example --edns=show --json | jq" 57 | stdout = { file = "outputs/other-flags.opt.example.json" } 58 | stderr = { empty = true } 59 | status = 0 60 | tags = [ "opt", "madns", "json" ] 61 | 62 | [[cmd]] 63 | name = "Running with ‘named.opt.invalid --json’ prints the correct OPT record structure" 64 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} A named.opt.invalid --edns=show --json | jq" 65 | stdout = { file = "outputs/named.opt.invalid.json" } 66 | stderr = { empty = true } 67 | status = 0 68 | tags = [ "opt", "madns", "json" ] 69 | 70 | 71 | # OPT record invalid packets 72 | 73 | [[cmd]] 74 | name = "Running with ‘incomplete.opt.invalid’ displays a record length error" 75 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} A incomplete.opt.invalid" 76 | stdout = { empty = true } 77 | stderr = { string = "Error [protocol]: Malformed packet: insufficient data" } 78 | status = 1 79 | tags = [ "opt", "madns" ] 80 | -------------------------------------------------------------------------------- /xtests/madns/outputs/a.example.ansitxt: -------------------------------------------------------------------------------- 1 | A a.example. 10m00s 127.0.0.1 2 | -------------------------------------------------------------------------------- /xtests/madns/outputs/a.example.json: -------------------------------------------------------------------------------- 1 | { 2 | "responses": [ 3 | { 4 | "queries": [ 5 | { 6 | "name": "a.example.", 7 | "class": "IN", 8 | "type": "A" 9 | } 10 | ], 11 | "answers": [ 12 | { 13 | "name": "a.example.", 14 | "class": "IN", 15 | "ttl": 600, 16 | "type": "A", 17 | "data": { 18 | "address": "127.0.0.1" 19 | } 20 | } 21 | ], 22 | "authorities": [], 23 | "additionals": [] 24 | } 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /xtests/madns/outputs/aaaa.example.ansitxt: -------------------------------------------------------------------------------- 1 | AAAA aaaa.example. 10m00s ::1 2 | -------------------------------------------------------------------------------- /xtests/madns/outputs/aaaa.example.json: -------------------------------------------------------------------------------- 1 | { 2 | "responses": [ 3 | { 4 | "queries": [ 5 | { 6 | "name": "aaaa.example.", 7 | "class": "IN", 8 | "type": "AAAA" 9 | } 10 | ], 11 | "answers": [ 12 | { 13 | "name": "aaaa.example.", 14 | "class": "IN", 15 | "ttl": 600, 16 | "type": "AAAA", 17 | "data": { 18 | "address": "::1" 19 | } 20 | } 21 | ], 22 | "authorities": [], 23 | "additionals": [] 24 | } 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /xtests/madns/outputs/ansi.str.example.ansitxt: -------------------------------------------------------------------------------- 1 | CNAME ansi.str.example. 10m00s "\u{1b}[32mgreen.\u{1b}[34mblue.\u{1b}[31mred.\u{1b}[0m." 2 | -------------------------------------------------------------------------------- /xtests/madns/outputs/ansi.str.example.json: -------------------------------------------------------------------------------- 1 | { 2 | "responses": [ 3 | { 4 | "queries": [ 5 | { 6 | "name": "ansi.str.example.", 7 | "class": "IN", 8 | "type": "CNAME" 9 | } 10 | ], 11 | "answers": [ 12 | { 13 | "name": "ansi.str.example.", 14 | "class": "IN", 15 | "ttl": 600, 16 | "type": "CNAME", 17 | "data": { 18 | "domain": "\u001b[32mgreen.\u001b[34mblue.\u001b[31mred.\u001b[0m." 19 | } 20 | } 21 | ], 22 | "authorities": [], 23 | "additionals": [] 24 | } 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /xtests/madns/outputs/bad-regex.naptr.example.ansitxt: -------------------------------------------------------------------------------- 1 | NAPTR bad-regex.naptr.example. 10m00s 5 10 "s" "SRV" "(((((((((((((((((((((((((" "srv.example." 2 | -------------------------------------------------------------------------------- /xtests/madns/outputs/bad-utf8.caa.example.ansitxt: -------------------------------------------------------------------------------- 1 | CAA bad-utf8.caa.example. 10m00s "issuewild" "\208\208\160\255" (non-critical) 2 | -------------------------------------------------------------------------------- /xtests/madns/outputs/bad-utf8.caa.example.json: -------------------------------------------------------------------------------- 1 | { 2 | "responses": [ 3 | { 4 | "queries": [ 5 | { 6 | "name": "bad-utf8.caa.example.", 7 | "class": "IN", 8 | "type": "CAA" 9 | } 10 | ], 11 | "answers": [ 12 | { 13 | "name": "bad-utf8.caa.example.", 14 | "class": "IN", 15 | "ttl": 600, 16 | "type": "CAA", 17 | "data": { 18 | "critical": false, 19 | "tag": "issuewild", 20 | "value": "�Р�" 21 | } 22 | } 23 | ], 24 | "authorities": [], 25 | "additionals": [] 26 | } 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /xtests/madns/outputs/bad-utf8.hinfo.example.ansitxt: -------------------------------------------------------------------------------- 1 | HINFO bad-utf8.hinfo.example. 10m00s "\208\208\160\255" "\208\208\160\255" 2 | -------------------------------------------------------------------------------- /xtests/madns/outputs/bad-utf8.hinfo.example.json: -------------------------------------------------------------------------------- 1 | { 2 | "responses": [ 3 | { 4 | "queries": [ 5 | { 6 | "name": "bad-utf8.hinfo.example.", 7 | "class": "IN", 8 | "type": "HINFO" 9 | } 10 | ], 11 | "answers": [ 12 | { 13 | "name": "bad-utf8.hinfo.example.", 14 | "class": "IN", 15 | "ttl": 600, 16 | "type": "HINFO", 17 | "data": { 18 | "cpu": "�Р�", 19 | "os": "�Р�" 20 | } 21 | } 22 | ], 23 | "authorities": [], 24 | "additionals": [] 25 | } 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /xtests/madns/outputs/bad-utf8.naptr.invalid.ansitxt: -------------------------------------------------------------------------------- 1 | NAPTR bad-utf8.naptr.invalid. 10m00s 5 10 "\208\208\160\255" "\208\208\160\255" "\208\208\160\255" "�Р�." 2 | -------------------------------------------------------------------------------- /xtests/madns/outputs/bad-utf8.naptr.invalid.json: -------------------------------------------------------------------------------- 1 | { 2 | "responses": [ 3 | { 4 | "queries": [ 5 | { 6 | "name": "bad-utf8.naptr.invalid.", 7 | "class": "IN", 8 | "type": "NAPTR" 9 | } 10 | ], 11 | "answers": [ 12 | { 13 | "name": "bad-utf8.naptr.invalid.", 14 | "class": "IN", 15 | "ttl": 600, 16 | "type": "NAPTR", 17 | "data": { 18 | "order": 5, 19 | "flags": "�Р�", 20 | "service": "�Р�", 21 | "regex": "�Р�", 22 | "replacement": "�Р�." 23 | } 24 | } 25 | ], 26 | "authorities": [], 27 | "additionals": [] 28 | } 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /xtests/madns/outputs/bad-utf8.txt.example.ansitxt: -------------------------------------------------------------------------------- 1 | TXT bad-utf8.txt.example. 10m00s "\208\208\160\255" 2 | -------------------------------------------------------------------------------- /xtests/madns/outputs/bad-utf8.txt.example.json: -------------------------------------------------------------------------------- 1 | { 2 | "responses": [ 3 | { 4 | "queries": [ 5 | { 6 | "name": "bad-utf8.txt.example.", 7 | "class": "IN", 8 | "type": "TXT" 9 | } 10 | ], 11 | "answers": [ 12 | { 13 | "name": "bad-utf8.txt.example.", 14 | "class": "IN", 15 | "ttl": 600, 16 | "type": "TXT", 17 | "data": { 18 | "messages": [ 19 | "�Р�" 20 | ] 21 | } 22 | } 23 | ], 24 | "authorities": [], 25 | "additionals": [] 26 | } 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /xtests/madns/outputs/bad-utf8.uri.example.ansitxt: -------------------------------------------------------------------------------- 1 | URI bad-utf8.uri.example. 10m00s 10 16 "\208\208\160\255" 2 | -------------------------------------------------------------------------------- /xtests/madns/outputs/bad-utf8.uri.example.json: -------------------------------------------------------------------------------- 1 | { 2 | "responses": [ 3 | { 4 | "queries": [ 5 | { 6 | "name": "bad-utf8.uri.example.", 7 | "class": "IN", 8 | "type": "URI" 9 | } 10 | ], 11 | "answers": [ 12 | { 13 | "name": "bad-utf8.uri.example.", 14 | "class": "IN", 15 | "ttl": 600, 16 | "type": "URI", 17 | "data": { 18 | "priority": 10, 19 | "weight": 16, 20 | "target": "�Р�" 21 | } 22 | } 23 | ], 24 | "authorities": [], 25 | "additionals": [] 26 | } 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /xtests/madns/outputs/caa.example.ansitxt: -------------------------------------------------------------------------------- 1 | CAA caa.example. 10m00s "issuewild" "trustworthy.example" (non-critical) 2 | -------------------------------------------------------------------------------- /xtests/madns/outputs/caa.example.json: -------------------------------------------------------------------------------- 1 | { 2 | "responses": [ 3 | { 4 | "queries": [ 5 | { 6 | "name": "caa.example.", 7 | "class": "IN", 8 | "type": "CAA" 9 | } 10 | ], 11 | "answers": [ 12 | { 13 | "name": "caa.example.", 14 | "class": "IN", 15 | "ttl": 600, 16 | "type": "CAA", 17 | "data": { 18 | "critical": false, 19 | "tag": "issuewild", 20 | "value": "trustworthy.example" 21 | } 22 | } 23 | ], 24 | "authorities": [], 25 | "additionals": [] 26 | } 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /xtests/madns/outputs/cname.example.ansitxt: -------------------------------------------------------------------------------- 1 | CNAME cname.example. 10m00s "dns.lookup.dog." 2 | -------------------------------------------------------------------------------- /xtests/madns/outputs/cname.example.json: -------------------------------------------------------------------------------- 1 | { 2 | "responses": [ 3 | { 4 | "queries": [ 5 | { 6 | "name": "cname.example.", 7 | "class": "IN", 8 | "type": "CNAME" 9 | } 10 | ], 11 | "answers": [ 12 | { 13 | "name": "cname.example.", 14 | "class": "IN", 15 | "ttl": 600, 16 | "type": "CNAME", 17 | "data": { 18 | "domain": "dns.lookup.dog." 19 | } 20 | } 21 | ], 22 | "authorities": [], 23 | "additionals": [] 24 | } 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /xtests/madns/outputs/critical.caa.example.ansitxt: -------------------------------------------------------------------------------- 1 | CAA critical.caa.example. 10m00s "issuewild" "trustworthy.example" (critical) 2 | -------------------------------------------------------------------------------- /xtests/madns/outputs/critical.caa.example.json: -------------------------------------------------------------------------------- 1 | { 2 | "responses": [ 3 | { 4 | "queries": [ 5 | { 6 | "name": "critical.caa.example.", 7 | "class": "IN", 8 | "type": "CAA" 9 | } 10 | ], 11 | "answers": [ 12 | { 13 | "name": "critical.caa.example.", 14 | "class": "IN", 15 | "ttl": 600, 16 | "type": "CAA", 17 | "data": { 18 | "critical": true, 19 | "tag": "issuewild", 20 | "value": "trustworthy.example" 21 | } 22 | } 23 | ], 24 | "authorities": [], 25 | "additionals": [] 26 | } 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /xtests/madns/outputs/do-flag.opt.example.ansitxt: -------------------------------------------------------------------------------- 1 | A do-flag.opt.example. 10m00s 127.0.0.1 2 | OPT  + 1452 0 0 32768 [] 3 | -------------------------------------------------------------------------------- /xtests/madns/outputs/do-flag.opt.example.json: -------------------------------------------------------------------------------- 1 | { 2 | "responses": [ 3 | { 4 | "queries": [ 5 | { 6 | "name": "do-flag.opt.example.", 7 | "class": "IN", 8 | "type": "A" 9 | } 10 | ], 11 | "answers": [ 12 | { 13 | "name": "do-flag.opt.example.", 14 | "class": "IN", 15 | "ttl": 600, 16 | "type": "A", 17 | "data": { 18 | "address": "127.0.0.1" 19 | } 20 | } 21 | ], 22 | "authorities": [], 23 | "additionals": [ 24 | { 25 | "name": "", 26 | "type": "OPT", 27 | "data": { 28 | "version": 0, 29 | "data": [] 30 | } 31 | } 32 | ] 33 | } 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /xtests/madns/outputs/eui48.example.ansitxt: -------------------------------------------------------------------------------- 1 | EUI48 eui48.example. 10m00s "12-34-56-78-90-ab" 2 | -------------------------------------------------------------------------------- /xtests/madns/outputs/eui48.example.json: -------------------------------------------------------------------------------- 1 | { 2 | "responses": [ 3 | { 4 | "queries": [ 5 | { 6 | "name": "eui48.example.", 7 | "class": "IN", 8 | "type": "EUI48" 9 | } 10 | ], 11 | "answers": [ 12 | { 13 | "name": "eui48.example.", 14 | "class": "IN", 15 | "ttl": 600, 16 | "type": "EUI48", 17 | "data": { 18 | "identifier": "12-34-56-78-90-ab" 19 | } 20 | } 21 | ], 22 | "authorities": [], 23 | "additionals": [] 24 | } 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /xtests/madns/outputs/eui64.example.ansitxt: -------------------------------------------------------------------------------- 1 | EUI64 eui64.example. 10m00s "12-34-56-ff-fe-78-90-ab" 2 | -------------------------------------------------------------------------------- /xtests/madns/outputs/eui64.example.json: -------------------------------------------------------------------------------- 1 | { 2 | "responses": [ 3 | { 4 | "queries": [ 5 | { 6 | "name": "eui64.example.", 7 | "class": "IN", 8 | "type": "EUI64" 9 | } 10 | ], 11 | "answers": [ 12 | { 13 | "name": "eui64.example.", 14 | "class": "IN", 15 | "ttl": 600, 16 | "type": "EUI64", 17 | "data": { 18 | "identifier": "12-34-56-ff-fe-78-90-ab" 19 | } 20 | } 21 | ], 22 | "authorities": [], 23 | "additionals": [] 24 | } 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /xtests/madns/outputs/far-negative-latitude.loc.invalid.ansitxt: -------------------------------------------------------------------------------- 1 | LOC far-negative-latitude.loc.invalid. 10m00s 3e2 (0, 0) (Out of range, 0°0′0″ E, 0m) 2 | -------------------------------------------------------------------------------- /xtests/madns/outputs/far-negative-latitude.loc.invalid.json: -------------------------------------------------------------------------------- 1 | { 2 | "responses": [ 3 | { 4 | "queries": [ 5 | { 6 | "name": "far-negative-latitude.loc.invalid.", 7 | "class": "IN", 8 | "type": "LOC" 9 | } 10 | ], 11 | "answers": [ 12 | { 13 | "name": "far-negative-latitude.loc.invalid.", 14 | "class": "IN", 15 | "ttl": 600, 16 | "type": "LOC", 17 | "data": { 18 | "size": "3e2", 19 | "precision": { 20 | "horizontal": 0, 21 | "vertical": 0 22 | }, 23 | "point": { 24 | "latitude": null, 25 | "longitude": "0°0′0″ E", 26 | "altitude": "0m" 27 | } 28 | } 29 | } 30 | ], 31 | "authorities": [], 32 | "additionals": [] 33 | } 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /xtests/madns/outputs/far-negative-longitude.loc.invalid.ansitxt: -------------------------------------------------------------------------------- 1 | LOC far-negative-longitude.loc.invalid. 10m00s 3e2 (0, 0) (0°0′0″ N, Out of range, 0m) 2 | -------------------------------------------------------------------------------- /xtests/madns/outputs/far-negative-longitude.loc.invalid.json: -------------------------------------------------------------------------------- 1 | { 2 | "responses": [ 3 | { 4 | "queries": [ 5 | { 6 | "name": "far-negative-longitude.loc.invalid.", 7 | "class": "IN", 8 | "type": "LOC" 9 | } 10 | ], 11 | "answers": [ 12 | { 13 | "name": "far-negative-longitude.loc.invalid.", 14 | "class": "IN", 15 | "ttl": 600, 16 | "type": "LOC", 17 | "data": { 18 | "size": "3e2", 19 | "precision": { 20 | "horizontal": 0, 21 | "vertical": 0 22 | }, 23 | "point": { 24 | "latitude": "0°0′0″ N", 25 | "longitude": null, 26 | "altitude": "0m" 27 | } 28 | } 29 | } 30 | ], 31 | "authorities": [], 32 | "additionals": [] 33 | } 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /xtests/madns/outputs/far-positive-latitude.loc.invalid.ansitxt: -------------------------------------------------------------------------------- 1 | LOC far-positive-latitude.loc.invalid. 10m00s 3e2 (0, 0) (Out of range, 0°0′0″ E, 0m) 2 | -------------------------------------------------------------------------------- /xtests/madns/outputs/far-positive-latitude.loc.invalid.json: -------------------------------------------------------------------------------- 1 | { 2 | "responses": [ 3 | { 4 | "queries": [ 5 | { 6 | "name": "far-positive-latitude.loc.invalid.", 7 | "class": "IN", 8 | "type": "LOC" 9 | } 10 | ], 11 | "answers": [ 12 | { 13 | "name": "far-positive-latitude.loc.invalid.", 14 | "class": "IN", 15 | "ttl": 600, 16 | "type": "LOC", 17 | "data": { 18 | "size": "3e2", 19 | "precision": { 20 | "horizontal": 0, 21 | "vertical": 0 22 | }, 23 | "point": { 24 | "latitude": null, 25 | "longitude": "0°0′0″ E", 26 | "altitude": "0m" 27 | } 28 | } 29 | } 30 | ], 31 | "authorities": [], 32 | "additionals": [] 33 | } 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /xtests/madns/outputs/far-positive-longitude.loc.invalid.ansitxt: -------------------------------------------------------------------------------- 1 | LOC far-positive-longitude.loc.invalid. 10m00s 3e2 (0, 0) (0°0′0″ N, Out of range, 0m) 2 | -------------------------------------------------------------------------------- /xtests/madns/outputs/far-positive-longitude.loc.invalid.json: -------------------------------------------------------------------------------- 1 | { 2 | "responses": [ 3 | { 4 | "queries": [ 5 | { 6 | "name": "far-positive-longitude.loc.invalid.", 7 | "class": "IN", 8 | "type": "LOC" 9 | } 10 | ], 11 | "answers": [ 12 | { 13 | "name": "far-positive-longitude.loc.invalid.", 14 | "class": "IN", 15 | "ttl": 600, 16 | "type": "LOC", 17 | "data": { 18 | "size": "3e2", 19 | "precision": { 20 | "horizontal": 0, 21 | "vertical": 0 22 | }, 23 | "point": { 24 | "latitude": "0°0′0″ N", 25 | "longitude": null, 26 | "altitude": "0m" 27 | } 28 | } 29 | } 30 | ], 31 | "authorities": [], 32 | "additionals": [] 33 | } 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /xtests/madns/outputs/hinfo.example.ansitxt: -------------------------------------------------------------------------------- 1 | HINFO hinfo.example. 10m00s "some-kinda-cpu" "some-kinda-os" 2 | -------------------------------------------------------------------------------- /xtests/madns/outputs/hinfo.example.json: -------------------------------------------------------------------------------- 1 | { 2 | "responses": [ 3 | { 4 | "queries": [ 5 | { 6 | "name": "hinfo.example.", 7 | "class": "IN", 8 | "type": "HINFO" 9 | } 10 | ], 11 | "answers": [ 12 | { 13 | "name": "hinfo.example.", 14 | "class": "IN", 15 | "ttl": 600, 16 | "type": "HINFO", 17 | "data": { 18 | "cpu": "some-kinda-cpu", 19 | "os": "some-kinda-os" 20 | } 21 | } 22 | ], 23 | "authorities": [], 24 | "additionals": [] 25 | } 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /xtests/madns/outputs/loc.example.ansitxt: -------------------------------------------------------------------------------- 1 | LOC loc.example. 10m00s 3e2 (0, 0) (51°30′12.748″ N, 0°7′39.611″ W, 0m) 2 | -------------------------------------------------------------------------------- /xtests/madns/outputs/loc.example.json: -------------------------------------------------------------------------------- 1 | { 2 | "responses": [ 3 | { 4 | "queries": [ 5 | { 6 | "name": "loc.example.", 7 | "class": "IN", 8 | "type": "LOC" 9 | } 10 | ], 11 | "answers": [ 12 | { 13 | "name": "loc.example.", 14 | "class": "IN", 15 | "ttl": 600, 16 | "type": "LOC", 17 | "data": { 18 | "size": "3e2", 19 | "precision": { 20 | "horizontal": 0, 21 | "vertical": 0 22 | }, 23 | "point": { 24 | "latitude": "51°30′12.748″ N", 25 | "longitude": "0°7′39.611″ W", 26 | "altitude": "0m" 27 | } 28 | } 29 | } 30 | ], 31 | "authorities": [], 32 | "additionals": [] 33 | } 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /xtests/madns/outputs/mx.example.ansitxt: -------------------------------------------------------------------------------- 1 | MX mx.example. 10m00s 10 "exchange.example." 2 | -------------------------------------------------------------------------------- /xtests/madns/outputs/mx.example.json: -------------------------------------------------------------------------------- 1 | { 2 | "responses": [ 3 | { 4 | "queries": [ 5 | { 6 | "name": "mx.example.", 7 | "class": "IN", 8 | "type": "MX" 9 | } 10 | ], 11 | "answers": [ 12 | { 13 | "name": "mx.example.", 14 | "class": "IN", 15 | "ttl": 600, 16 | "type": "MX", 17 | "data": { 18 | "preference": 10, 19 | "exchange": "exchange.example." 20 | } 21 | } 22 | ], 23 | "authorities": [], 24 | "additionals": [] 25 | } 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /xtests/madns/outputs/named.opt.invalid.ansitxt: -------------------------------------------------------------------------------- 1 | A named.opt.invalid. 10m00s 127.0.0.1 2 | OPT bingle.bongle.dingle.dangle. + 1452 0 0 0 [] 3 | -------------------------------------------------------------------------------- /xtests/madns/outputs/named.opt.invalid.json: -------------------------------------------------------------------------------- 1 | { 2 | "responses": [ 3 | { 4 | "queries": [ 5 | { 6 | "name": "named.opt.invalid.", 7 | "class": "IN", 8 | "type": "A" 9 | } 10 | ], 11 | "answers": [ 12 | { 13 | "name": "named.opt.invalid.", 14 | "class": "IN", 15 | "ttl": 600, 16 | "type": "A", 17 | "data": { 18 | "address": "127.0.0.1" 19 | } 20 | } 21 | ], 22 | "authorities": [], 23 | "additionals": [ 24 | { 25 | "name": "bingle.bongle.dingle.dangle.", 26 | "type": "OPT", 27 | "data": { 28 | "version": 0, 29 | "data": [] 30 | } 31 | } 32 | ] 33 | } 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /xtests/madns/outputs/naptr.example.ansitxt: -------------------------------------------------------------------------------- 1 | NAPTR naptr.example. 10m00s 5 10 "s" "SRV" "\\d\\d:\\d\\d:\\d\\d" "srv.example." 2 | -------------------------------------------------------------------------------- /xtests/madns/outputs/naptr.example.json: -------------------------------------------------------------------------------- 1 | { 2 | "responses": [ 3 | { 4 | "queries": [ 5 | { 6 | "name": "naptr.example.", 7 | "class": "IN", 8 | "type": "NAPTR" 9 | } 10 | ], 11 | "answers": [ 12 | { 13 | "name": "naptr.example.", 14 | "class": "IN", 15 | "ttl": 600, 16 | "type": "NAPTR", 17 | "data": { 18 | "order": 5, 19 | "flags": "s", 20 | "service": "SRV", 21 | "regex": "\\d\\d:\\d\\d:\\d\\d", 22 | "replacement": "srv.example." 23 | } 24 | } 25 | ], 26 | "authorities": [], 27 | "additionals": [] 28 | } 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /xtests/madns/outputs/newline.str.example.ansitxt: -------------------------------------------------------------------------------- 1 | CNAME newline.str.example. 10m00s "some\nnew\r\nlines\n.example." 2 | -------------------------------------------------------------------------------- /xtests/madns/outputs/newline.str.example.json: -------------------------------------------------------------------------------- 1 | { 2 | "responses": [ 3 | { 4 | "queries": [ 5 | { 6 | "name": "newline.str.example.", 7 | "class": "IN", 8 | "type": "CNAME" 9 | } 10 | ], 11 | "answers": [ 12 | { 13 | "name": "newline.str.example.", 14 | "class": "IN", 15 | "ttl": 600, 16 | "type": "CNAME", 17 | "data": { 18 | "domain": "some\nnew\r\nlines\n.example." 19 | } 20 | } 21 | ], 22 | "authorities": [], 23 | "additionals": [] 24 | } 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /xtests/madns/outputs/ns.example.ansitxt: -------------------------------------------------------------------------------- 1 | NS ns.example. 10m00s "a.gtld-servers.net." 2 | -------------------------------------------------------------------------------- /xtests/madns/outputs/ns.example.json: -------------------------------------------------------------------------------- 1 | { 2 | "responses": [ 3 | { 4 | "queries": [ 5 | { 6 | "name": "ns.example.", 7 | "class": "IN", 8 | "type": "NS" 9 | } 10 | ], 11 | "answers": [ 12 | { 13 | "name": "ns.example.", 14 | "class": "IN", 15 | "ttl": 600, 16 | "type": "NS", 17 | "data": { 18 | "nameserver": "a.gtld-servers.net." 19 | } 20 | } 21 | ], 22 | "authorities": [], 23 | "additionals": [] 24 | } 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /xtests/madns/outputs/null.str.example.ansitxt: -------------------------------------------------------------------------------- 1 | CNAME null.str.example. 10m00s "some\u{0}null\u{0}\u{0}chars\u{0}.example." 2 | -------------------------------------------------------------------------------- /xtests/madns/outputs/null.str.example.json: -------------------------------------------------------------------------------- 1 | { 2 | "responses": [ 3 | { 4 | "queries": [ 5 | { 6 | "name": "null.str.example.", 7 | "class": "IN", 8 | "type": "CNAME" 9 | } 10 | ], 11 | "answers": [ 12 | { 13 | "name": "null.str.example.", 14 | "class": "IN", 15 | "ttl": 600, 16 | "type": "CNAME", 17 | "data": { 18 | "domain": "some\u0000null\u0000\u0000chars\u0000.example." 19 | } 20 | } 21 | ], 22 | "authorities": [], 23 | "additionals": [] 24 | } 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /xtests/madns/outputs/openpgpkey.example.ansitxt: -------------------------------------------------------------------------------- 1 | OPENPGPKEY openpgpkey.example. 10m00s "EjRWeA==" 2 | -------------------------------------------------------------------------------- /xtests/madns/outputs/openpgpkey.example.json: -------------------------------------------------------------------------------- 1 | { 2 | "responses": [ 3 | { 4 | "queries": [ 5 | { 6 | "name": "openpgpkey.example.", 7 | "class": "IN", 8 | "type": "OPENPGPKEY" 9 | } 10 | ], 11 | "answers": [ 12 | { 13 | "name": "openpgpkey.example.", 14 | "class": "IN", 15 | "ttl": 600, 16 | "type": "OPENPGPKEY", 17 | "data": { 18 | "key": "EjRWeA==" 19 | } 20 | } 21 | ], 22 | "authorities": [], 23 | "additionals": [] 24 | } 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /xtests/madns/outputs/opt.example.ansitxt: -------------------------------------------------------------------------------- 1 | A opt.example. 10m00s 127.0.0.1 2 | OPT  + 1452 0 0 0 [] 3 | -------------------------------------------------------------------------------- /xtests/madns/outputs/opt.example.json: -------------------------------------------------------------------------------- 1 | { 2 | "responses": [ 3 | { 4 | "queries": [ 5 | { 6 | "name": "opt.example.", 7 | "class": "IN", 8 | "type": "A" 9 | } 10 | ], 11 | "answers": [ 12 | { 13 | "name": "opt.example.", 14 | "class": "IN", 15 | "ttl": 600, 16 | "type": "A", 17 | "data": { 18 | "address": "127.0.0.1" 19 | } 20 | } 21 | ], 22 | "authorities": [], 23 | "additionals": [ 24 | { 25 | "name": "", 26 | "type": "OPT", 27 | "data": { 28 | "version": 0, 29 | "data": [] 30 | } 31 | } 32 | ] 33 | } 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /xtests/madns/outputs/other-flags.opt.example.ansitxt: -------------------------------------------------------------------------------- 1 | A other-flags.opt.example. 10m00s 127.0.0.1 2 | OPT  + 1452 0 0 32767 [] 3 | -------------------------------------------------------------------------------- /xtests/madns/outputs/other-flags.opt.example.json: -------------------------------------------------------------------------------- 1 | { 2 | "responses": [ 3 | { 4 | "queries": [ 5 | { 6 | "name": "other-flags.opt.example.", 7 | "class": "IN", 8 | "type": "A" 9 | } 10 | ], 11 | "answers": [ 12 | { 13 | "name": "other-flags.opt.example.", 14 | "class": "IN", 15 | "ttl": 600, 16 | "type": "A", 17 | "data": { 18 | "address": "127.0.0.1" 19 | } 20 | } 21 | ], 22 | "authorities": [], 23 | "additionals": [ 24 | { 25 | "name": "", 26 | "type": "OPT", 27 | "data": { 28 | "version": 0, 29 | "data": [] 30 | } 31 | } 32 | ] 33 | } 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /xtests/madns/outputs/others.caa.example.ansitxt: -------------------------------------------------------------------------------- 1 | CAA caa.example. 10m00s "issuewild" "trustworthy.example" (non-critical) 2 | -------------------------------------------------------------------------------- /xtests/madns/outputs/others.caa.example.json: -------------------------------------------------------------------------------- 1 | { 2 | "responses": [ 3 | { 4 | "queries": [ 5 | { 6 | "name": "caa.example.", 7 | "class": "IN", 8 | "type": "CAA" 9 | } 10 | ], 11 | "answers": [ 12 | { 13 | "name": "caa.example.", 14 | "class": "IN", 15 | "ttl": 600, 16 | "type": "CAA", 17 | "data": { 18 | "critical": false, 19 | "tag": "issuewild", 20 | "value": "trustworthy.example" 21 | } 22 | } 23 | ], 24 | "authorities": [], 25 | "additionals": [] 26 | } 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /xtests/madns/outputs/ptr.example.ansitxt: -------------------------------------------------------------------------------- 1 | PTR ptr.example. 10m00s "dns.example." 2 | -------------------------------------------------------------------------------- /xtests/madns/outputs/ptr.example.json: -------------------------------------------------------------------------------- 1 | { 2 | "responses": [ 3 | { 4 | "queries": [ 5 | { 6 | "name": "ptr.example.", 7 | "class": "IN", 8 | "type": "PTR" 9 | } 10 | ], 11 | "answers": [ 12 | { 13 | "name": "ptr.example.", 14 | "class": "IN", 15 | "ttl": 600, 16 | "type": "PTR", 17 | "data": { 18 | "cname": "dns.example." 19 | } 20 | } 21 | ], 22 | "authorities": [], 23 | "additionals": [] 24 | } 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /xtests/madns/outputs/slash.uri.example.ansitxt: -------------------------------------------------------------------------------- 1 | URI slash.uri.example. 10m00s 10 1 "/" 2 | -------------------------------------------------------------------------------- /xtests/madns/outputs/soa.example.ansitxt: -------------------------------------------------------------------------------- 1 | SOA soa.example. 10m00s "mname.example." "rname.example." 1564274434 1d0h00m00s 2h00m00s 7d0h00m00s 5m00s 2 | -------------------------------------------------------------------------------- /xtests/madns/outputs/soa.example.json: -------------------------------------------------------------------------------- 1 | { 2 | "responses": [ 3 | { 4 | "queries": [ 5 | { 6 | "name": "soa.example.", 7 | "class": "IN", 8 | "type": "SOA" 9 | } 10 | ], 11 | "answers": [ 12 | { 13 | "name": "soa.example.", 14 | "class": "IN", 15 | "ttl": 600, 16 | "type": "SOA", 17 | "data": { 18 | "mname": "mname.example." 19 | } 20 | } 21 | ], 22 | "authorities": [], 23 | "additionals": [] 24 | } 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /xtests/madns/outputs/srv.example.ansitxt: -------------------------------------------------------------------------------- 1 | SRV srv.example. 10m00s 1 1 "service.example.":37500 2 | -------------------------------------------------------------------------------- /xtests/madns/outputs/srv.example.json: -------------------------------------------------------------------------------- 1 | { 2 | "responses": [ 3 | { 4 | "queries": [ 5 | { 6 | "name": "srv.example.", 7 | "class": "IN", 8 | "type": "SRV" 9 | } 10 | ], 11 | "answers": [ 12 | { 13 | "name": "srv.example.", 14 | "class": "IN", 15 | "ttl": 600, 16 | "type": "SRV", 17 | "data": { 18 | "priority": 1, 19 | "weight": 1, 20 | "port": 37500, 21 | "target": "service.example." 22 | } 23 | } 24 | ], 25 | "authorities": [], 26 | "additionals": [] 27 | } 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /xtests/madns/outputs/sshfp.example.ansitxt: -------------------------------------------------------------------------------- 1 | SSHFP sshfp.example. 10m00s 1 1 212223242526 2 | -------------------------------------------------------------------------------- /xtests/madns/outputs/sshfp.example.json: -------------------------------------------------------------------------------- 1 | { 2 | "responses": [ 3 | { 4 | "queries": [ 5 | { 6 | "name": "sshfp.example.", 7 | "class": "IN", 8 | "type": "SSHFP" 9 | } 10 | ], 11 | "answers": [ 12 | { 13 | "name": "sshfp.example.", 14 | "class": "IN", 15 | "ttl": 600, 16 | "type": "SSHFP", 17 | "data": { 18 | "algorithm": 1, 19 | "fingerprint_type": 1, 20 | "fingerprint": "212223242526" 21 | } 22 | } 23 | ], 24 | "authorities": [], 25 | "additionals": [] 26 | } 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /xtests/madns/outputs/tab.str.example.ansitxt: -------------------------------------------------------------------------------- 1 | CNAME tab.str.example. 10m00s "some\ttab\t\tchars\t.example." 2 | -------------------------------------------------------------------------------- /xtests/madns/outputs/tab.str.example.json: -------------------------------------------------------------------------------- 1 | { 2 | "responses": [ 3 | { 4 | "queries": [ 5 | { 6 | "name": "tab.str.example.", 7 | "class": "IN", 8 | "type": "CNAME" 9 | } 10 | ], 11 | "answers": [ 12 | { 13 | "name": "tab.str.example.", 14 | "class": "IN", 15 | "ttl": 600, 16 | "type": "CNAME", 17 | "data": { 18 | "domain": "some\ttab\t\tchars\t.example." 19 | } 20 | } 21 | ], 22 | "authorities": [], 23 | "additionals": [] 24 | } 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /xtests/madns/outputs/tlsa.example.ansitxt: -------------------------------------------------------------------------------- 1 | TLSA tlsa.example. 10m00s 3 1 1 "112233445566" 2 | -------------------------------------------------------------------------------- /xtests/madns/outputs/tlsa.example.json: -------------------------------------------------------------------------------- 1 | { 2 | "responses": [ 3 | { 4 | "queries": [ 5 | { 6 | "name": "tlsa.example.", 7 | "class": "IN", 8 | "type": "TLSA" 9 | } 10 | ], 11 | "answers": [ 12 | { 13 | "name": "tlsa.example.", 14 | "class": "IN", 15 | "ttl": 600, 16 | "type": "TLSA", 17 | "data": { 18 | "certificate_usage": 3, 19 | "selector": 1, 20 | "matching_type": 1, 21 | "certificate_data": "112233445566" 22 | } 23 | } 24 | ], 25 | "authorities": [], 26 | "additionals": [] 27 | } 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /xtests/madns/outputs/txt.example.ansitxt: -------------------------------------------------------------------------------- 1 | TXT txt.example. 10m00s "Cache Invalidation and Naming Things" 2 | -------------------------------------------------------------------------------- /xtests/madns/outputs/txt.example.json: -------------------------------------------------------------------------------- 1 | { 2 | "responses": [ 3 | { 4 | "queries": [ 5 | { 6 | "name": "txt.example.", 7 | "class": "IN", 8 | "type": "TXT" 9 | } 10 | ], 11 | "answers": [ 12 | { 13 | "name": "txt.example.", 14 | "class": "IN", 15 | "ttl": 600, 16 | "type": "TXT", 17 | "data": { 18 | "messages": [ 19 | "Cache Invalidation and Naming Things" 20 | ] 21 | } 22 | } 23 | ], 24 | "authorities": [], 25 | "additionals": [] 26 | } 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /xtests/madns/outputs/upperbit.str.example.ansitxt: -------------------------------------------------------------------------------- 1 | CNAME upperbit.str.example. 10m00s "\u{7f}�����.example." 2 | -------------------------------------------------------------------------------- /xtests/madns/outputs/upperbit.str.example.json: -------------------------------------------------------------------------------- 1 | { 2 | "responses": [ 3 | { 4 | "queries": [ 5 | { 6 | "name": "upperbit.str.example.", 7 | "class": "IN", 8 | "type": "CNAME" 9 | } 10 | ], 11 | "answers": [ 12 | { 13 | "name": "upperbit.str.example.", 14 | "class": "IN", 15 | "ttl": 600, 16 | "type": "CNAME", 17 | "data": { 18 | "domain": "\u007f�����.example." 19 | } 20 | } 21 | ], 22 | "authorities": [], 23 | "additionals": [] 24 | } 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /xtests/madns/outputs/uri.example.ansitxt: -------------------------------------------------------------------------------- 1 | URI uri.example. 10m00s 10 16 "https://rfcs.io/" 2 | -------------------------------------------------------------------------------- /xtests/madns/outputs/uri.example.json: -------------------------------------------------------------------------------- 1 | { 2 | "responses": [ 3 | { 4 | "queries": [ 5 | { 6 | "name": "uri.example.", 7 | "class": "IN", 8 | "type": "URI" 9 | } 10 | ], 11 | "answers": [ 12 | { 13 | "name": "uri.example.", 14 | "class": "IN", 15 | "ttl": 600, 16 | "type": "URI", 17 | "data": { 18 | "priority": 10, 19 | "weight": 16, 20 | "target": "https://rfcs.io/" 21 | } 22 | } 23 | ], 24 | "authorities": [], 25 | "additionals": [] 26 | } 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /xtests/madns/outputs/utf8.caa.example.ansitxt: -------------------------------------------------------------------------------- 1 | CAA utf8.caa.example. 10m00s "issuewild" "trustworthy\240\159\140\180example" (non-critical) 2 | -------------------------------------------------------------------------------- /xtests/madns/outputs/utf8.caa.example.json: -------------------------------------------------------------------------------- 1 | { 2 | "responses": [ 3 | { 4 | "queries": [ 5 | { 6 | "name": "utf8.caa.example.", 7 | "class": "IN", 8 | "type": "CAA" 9 | } 10 | ], 11 | "answers": [ 12 | { 13 | "name": "utf8.caa.example.", 14 | "class": "IN", 15 | "ttl": 600, 16 | "type": "CAA", 17 | "data": { 18 | "critical": false, 19 | "tag": "issuewild", 20 | "value": "trustworthy🌴example" 21 | } 22 | } 23 | ], 24 | "authorities": [], 25 | "additionals": [] 26 | } 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /xtests/madns/outputs/utf8.hinfo.example.ansitxt: -------------------------------------------------------------------------------- 1 | HINFO utf8.hinfo.example. 10m00s "some\240\159\140\180kinda\240\159\140\180cpu" "some\240\159\140\180kinda\240\159\140\180os" 2 | -------------------------------------------------------------------------------- /xtests/madns/outputs/utf8.hinfo.example.json: -------------------------------------------------------------------------------- 1 | { 2 | "responses": [ 3 | { 4 | "queries": [ 5 | { 6 | "name": "utf8.hinfo.example.", 7 | "class": "IN", 8 | "type": "HINFO" 9 | } 10 | ], 11 | "answers": [ 12 | { 13 | "name": "utf8.hinfo.example.", 14 | "class": "IN", 15 | "ttl": 600, 16 | "type": "HINFO", 17 | "data": { 18 | "cpu": "some🌴kinda🌴cpu", 19 | "os": "some🌴kinda🌴os" 20 | } 21 | } 22 | ], 23 | "authorities": [], 24 | "additionals": [] 25 | } 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /xtests/madns/outputs/utf8.naptr.invalid.ansitxt: -------------------------------------------------------------------------------- 1 | NAPTR utf8.naptr.invalid. 10m00s 5 10 "\240\159\140\180" "\240\159\140\180" "\240\159\140\180" "🌴." 2 | -------------------------------------------------------------------------------- /xtests/madns/outputs/utf8.naptr.invalid.json: -------------------------------------------------------------------------------- 1 | { 2 | "responses": [ 3 | { 4 | "queries": [ 5 | { 6 | "name": "utf8.naptr.invalid.", 7 | "class": "IN", 8 | "type": "NAPTR" 9 | } 10 | ], 11 | "answers": [ 12 | { 13 | "name": "utf8.naptr.invalid.", 14 | "class": "IN", 15 | "ttl": 600, 16 | "type": "NAPTR", 17 | "data": { 18 | "order": 5, 19 | "flags": "🌴", 20 | "service": "🌴", 21 | "regex": "🌴", 22 | "replacement": "🌴." 23 | } 24 | } 25 | ], 26 | "authorities": [], 27 | "additionals": [] 28 | } 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /xtests/madns/outputs/utf8.txt.example.ansitxt: -------------------------------------------------------------------------------- 1 | TXT utf8.txt.example. 10m00s "\240\159\146\176Cache \240\159\153\133\226\128\141\239\184\143Invalidation \226\133\139and \240\159\147\155Naming \240\159\142\179Things" 2 | -------------------------------------------------------------------------------- /xtests/madns/outputs/utf8.txt.example.json: -------------------------------------------------------------------------------- 1 | { 2 | "responses": [ 3 | { 4 | "queries": [ 5 | { 6 | "name": "utf8.txt.example.", 7 | "class": "IN", 8 | "type": "TXT" 9 | } 10 | ], 11 | "answers": [ 12 | { 13 | "name": "utf8.txt.example.", 14 | "class": "IN", 15 | "ttl": 600, 16 | "type": "TXT", 17 | "data": { 18 | "messages": [ 19 | "💰Cache 🙅‍️Invalidation ⅋and 📛Naming 🎳Things" 20 | ] 21 | } 22 | } 23 | ], 24 | "authorities": [], 25 | "additionals": [] 26 | } 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /xtests/madns/outputs/utf8.uri.example.ansitxt: -------------------------------------------------------------------------------- 1 | URI utf8.uri.example. 10m00s 10 16 "https://\240\159\146\169.la/" 2 | -------------------------------------------------------------------------------- /xtests/madns/outputs/utf8.uri.example.json: -------------------------------------------------------------------------------- 1 | { 2 | "responses": [ 3 | { 4 | "queries": [ 5 | { 6 | "name": "utf8.uri.example.", 7 | "class": "IN", 8 | "type": "URI" 9 | } 10 | ], 11 | "answers": [ 12 | { 13 | "name": "utf8.uri.example.", 14 | "class": "IN", 15 | "ttl": 600, 16 | "type": "URI", 17 | "data": { 18 | "priority": 10, 19 | "weight": 16, 20 | "target": "https://💩.la/" 21 | } 22 | } 23 | ], 24 | "authorities": [], 25 | "additionals": [] 26 | } 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /xtests/madns/protocol-chars.toml: -------------------------------------------------------------------------------- 1 | # Character escaping 2 | 3 | [[cmd]] 4 | name = "Running with ‘ansi.str.example’ properly escapes the codes" 5 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} CNAME ansi.str.example" 6 | stdout = { file = "outputs/ansi.str.example.ansitxt" } 7 | stderr = { empty = true } 8 | status = 0 9 | tags = [ "protocol", "madns" ] 10 | 11 | [[cmd]] 12 | name = "Running with ‘newline.str.example’ properly escapes the newlines" 13 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} CNAME newline.str.example" 14 | stdout = { file = "outputs/newline.str.example.ansitxt" } 15 | stderr = { empty = true } 16 | status = 0 17 | tags = [ "protocol", "madns" ] 18 | 19 | [[cmd]] 20 | name = "Running with ‘null.str.example’ properly handles the null bytes" 21 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} CNAME null.str.example" 22 | stdout = { file = "outputs/null.str.example.ansitxt" } 23 | stderr = { empty = true } 24 | status = 0 25 | tags = [ "protocol", "madns" ] 26 | 27 | [[cmd]] 28 | name = "Running with ‘tab.str.example’ properly escapes the tabs" 29 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} CNAME tab.str.example" 30 | stdout = { file = "outputs/tab.str.example.ansitxt" } 31 | stderr = { empty = true } 32 | status = 0 33 | tags = [ "protocol", "madns" ] 34 | 35 | [[cmd]] 36 | name = "Running with ‘upperbit.str.example’ properly escapes the upper-bit characters" 37 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} CNAME upperbit.str.example" 38 | stdout = { file = "outputs/upperbit.str.example.ansitxt" } 39 | stderr = { empty = true } 40 | status = 0 41 | tags = [ "protocol", "madns" ] 42 | 43 | 44 | # Character escaping (JSON) 45 | 46 | [[cmd]] 47 | name = "Running with ‘ansi.str.example --json’ properly escapes the codes in the JSON string" 48 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} CNAME ansi.str.example --json | jq" 49 | stdout = { file = "outputs/ansi.str.example.json" } 50 | stderr = { empty = true } 51 | status = 0 52 | tags = [ "protocol", "madns", "json" ] 53 | 54 | [[cmd]] 55 | name = "Running with ‘newline.str.example --json’ properly escapes the newlines in the JSON string" 56 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} CNAME newline.str.example --json | jq" 57 | stdout = { file = "outputs/newline.str.example.json" } 58 | stderr = { empty = true } 59 | status = 0 60 | tags = [ "protocol", "madns", "json" ] 61 | 62 | [[cmd]] 63 | name = "Running with ‘null.str.example --json’ properly handles the null bytes in the JSON string" 64 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} CNAME null.str.example --json | jq" 65 | stdout = { file = "outputs/null.str.example.json" } 66 | stderr = { empty = true } 67 | status = 0 68 | tags = [ "protocol", "madns", "json" ] 69 | 70 | [[cmd]] 71 | name = "Running with ‘tab.str.example --json’ properly escapes the tabs in the JSON string" 72 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} CNAME tab.str.example --json | jq" 73 | stdout = { file = "outputs/tab.str.example.json" } 74 | stderr = { empty = true } 75 | status = 0 76 | tags = [ "protocol", "madns", "json" ] 77 | 78 | [[cmd]] 79 | name = "Running with ‘upperbit.str.example --json’ properly escapes the upper-bit characters in the JSON string" 80 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} CNAME upperbit.str.example --json | jq" 81 | stdout = { file = "outputs/upperbit.str.example.json" } 82 | stderr = { empty = true } 83 | status = 0 84 | tags = [ "protocol", "madns", "json" ] 85 | -------------------------------------------------------------------------------- /xtests/madns/protocol-compression.toml: -------------------------------------------------------------------------------- 1 | [[cmd]] 2 | name = "Running with ‘out-of-range.invalid’ displays a protocol error" 3 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} A out-of-range.invalid" 4 | stdout = { empty = true } 5 | stderr = { string = "Error [protocol]: Malformed packet: insufficient data" } 6 | status = 1 7 | tags = [ "protocol", "madns" ] 8 | 9 | [[cmd]] 10 | name = "Running with ‘recursive-1.invalid’ displays a recursion error" 11 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} A recursive-1.invalid" 12 | stdout = { empty = true } 13 | stderr = { string = "Error [protocol]: Malformed packet: too much recursion: [37]" } 14 | status = 1 15 | tags = [ "protocol", "madns" ] 16 | 17 | [[cmd]] 18 | name = "Running with ‘recursive-2.invalid’ displays a recursion error" 19 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} A recursive-2.invalid" 20 | stdout = { empty = true } 21 | stderr = { string = "Error [protocol]: Malformed packet: too much recursion: [53, 37]" } 22 | status = 1 23 | tags = [ "protocol", "madns" ] 24 | -------------------------------------------------------------------------------- /xtests/madns/protocol-error-codes.toml: -------------------------------------------------------------------------------- 1 | [[cmd]] 2 | name = "Running with ‘formerr.invalid’ displays the error code" 3 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} A formerr.invalid" 4 | stdout = { string = "Status: Format Error" } 5 | stderr = { empty = true } 6 | status = 0 7 | tags = [ "protocol", "madns" ] 8 | 9 | [[cmd]] 10 | name = "Running with ‘servfail.invalid’ displays the error code" 11 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} A servfail.invalid" 12 | stdout = { string = "Status: Server Failure" } 13 | stderr = { empty = true } 14 | status = 0 15 | tags = [ "protocol", "madns" ] 16 | 17 | [[cmd]] 18 | name = "Running with ‘nxdomain.invalid’ displays the error code" 19 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} A nxdomain.invalid" 20 | stdout = { string = "Status: NXDomain" } 21 | stderr = { empty = true } 22 | status = 0 23 | tags = [ "protocol", "madns" ] 24 | 25 | [[cmd]] 26 | name = "Running with ‘notimp.invalid’ displays the error code" 27 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} A notimp.invalid" 28 | stdout = { string = "Status: Not Implemented" } 29 | stderr = { empty = true } 30 | status = 0 31 | tags = [ "protocol", "madns" ] 32 | 33 | [[cmd]] 34 | name = "Running with ‘refused.invalid’ displays the error code" 35 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} A refused.invalid" 36 | stdout = { string = "Status: Query Refused" } 37 | stderr = { empty = true } 38 | status = 0 39 | tags = [ "protocol", "madns" ] 40 | -------------------------------------------------------------------------------- /xtests/madns/ptr-records.toml: -------------------------------------------------------------------------------- 1 | # PTR record successes 2 | 3 | [[cmd]] 4 | name = "Running with ‘ptr.example’ prints the correct PTR record" 5 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} PTR ptr.example" 6 | stdout = { file = "outputs/ptr.example.ansitxt" } 7 | stderr = { empty = true } 8 | status = 0 9 | tags = [ "ptr", "madns" ] 10 | 11 | 12 | # PTR record successes (JSON) 13 | 14 | [[cmd]] 15 | name = "Running with ‘ptr.example --json’ prints the correct PTR record structure" 16 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} PTR ptr.example --json | jq" 17 | stdout = { file = "outputs/ptr.example.json" } 18 | stderr = { empty = true } 19 | status = 0 20 | tags = [ "ptr", "madns", "json" ] 21 | 22 | 23 | # PTR record invalid packets 24 | 25 | [[cmd]] 26 | name = "Running with ‘empty.ptr.invalid’ displays a protocol error" 27 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} PTR empty.ptr.invalid" 28 | stdout = { empty = true } 29 | stderr = { string = "Error [protocol]: Malformed packet: insufficient data" } 30 | status = 1 31 | tags = [ "ptr", "madns" ] 32 | 33 | [[cmd]] 34 | name = "Running with ‘incomplete.ptr.invalid’ displays a protocol error" 35 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} PTR incomplete.ptr.invalid" 36 | stdout = { empty = true } 37 | stderr = { string = "Error [protocol]: Malformed packet: insufficient data" } 38 | status = 1 39 | tags = [ "ptr", "madns" ] 40 | -------------------------------------------------------------------------------- /xtests/madns/soa-records.toml: -------------------------------------------------------------------------------- 1 | # SOA record successes 2 | 3 | [[cmd]] 4 | name = "Running with ‘soa.example’ prints the correct SOA record" 5 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} SOA soa.example" 6 | stdout = { file = "outputs/soa.example.ansitxt" } 7 | stderr = { empty = true } 8 | status = 0 9 | tags = [ "soa", "madns" ] 10 | 11 | 12 | # SOA record successes (JSON) 13 | 14 | [[cmd]] 15 | name = "Running with ‘soa.example --json’ prints the correct SOA record structure" 16 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} SOA soa.example --json | jq" 17 | stdout = { file = "outputs/soa.example.json" } 18 | stderr = { empty = true } 19 | status = 0 20 | tags = [ "soa", "madns", "json" ] 21 | 22 | 23 | # SOA record invalid packets 24 | 25 | [[cmd]] 26 | name = "Running with ‘empty.soa.invalid’ displays a protocol error" 27 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} SOA empty.soa.invalid" 28 | stdout = { empty = true } 29 | stderr = { string = "Error [protocol]: Malformed packet: insufficient data" } 30 | status = 1 31 | tags = [ "soa", "madns" ] 32 | 33 | [[cmd]] 34 | name = "Running with ‘incomplete.soa.invalid’ displays a protocol error" 35 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} SOA incomplete.soa.invalid" 36 | stdout = { empty = true } 37 | stderr = { string = "Error [protocol]: Malformed packet: insufficient data" } 38 | status = 1 39 | tags = [ "soa", "madns" ] 40 | -------------------------------------------------------------------------------- /xtests/madns/srv-records.toml: -------------------------------------------------------------------------------- 1 | # SRV record successes 2 | 3 | [[cmd]] 4 | name = "Running with ‘srv.example’ prints the correct SRV record" 5 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} SRV srv.example" 6 | stdout = { file = "outputs/srv.example.ansitxt" } 7 | stderr = { empty = true } 8 | status = 0 9 | tags = [ "soa", "madns" ] 10 | 11 | 12 | # SRV record successes (JSON) 13 | 14 | [[cmd]] 15 | name = "Running with ‘srv.example --json’ prints the correct SRV record structure" 16 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} SRV srv.example --json | jq" 17 | stdout = { file = "outputs/srv.example.json" } 18 | stderr = { empty = true } 19 | status = 0 20 | tags = [ "soa", "madns", "json" ] 21 | 22 | 23 | # SRV record invalid packets 24 | 25 | [[cmd]] 26 | name = "Running with ‘empty.srv.invalid’ displays a protocol error" 27 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} SRV empty.srv.invalid" 28 | stdout = { empty = true } 29 | stderr = { string = "Error [protocol]: Malformed packet: insufficient data" } 30 | status = 1 31 | tags = [ "soa", "madns" ] 32 | 33 | [[cmd]] 34 | name = "Running with ‘incomplete.srv.invalid’ displays a protocol error" 35 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} SRV incomplete.srv.invalid" 36 | stdout = { empty = true } 37 | stderr = { string = "Error [protocol]: Malformed packet: insufficient data" } 38 | status = 1 39 | tags = [ "soa", "madns" ] 40 | -------------------------------------------------------------------------------- /xtests/madns/sshfp-records.toml: -------------------------------------------------------------------------------- 1 | # SSHFP record successes 2 | 3 | [[cmd]] 4 | name = "Running with ‘sshfp.example’ prints the correct SSHFP record" 5 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} SSHFP sshfp.example" 6 | stdout = { file = "outputs/sshfp.example.ansitxt" } 7 | stderr = { empty = true } 8 | status = 0 9 | tags = [ "sshfp", "madns" ] 10 | 11 | 12 | # SSHFP record successes (JSON) 13 | 14 | [[cmd]] 15 | name = "Running with ‘sshfp.example --json’ prints the correct SSHFP record structure" 16 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} SSHFP sshfp.example --json | jq" 17 | stdout = { file = "outputs/sshfp.example.json" } 18 | stderr = { empty = true } 19 | status = 0 20 | tags = [ "sshfp", "madns", "json" ] 21 | 22 | 23 | # SSHFP record invalid packets 24 | 25 | [[cmd]] 26 | name = "Running with ‘empty.sshfp.invalid’ displays a protocol error" 27 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} SSHFP empty.sshfp.invalid" 28 | stdout = { empty = true } 29 | stderr = { string = "Error [protocol]: Malformed packet: insufficient data" } 30 | status = 1 31 | tags = [ "sshfp", "madns" ] 32 | 33 | [[cmd]] 34 | name = "Running with ‘incomplete.sshfp.invalid’ displays a protocol error" 35 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} SSHFP incomplete.sshfp.invalid" 36 | stdout = { empty = true } 37 | stderr = { string = "Error [protocol]: Malformed packet: insufficient data" } 38 | status = 1 39 | tags = [ "sshfp", "madns" ] 40 | -------------------------------------------------------------------------------- /xtests/madns/tlsa-records.toml: -------------------------------------------------------------------------------- 1 | # TLSA record successes 2 | 3 | [[cmd]] 4 | name = "Running with ‘tlsa.example’ prints the correct TLSA record" 5 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} TLSA tlsa.example" 6 | stdout = { file = "outputs/tlsa.example.ansitxt" } 7 | stderr = { empty = true } 8 | status = 0 9 | tags = [ "tlsa", "madns" ] 10 | 11 | 12 | # TLSA record successes (JSON) 13 | 14 | [[cmd]] 15 | name = "Running with ‘tlsa.example --json’ prints the correct TLSA record structure" 16 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} TLSA tlsa.example --json | jq" 17 | stdout = { file = "outputs/tlsa.example.json" } 18 | stderr = { empty = true } 19 | status = 0 20 | tags = [ "tlsa", "madns", "json" ] 21 | 22 | 23 | # TLSA record invalid packets 24 | 25 | [[cmd]] 26 | name = "Running with ‘empty.tlsa.invalid’ displays a protocol error" 27 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} TLSA empty.tlsa.invalid" 28 | stdout = { empty = true } 29 | stderr = { string = "Error [protocol]: Malformed packet: insufficient data" } 30 | status = 1 31 | tags = [ "tlsa", "madns" ] 32 | 33 | [[cmd]] 34 | name = "Running with ‘incomplete.tlsa.invalid’ displays a protocol error" 35 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} TLSA incomplete.tlsa.invalid" 36 | stdout = { empty = true } 37 | stderr = { string = "Error [protocol]: Malformed packet: insufficient data" } 38 | status = 1 39 | tags = [ "tlsa", "madns" ] 40 | -------------------------------------------------------------------------------- /xtests/madns/txt-records.toml: -------------------------------------------------------------------------------- 1 | # TXT record successes 2 | 3 | [[cmd]] 4 | name = "Running with ‘txt.example’ prints the correct TXT record" 5 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} TXT txt.example" 6 | stdout = { file = "outputs/txt.example.ansitxt" } 7 | stderr = { empty = true } 8 | status = 0 9 | tags = [ "txt", "madns" ] 10 | 11 | [[cmd]] 12 | name = "Running with ‘utf8.txt.example’ escapes characters in the message" 13 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} TXT utf8.txt.example" 14 | stdout = { file = "outputs/utf8.txt.example.ansitxt" } 15 | stderr = { empty = true } 16 | status = 0 17 | tags = [ "txt", "madns", "chars" ] 18 | 19 | [[cmd]] 20 | name = "Running with ‘bad-utf8.txt.example’ escapes characters in the message and does not crash" 21 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} TXT bad-utf8.txt.example" 22 | stdout = { file = "outputs/bad-utf8.txt.example.ansitxt" } 23 | stderr = { empty = true } 24 | status = 0 25 | tags = [ "txt", "madns", "chars" ] 26 | 27 | 28 | # TXT record successes (JSON) 29 | 30 | [[cmd]] 31 | name = "Running with ‘txt.example --json’ prints the correct TXT record structure" 32 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} TXT txt.example --json | jq" 33 | stdout = { file = "outputs/txt.example.json" } 34 | stderr = { empty = true } 35 | status = 0 36 | tags = [ "txt", "madns", "json" ] 37 | 38 | 39 | [[cmd]] 40 | name = "Running with ‘utf8.txt.example --json’ interprets the response as UTF-8" 41 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} TXT utf8.txt.example --json | jq" 42 | stdout = { file = "outputs/utf8.txt.example.json" } 43 | stderr = { empty = true } 44 | status = 0 45 | tags = [ "txt", "madns", "chars", "json" ] 46 | 47 | [[cmd]] 48 | name = "Running with ‘bad-utf8.txt.example --json’ uses UTF-8 replacement characters" 49 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} TXT bad-utf8.txt.example --json | jq" 50 | stdout = { file = "outputs/bad-utf8.txt.example.json" } 51 | stderr = { empty = true } 52 | status = 0 53 | tags = [ "txt", "madns", "chars", "json" ] 54 | 55 | 56 | # TXT record invalid packets 57 | 58 | [[cmd]] 59 | name = "Running with ‘empty.txt.invalid’ displays a protocol error" 60 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} TXT empty.txt.invalid" 61 | stdout = { empty = true } 62 | stderr = { string = "Error [protocol]: Malformed packet: insufficient data" } 63 | status = 1 64 | tags = [ "txt", "madns" ] 65 | 66 | [[cmd]] 67 | name = "Running with ‘incomplete.txt.invalid’ displays a protocol error" 68 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} TXT incomplete.txt.invalid" 69 | stdout = { empty = true } 70 | stderr = { string = "Error [protocol]: Malformed packet: insufficient data" } 71 | status = 1 72 | tags = [ "txt", "madns" ] 73 | -------------------------------------------------------------------------------- /xtests/madns/uri-records.toml: -------------------------------------------------------------------------------- 1 | # URI record successes 2 | 3 | [[cmd]] 4 | name = "Running with ‘uri.example’ prints the correct URI record" 5 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} URI uri.example" 6 | stdout = { file = "outputs/uri.example.ansitxt" } 7 | stderr = { empty = true } 8 | status = 0 9 | tags = [ "uri", "madns" ] 10 | 11 | [[cmd]] 12 | name = "Running with ‘slash.uri.example’ still prints the correct URI record" 13 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} URI slash.uri.example" 14 | stdout = { file = "outputs/slash.uri.example.ansitxt" } 15 | stderr = { empty = true } 16 | status = 0 17 | tags = [ "uri", "madns" ] 18 | 19 | [[cmd]] 20 | name = "Running with ‘utf8.uri.example’ escapes characters in the URI" 21 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} URI utf8.uri.example" 22 | stdout = { file = "outputs/utf8.uri.example.ansitxt" } 23 | stderr = { empty = true } 24 | status = 0 25 | tags = [ "uri", "madns", "chars" ] 26 | 27 | [[cmd]] 28 | name = "Running with ‘bad-utf8.uri.example’ escapes characters in the URI and does not crash" 29 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} URI bad-utf8.uri.example" 30 | stdout = { file = "outputs/bad-utf8.uri.example.ansitxt" } 31 | stderr = { empty = true } 32 | status = 0 33 | tags = [ "uri", "madns", "chars" ] 34 | 35 | 36 | # URI record successes (JSON) 37 | 38 | [[cmd]] 39 | name = "Running with ‘uri.example --json’ prints the correct URI record structure" 40 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} URI uri.example --json | jq" 41 | stdout = { file = "outputs/uri.example.json" } 42 | stderr = { empty = true } 43 | status = 0 44 | tags = [ "uri", "madns", "json" ] 45 | 46 | [[cmd]] 47 | name = "Running with ‘utf8.uri.example --json’ interprets the response as UTF-8" 48 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} URI utf8.uri.example --json | jq" 49 | stdout = { file = "outputs/utf8.uri.example.json" } 50 | stderr = { empty = true } 51 | status = 0 52 | tags = [ "uri", "madns", "chars", "json" ] 53 | 54 | [[cmd]] 55 | name = "Running with ‘bad-utf8.uri.example --json’ uses UTF-8 replacement characters" 56 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} URI bad-utf8.uri.example --json | jq" 57 | stdout = { file = "outputs/bad-utf8.uri.example.json" } 58 | stderr = { empty = true } 59 | status = 0 60 | tags = [ "uri", "madns", "chars", "json" ] 61 | 62 | 63 | # URI record invalid packets 64 | 65 | [[cmd]] 66 | name = "Running with ‘missing-data.uri.invalid’ displays a packet length error" 67 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} URI missing-data.uri.invalid" 68 | stdout = { empty = true } 69 | stderr = { string = "Error [protocol]: Malformed packet: record length should be at least 5, got 4" } 70 | status = 1 71 | tags = [ "uri", "madns" ] 72 | 73 | [[cmd]] 74 | name = "Running with ‘empty.uri.invalid’ displays a protocol error" 75 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} URI empty.uri.invalid" 76 | stdout = { empty = true } 77 | stderr = { string = "Error [protocol]: Malformed packet: insufficient data" } 78 | status = 1 79 | tags = [ "uri", "madns" ] 80 | 81 | [[cmd]] 82 | name = "Running with ‘incomplete.uri.invalid’ displays a protocol error" 83 | shell = "dog --colour=always ${MADNS_ARGS:-@madns.binarystar.systems:5301 --tcp} URI incomplete.uri.invalid" 84 | stdout = { empty = true } 85 | stderr = { string = "Error [protocol]: Malformed packet: insufficient data" } 86 | status = 1 87 | tags = [ "uri", "madns" ] 88 | -------------------------------------------------------------------------------- /xtests/options/errors.toml: -------------------------------------------------------------------------------- 1 | [[cmd]] 2 | name = "Running dog with ‘--wibble’ warns about the invalid argument" 3 | shell = "dog --wibble" 4 | stdout = { empty = true } 5 | stderr = { file = "outputs/invalid-argument.txt" } 6 | status = 3 7 | tags = [ 'options' ] 8 | 9 | [[cmd]] 10 | name = "Running dog with ‘--class’ warns about the missing argument parameter" 11 | shell = "dog --class" 12 | stdout = { empty = true } 13 | stderr = { file = "outputs/missing-parameter.txt" } 14 | status = 3 15 | tags = [ 'options' ] 16 | 17 | [[cmd]] 18 | name = "Running dog with ‘--type XYZZY’ warns about the invalid record type" 19 | shell = "dog --type XYZZY dns.google" 20 | stdout = { empty = true } 21 | stderr = { file = "outputs/invalid-query-type.txt" } 22 | status = 3 23 | tags = [ 'options' ] 24 | 25 | [[cmd]] 26 | name = "Running dog with ‘--class XYZZY’ warns about the invalid class" 27 | shell = "dog --class XYZZY dns.google" 28 | stdout = { empty = true } 29 | stderr = { file = "outputs/invalid-query-class.txt" } 30 | status = 3 31 | tags = [ 'options' ] 32 | 33 | [[cmd]] 34 | name = "Running dog with ‘-Z aoeu’ warns about the invalid protocol tweak" 35 | shell = "dog -Z aoeu dns.google" 36 | stdout = { empty = true } 37 | stderr = { file = "outputs/invalid-protocol-tweak.txt" } 38 | status = 3 39 | tags = [ 'options' ] 40 | 41 | [[cmd]] 42 | name = "Running dog with ‘OPT’ warns that OPT requests are sent by default" 43 | shell = "dog OPT dns.google" 44 | stdout = { empty = true } 45 | stderr = { file = "outputs/opt-query.txt" } 46 | status = 3 47 | tags = [ 'options' ] 48 | 49 | [[cmd]] 50 | name = "Running dog with ‘opt’ also warns that OPT requests are sent by default" 51 | shell = "dog opt dns.google" 52 | stdout = { empty = true } 53 | stderr = { file = "outputs/opt-query.txt" } 54 | status = 3 55 | tags = [ 'options' ] 56 | 57 | [[cmd]] 58 | name = "Running dog with ‘--type OPT’ warns that OPT requests are sent by default" 59 | shell = "dog --type OPT dns.google" 60 | stdout = { empty = true } 61 | stderr = { file = "outputs/opt-query.txt" } 62 | status = 3 63 | tags = [ 'options' ] 64 | 65 | [[cmd]] 66 | name = "Running dog with ‘--type opt’ also warns that OPT requests are sent by default" 67 | shell = "dog --type opt dns.google" 68 | stdout = { empty = true } 69 | stderr = { file = "outputs/opt-query.txt" } 70 | status = 3 71 | tags = [ 'options' ] 72 | 73 | [[cmd]] 74 | name = "Running dog with a domain longer than 255 bytes warns about it being too long" 75 | shell = "dog 12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890" 76 | stdout = { empty = true } 77 | stderr = { file = "outputs/huge-domain.txt" } 78 | status = 3 79 | tags = [ 'options' ] 80 | 81 | [[cmd]] 82 | name = "Running dog with ‘--https’ and no nameserver warns that one is missing" 83 | shell = "dog --https dns.google" 84 | stdout = { empty = true } 85 | stderr = { file = "outputs/missing-nameserver.txt" } 86 | status = 3 87 | tags = [ 'options' ] 88 | -------------------------------------------------------------------------------- /xtests/options/help.toml: -------------------------------------------------------------------------------- 1 | # help 2 | 3 | [[cmd]] 4 | name = "Running ‘dog --help’ shows help" 5 | shell = "dog --help" 6 | stdout = { string = "dog ●" } 7 | stderr = { empty = true } 8 | status = 0 9 | tags = [ 'options' ] 10 | 11 | [[cmd]] 12 | name = "Running ‘dog --help --color=automatic’ not to a terminal shows help without colour" 13 | shell = "dog --help --color=automatic" 14 | stdout = { string = "dog ●" } 15 | stderr = { empty = true } 16 | status = 0 17 | tags = [ 'options' ] 18 | 19 | [[cmd]] 20 | name = "Running ‘dog --help --colour=always’ shows help with colour" 21 | shell = "dog --help --colour=always" 22 | stdout = { string = "dog \u001B[1;32m●\u001B[0m" } 23 | stderr = { empty = true } 24 | status = 0 25 | tags = [ 'options' ] 26 | 27 | [[cmd]] 28 | name = "Running ‘dog --help --color=never’ shows without colour" 29 | shell = "dog --help --color=never" 30 | stdout = { string = "dog ●" } 31 | stderr = { empty = true } 32 | status = 0 33 | tags = [ 'options' ] 34 | 35 | [[cmd]] 36 | name = "Running ‘dog’ with no arguments shows help" 37 | shell = "dog" 38 | stdout = { string = "dog ●" } 39 | stderr = { empty = true } 40 | status = 3 41 | tags = [ 'options' ] 42 | 43 | 44 | # versions 45 | 46 | [[cmd]] 47 | name = "Running ‘dog --version’ shows version information" 48 | shell = "dog --version" 49 | stdout = { string = "dog ●" } 50 | stderr = { empty = true } 51 | status = 0 52 | tags = [ 'options' ] 53 | 54 | [[cmd]] 55 | name = "Running ‘dog --version --colour=automatic’ not to a terminal shows version information without colour" 56 | shell = "dog --version --colour=automatic" 57 | stdout = { string = "dog ●" } 58 | stderr = { empty = true } 59 | status = 0 60 | tags = [ 'options' ] 61 | 62 | [[cmd]] 63 | name = "Running ‘dog --version --color=always’ shows version information with colour" 64 | shell = "dog --version --color=always" 65 | stdout = { string = "dog \u001B[1;32m●\u001B[0m" } 66 | stderr = { empty = true } 67 | status = 0 68 | tags = [ 'options' ] 69 | 70 | [[cmd]] 71 | name = "Running ‘dog --version --colour=never’ shows version information without colour" 72 | shell = "dog --version --colour=never" 73 | stdout = { string = "dog ●" } 74 | stderr = { empty = true } 75 | status = 0 76 | tags = [ 'options' ] 77 | -------------------------------------------------------------------------------- /xtests/options/outputs/huge-domain.txt: -------------------------------------------------------------------------------- 1 | dog: Invalid options: Invalid domain "12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890" 2 | -------------------------------------------------------------------------------- /xtests/options/outputs/invalid-argument.txt: -------------------------------------------------------------------------------- 1 | dog: Invalid options: Unrecognized option: 'wibble' 2 | -------------------------------------------------------------------------------- /xtests/options/outputs/invalid-protocol-tweak.txt: -------------------------------------------------------------------------------- 1 | dog: Invalid options: Invalid protocol tweak "aoeu" 2 | -------------------------------------------------------------------------------- /xtests/options/outputs/invalid-query-class.txt: -------------------------------------------------------------------------------- 1 | dog: Invalid options: Invalid query class "XYZZY" 2 | -------------------------------------------------------------------------------- /xtests/options/outputs/invalid-query-type.txt: -------------------------------------------------------------------------------- 1 | dog: Invalid options: Invalid query type "XYZZY" 2 | -------------------------------------------------------------------------------- /xtests/options/outputs/missing-nameserver.txt: -------------------------------------------------------------------------------- 1 | dog: Invalid options: You must pass a URL as a nameserver when using --https 2 | -------------------------------------------------------------------------------- /xtests/options/outputs/missing-parameter.txt: -------------------------------------------------------------------------------- 1 | dog: Invalid options: Argument to option 'class' missing 2 | -------------------------------------------------------------------------------- /xtests/options/outputs/opt-query.txt: -------------------------------------------------------------------------------- 1 | dog: Invalid options: OPT request is sent by default (see -Z flag) 2 | --------------------------------------------------------------------------------